carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostops
2
3import (
4 "context"
5 "crypto/rand"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "os"
10
11 "github.com/carlosframework/platform/internal/release"
12)
13
14// BackfillAppGrant is the only legacy-app initialization proof. Its fields
15// are unexported: console code cannot fabricate one.
16type BackfillAppGrant struct {
17 account, app, digest string
18}
19
20func (a ProductionAuthority) BackfillAppGrant(ctx context.Context, account, app string) (BackfillAppGrant, error) {
21 digest, err := a.AuthorizeBackfillApp(ctx, account, app)
22 if err != nil {
23 return BackfillAppGrant{}, err
24 }
25 if digest == "" {
26 return BackfillAppGrant{}, ErrIdentity
27 }
28 return BackfillAppGrant{account: account, app: app, digest: digest}, nil
29}
30
31func newAppID() (string, error) {
32 var nonce [16]byte
33 if _, err := rand.Read(nonce[:]); err != nil {
34 return "", err
35 }
36 return hex.EncodeToString(nonce[:]), nil
37}
38
39func freshAdmission(appID string) AppAdmission {
40 return AppAdmission{V: 1, AppID: appID, Revision: 1, Epoch: 1, State: "open", Registrations: map[string]Registration{}, Effects: map[string]Effect{}}
41}
42
43// nameFree is the recreation census: under the app prefix only the retained
44// admission object may remain, after discarding a box request that names
45// the retired incarnation.
46func (w *ControlWriter) nameFree(ctx context.Context, account, app string, completed bool) (bool, error) {
47 prefix := "apps/" + account + "/" + app + "/"
48 keys, err := w.raw.List(ctx, prefix)
49 if err != nil {
50 return false, err
51 }
52 requestKey := prefix + "purge-request.json"
53 for _, key := range keys {
54 if key == requestKey {
55 removed, err := w.DiscardPurgeRequest(ctx, account, app)
56 if err != nil || !removed {
57 return false, err
58 }
59 if keys, err = w.raw.List(ctx, prefix); err != nil {
60 return false, err
61 }
62 break
63 }
64 }
65 if !completed {
66 return len(keys) == 0, nil
67 }
68 return len(keys) == 1 && keys[0] == prefix+"admission.json", nil
69}
70
71// EnrollNewApp reserves the canonical identity for a name this console just
72// won in SQL. Absent: first incarnation. Completed and name-free: the
73// dedicated recreation transition (new app_id, epoch+1, last_purge kept).
74// Open: another console or an ambiguous earlier write already enrolled it.
75func (w *ControlWriter) EnrollNewApp(ctx context.Context, account, app string) (string, error) {
76 if err := w.ready(); err != nil {
77 return "", err
78 }
79 key, err := AdmissionKey(account, app)
80 if err != nil {
81 return "", err
82 }
83 existing, err := ReadCanonicalAdmission(ctx, w.raw, account, app)
84 absent := errors.Is(err, os.ErrNotExist)
85 if err != nil && !absent {
86 return "", err
87 }
88 if !absent && !CompletedAdmission(existing.Value) {
89 if existing.Value.State != "open" || existing.Value.Op != nil {
90 return "", ErrPending
91 }
92 return existing.Value.AppID, nil
93 }
94 free, err := w.nameFree(ctx, account, app, !absent)
95 if err != nil {
96 return "", err
97 }
98 if !free {
99 return "", ErrPending
100 }
101 appID, err := newAppID()
102 if err != nil {
103 return "", err
104 }
105 next, tag := freshAdmission(appID), ""
106 if !absent {
107 if existing.Value.Revision == ^uint64(0) || existing.Value.Epoch == ^uint64(0) {
108 return "", ErrPending
109 }
110 next.Revision, next.Epoch = existing.Value.Revision+1, existing.Value.Epoch+1
111 next.LastPurge, tag = existing.Value.LastPurge, existing.ETag
112 }
113 body, err := json.Marshal(next)
114 if err != nil {
115 return "", err
116 }
117 if _, err = w.raw.Put(ctx, key, body, tag); err != nil {
118 if !errors.Is(err, release.ErrCASConflict) {
119 return "", err
120 }
121 winner, rerr := ReadCanonicalAdmission(ctx, w.raw, account, app)
122 if rerr != nil {
123 return "", rerr
124 }
125 if winner.Value.State != "open" || winner.Value.Op != nil {
126 return "", ErrPending
127 }
128 return winner.Value.AppID, nil
129 }
130 return appID, nil
131}
132
133// EnrollBackfillApp initializes a legacy live app from a signed backfill
134// grant. It never revives a completed incarnation.
135func (w *ControlWriter) EnrollBackfillApp(ctx context.Context, g BackfillAppGrant) (string, error) {
136 if err := w.ready(); err != nil {
137 return "", err
138 }
139 if g.digest == "" {
140 return "", ErrIdentity
141 }
142 key, err := AdmissionKey(g.account, g.app)
143 if err != nil {
144 return "", err
145 }
146 existing, err := ReadCanonicalAdmission(ctx, w.raw, g.account, g.app)
147 if err == nil {
148 if CompletedAdmission(existing.Value) || existing.Value.State != "open" || existing.Value.Op != nil {
149 return "", ErrPending
150 }
151 return existing.Value.AppID, nil
152 }
153 if !errors.Is(err, os.ErrNotExist) {
154 return "", err
155 }
156 appID, err := newAppID()
157 if err != nil {
158 return "", err
159 }
160 body, err := json.Marshal(freshAdmission(appID))
161 if err != nil {
162 return "", err
163 }
164 if _, err = w.raw.Put(ctx, key, body, ""); err != nil {
165 if !errors.Is(err, release.ErrCASConflict) {
166 return "", err
167 }
168 winner, rerr := ReadCanonicalAdmission(ctx, w.raw, g.account, g.app)
169 if rerr != nil {
170 return "", rerr
171 }
172 if winner.Value.State != "open" || winner.Value.Op != nil {
173 return "", ErrPending
174 }
175 return winner.Value.AppID, nil
176 }
177 return appID, nil
178}
179