The controller asks no build of anybody (ADR 0501 §5, ADR 0525 clause 6, ADR 0503 §3). Every module of a walk is taken from the builder's done key; a key not done yet waits, named in the plan, and never fails it. - C2b/C2c: internal/builder and cmd/mesh-controller's asks go (askABuild, onlyTheBuildersOwn, buildOneAsked, takeIn from an outcome, buildAgentsOwn, the builder's say and its fallback, the daemon hearing an outcome). The server follows the retired seats' built subjects only to let go of them, because the installer's carried user list names them. - C2d: replay, queue, cancel, clear, kill, pause, resume, delivery-check, check-here and merge-gate are refused by name. - C3: the check queues (CHECKS_READY, CHECKS_OTHER) and their workers, the checks-running and plans-building buckets and their grants, the build queue and its hold (plans_building.go, queue.go, link/holds.go, builds_nats.go, checks_nats.go). - C4: merge_gate*.go, the pull request's repo-check path, the Checked event and its layers; a pull request asks the controller nothing but to tell mesh-config what its head declares. - C5: the builds bucket is written only by key_takein. - C10 (help and refusals): the terminal's help lists only what is served. Integration tests of the plan's tiers, sends, stops and retries now run on a stand-in builder with a done key for every module (walk_keys_scaffold_integration_test.go); the tests of the asks, the queue, the merge gate and the replays are deleted.
104 lines
3.5 KiB
Go
104 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// What a build is recorded as, and what it is announced and handed on as.
|
|
//
|
|
// **Recorded by what it is; routed where it is used** (novox/hq 04-ISSUES/102). The builder
|
|
// announces each artifact by the reference it pushed — `<registry>:<port>/<module>/<artifact>@sha256:…`
|
|
// — and the manifest with those references in it. The mesh records the digest and the path
|
|
// (catalogue.Recorded) and composes the store's address back in wherever a machine or a builder
|
|
// needs a reference it can fetch: a declaration, a replay to the catalogue, the bases a build is
|
|
// given. Nothing recorded carries a port, so moving the store is a settings change and not a
|
|
// rebuild of everything the mesh has ever built.
|
|
|
|
// routedManifest is a recorded manifest with the store's address, as this network reaches it now,
|
|
// composed into every reference the build made — recorded either way, before or after references
|
|
// were kept without their address.
|
|
func routedManifest(raw json.RawMessage, made []inventory.Artifact, address string) json.RawMessage {
|
|
recorded := map[string]bool{}
|
|
for _, a := range made {
|
|
recorded[catalogue.Recorded(a.Reference)] = true
|
|
}
|
|
return withReferences(raw, func(ref string) (string, bool) {
|
|
if !recorded[catalogue.Recorded(ref)] {
|
|
return ref, false
|
|
}
|
|
return catalogue.Rerouted(ref, address), true
|
|
})
|
|
}
|
|
|
|
// withReferences applies `rewrite` to each resource's `image` and `source`, and hands the manifest
|
|
// back untouched — byte for byte — when nothing changed or it could not be read: what a manifest
|
|
// is, is the parser's to say, and it says so with a better sentence than anything here would.
|
|
func withReferences(raw json.RawMessage, rewrite func(string) (string, bool)) json.RawMessage {
|
|
if len(raw) == 0 {
|
|
return raw
|
|
}
|
|
var manifest map[string]any
|
|
if err := json.Unmarshal(raw, &manifest); err != nil {
|
|
return raw
|
|
}
|
|
resources, _ := manifest["resources"].([]any)
|
|
changed := false
|
|
for _, r := range resources {
|
|
resource, ok := r.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, key := range []string{"image", "source"} {
|
|
if written, ok := resource[key].(string); ok {
|
|
if rewritten, did := rewrite(written); did && rewritten != written {
|
|
resource[key] = rewritten
|
|
changed = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !changed {
|
|
return raw
|
|
}
|
|
out, err := json.Marshal(manifest)
|
|
if err != nil {
|
|
return raw
|
|
}
|
|
return out
|
|
}
|
|
|
|
// routedArtifacts is a build's artifacts as something can fetch them now.
|
|
func routedArtifacts(made []inventory.Artifact, address string) []inventory.Artifact {
|
|
if address == "" {
|
|
return made
|
|
}
|
|
out := make([]inventory.Artifact, 0, len(made))
|
|
for _, a := range made {
|
|
out = append(out, inventory.Artifact{Name: a.Name, Kind: a.Kind,
|
|
Reference: catalogue.Rerouted(a.Reference, address)})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// whereTheStoreIs is the artifact store's address as something on `forNode` reaches it, or "" —
|
|
// read for a caller that has the inventory open and nothing else in hand. With no node named, the
|
|
// store's own node: loopback when nothing is on the network yet.
|
|
func whereTheStoreIs(ctx context.Context, inv *inventory.Inventory, forNode string) (string, error) {
|
|
shelf, err := inv.Catalogue(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if forNode == "" {
|
|
if holder, _, found, err := artifactStoreHolder(ctx, inv); err != nil {
|
|
return "", err
|
|
} else if found {
|
|
forNode = holder
|
|
}
|
|
}
|
|
return artifactStoreAddress(ctx, inv, shelf, forNode)
|
|
}
|