package delivery import ( "encoding/json" "fmt" "sort" "time" ) // Whether the artifact store still holds what a done key names (novox/hq issue 1937). A build module pushes a bundle or // an archive as a bare blob (ADR 0498 ยง1), and the store's nightly collector deletes every blob no manifest names; only // the controller's take-in and sweep hold one by its manifest. A done key the controller has not taken in can therefore // name a blob that is gone, and reusing it sends a definition every machine fails on. So the builder asks before it // reuses a done key, and the controller says when a take-in found a gone blob. const ( // HeldVerb is the controller seat's verb the builder asks, as a module's invokes names it: references in, each // answered held or gone. HeldVerb = "seat:mesh-controller.held" // ArtifactsGoneEvent is the controller seat's event said when a take-in is refused because the store does not hold // an artifact the key names: as a consumer names it, `mesh-controller.artifacts-gone`. ArtifactsGoneEvent = "artifacts-gone" // ArtifactsGoneSubject is where it is published (the seat's event subject). ArtifactsGoneSubject = "mesh.seat.mesh-controller.event." + ArtifactsGoneEvent ) // HeldAnswer is the answer of HeldVerb: the references the store holds, and those it answered 404 for. A reference the // store could not be asked about is in neither, and named in Unknown with why. type HeldAnswer struct { Store string `json:"store,omitempty"` Held []string `json:"held"` Gone []string `json:"gone"` Unknown map[string]string `json:"unknown,omitempty"` } // DecodeHeldAnswer reads HeldVerb's answer: the verb answers {output, ok, answer}, the answer being HeldAnswer. A // reference asked that the answer names in none of held, gone and unknown is an error: an answer that leaves one out // would let a key with a gone blob be reused. func DecodeHeldAnswer(raw []byte, asked []string) (HeldAnswer, error) { var wrapped struct { Output string `json:"output"` OK bool `json:"ok"` Answer json.RawMessage `json:"answer"` } if err := json.Unmarshal(raw, &wrapped); err != nil { return HeldAnswer{}, fmt.Errorf("the answer of %s is not {output, ok, answer}: %w", HeldVerb, err) } if !wrapped.OK || len(wrapped.Answer) == 0 { return HeldAnswer{}, fmt.Errorf("%s did not answer: %s", HeldVerb, wrapped.Output) } var a HeldAnswer if err := json.Unmarshal(wrapped.Answer, &a); err != nil { return HeldAnswer{}, fmt.Errorf("the answer of %s is not {held, gone}: %w", HeldVerb, err) } named := map[string]bool{} for _, r := range a.Held { named[r] = true } for _, r := range a.Gone { named[r] = true } for r := range a.Unknown { named[r] = true } for _, r := range asked { if !named[r] { return HeldAnswer{}, fmt.Errorf("the answer of %s names nothing of %s", HeldVerb, r) } } return a, nil } // ArtifactsGone is the body of ArtifactsGoneEvent: the module whose builder's key the controller would not take in, // the key and its fingerprint, and every reference of it the store does not hold. type ArtifactsGone struct { Event string `json:"event"` Module string `json:"module"` Key string `json:"key"` Fingerprint string `json:"fingerprint"` Gone []string `json:"gone"` At time.Time `json:"at"` } // EncodeArtifactsGone is the event's body, the references sorted. func EncodeArtifactsGone(e ArtifactsGone) ([]byte, error) { e.Event = ArtifactsGoneEvent e.Gone = append([]string(nil), e.Gone...) sort.Strings(e.Gone) if e.Module == "" || e.Fingerprint == "" || len(e.Gone) == 0 { return nil, fmt.Errorf("delivery: %s names a module, a fingerprint and at least one gone reference", ArtifactsGoneEvent) } return json.Marshal(e) } // DecodeArtifactsGone reads the event's body, refusing one that names no module, fingerprint or reference. func DecodeArtifactsGone(raw []byte) (ArtifactsGone, error) { var e ArtifactsGone if err := json.Unmarshal(raw, &e); err != nil { return ArtifactsGone{}, fmt.Errorf("delivery: %s is not one: %w", ArtifactsGoneEvent, err) } if e.Event != ArtifactsGoneEvent || e.Module == "" || e.Fingerprint == "" || len(e.Gone) == 0 { return ArtifactsGone{}, fmt.Errorf("delivery: %s names no module, fingerprint or gone reference: %s", ArtifactsGoneEvent, raw) } return e, nil }