Three readers did not follow a moved foundation port (novox/hq 04-ISSUES/102),
and each took the control-node down in its own way: the control plane's own
store and broker connections, sealed at genesis with the port inside; and every
build the mesh ever recorded, kept as `<registry>:<port>/<module>/<artifact>@…`.
The control plane cannot open its own sealed connections to move a port, and it
cannot bind the store as a consumer would — a binding mints a credential. So its
settings get a third twin, `NAME_PORT`, read on top of the sealed value by the
store, the broker, the management API and the bus connection, and filled into
its container by a placeholder that names a seat, `${seat:mesh-store:5432}`,
from the node's given or mesh-assigned ports — never the manifest's number, and
empty when the mesh has nothing to add, so what genesis wrote stands. A value
that is still a placeholder is nothing said, aloud: the manifest naming it lands
in the next commit, once every control plane that composes it knows it.
A build is now recorded by digest and path — `artifact-store://<module>/<artifact>@…`
— and the store's address is composed in where a reference is used: the
declaration, the trust file, the bases a build is handed, a replay to the
catalogue. Over the network as `<node>.internal:<port>`; on the store's own node
before any network exists — every genesis push before its "network" step — by
loopback. A reference recorded before this, with an address, is re-routed the
same way when the mesh built it. The trust file and every provider's address
come from one derivation: the node's given port, over the mesh's assignment,
over the manifest's number.
novox/hq 04-ISSUES/102
151 lines
5.1 KiB
Go
151 lines
5.1 KiB
Go
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 — `<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.
|
|
|
|
// 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)
|
|
}
|