mesh/delivery covered after its stop: a later walk that contains it was delivered: plan-1792875397682979170 (novox/mesh-sdk at 312878be into main, done, …
mesh/delivery-group group fix/1937-done-key-blobs closed: a member was closed before the group merged
mesh/check pass 34f210e5eba1 on e1f393bbb12e | unit 202 run, 3523 kept | integration 2223 run, 2682 kept, 7 skipped | over bound: unit, integration (s…
The store's nightly collector deletes every bare blob no manifest names, and a done key the controller never took in holds nothing: node-tools, mesh-host and the forge bridge kept done keys naming blobs that answer 404. held lets the builder ask before it reuses a key; artifacts-gone is said when a take-in finds a gone blob. delivery/held.go is the shared contract.
105 lines
4.3 KiB
Go
105 lines
4.3 KiB
Go
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
|
|
}
|