/** * Rewriting an image reference to the one a scenario's own registry serves. * * **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: that image does not exist yet, and when it * does its digest belongs to whichever registry served it. * * The bundle has always had this problem and solves it by rewriting references once the scenario's * registry is up and its digests are known. Modules have exactly the same problem and were solving * it by shipping sixty-four zeros, which parses, resolves, composes — and stops on the machine. * * So the rewriting is shared rather than copied, and matches on the **repository**, because that * is the part a person writes and the only part that survives being served somewhere else. */ /** `192.0.2.250:5000/ghcr.io/mailu/admin@sha256:…` → `ghcr.io/mailu/admin` */ export function repositoryOf(pinned: string): string { const at = pinned.indexOf("@"); const body = at === -1 ? pinned : pinned.slice(0, at); const slash = body.indexOf("/"); // Everything after the registry. A reference with no slash at all is its own repository. return slash === -1 ? body : body.slice(slash + 1); } /** * Replace every reference to a stocked repository with the reference this scenario serves. * * Matching is on the repository and ignores whatever registry and digest were written down — * a file may name `postgres@sha256:7456…` or `mesh-provision-postgres@sha256:0000…` and both mean * *the postgres this scenario has*. That is the whole point: the text says which image, the * scenario says which copy. * * A repository the scenario did not stock is left alone rather than blanked. It may be reachable * some other way, and silently emptying a reference would produce the exact failure this exists to * prevent. */ export function pinnedInto(text: string, served: string[]): string { let out = text; for (const pinned of served) { const repository = repositoryOf(pinned); const escaped = 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"), pinned, ); } return out; } /** 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]!); }