carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package maintenanceguard
2
3import (
4 "bytes"
5 "context"
6 "errors"
7 "os"
8)
9
10// Store is structural so release, provision and host can share these rules
11// without importing each other. Put uses empty ETag for create-only writes.
12type Store interface {
13 Get(context.Context, string) ([]byte, string, error)
14 Put(context.Context, string, []byte, string) (string, error)
15}
16type Repository struct{ store Store }
17
18func New(store Store) *Repository { return &Repository{store: store} }
19
20type ReferenceKind string
21
22const (
23 RequestReference ReferenceKind = "requests"
24 AttemptReference ReferenceKind = "attempts"
25 ReceiptReference ReferenceKind = "receipts"
26)
27
28func (k ReferenceKind) valid() bool {
29 return k == RequestReference || k == AttemptReference || k == ReceiptReference
30}
31
32// PutReference stores permanent, nonsecret metadata. Expiring private payloads
33// belong elsewhere: active guards must keep resolving these immutable bytes.
34// The caller validates metadata, ownership and any encrypted payload bindings.
35func (r *Repository) PutReference(ctx context.Context, host string, kind ReferenceKind, id string, raw []byte) (Reference, error) {
36 if !kind.valid() || len(raw) == 0 || len(raw) > MaxReferenceBytes {
37 return Reference{}, ErrInvalid
38 }
39 key, e := refKey(host, string(kind), id)
40 if e != nil {
41 return Reference{}, e
42 }
43 if e = r.putImmutable(ctx, key, raw); e != nil {
44 return Reference{}, e
45 }
46 return Reference{ID: id, Digest: digest(raw)}, nil
47}
48func (r *Repository) putImmutable(ctx context.Context, key string, raw []byte) error {
49 old, _, e := r.store.Get(ctx, key)
50 if e == nil {
51 if bytes.Equal(old, raw) {
52 return nil
53 }
54 return ErrConflict
55 }
56 if !errors.Is(e, os.ErrNotExist) {
57 return e
58 }
59 _, e = r.store.Put(ctx, key, raw, "")
60 return e
61}
62func (r *Repository) checkReference(ctx context.Context, host string, kind ReferenceKind, ref Reference) error {
63 if !ref.valid() {
64 return ErrInvalid
65 }
66 key, e := refKey(host, string(kind), ref.ID)
67 if e != nil {
68 return e
69 }
70 raw, _, e := r.store.Get(ctx, key)
71 if e != nil || len(raw) == 0 || len(raw) > MaxReferenceBytes || digest(raw) != ref.Digest {
72 return ErrUnavailable
73 }
74 return nil
75}
76func (r *Repository) checkGuardReferences(ctx context.Context, g Guard) error {
77 for _, v := range []struct {
78 kind ReferenceKind
79 ref Reference
80 }{{RequestReference, g.Request}, {AttemptReference, g.Attempt}} {
81 if e := r.checkReference(ctx, g.Identity.Host, v.kind, v.ref); e != nil {
82 return e
83 }
84 }
85 if g.Evidence != nil {
86 return r.checkReference(ctx, g.Identity.Host, ReceiptReference, *g.Evidence)
87 }
88 return nil
89}
90