Both beds now pass a repository and a commit, and refuse to run without them rather than raising a machine the installer cannot finish.
137 lines
5.6 KiB
TypeScript
137 lines
5.6 KiB
TypeScript
/**
|
|
* Rebuild what the lab runs, from source, before it runs.
|
|
*
|
|
* **A stale artifact reporting success against old rules is the fault this project keeps writing
|
|
* down** (novox/hq 04-ISSUES/005). The lab consumes a handful of artifacts from two repositories,
|
|
* and they
|
|
* were rebuilt by hand, one at a time, from memory. A rename in the control plane's catalogue needs
|
|
* both the control-plane image *and* the builder binary, because both parse manifests; rebuilding
|
|
* one left a binary eleven hours old refusing a field the mesh had just renamed, and cost a full
|
|
* run to find out.
|
|
*
|
|
* In the repository rather than in a shell script beside it, for the reason 005 is about: a step
|
|
* that lives in somebody's terminal history runs when they remember, and remembering is not a
|
|
* mechanism.
|
|
*/
|
|
|
|
import { spawnSync } from "node:child_process";
|
|
import { repositories } from "./repos.ts";
|
|
|
|
export interface Build {
|
|
/** What it produces, for the log. */
|
|
what: string;
|
|
/** The repository root to run in. */
|
|
in: string;
|
|
argv: string[];
|
|
env?: NodeJS.ProcessEnv;
|
|
}
|
|
|
|
/**
|
|
* planned is what must be built, given where this run has been pointed.
|
|
*
|
|
* Derived from the same environment the suite is configured by, so there is one place that says
|
|
* where a repository is. A repository this run was not pointed at is not built — and, per
|
|
* {@link whatWasTested}, is not claimed in the receipt either.
|
|
*/
|
|
export function planned(env: NodeJS.ProcessEnv = process.env): Build[] {
|
|
const builds: Build[] = [];
|
|
const where = repositories(env);
|
|
const host = env["MESH_LAB_HOST_BINARY"];
|
|
if (host && where["mesh-host"]) {
|
|
builds.push({
|
|
what: "host",
|
|
in: where["mesh-host"],
|
|
argv: ["go", "build", "-ldflags=-s -w -X main.builtFor=arch", "-o", host, "./cmd/mesh-host"],
|
|
env: { CGO_ENABLED: "0" },
|
|
});
|
|
}
|
|
const control = where["mesh-control"];
|
|
if (control) {
|
|
// **Every image the lab runs, not only the control plane's.**
|
|
//
|
|
// On 2026-09-01 a suite ran with a control-plane image built that minute and a provisioner
|
|
// image built the day before. The rotation test failed against a real database, and the
|
|
// failure looked exactly like the change under test being wrong — the provisioner was
|
|
// creating logins by a naming rule that had been replaced.
|
|
//
|
|
// This is the same fault the builder line below was added for, one target along. A rebuild
|
|
// that covers most of what a run uses is worse than one that covers none, because the run
|
|
// that follows it is believed.
|
|
builds.push({
|
|
what: "images",
|
|
in: control,
|
|
argv: ["make", "image", "builder-image", "provisioner-image", "objectstore-image",
|
|
"redis-provisioner-image", "proxy-image"],
|
|
});
|
|
const builder = env["MESH_LAB_BUILDER"];
|
|
if (builder) {
|
|
// Both of these parse manifests. Building one and not the other is the eleven-hour-old
|
|
// binary above, so they are one step and not two.
|
|
builds.push({
|
|
what: "builder",
|
|
in: control,
|
|
argv: ["go", "build", "-o", builder, "./cmd/mesh-builder"],
|
|
});
|
|
}
|
|
}
|
|
|
|
// **The installer, carrying the control plane's image.**
|
|
//
|
|
// Last, and that is an ordering rather than a preference: `make bootstrap` embeds the output of
|
|
// `docker save <image>`, so the image has to have been built by the step above or the installer
|
|
// carries whatever was lying around — the eleven-hour-old artifact again, this time inside a
|
|
// binary where nothing would ever notice.
|
|
//
|
|
// It is built here at all because the bed now bootstraps THROUGH it (novox/hq ADR 0067): the
|
|
// anchor is brought into existence by running the same program a bare machine runs, rather than
|
|
// by the bed applying a substrate bundle by hand and calling that an install. An installer that
|
|
// was stale would be a bed proving something about last week's procedure.
|
|
const installer = env["MESH_LAB_BOOTSTRAP_BINARY"];
|
|
if (installer && where["mesh-host"]) {
|
|
builds.push({
|
|
what: "installer",
|
|
in: where["mesh-host"],
|
|
argv: ["make", "bootstrap", `IMAGE=${carriedImage(env)}`, `BOOTSTRAP_OUT=${installer}`],
|
|
});
|
|
}
|
|
return builds;
|
|
}
|
|
|
|
/**
|
|
* The image the installer carries.
|
|
*
|
|
* **It is the builder, not the control plane** (novox/hq ADR 0073). The installer used to carry the
|
|
* thing it was going to run and now carries the thing that makes it, so a raised mesh holds a
|
|
* control plane it built from a repository and a commit rather than one it was handed.
|
|
*
|
|
* `mesh-builder:development` is what mesh-control's `make builder-image` tags — one tag, said in
|
|
* one place. Overridable because a release installer carries a release image, and nothing about
|
|
* that is the lab's business.
|
|
*/
|
|
export function carriedImage(env: NodeJS.ProcessEnv = process.env): string {
|
|
return env["MESH_LAB_CARRIED_IMAGE"] ?? "mesh-builder:development";
|
|
}
|
|
|
|
/** rebuild runs the plan, and throws on the first failure rather than testing a stale artifact. */
|
|
export function rebuild(env: NodeJS.ProcessEnv = process.env): string[] {
|
|
const built: string[] = [];
|
|
for (const build of planned(env)) {
|
|
const [command, ...args] = build.argv;
|
|
const ran = spawnSync(command!, args, {
|
|
cwd: build.in,
|
|
env: { ...env, ...build.env },
|
|
encoding: "utf8",
|
|
});
|
|
if (ran.status !== 0) {
|
|
// Loudly, and stopping. A suite that runs anyway is a suite reporting on code that is not
|
|
// the code in front of you, which is the whole of 005.
|
|
throw new Error(
|
|
`could not build the ${build.what}: ${build.argv.join(" ")} in ${build.in}\n\n` +
|
|
`${(ran.stderr || ran.stdout || String(ran.error)).trim()}`,
|
|
);
|
|
}
|
|
built.push(build.what);
|
|
}
|
|
return built;
|
|
}
|