mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
test(testbed): the stage fence must assert CONTENT ARRIVED, not just a directory
Caught while dev-planner was building against this helper: my runbook's fence asserts a real file (shared.yaml) but the HELPER fenced only the directory — which mkdir -p had just created. So a delivery that shipped NOTHING would pass it: a shell pipeline reports only the LAST command's exit status, so a failing tar piped into a succeeding docker exec yields an empty stage that is perfectly readable and writable. The fence would go green and the real symptom would surface as a file-not-found from rig up, three steps downstream. stageFenceArgv now takes an optional expectFile and targets it (writability still probed on the directory); stageTopologyPlan threads it through. The weaker directory-only form remains available for callers with nothing specific to expect, but the topology path now names its file. Same class as the four before it: a check that passes while proving nothing. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
cae0964b19
commit
4fa5ba48ea
2 files changed
+25
-4
No files matched your search
@@ -157,10 +157,16 @@ export function stageExtractArgv(container: string, stagePath: string): string[]
|
||||
/** The pre-flight the stage must pass before anything depends on it: readable AND
|
||||
* WRITABLE as the exec user, probed by DOING (touch/rm) rather than by reading mode
|
||||
* bits — bits lie under ownership, ACLs, and read-only mounts. */
|
||||
export function stageFenceArgv(container: string, stagePath: string): string[] {
|
||||
export function stageFenceArgv(container: string, stagePath: string, expectFile?: string): string[] {
|
||||
// ASSERT CONTENT ARRIVED, not merely that the directory exists — `mkdir -p` created
|
||||
// it, so a delivery that shipped NOTHING (a tar failure whose non-zero exit the pipe
|
||||
// masked, since a shell pipeline reports only the LAST command's status) leaves an
|
||||
// empty-but-perfectly-readable-and-writable stage. Fencing the directory alone would
|
||||
// pass it and defer the real symptom to a file-not-found from `rig up` downstream.
|
||||
const target = expectFile ? `${stagePath}/${expectFile}` : stagePath;
|
||||
return [
|
||||
"exec", container, "sh", "-c",
|
||||
`test -r '${stagePath}' && touch '${stagePath}/.fence-write' && rm -f '${stagePath}/.fence-write'`,
|
||||
`test -r '${target}' && touch '${stagePath}/.fence-write' && rm -f '${stagePath}/.fence-write'`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -177,7 +183,7 @@ export function stageFenceArgv(container: string, stagePath: string): string[] {
|
||||
* Returns docker argv arrays for the consumer's own docker seam; `tarSource` is
|
||||
* the tar-side argv whose stdout pipes into `extract`'s stdin.
|
||||
*/
|
||||
export function stageTopologyPlan(opts: { container: string; hostDir: string; name?: string }): {
|
||||
export function stageTopologyPlan(opts: { container: string; hostDir: string; name?: string; expectFile?: string }): {
|
||||
stagePath: string;
|
||||
steps: Array<{ label: string; argv: string[]; stdinFrom?: string[] }>;
|
||||
} {
|
||||
@@ -191,7 +197,7 @@ export function stageTopologyPlan(opts: { container: string; hostDir: string; na
|
||||
argv: stageExtractArgv(opts.container, stagePath),
|
||||
stdinFrom: stageTarSourceArgv(opts.hostDir),
|
||||
},
|
||||
{ label: "fence", argv: stageFenceArgv(opts.container, stagePath) },
|
||||
{ label: "fence", argv: stageFenceArgv(opts.container, stagePath, opts.expectFile) },
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -92,3 +92,18 @@ describe("stageTopologyPlan — the ordering is the contract", () => {
|
||||
expect(plan.steps[1]!.stdinFrom).toEqual(["-C", "/host/topologies", "-cf", "-", "."]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("stage fence asserts CONTENT ARRIVED, not just a directory", () => {
|
||||
it("with expectFile the fence targets the FILE (an empty stage cannot pass)", async () => {
|
||||
const m = await import("./helpers/testbed-published-daemon.js");
|
||||
const cmd = m.stageFenceArgv("H_A", "/home/openrig/topologies", "shared.yaml").join(" ");
|
||||
expect(cmd).toContain("test -r '/home/openrig/topologies/shared.yaml'");
|
||||
expect(cmd).toContain("touch '/home/openrig/topologies/.fence-write'"); // writability still on the dir
|
||||
});
|
||||
|
||||
it("stageTopologyPlan threads expectFile into the fence step", async () => {
|
||||
const m = await import("./helpers/testbed-published-daemon.js");
|
||||
const plan = m.stageTopologyPlan({ container: "H_A", hostDir: "/host/t", expectFile: "rig-a.yaml" });
|
||||
expect(plan.steps[2]!.argv.join(" ")).toContain("/home/openrig/topologies/rig-a.yaml");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user