| new file mode 100644 |
| index 000000000..15623ecbd |
| --- /dev/null |
| +++ b/internal/edgesfile/edgesfile.go |
| @@ -0,0 +1,89 @@ |
| +// Package edgesfile READS the control/edges/<box>.json wire shape — the |
| +// per-box publication of what a box homes (multi-region ingress spec, Part |
| +// 3) — for consumers that need the deployment-wide picture rather than one |
| +// box's own view. |
| +// |
| +// The WRITER's shape lives in internal/host (edgesFile/edgeHost): the box |
| +// agent owns what it publishes, and this package deliberately does not try |
| +// to own it too. internal/host cannot host this reader — the console does |
| +// not import internal/host, and internal/host imports internal/edge and |
| +// never the reverse — so the reader lives one level down where the console, |
| +// internal/domains and the box agent can all reach it. |
| +// |
| +// The two shapes are joined by a test, not by a shared type: keeping them |
| +// separate lets the writer add a field and roll before every reader |
| +// understands it, which is the whole point of an additive wire. |
| +// |
| +// Two readers deliberately keep their own narrow decoders and are NOT |
| +// migrated: internal/edge/homemap.go (the serving hot path, with its own |
| +// polling and staleness machinery) and internal/steering (which reads no |
| +// Hosts at all — steering is any-edge-any-app). Both are named here so the |
| +// consumer list stays complete rather than merely short. |
| +package edgesfile |
| + |
| +import ( |
| + "encoding/json" |
| + "fmt" |
| +) |
| + |
| +// Prefix is the flat prefix every box's file lives under. |
| +const Prefix = "control/edges/" |
| + |
| +// RoleHome is the role a box publishes for a host it actually serves. An |
| +// entry with any other role is either a cert-only role (see Host.Wildcard) |
| +// or a role a newer build publishes; readers must ignore what they do not |
| +// recognise rather than route on it or fail the parse. |
| +const RoleHome = "home" |
| + |
| +// Key is the bucket key for box's file. |
| +func Key(box string) string { return Prefix + box + ".json" } |
| + |
| +// Host is one per-host entry, decoded. Every mark is additive, and each |
| +// defaults to false on a file written before that mark existed — but they |
| +// fail in OPPOSITE directions, which is deliberate and load-bearing: |
| +// |
| +// - Platform absent reads as "not platform", so a pre-mark publisher's rows |
| +// derive no wildcard parent (#300 fails CLOSED). |
| +// - Alias absent reads as "not an alias", so a pre-mark publisher's rows |
| +// keep full route authority and cannot be outranked by a claimed alias |
| +// (#287 fails OPEN, which is why its readers must roll first). |
| +// |
| +// The writer always emits "platform" and omits the other two when false; |
| +// this reader does not care which, because absent and false decode alike. |
| +// What the reader must never do is re-serialize a Host and call it a |
| +// publication — that is the writer's job, and round-tripping through this |
| +// struct would drop any field a newer box publishes. |
| +type Host struct { |
| + Role string `json:"role"` |
| + Platform bool `json:"platform"` |
| + Alias bool `json:"alias"` |
| + // Wildcard marks a delegated customer wildcard parent (wildcard_domains, |
| + // #305): the key is "*.<parent>". With Role RoleHome the box also serves |
| + // the catch-all; with any other role it is the cert half alone. |
| + Wildcard bool `json:"wildcard"` |
| +} |
| + |
| +// File is one box's published file. |
| +type File struct { |
| + V int `json:"v"` |
| + Box string `json:"box"` |
| + EIP string `json:"eip"` |
| + Region string `json:"region,omitempty"` |
| + Generation string `json:"generation,omitempty"` |
| + Hosts map[string]Host `json:"hosts"` |
| +} |
| + |
| +// Parse decodes one box's body. Pure — no store I/O. V < 1 (no version field |
| +// at all) is refused as not a file this reader understands; a HIGHER version |
| +// is accepted, because the schema grows additively and a newer box's file |
| +// still carries box/eip/hosts verbatim. |
| +func Parse(body []byte) (File, error) { |
| + var f File |
| + if err := json.Unmarshal(body, &f); err != nil { |
| + return File{}, fmt.Errorf("edgesfile: parse: %w", err) |
| + } |
| + if f.V < 1 { |
| + return File{}, fmt.Errorf("edgesfile: unversioned edges file") |
| + } |
| + return f, nil |
| +} |