The beds name images the way a machine would find them
Twenty-eight integration tests each carried their own copy of the same two helpers, which pointed a manifest and the substrate bundle at whatever the lab's registry had assigned. They now share two in the harness, and the difference is the point: ours is rewritten to the ID the machine holds it under, and everything else is left exactly as written so the machine pulls it. **The substrate bundle is where the fiction was most load-bearing.** mesh-host's `examples/substrate-first-node.lock` pins all three of its images at `192.0.2.250:5000/…`, which is the address the lab's registry served from — it was written for a target, and the target was the lab. Two of those are ordinary third-party images and become the digests mesh-catalog's own postgres and lavinmq modules pin, so the substrate's store and broker are literally the images the mesh runs. mesh-control exists in no registry at all and becomes the ID the machine was handed. **The bundle itself should be fixed in mesh-host and this substitution deleted with it.** Beds that wrote a manifest by hand named an image by repository and let the rewrite supply a digest. There is nothing to supply one now, so `onTheMachine` refuses an unpinned reference and hands back the digest the catalogue pins — a bed runs the image the mesh ships, and a bed that drifts from the catalogue is testing a different postgres. Three beds took a third-party image out of the raised list, which no longer contains one: certificates (pebble), objectstore (minio and its client) and provisioner (postgres) now name theirs and pull it. builds and mesh publish into the MESH's own artifact store — the `registry` module's image, on the node, on 5000 — rather than into scenery the lab raised. That is a different claim, and only one of them exists in production. New unit tests cover what a full raise would otherwise be the only way to check: the routes an egress machine gets (that its gateway is still the path to the rest of the scenario, that a range with no path is unreachable rather than leaked to the uplink, that each family gets its own next hop), which machine is handed which of our images, and the `images:` rule that refuses a third-party entry. The "shipped scenarios are valid" test now loads every scenario rather than two of them. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -20,7 +20,8 @@ import { loadScenario } from "../../src/declaration/parse.ts";
|
||||
import { raise } from "../../src/lifecycle/raise.ts";
|
||||
import { destroy, exec } from "../../src/lifecycle/operate.ts";
|
||||
import { hostBinaryPath, HOST_PATH } from "../../src/lifecycle/place.ts";
|
||||
import { labIsUsable, destroyAll } from "./harness.ts";
|
||||
import { labIsUsable, destroyAll, substrateBundle } from "./harness.ts";
|
||||
import type { HeldImage } from "../../src/pinning.ts";
|
||||
import { incus } from "../../src/incus/client.ts";
|
||||
import { machineName } from "../../src/lifecycle/names.ts";
|
||||
|
||||
@@ -42,7 +43,22 @@ const skip = !capability.usable
|
||||
const SCENARIO = "first-node";
|
||||
const MACHINE = "anchor";
|
||||
let instanceId = "";
|
||||
let registry = "";
|
||||
|
||||
/**
|
||||
* Where a build publishes to.
|
||||
*
|
||||
* **The mesh has a registry, and this is that one.** `mesh-catalog/modules/registry` serves the
|
||||
* mesh's artifact store on port 5000; a build publishes into it. This test starts the same image
|
||||
* on the machine directly rather than assigning the module, because what is under test is the
|
||||
* build chain and not module delivery.
|
||||
*
|
||||
* It used to publish into the registry the LAB raised inside the scenario — scenery pretending to
|
||||
* be upstream, which is the thing this change removed. A registry the mesh runs and a registry the
|
||||
* lab runs are different claims, and only the first exists in production.
|
||||
*/
|
||||
const ARTIFACT_STORE =
|
||||
"registry@sha256:a3d8aaa63ed8681a604f1dea0aa03f100d5895b6a58ace528858a7b332415373";
|
||||
const registry = "127.0.0.1:5000";
|
||||
|
||||
function quote(s: string): string {
|
||||
return `'${s.replaceAll("'", `'\\''`)}'`;
|
||||
@@ -66,28 +82,33 @@ async function mesh(command: string): Promise<string> {
|
||||
return must(`docker exec mesh-control /mesh-control ${command}`);
|
||||
}
|
||||
|
||||
/** The bundle, pointed at this scenario's own registry. */
|
||||
function bundleFor(images: string[]): string {
|
||||
let text = readFileSync(bundle, "utf8");
|
||||
for (const pinned of images) {
|
||||
const repository = pinned.slice(pinned.indexOf("/") + 1, pinned.indexOf("@"));
|
||||
const escaped = repository.replaceAll("/", "\\/").replaceAll(".", "\\.");
|
||||
text = text.replaceAll(new RegExp(`[A-Za-z0-9_.:-]+\\/${escaped}@sha256:[0-9a-f]+`, "g"), pinned);
|
||||
}
|
||||
return text;
|
||||
/** The bundle: ours by the ID the machine holds, everything else upstream. */
|
||||
function bundleFor(images: HeldImage[]): string {
|
||||
return substrateBundle(bundle, images);
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
if (skip) return;
|
||||
const raised = await raise(loadScenario(`scenarios/${SCENARIO}.yml`), {});
|
||||
instanceId = raised.instanceId;
|
||||
const first = raised.images[0];
|
||||
assert.ok(first, "the scenario stocked no images, so there is no registry to publish to");
|
||||
registry = first.slice(0, first.indexOf("/"));
|
||||
|
||||
await must(`cat > /tmp/substrate.lock <<'MESHBUNDLE'\n${bundleFor(raised.images)}\nMESHBUNDLE`);
|
||||
await must(`${HOST_PATH} apply /tmp/substrate.lock`);
|
||||
|
||||
// The mesh's artifact store, standing where the `registry` module would. Read back rather than
|
||||
// assumed: a builder publishing into a registry that never came up fails several minutes later,
|
||||
// as a manifest naming a blob nobody has.
|
||||
await must(
|
||||
`docker run -d --name mesh-registry --restart unless-stopped ` +
|
||||
`-p ${registry}:5000 ${ARTIFACT_STORE}`,
|
||||
);
|
||||
let serving = false;
|
||||
for (let i = 0; i < 30 && !serving; i++) {
|
||||
({ ok: serving } = await on(`curl -sf http://${registry}/v2/ >/dev/null`));
|
||||
if (!serving) await new Promise((r) => setTimeout(r, 2_000));
|
||||
}
|
||||
assert.ok(serving, "the mesh's artifact store never answered, so a build has nowhere to publish");
|
||||
|
||||
// A module repository on the machine. Local rather than fetched, because what is under test is
|
||||
// the mesh's chain and not whether the lab can reach a forge.
|
||||
await must(`mkdir -p /root/shell/files`);
|
||||
|
||||
Reference in New Issue
Block a user