lab: a read-only forge account of its own, and no credential left on disk after a stop (hq issue 624, review 196)
mesh/merge-gate head 8d8dcd5ebb86 merged into trunk cd239283207a — pass: builds gitea, lab → novox, shanks; no bus step; every machine composes with the ch…
mesh/repo-check head 8d8dcd5ebb86 merged into trunk cd239283207a — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
mesh/merge-gate head 8d8dcd5ebb86 merged into trunk cd239283207a — pass: builds gitea, lab → novox, shanks; no bus step; every machine composes with the ch…
mesh/repo-check head 8d8dcd5ebb86 merged into trunk cd239283207a — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
The lab's credential came from the package team, which also writes the @novox packages, and the branches a run clones are not reviewed yet. The lab now contributes {"access": "read"} to its npm-package-registry requirement, and gitea's provisioner puts such a consumer on a team of its own, readers: code read on the org's repositories, package read, no write, and off the package team. The build agent, which publishes, stays on packages. A stop's SIGTERM to the run's process group killed the wrapper before it removed the credential file. The run's script now removes it as soon as its clones are done and traps every way it ends, and a run's start and lab_check remove what a crash or a reboot left.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
@@ -149,3 +149,48 @@ test("git presents the stored credential to the forge, and only when one is give
|
||||
assert.doesNotMatch(other.stdout, /password=pw/);
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
// Review 196 on mesh-catalog #272: the credential file outlived a stop, because SIGTERM to the run's
|
||||
// process group killed the wrapper before its cleanup. It is now removed right after the clones, on
|
||||
// every way the script ends, and at the next run's start and in lab_check for what a crash left.
|
||||
import { cleanCredentials, credentialGuard } from "../tools/runs.ts";
|
||||
|
||||
test("a stopped script removes the credential, whatever ends it", () => {
|
||||
for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"] as const) {
|
||||
const dir = mkdtempSync(join(tmpdir(), "lab-guard-"));
|
||||
const file = join(dir, ".run-x.git-credentials");
|
||||
writeFileSync(file, "http://u:p@h\n", { mode: 0o600 });
|
||||
const ran = spawnSync("bash", ["-c", `${credentialGuard(file)}\nkill -${signal.slice(3)} $$\nsleep 5`], { encoding: "utf8", timeout: 10_000 });
|
||||
assert.notEqual(ran.status, 0, `${signal}: the script was not stopped`);
|
||||
assert.equal(existsSync(file), false, `${signal} left the credential on disk`);
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("the script removes the credential as soon as its clones are done", () => {
|
||||
const s = script("/work", "run-x", "http://forge", ["t.test.ts"], {}, "/work/.run-x.git-credentials");
|
||||
const lastClone = s.lastIndexOf("clone --quiet");
|
||||
const removed = s.indexOf("rm -f '/work/.run-x.git-credentials'", lastClone);
|
||||
const building = s.indexOf("npm ci");
|
||||
assert.ok(lastClone > 0 && removed > lastClone && removed < building, "not removed between the clones and the build");
|
||||
assert.match(s, /trap 'rm -f '\\''\/work\/\.run-x\.git-credentials'\\''' EXIT/);
|
||||
});
|
||||
|
||||
test("credentials a crashed run left are removed, but not the running one's", () => {
|
||||
const work = mkdtempSync(join(tmpdir(), "lab-work-"));
|
||||
for (const n of [".run-a.git-credentials", ".run-b.git-credentials", "run-a", "other"]) writeFileSync(join(work, n), "x");
|
||||
const removed = cleanCredentials(work, join(work, ".run-b.git-credentials"));
|
||||
assert.deepEqual(removed, [join(work, ".run-a.git-credentials")]);
|
||||
assert.ok(existsSync(join(work, ".run-b.git-credentials")) && existsSync(join(work, "run-a")) && existsSync(join(work, "other")));
|
||||
rmSync(work, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
// The lab asks the forge for read only (review 196): its credential is for clones of branches nobody has
|
||||
// reviewed yet, and must not be able to publish the mesh's packages.
|
||||
test("the lab asks the package registry for read only", () => {
|
||||
const m = JSON.parse(readFileSync(join(import.meta.dirname, "..", "module.json"), "utf8")) as {
|
||||
requires: unknown[]; contributes?: Record<string, Record<string, unknown>>;
|
||||
};
|
||||
assert.ok(m.requires.includes("npm-package-registry"));
|
||||
assert.equal(m.contributes?.["npm-package-registry"]?.access, "read");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user