151 lines
5.0 KiB
Go
151 lines
5.0 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.
|
|
|
|
// 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)
|
|
}
|