package main import ( "context" "encoding/json" "github.com/novox/mesh-controller/internal/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 — `://@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. // recordedManifest is a build's manifest with the store's address taken off every reference the // build itself made. Other references — an image a module runs from a public registry — are what // they were, which is why this rewrites only what `made` names rather than everything that looks // like an address. func recordedManifest(raw json.RawMessage, made []inventory.Artifact) json.RawMessage { announced := map[string]bool{} for _, a := range made { announced[a.Reference] = true } return withReferences(raw, func(ref string) (string, bool) { if !announced[ref] { return ref, false } return catalogue.Recorded(ref), true }) } // 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) } // whereABuilderReachesTheStore is the store's address for the machine that builds: the network's // when there is one, else loopback on the store's own node — when that node also holds a module // requiring the store, which is what a builder is (genesis: one node holds both). func whereABuilderReachesTheStore(ctx context.Context, inv *inventory.Inventory) (string, error) { shelf, err := inv.Catalogue(ctx) if err != nil { return "", err } holder, _, found, err := artifactStoreHolder(ctx, inv) if err != nil || !found { return "", err } assigned, err := inv.Assigned(ctx, holder) if err != nil { return "", err } besideIt := false for _, a := range assigned { for _, r := range shelf[a].Requires { if r == catalogue.ArtifactStoreProvision { besideIt = true } } } if !besideIt { holder = "" } return artifactStoreAddress(ctx, inv, shelf, holder) }