One name per thing, per the HQ glossary: the module/container/image/binary/repo becomes mesh-controller, the seat the-controller, and the store+broker pair the foundation (embedded base bundles, default template and example lock renamed with their go:embed directives). No behaviour change — a pure vocabulary rename. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
143 lines
7.2 KiB
TypeScript
143 lines
7.2 KiB
TypeScript
/**
|
|
* Naming the mesh's own images by what they are.
|
|
*
|
|
* **A digest is not knowable until something is built** (novox/hq 04-ISSUES/025). A manifest in a
|
|
* repository can pin a third-party image, because somebody can ask a registry what a tag points
|
|
* at. It cannot pin an image the mesh builds itself: mesh-controller, mesh-builder, mesh-route-proxy,
|
|
* the per-module runtimes and the provisioners exist in no registry, so there is no manifest
|
|
* digest to write down. The catalogue ships sixty-four zeros for them, which parses, resolves,
|
|
* composes — and stops on the machine.
|
|
*
|
|
* The lab used to answer that with a registry of its own: raise one inside the scenario, push
|
|
* everything into it, and rewrite every reference — third-party ones included — to the digest it
|
|
* assigned. **That registry does not exist in production, so the lab was testing a fiction**, and
|
|
* the fiction hid the bootstrap problems it was supposed to find.
|
|
*
|
|
* What is true instead is two things:
|
|
*
|
|
* - **Third-party images are pulled from the internet.** They are left exactly as written, and
|
|
* the machine fetches them over its `egress` uplink the way any machine does.
|
|
* - **The mesh's own images are built and handed over.** They are loaded onto the machine from
|
|
* the workstation that built them, and named by the digest of their own image configuration —
|
|
* a bare `sha256:…`, which mesh-host accepts as "an image this machine already holds"
|
|
* (mesh-host, *an image may be named by the digest of its own configuration*).
|
|
*
|
|
* So the rewriting that remains is only the second kind, and it matches on the **repository**,
|
|
* because that is the part a person writes and the only part a placeholder digest does not say.
|
|
*/
|
|
|
|
/** What the lab loaded onto a machine, and what a declaration should call it. */
|
|
export interface HeldImage {
|
|
/** As the scenario asked for it, a tag this workstation holds: `mesh-runtime-postgres:development`. */
|
|
requested: string;
|
|
/** What a manifest names it by, with no tag and no digest: `mesh-runtime-postgres`. */
|
|
repository: string;
|
|
/**
|
|
* The reference a declaration uses: a bare `sha256:<64 hex>`.
|
|
*
|
|
* The image's own ID — the digest of its configuration — which `docker load` preserves, so the
|
|
* name is the same on the workstation that built it and on every machine it was handed to.
|
|
*/
|
|
reference: string;
|
|
}
|
|
|
|
/** `alpine:3.20` and `alpine` both mean `alpine`; `foo/bar:1` means `foo/bar`. */
|
|
export function repositoryOf(reference: string): string {
|
|
const withoutDigest = reference.split("@")[0] ?? reference;
|
|
const lastColon = withoutDigest.lastIndexOf(":");
|
|
const lastSlash = withoutDigest.lastIndexOf("/");
|
|
return lastColon > lastSlash ? withoutDigest.slice(0, lastColon) : withoutDigest;
|
|
}
|
|
|
|
/**
|
|
* Whether a reference names an image the mesh builds for itself.
|
|
*
|
|
* **Derived from the shape the build produces, not from a list of names.** `make image
|
|
* builder-image provisioner-image objectstore-image redis-provisioner-image proxy-image` in
|
|
* mesh-controller and `scripts/build-module-runtime.sh` here both tag their output `mesh-<something>`
|
|
* with no registry host and no upstream organisation — that is what "built here, published
|
|
* nowhere" looks like, and a hardcoded list would go stale the first time a module is added.
|
|
*
|
|
* The absence of a slash carries the weight: `ghcr.io/mailu/admin` and
|
|
* `registry.example/novox/www` both say where they are fetched from, and a bare
|
|
* `mesh-runtime-plex` says there is nowhere.
|
|
*/
|
|
export function isMeshBuilt(reference: string): boolean {
|
|
const repository = repositoryOf(reference);
|
|
return !repository.includes("/") && repository.startsWith("mesh-");
|
|
}
|
|
|
|
/**
|
|
* Registries an anonymous pull works against.
|
|
*
|
|
* Not a list of what is trusted — a list of where no account is needed. Everything else wants one,
|
|
* and a scenario machine has none.
|
|
*/
|
|
const PUBLIC_REGISTRIES = [
|
|
"docker.io", "ghcr.io", "quay.io", "lscr.io", "gcr.io", "registry.k8s.io",
|
|
"public.ecr.aws", "mcr.microsoft.com", "docker.elastic.co", "registry.gitlab.com",
|
|
];
|
|
|
|
/** The registry a reference names, or "" when it names none and so means Docker Hub. */
|
|
export function registryOf(reference: string): string {
|
|
const first = repositoryOf(reference).split("/")[0] ?? "";
|
|
// A first segment is a registry only if it looks like a host: `novox/www` is an organisation on
|
|
// Docker Hub; `registry.example/novox/www` is somewhere else entirely.
|
|
return first.includes(".") || first.includes(":") ? first : "";
|
|
}
|
|
|
|
/**
|
|
* Whether the workstation has to hand this image over rather than let the machine fetch it.
|
|
*
|
|
* **Two reasons, one consequence.** An image the mesh builds for itself exists in no registry at
|
|
* all. An image in the operator's *private* registry exists in one the machines have no account
|
|
* for, and the pull fails with `no basic auth credentials` — which is not something more patience
|
|
* fixes. Either way the machine cannot get it alone, so the workstation, which does hold the
|
|
* credential, exports it and loads it.
|
|
*
|
|
* **This stands in for something, and it is worth saying what.** In a finished mesh these are built
|
|
* by the mesh's builder and published to the mesh's own store, and every machine pulls them from
|
|
* there with a credential the mesh granted it. Until that store exists there is nowhere for them to
|
|
* come from — and handing them over is the closest honest thing to it, rather than a registry the
|
|
* lab invents, which is exactly what was just removed.
|
|
*/
|
|
export function mustBeHandedOver(reference: string): boolean {
|
|
if (isMeshBuilt(reference)) return true;
|
|
const registry = registryOf(reference);
|
|
return registry !== "" && !PUBLIC_REGISTRIES.includes(registry);
|
|
}
|
|
|
|
/**
|
|
* Replace every reference to one of the mesh's own images with the image the machine holds.
|
|
*
|
|
* Matching is on the repository and ignores whatever digest was written down — a manifest says
|
|
* `mesh-runtime-postgres@sha256:0000…` and means *the runtime this machine was given*.
|
|
*
|
|
* **Everything else is left exactly as it is.** `postgres@sha256:7456…`, `gitea/gitea@sha256:…`
|
|
* and `ghcr.io/mailu/admin@sha256:…` are pulled from the internet over the machine's uplink, which
|
|
* is what a real machine does and the reason the lab's own registry is gone.
|
|
*/
|
|
export function pinnedInto(text: string, held: HeldImage[]): string {
|
|
let out = text;
|
|
for (const image of held) {
|
|
const escaped = image.repository.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
// Optionally a registry, then the repository, then any digest. Anchored on a quote or
|
|
// whitespace so a longer repository ending in a shorter one is not half-replaced.
|
|
out = out.replaceAll(
|
|
new RegExp(`(?<=^|["\\s])(?:[A-Za-z0-9_.:-]+\\/)*${escaped}@sha256:[0-9a-f]{64}`, "g"),
|
|
image.reference,
|
|
);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** The reference for one repository, or undefined if this scenario loaded no such image. */
|
|
export function referenceFor(held: HeldImage[], repository: string): string | undefined {
|
|
return held.find((image) => image.repository === repository)?.reference;
|
|
}
|
|
|
|
/** Whether anything is still pinned to a placeholder, which would fail on the machine. */
|
|
export function stillUnpinned(text: string): string[] {
|
|
return [...text.matchAll(/([A-Za-z0-9_.:/-]+)@sha256:0{64}/g)].map((m) => m[1]!);
|
|
}
|