mesh/merge-gate out of date: judged against trunk 19ecad261cf2, the trunk is now 8922c890595b; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 19ecad261cf2, the trunk is now 8922c890595b; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
mesh-tools-go is private, and the lab cloned over HTTP with no credential: a run failed at its checkout, "could not read Username". The lab now requires the package registry, as the build agent does; the forge's provisioner binds it an account and seals that account's password, and a run offers them to the forge's own origin through git's credential store, from a file beside the run and removed when it ends: never in a URL, the run's script, an argument or the log. lab_check reads every repository a run clones with that same credential and names one it cannot read.
152 lines
8.3 KiB
TypeScript
152 lines
8.3 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { spawnSync } from "node:child_process";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import { installPlan, installStep, NPM_REPOSITORIES, REPOSITORIES, script } from "../tools/runs.ts";
|
|
|
|
// Defends novox/hq issue 624: lab_run ran `npm ci` at the root of every checkout it installs, and
|
|
// mesh-tools keeps its package in node-tools/ with no lock file at its root, so every run failed at its
|
|
// setup, in 9 seconds, before anything was built.
|
|
|
|
function aCheckout(files: Record<string, string>): string {
|
|
const root = mkdtempSync(join(tmpdir(), "lab-checkout-"));
|
|
for (const [path, content] of Object.entries(files)) {
|
|
mkdirSync(join(root, path, ".."), { recursive: true });
|
|
writeFileSync(join(root, path), content);
|
|
}
|
|
return root;
|
|
}
|
|
|
|
test("a checkout with a lock file at its root is installed there", () => {
|
|
const root = aCheckout({ "package.json": "{}", "package-lock.json": "{}", "node-tools/package.json": "{}" });
|
|
assert.deepEqual(installPlan(root), { install: [root], unlocked: [] });
|
|
rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
test("a checkout whose package lives one directory down is installed there, as mesh-tools' node-tools", () => {
|
|
const root = aCheckout({ "node-tools/package.json": "{}", "node-tools/package-lock.json": "{}", "Dockerfile": "" });
|
|
assert.deepEqual(installPlan(root), { install: [join(root, "node-tools")], unlocked: [] });
|
|
rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
test("a package.json with no lock file is named, never installed", () => {
|
|
const root = aCheckout({ "package.json": "{}" });
|
|
assert.deepEqual(installPlan(root), { install: [], unlocked: [root] });
|
|
rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
// The shell a run executes follows the same rule: npm is asked where installPlan says, and a checkout
|
|
// with nothing installable fails the run loudly, naming it.
|
|
function runStep(root: string, build: boolean): { ok: boolean; said: string; asked: string[] } {
|
|
const bin = mkdtempSync(join(tmpdir(), "lab-npm-"));
|
|
const record = join(bin, "asked");
|
|
writeFileSync(join(bin, "npm"), `#!/bin/sh\necho "$(pwd) $*" >> ${JSON.stringify(record)}\n`);
|
|
chmodSync(join(bin, "npm"), 0o755);
|
|
const ran = spawnSync("bash", ["-c", `set -euo pipefail\n${installStep(root, build)}`], {
|
|
encoding: "utf8", env: { ...process.env, PATH: `${bin}:${process.env.PATH}` },
|
|
});
|
|
let asked: string[] = [];
|
|
try {
|
|
asked = readFileSync(record, "utf8").trim().split("\n").filter(Boolean);
|
|
} catch { /* nothing asked */ }
|
|
rmSync(bin, { recursive: true, force: true });
|
|
return { ok: ran.status === 0, said: `${ran.stdout}${ran.stderr}`, asked };
|
|
}
|
|
|
|
test("a run installs and builds mesh-tools in node-tools, where its lock file is", () => {
|
|
const root = aCheckout({ "node-tools/package.json": "{}", "node-tools/package-lock.json": "{}" });
|
|
const r = runStep(root, true);
|
|
assert.ok(r.ok, r.said);
|
|
assert.deepEqual(r.asked, [
|
|
`${join(root, "node-tools")} ci --no-audit --no-fund --loglevel=error`,
|
|
`${join(root, "node-tools")} run build --if-present`,
|
|
]);
|
|
rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
test("a run installs a checkout with its lock file at the root there, and builds only when asked", () => {
|
|
const root = aCheckout({ "package.json": "{}", "package-lock.json": "{}" });
|
|
const r = runStep(root, false);
|
|
assert.ok(r.ok, r.said);
|
|
assert.deepEqual(r.asked, [`${root} ci --no-audit --no-fund --loglevel=error`]);
|
|
rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
test("a run fails at its setup, naming the checkout, when nothing of it can be installed", () => {
|
|
const layouts: Record<string, string>[] = [{ "package.json": "{}" }, { "Dockerfile": "" }, { "sub/package.json": "{}" }];
|
|
for (const files of layouts) {
|
|
const root = aCheckout(files);
|
|
const r = runStep(root, false);
|
|
assert.equal(r.ok, false, `installed ${JSON.stringify(files)}`);
|
|
assert.match(r.said, /issue 624/);
|
|
assert.deepEqual(r.asked, []);
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
// A run checks out mesh-tools-go too (novox/hq issue 616): genesis builds the Go toolchain from the
|
|
// repository beside mesh-tools, and a bed that raises genesis serves it. And it tells the beds where the
|
|
// checkouts are.
|
|
test("a run checks out mesh-tools-go and says where its checkouts are", () => {
|
|
assert.ok(REPOSITORIES.includes("mesh-tools-go"));
|
|
const s = script("/work", "run-x", "http://forge", ["test/integration/genesis-keeps-its-bus.test.ts"], {});
|
|
assert.match(s, /novox\/mesh-tools-go\.git' '\/work\/run-x\/mesh-tools-go'/);
|
|
assert.match(s, /export MESH_LAB_REPOS='\/work\/run-x'/);
|
|
assert.doesNotMatch(s, /cd '\/work\/run-x'\/\$repo && npm ci/);
|
|
for (const repo of NPM_REPOSITORIES) assert.ok(s.includes(`'/work/run-x/${repo}'/package-lock.json`), repo);
|
|
});
|
|
|
|
// Defends novox/hq issue 624, its second half: mesh-tools-go is private, and the lab cloned over HTTP with
|
|
// no credential ("could not read Username"). A run clones with the forge credential the mesh binds and
|
|
// seals to the lab, through git's credential store, and the secret is in no script, URL or argument.
|
|
import { credentialArgs, credentialPath, forgeCredential } from "../tools/runs.ts";
|
|
|
|
function aBinding(as: string, secret: string): { binding: string; secret: string } {
|
|
const dir = mkdtempSync(join(tmpdir(), "lab-binding-"));
|
|
writeFileSync(join(dir, "package-registry.json"), JSON.stringify({ provision: "npm-package-registry", at: "10.42.0.1", as,
|
|
serves: { scheme: "http", port: 3000 } }));
|
|
writeFileSync(join(dir, "package-registry.secret"), `${secret}\n`);
|
|
return { binding: join(dir, "package-registry.json"), secret: join(dir, "package-registry.secret") };
|
|
}
|
|
|
|
test("the forge credential is the bound account and its sealed secret, for the forge's own origin", () => {
|
|
const b = aBinding("lab-shanks", "s3cret-of-forty-characters");
|
|
assert.equal(forgeCredential("http://novox.internal:20000", b.binding, b.secret),
|
|
"http://lab-shanks:s3cret-of-forty-characters@novox.internal:20000");
|
|
assert.equal(forgeCredential("http://novox.internal:20000/", b.binding, ""), undefined);
|
|
assert.equal(forgeCredential("", b.binding, b.secret), undefined);
|
|
assert.equal(forgeCredential("http://novox.internal:20000", "/nowhere", b.secret), undefined);
|
|
});
|
|
|
|
test("a run clones through git's credential store, and its script carries no secret", () => {
|
|
const b = aBinding("lab-shanks", "s3cret-of-forty-characters");
|
|
const credential = forgeCredential("http://novox.internal:20000", b.binding, b.secret);
|
|
const path = credentialPath("/work", "run-x");
|
|
const s = script("/work", "run-x", "http://novox.internal:20000", ["t.test.ts"], {}, path);
|
|
assert.ok(credential);
|
|
assert.doesNotMatch(s, /s3cret/);
|
|
assert.match(s, /git -c credential\.helper= -c 'credential\.helper=store --file=\/work\/\.run-x\.git-credentials' clone --quiet --depth 50 --branch 'main' 'http:\/\/novox\.internal:20000\/novox\/mesh-tools-go\.git'/);
|
|
assert.match(s, /export GIT_TERMINAL_PROMPT=0/);
|
|
assert.equal(path.startsWith("/work/run-x/"), false, "the credential is kept out of the run's own directory");
|
|
assert.equal(credentialArgs(undefined), "");
|
|
});
|
|
|
|
// Held against git itself: the credential in the store is offered to the forge's origin when it asks, and
|
|
// a clone without it fails at once rather than prompting.
|
|
test("git presents the stored credential to the forge, and only when one is given", () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "lab-git-"));
|
|
const store = join(dir, "git-credentials");
|
|
writeFileSync(store, "http://lab-shanks:pw@forge.invalid:20000\n", { mode: 0o600 });
|
|
const fill = spawnSync("sh", ["-c", `printf 'protocol=http\\nhost=forge.invalid:20000\\n\\n' | git ${credentialArgs(store)}credential fill`],
|
|
{ encoding: "utf8", env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } });
|
|
assert.match(fill.stdout, /username=lab-shanks/);
|
|
assert.match(fill.stdout, /password=pw/);
|
|
const other = spawnSync("sh", ["-c", `printf 'protocol=http\\nhost=elsewhere.invalid\\n\\n' | git ${credentialArgs(store)}credential fill`],
|
|
{ encoding: "utf8", env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } });
|
|
assert.doesNotMatch(other.stdout, /password=pw/);
|
|
rmSync(dir, { recursive: true, force: true });
|
|
});
|