mesh.test.ts: meshboard gets a slug (ADR 0049); the builder-as-module test is retired, genesis proves it
This commit is contained in:
@@ -326,7 +326,8 @@ test("a credential reaches both ends and the mesh holds neither", { skip, timeou
|
||||
// real program takes a credential: a sealed file is a password alone, and almost nothing reads
|
||||
// one. The mesh cannot compose the document — it discarded the value — so the module supplies it
|
||||
// with `${secret:...}` in it and the host, the only thing that sees both halves, fills it in.
|
||||
await must("anchor", `printf %s '{"module":"meshboard","version":"1",` +
|
||||
// A slug, so its identity on a backend stays within an S3 access key's 20 characters (ADR 0049).
|
||||
await must("anchor", `printf %s '{"module":"meshboard","version":"1","slug":"board",` +
|
||||
`"requires":["postgres-database"],"contributes":{"postgres-database":{"name":"meshboard"}},` +
|
||||
`"binds":{"postgres-database":"/etc/meshboard/database.json"},` +
|
||||
`"secrets":{"postgres-database":"/etc/meshboard/database.password"},` +
|
||||
@@ -814,117 +815,11 @@ test("a machine filters exactly what its modules declared, and nothing else", {
|
||||
"the port stayed open after the module that wanted it was removed");
|
||||
});
|
||||
|
||||
test("the builder is a module the mesh assigns, with a credential the mesh delivered", {
|
||||
skip: skip || (!builder ? "set MESH_LAB_BUILDER to a built mesh-builder" : false),
|
||||
timeout: 900_000,
|
||||
}, async () => {
|
||||
// Until this, the builder was a program somebody started on a machine with whatever credential
|
||||
// they had to hand — in practice the broker's administrative one. A program documented as
|
||||
// holding its own credential and given somebody else's is worse than one with no story at all.
|
||||
//
|
||||
// So: the mesh issues a scoped account, seals it to the machine, and delivers it with the
|
||||
// declaration. Nobody types it and the mesh cannot read it back.
|
||||
await must("anchor", `mkdir -p /root/builder && printf %s '{"module":"self-builder","version":"1",` +
|
||||
`"requires":["artifact-store"],"capabilities":["container-runtime"],` +
|
||||
`"claims":[{"name":"the-build-machine","scope":"node"}],` +
|
||||
`"binds":{"artifact-store":"/var/lib/mesh/builder/artifact-store.json"},` +
|
||||
`"own-secrets":{"broker":"/var/lib/mesh/builder/broker"},` +
|
||||
`"build":{"artifacts":[{"name":"builder","kind":"upstream",` +
|
||||
`"from":"${pinned("mesh-builder")}"}]},` +
|
||||
`"resources":[` +
|
||||
`{"id":"state","type":"directory","path":"/var/lib/mesh/builder","mode":"0700"},` +
|
||||
`{"id":"workspace","type":"directory","path":"/var/lib/mesh/builder/workspace","mode":"0700"},` +
|
||||
`{"id":"run","type":"container","name":"mesh-builder","artifact":"builder",` +
|
||||
`"network":"host",` +
|
||||
`"volumes":["/var/lib/mesh/builder:/var/lib/mesh/builder",` +
|
||||
`"/var/run/docker.sock:/var/run/docker.sock"],` +
|
||||
`"env":{"MESH_BROKER_FILE":"/var/lib/mesh/builder/broker",` +
|
||||
`"MESH_BINDING":"/var/lib/mesh/builder/artifact-store.json",` +
|
||||
`"MESH_WORKSPACE":"/var/lib/mesh/builder/workspace"}}]}' > /root/builder/module.json`);
|
||||
await must("anchor", `cd /root/builder && git init -q . && git add -A && ` +
|
||||
`git -c user.email=lab -c user.name=lab commit -qm builder`);
|
||||
|
||||
// The builder's own image is built by the builder that is already running — the same
|
||||
// chicken-and-egg as the registry, resolved the same way. The one started by hand does this
|
||||
// last piece of work and is then replaced by the module it just built.
|
||||
await mesh("build /root/builder --wait 300s", 420_000);
|
||||
|
||||
// The mesh makes the account and seals the URL to this machine. Nothing is printed that would
|
||||
// work if it were pasted somewhere else.
|
||||
const issued = await mesh("builder issue lab-builder --node anchor");
|
||||
assert.match(issued, /sealed to anchor/, issued);
|
||||
assert.doesNotMatch(issued, /amqps:\/\/lab-builder:/,
|
||||
"the credential was printed, so the one copy that matters is on a terminal");
|
||||
|
||||
// Now the hand-started one goes, or two builders race for the same queue and whichever answers
|
||||
// proves nothing. By process name: `pkill -f` matches the shell running it too, which kills the
|
||||
// connection carrying the command and hangs the caller waiting for a reply that will never
|
||||
// come. Cost an hour once, in this file.
|
||||
await on("anchor", `pkill -x mesh-builder`);
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
assert.ok(!(await on("anchor", `pgrep -x mesh-builder`)).ok,
|
||||
"the hand-started builder is still running, so this would test that one");
|
||||
|
||||
await mesh("assign anchor self-builder");
|
||||
await mesh("push anchor");
|
||||
await new Promise((r) => setTimeout(r, 20_000));
|
||||
|
||||
const running = await must("anchor", `docker ps --format '{{.Names}}'`);
|
||||
assert.match(running, /mesh-builder/,
|
||||
`the builder was assigned and is not running:\n${running}\n` +
|
||||
`${(await on("anchor", `tail -30 /var/log/mesh-host.log`)).out}`);
|
||||
|
||||
// Running is not connected. A builder that cannot reach the broker sits there, and every
|
||||
// outward sign — the container is up, the credential is on disk — says it is working.
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
const said = await on("anchor", `docker logs mesh-builder 2>&1 | tail -20`);
|
||||
assert.doesNotMatch(said.out, /cannot reach the broker/,
|
||||
`the builder is running and cannot reach the broker:\n${said.out}`);
|
||||
|
||||
// The credential arrived, is readable only by the machine, and is the scoped account rather
|
||||
// than the broker's own.
|
||||
assert.match(await must("anchor", `stat -c %a /var/lib/mesh/builder/broker`), /^600/);
|
||||
const credential = await must("anchor", `cat /var/lib/mesh/builder/broker`);
|
||||
assert.match(credential, /"url":"amqps:\/\/lab-builder:/,
|
||||
"the builder is using an account that is not its own");
|
||||
assert.doesNotMatch(credential, /guest:guest/, "the builder holds the broker's own account");
|
||||
// And what to check the broker against. A mesh's broker presents a certificate of the mesh's
|
||||
// own, so a URL alone reaches only a broker some public authority vouches for — which is no
|
||||
// mesh broker at all, and fails at TLS with an error about an unknown authority.
|
||||
assert.match(credential, /"fingerprint":"(sha256:)?[0-9a-f]{64}"/,
|
||||
`the builder was given nothing to verify the broker with:\n${credential}`);
|
||||
|
||||
// And it works: the mesh asks this builder to build something, and it does. Answering is the
|
||||
// only proof that the delivered credential authenticates — a container that is up with a
|
||||
// credential it cannot use looks identical from outside.
|
||||
// Somewhere the builder can actually see. A builder that is a module runs in a container, so
|
||||
// the machine's filesystem is not its own — a path like /root only works for a builder somebody
|
||||
// started on the host, which is what the first build above used. In a real mesh a module is
|
||||
// cloned from the forge over a URL; here it goes in the directory the module already mounts,
|
||||
// which is the same fact wearing different clothes.
|
||||
const repo = "/var/lib/mesh/builder/repositories/built";
|
||||
await must("anchor", `mkdir -p ${repo} && printf %s '{"module":"built","version":"1",` +
|
||||
`"resources":[{"id":"marker","type":"file","path":"/etc/built","content":"yes","mode":"0644"}]}' ` +
|
||||
`> ${repo}/module.json`);
|
||||
await must("anchor", `cd ${repo} && git init -q . && git add -A && ` +
|
||||
`git -c user.email=lab -c user.name=lab commit -qm built`);
|
||||
try {
|
||||
await mesh(`build ${repo} --wait 300s`, 420_000);
|
||||
} catch (why) {
|
||||
// The builder's own account of itself. Without it the failure is "nothing consumed the
|
||||
// queue", which names no cause and is the same sentence whether the credential was refused,
|
||||
// the queue was never declared, or the process died three seconds in.
|
||||
const said = (await on("anchor", `docker logs mesh-builder 2>&1 | tail -40`)).out;
|
||||
throw new Error(`${(why as Error).message}\n\nwhat the builder said:\n${said}`);
|
||||
}
|
||||
|
||||
// Naming the module, and not merely containing its name: `builds` says "nothing has been built
|
||||
// yet" when there is nothing, and that sentence contains the word this was matching on.
|
||||
const recorded = await mesh("builds built");
|
||||
assert.doesNotMatch(recorded, /nothing has been built/,
|
||||
`the build was accepted and no build was recorded against the module:\n${recorded}`);
|
||||
assert.match(recorded, /built/, recorded);
|
||||
});
|
||||
// The builder as a module the mesh assigns, with a credential the mesh delivered, is what genesis
|
||||
// proves now (genesis-single installs the catalogue's builder through the installer, novox/hq ADR
|
||||
// 0069). The test that lived here declared the builder's image as an upstream artifact by the bare
|
||||
// image ID the lab holds, which is not a reference a registry copy can fetch (ADR 0096); retired
|
||||
// 2026-09-21 rather than rewritten into a second genesis.
|
||||
|
||||
test("rotating a credential moves both ends, and the old one stops working", {
|
||||
skip, timeout: 900_000,
|
||||
|
||||
Reference in New Issue
Block a user