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 "fmt"
5 "regexp"
6 "strings"
7 "time"
8
9 "github.com/carlosframework/platform/internal/controlname"
10 "github.com/carlosframework/platform/internal/economics"
11 "github.com/carlosframework/platform/internal/hostactors"
12 "github.com/carlosframework/platform/internal/infraview"
13 "github.com/carlosframework/platform/internal/moves"
14 "github.com/carlosframework/platform/internal/release"
15 "github.com/carlosframework/platform/internal/telemetry"
16 "github.com/carlosframework/platform/internal/usage"
17)
18
19// classifyDeploymentFamily reviews the deployment- and account-level
20// families the route/worker census found writing under HostClaim. Each is an
21// exact key shape owned by one writer, and its body must be what that writer
22// serializes there: decoded strictly (no unknown field, no trailing value)
23// into the writer's own type and bound to the key it is written at. None of
24// these writers ever deletes, so every family is Put-only and a delete stays
25// unclassified. ok=false means the key is none of these shapes; a lookalike
26// under the same roots (an extra segment, an app-shaped subkey, another
27// suffix) falls through to ErrUnclassified.
28func classifyDeploymentFamily(w *ControlWriter, t MutationTarget) (Classification, bool, error) {
29 fam, check := deploymentFamily(t.Key)
30 if check == nil {
31 return Classification{}, false, nil
32 }
33 if t.Method != "Put" {
34 return Classification{}, true, fmt.Errorf("%w: %s refuses %s", ErrUnclassified, t.Key, t.Method)
35 }
36 reason, err := check(w, t)
37 if err != nil {
38 return Classification{}, true, err
39 }
40 return Classification{Family: fam, NonApp: reason}, true, nil
41}
42
43type familyCheck func(*ControlWriter, MutationTarget) (string, error)
44
45// deploymentFamily matches the exact key shapes. It parses; the check
46// validates the body against the key.
47func deploymentFamily(key string) (string, familyCheck) {
48 p := strings.Split(key, "/")
49 switch {
50 case key == economics.ActiveKey:
51 return "economics-active", checkEconomicsActive
52 case len(p) == 3 && p[0] == "economics" && p[1] == "bills":
53 if month, ok := jsonName(p[2]); ok && validMonth(month) {
54 return "economics-bill", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkBill(month, t) }
55 }
56 case len(p) == 3 && p[0] == "economics" && p[1] == "scenarios":
57 if name, ok := jsonName(p[2]); ok && economics.ValidScenarioName(name) {
58 return "economics-scenario", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkScenario(name, t) }
59 }
60 case key == infraview.InventoryKey:
61 return "infra-inventory", checkInventory
62 case len(p) == 3 && p[0] == "usage" && (p[1] == "census" || p[1] == "storage"):
63 if date, ok := jsonName(p[2]); ok && validDate(date) {
64 if p[1] == "census" {
65 return "usage-census", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkCensusDay(date, t) }
66 }
67 return "usage-storage", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkStorageDay(date, t) }
68 }
69 case len(p) == 3 && p[0] == "usage" && p[1] == "scan":
70 if date, ok := jsonName(p[2]); ok && validDate(date) {
71 return "usage-scan", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkScanDay(date, t) }
72 }
73 case len(p) == 4 && p[0] == "usage" && p[1] == "accounts" && controlname.ValidAccount(p[2]):
74 if month, ok := jsonName(p[3]); ok && validMonth(month) {
75 acct := p[2]
76 return "usage-statement", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkStatement(acct, month, t) }
77 }
78 case len(p) == 3 && p[0] == "status" && p[1] == "consoles":
79 if id, ok := jsonName(p[2]); ok && consoleSegment(id) {
80 return "console-report", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkConsoleReport(id, t) }
81 }
82 case key == moves.ConsoleKey:
83 return "moves-console", checkMovesConsole
84 case len(p) == 3 && p[0] == "accounts" && p[2] == "telemetry.json" && controlname.ValidAccount(p[1]):
85 return "account-telemetry", checkAccountTelemetry
86 case key == release.SystemKey(release.AntivirusName):
87 return "system-antivirus", checkAntivirus
88 case len(p) == 4 && p[0] == "bootstrap" && p[1] == "router":
89 version, platform := p[2], strings.TrimPrefix(p[3], "carlos-")
90 if release.ValidVersion(version) && version != "dev" && systemPlatformRe.MatchString(platform) && key == release.SystemArtifactKey(version, platform) {
91 return "system-artifact", checkSystemArtifact
92 }
93 case len(p) == 4 && p[0] == "control" && p[1] == "system" && p[2] == release.SystemComponentRouter:
94 if channel, ok := jsonName(p[3]); ok && (channel == release.RungCanary || channel == release.RungStable) {
95 return "system-pointer", func(w *ControlWriter, t MutationTarget) (string, error) { return checkSystemPointer(w, channel, t) }
96 }
97 }
98 return "", nil
99}
100
101// systemPlatformRe mirrors release's unexported <goos>-<goarch> shape.
102var systemPlatformRe = regexp.MustCompile(`^[a-z0-9]+-[a-z0-9]+$`)
103
104func jsonName(file string) (string, bool) {
105 name, ok := strings.CutSuffix(file, ".json")
106 return name, ok && name != ""
107}
108
109func validMonth(s string) bool {
110 at, err := time.Parse("2006-01", s)
111 return err == nil && at.Format("2006-01") == s
112}
113
114func validDate(s string) bool {
115 at, err := time.Parse("2006-01-02", s)
116 return err == nil && at.Format("2006-01-02") == s
117}
118
119// consoleSegment is hostactors' own segment rule for a console id, the one
120// its census reader applies before reading status/consoles/<id>.json.
121func consoleSegment(s string) bool {
122 return s != "" && s != "." && s != ".." && !strings.ContainsAny(s, "/\\\x00")
123}
124
125func requireCreateOnly(t MutationTarget) error {
126 if t.ETag != "" {
127 return fmt.Errorf("%w: %s is create-only", ErrIdentity, t.Key)
128 }
129 return nil
130}
131
132func checkEconomicsActive(_ *ControlWriter, t MutationTarget) (string, error) {
133 var a economics.Active
134 if err := strictJSON(t.After, &a); err != nil {
135 return "", err
136 }
137 if a.Schema != 1 || !economics.ValidScenarioName(a.Name) {
138 return "", ErrIdentity
139 }
140 return "operator economics: which pricing scenario the fleet dashboards load; holds no app record", nil
141}
142
143func checkBill(month string, t MutationTarget) (string, error) {
144 var b economics.Bill
145 if err := strictJSON(t.After, &b); err != nil {
146 return "", err
147 }
148 if b.Schema != 1 || b.Month != month || (b.Source != economics.SourceManual && b.Source != economics.SourceCostExplorer) {
149 return "", ErrIdentity
150 }
151 return "operator economics: the deployment's own cloud bill for one month, by service; holds no app record", nil
152}
153
154func checkScenario(name string, t MutationTarget) (string, error) {
155 var sc economics.Scenario
156 if err := strictJSON(t.After, &sc); err != nil {
157 return "", err
158 }
159 if sc.Name != name || sc.Validate() != nil {
160 return "", ErrIdentity
161 }
162 return "operator economics: a named pricing knob-set for the dashboards; holds no app record", nil
163}
164
165func checkInventory(_ *ControlWriter, t MutationTarget) (string, error) {
166 var inv infraview.Inventory
167 if err := strictJSON(t.After, &inv); err != nil {
168 return "", err
169 }
170 return "operator infrastructure inventory snapshot of the deployment's cloud estate; holds no app record", nil
171}
172
173// Census and storage days are create-only history: a day once written is
174// never rewritten, by its writer or anything riding its key.
175func checkCensusDay(date string, t MutationTarget) (string, error) {
176 if err := requireCreateOnly(t); err != nil {
177 return "", err
178 }
179 var d usage.CensusDay
180 if err := strictJSON(t.After, &d); err != nil {
181 return "", err
182 }
183 if d.V != 1 || d.Date != date {
184 return "", ErrIdentity
185 }
186 return "usage metering: one day's per-account app and route counts, create-only; holds no app record", nil
187}
188
189func checkStorageDay(date string, t MutationTarget) (string, error) {
190 if err := requireCreateOnly(t); err != nil {
191 return "", err
192 }
193 var d usage.StorageDay
194 if err := strictJSON(t.After, &d); err != nil {
195 return "", err
196 }
197 if d.V != 1 || d.Date != date {
198 return "", ErrIdentity
199 }
200 return "usage metering: one day's measured storage sizes, create-only; a measurement of apps, not app state", nil
201}
202
203// checkScanDay admits one day's on-demand scan counts. NOT create-only, unlike
204// census and storage days: the drain folds every tick's deltas into the day
205// with a compare-and-swap (usage.UpsertScanDay), so the same key is rewritten
206// all day long. The body is still bound to its key and schema.
207func checkScanDay(date string, t MutationTarget) (string, error) {
208 var d usage.ScanDay
209 if err := strictJSON(t.After, &d); err != nil {
210 return "", err
211 }
212 if d.V != 1 || d.Date != date {
213 return "", ErrIdentity
214 }
215 return "usage metering: one day's per-account on-demand scan counts, folded per drain tick; a measurement of apps, not app state", nil
216}
217
218func checkStatement(acct, month string, t MutationTarget) (string, error) {
219 var st usage.Statement
220 if err := strictJSON(t.After, &st); err != nil {
221 return "", err
222 }
223 if st.V != 1 || st.Account != acct || st.Month != month {
224 return "", ErrIdentity
225 }
226 return "usage metering: one account's month-to-date meter totals for billing; holds no app record", nil
227}
228
229func checkConsoleReport(id string, t MutationTarget) (string, error) {
230 var r hostactors.Report
231 if err := strictJSON(t.After, &r); err != nil {
232 return "", err
233 }
234 if r.Component != "console" || r.Actor != id || r.InstanceID != id {
235 return "", ErrIdentity
236 }
237 return "console process report keyed by console id, the console analogue of status/boxes/; holds no app record", nil
238}
239
240func checkMovesConsole(_ *ControlWriter, t MutationTarget) (string, error) {
241 var rec moves.ConsoleRecord
242 if err := strictJSON(t.After, &rec); err != nil {
243 return "", err
244 }
245 if rec.V != 1 || rec.Protocol < 1 {
246 return "", ErrIdentity
247 }
248 return "the console's moves protocol and secrets-key fingerprint; deployment-wide, holds no app record", nil
249}
250
251// checkAccountTelemetry admits the account-wide default only: the analytics
252// and errors halves with their closed vocabularies. Apps inherit it, but an
253// app's own setting lives at apps/<sqid>/<app>/telemetry.json, which is app
254// scope; this object can carry nothing app-keyed.
255func checkAccountTelemetry(_ *ControlWriter, t MutationTarget) (string, error) {
256 var st telemetry.Settings
257 if err := strictJSON(t.After, &st); err != nil {
258 return "", err
259 }
260 if a := st.Analytics; a != nil {
261 if a.Count != "" && a.Count != telemetry.CountEdge && a.Count != telemetry.CountOff {
262 return "", ErrIdentity
263 }
264 if a.Door != "" && a.Door != telemetry.DoorOn && a.Door != telemetry.DoorOff {
265 return "", ErrIdentity
266 }
267 }
268 if e := st.Errors; e != nil && e.LogLevel != "" && !telemetry.ValidLogLevel(e.LogLevel) {
269 return "", ErrIdentity
270 }
271 return "account-wide telemetry default (analytics and error level); names no app, and an app's own setting is app scope", nil
272}
273
274func checkAntivirus(_ *ControlWriter, t MutationTarget) (string, error) {
275 var av release.AntivirusSettings
276 if err := strictJSON(t.After, &av); err != nil {
277 return "", err
278 }
279 if av.Schema != release.AntivirusSchema {
280 return "", ErrIdentity
281 }
282 return "deployment-wide object scanning switch; holds no app record", nil
283}
284
285// checkSystemArtifact admits a mirrored platform binary. Versions are
286// immutable, so the write is create-only; nothing adopts the bytes until a
287// root-signed system pointer names their sha256.
288func checkSystemArtifact(_ *ControlWriter, t MutationTarget) (string, error) {
289 if err := requireCreateOnly(t); err != nil {
290 return "", err
291 }
292 if len(t.After) == 0 {
293 return "", ErrIdentity
294 }
295 return "mirrored platform binary for one version and platform, create-only; not app code", nil
296}
297
298// checkSystemPointer admits the platform's own channel pointer only as the
299// deployment root signed it, for exactly the component and channel of its
300// key. A promotion or rollback must also pass release's full pointer
301// validation; a cancellation fence is the root-signed prior pointer.
302func checkSystemPointer(w *ControlWriter, channel string, t MutationTarget) (string, error) {
303 if w == nil {
304 return "", ErrContract
305 }
306 var p release.SystemPointer
307 if release.VerifyRootEnvelope(w.pub, t.After, &p) != nil {
308 return "", ErrIdentity
309 }
310 if p.Schema != 1 || p.Component != release.SystemComponentRouter || p.Channel != channel {
311 return "", ErrIdentity
312 }
313 if p.CancellationID == "" {
314 pub := release.SystemPublication{Component: p.Component, Channel: channel, ID: p.ActivationID, After: t.After}
315 if _, err := pub.Verify(w.pub); err != nil {
316 return "", ErrIdentity
317 }
318 }
319 return "root-signed platform channel pointer; names a platform release, never an app's", nil
320}
321