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
146 lines
6.5 KiB
Go
146 lines
6.5 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// What the mesh built, named by what it is rather than by where it was pushed.
|
|
//
|
|
// **An image is recorded by its digest and its path; the registry's address is a route to it**
|
|
// (novox/hq 04-ISSUES/102). A build used to be recorded as `<registry>:<port>/<module>/<artifact>@sha256:…`
|
|
// — the reference the builder pushed to, kept whole — and every declaration carried that literal.
|
|
// Move the registry's port, or the registry, and every fresh pull of a mesh image fails: a new
|
|
// node, a recreate after eviction. The digest is the identity; the address is the node's setting
|
|
// for the module that serves the artifact store, and it is read from there when a reference is
|
|
// composed, never written into a record.
|
|
//
|
|
// So a kept reference has a scheme of the mesh's own, named after the provision that answers it:
|
|
//
|
|
// artifact-store://<module>/<artifact>@sha256:<hex> an image
|
|
// artifact-store://<module>/<artifact>/blobs/sha256:<hex> an archive
|
|
//
|
|
// No runtime knows the scheme. That is the point of it being one: a reference that leaks to a
|
|
// machine uncomposed is refused by the runtime as malformed, in front of whoever sent it, rather
|
|
// than pulled from a public registry that happens to have a repository by that name — which is
|
|
// what an address-less `<module>/<artifact>@sha256:…` would be.
|
|
|
|
// ArtifactStoreScheme marks a reference to something in the mesh's artifact store, kept without
|
|
// the store's address.
|
|
const ArtifactStoreScheme = ArtifactStoreProvision + "://"
|
|
|
|
// Recorded is a reference as the mesh records it: the artifact store's address, if the builder wrote
|
|
// one, taken off.
|
|
//
|
|
// For a reference the builder announced — one it pushed to the store — which is the only kind
|
|
// this is called on. An image the builder named `<host>/<path>@sha256:…` is kept as its path; an
|
|
// archive it named `http://<host>/v2/<path>/blobs/<digest>` likewise. A reference with no address
|
|
// in it — an image id from a genesis build that had nowhere to publish, a package version — is
|
|
// what it was.
|
|
func Recorded(reference string) string {
|
|
if strings.HasPrefix(reference, ArtifactStoreScheme) {
|
|
return reference
|
|
}
|
|
if rest, isURL := strings.CutPrefix(reference, "http://"); isURL {
|
|
if _, path, ok := strings.Cut(rest, "/v2/"); ok && strings.Contains(path, "/blobs/") {
|
|
return ArtifactStoreScheme + path
|
|
}
|
|
return reference
|
|
}
|
|
host, path, ok := strings.Cut(reference, "/")
|
|
if !ok || !isRegistryHost(host) || !strings.Contains(path, "@sha256:") {
|
|
return reference
|
|
}
|
|
return ArtifactStoreScheme + path
|
|
}
|
|
|
|
// isRegistryHost is the runtime's own rule for reading the first component of a reference as a
|
|
// registry rather than as a namespace: it has a dot or a port in it, or it is localhost.
|
|
func isRegistryHost(component string) bool {
|
|
return component == "localhost" || strings.ContainsAny(component, ".:")
|
|
}
|
|
|
|
// InArtifactStore reports whether a reference is a kept one, and what it names there.
|
|
func InArtifactStore(reference string) (path string, kept bool) {
|
|
return strings.CutPrefix(reference, ArtifactStoreScheme)
|
|
}
|
|
|
|
// Routed is a kept reference as a machine fetches it, through the artifact store at `address`
|
|
// (host:port). A reference that is not a kept one is what it was.
|
|
func Routed(reference, address string) string {
|
|
path, kept := InArtifactStore(reference)
|
|
if !kept {
|
|
return reference
|
|
}
|
|
if strings.Contains(path, "/blobs/") {
|
|
return "http://" + address + "/v2/" + path
|
|
}
|
|
return address + "/" + path
|
|
}
|
|
|
|
// Rerouted is a reference the mesh recorded, whichever way it was recorded, as a machine fetches
|
|
// it now: a kept one composed with the store's address, and one recorded before references were
|
|
// kept without their address — the builder's own `<host>/<path>@sha256:…` — re-routed to where
|
|
// the store is now. Only for references that are the mesh's own: everything a build record
|
|
// holds is, by construction.
|
|
func Rerouted(reference, address string) string {
|
|
return Routed(Recorded(reference), address)
|
|
}
|
|
|
|
// artifactsInto composes the artifact store's address into a resource's `image` and `source`.
|
|
//
|
|
// **Composed here, at the last moment before a machine, and stored nowhere.** A kept reference is
|
|
// routed through the store as this network reaches it now. A reference recorded with an address
|
|
// before references were kept without one is re-routed the same way — but only when the mesh
|
|
// built it (`with.Built` names every `<module>/<artifact>` it has), because a module may run an
|
|
// image from a public registry under its own name and that one is exactly where it says.
|
|
//
|
|
// A kept reference with no store to route it through is refused: sent as it is, the runtime would
|
|
// refuse the scheme on the machine, one push away from the reason.
|
|
//
|
|
// **What this does not reach: the images genesis pinned.** The installer builds the control plane
|
|
// and the builder before the mesh exists, pushes them itself and pins their manifests to
|
|
// `<registry>:<port>/mesh-controller@…` and `<registry>:<port>/mesh-builder@…` — single-segment
|
|
// repositories with no build record, so `with.Built` does not name them and they are left as
|
|
// written until each is rebuilt through the mesh, which records it by digest and path. Until then
|
|
// a registry that moves strands exactly those two on a recreate, and the control plane's is the
|
|
// one that cannot be repaired through the mesh. Rebuild both through `build` before moving the
|
|
// store (novox/hq 04-ISSUES/102, finding F4).
|
|
func artifactsInto(resource map[string]any, module string, with Rendering) error {
|
|
for _, key := range []string{"image", "source"} {
|
|
written, ok := resource[key].(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if _, kept := InArtifactStore(written); kept {
|
|
if with.ArtifactStore == "" {
|
|
return fmt.Errorf(
|
|
"%s's %v names %s, which is in the mesh's artifact store, and this mesh has no "+
|
|
"artifact store on its network to fetch it from — nothing assigned offers "+
|
|
"%s, or the machine offering it is not on the private network",
|
|
module, resource["id"], written, ArtifactStoreProvision)
|
|
}
|
|
resource[key] = Routed(written, with.ArtifactStore)
|
|
continue
|
|
}
|
|
if with.ArtifactStore == "" {
|
|
continue
|
|
}
|
|
recorded := Recorded(written)
|
|
if recorded == written {
|
|
continue
|
|
}
|
|
path, _ := InArtifactStore(recorded)
|
|
repository := path
|
|
if at := strings.IndexAny(path, "@"); at >= 0 {
|
|
repository = path[:at]
|
|
} else if blobs := strings.Index(path, "/blobs/"); blobs >= 0 {
|
|
repository = path[:blobs]
|
|
}
|
|
if with.Built[repository] {
|
|
resource[key] = Routed(recorded, with.ArtifactStore)
|
|
}
|
|
}
|
|
return nil
|
|
}
|