Multiple fixes: the vault bed keeps two secrets, the confluence bed asks through the control plane, the runtime build installs its SDK and speaks #44
@@ -33,7 +33,16 @@ SRCS=(); for f in \
|
|||||||
pg.d.ts; do
|
pg.d.ts; do
|
||||||
[ -f "$MOD/$f" ] && SRCS+=("$f")
|
[ -f "$MOD/$f" ] && SRCS+=("$f")
|
||||||
done
|
done
|
||||||
TSC="$MESH_SDK/node_modules/.bin/tsc"; ( cd "$MOD" && "$TSC" "${SRCS[@]}" --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist >/dev/null )
|
# The module compiles against the SDK, which its package.json names and nothing installs: a module
|
||||||
|
# never built on this workstation has no node_modules, and tsc fails on the first import. Installed
|
||||||
|
# as a package copy from the sibling checkout (never a link) when absent — the compile needs only
|
||||||
|
# the types; the image takes the SDK from MESH_SDK below.
|
||||||
|
if [ ! -e "$MOD/node_modules/@novox/mesh-sdk" ]; then
|
||||||
|
( cd "$MOD" && npm install --no-save --install-links --no-package-lock --ignore-scripts --silent "$MESH_SDK" ) \
|
||||||
|
|| { echo "cannot install the SDK into $MOD for the compile" >&2; exit 1; }
|
||||||
|
fi
|
||||||
|
# Output kept: a compile error hidden behind /dev/null is a build that fails saying nothing.
|
||||||
|
TSC="$MESH_SDK/node_modules/.bin/tsc"; ( cd "$MOD" && "$TSC" "${SRCS[@]}" --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist 1>&2 )
|
||||||
|
|
||||||
STAGE="$(mktemp -d)"; trap 'rm -rf "$STAGE"' EXIT
|
STAGE="$(mktemp -d)"; trap 'rm -rf "$STAGE"' EXIT
|
||||||
cp -r "$MESH_TOOLS/dist" "$STAGE/dist"
|
cp -r "$MESH_TOOLS/dist" "$STAGE/dist"
|
||||||
|
|||||||
+1
-1
@@ -134,7 +134,7 @@ export function rebuild(env: NodeJS.ProcessEnv = process.env, testFiles: string[
|
|||||||
// the code in front of you, which is the whole of 005.
|
// the code in front of you, which is the whole of 005.
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`could not build the ${build.what}: ${build.argv.join(" ")} in ${build.in}\n\n` +
|
`could not build the ${build.what}: ${build.argv.join(" ")} in ${build.in}\n\n` +
|
||||||
`${(ran.stderr || ran.stdout || String(ran.error)).trim()}`,
|
`${(ran.stderr || ran.stdout || (ran.error ? String(ran.error) : `exit status ${ran.status}, and it said nothing`)).trim()}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
built.push(build.what);
|
built.push(build.what);
|
||||||
|
|||||||
@@ -204,6 +204,15 @@ test("the mesh assigns confluence: its tools-only runtime comes up and serves th
|
|||||||
assert.match(served2, /serve\.confluence\.confluence_search/,
|
assert.match(served2, /serve\.confluence\.confluence_search/,
|
||||||
`confluence's runtime never bound its serve queue:\n${(await on(`docker logs mesh-runtime-confluence 2>&1 | tail -20`)).out}\n---\n${served2}`);
|
`confluence's runtime never bound its serve queue:\n${(await on(`docker logs mesh-runtime-confluence 2>&1 | tail -20`)).out}\n---\n${served2}`);
|
||||||
|
|
||||||
|
// --- and the control plane is the way to ask it (novox/hq ADR 0095, issue 049) ------------------
|
||||||
|
// No account in the mesh but the control plane's may create a reply queue and publish to a
|
||||||
|
// module's request key. Asked through it, the tool ANSWERS — with an error, since the lab has no
|
||||||
|
// Confluence and no token, which is an answer: the round trip is what is under test, and a
|
||||||
|
// timeout would read differently.
|
||||||
|
const asked = await on(`docker exec mesh-controller /mesh-controller ask confluence confluence_search '{"query":"mesh"}' --wait 60s`, 90_000);
|
||||||
|
assert.doesNotMatch(asked.out, /did not answer/, `the tool was never reached through the control plane:\n${asked.out}`);
|
||||||
|
assert.match(asked.out, /"(result|error)"/, `the control plane printed no answer:\n${asked.out}`);
|
||||||
|
|
||||||
// --- confluence got its own scoped broker account -----------------------------------------------
|
// --- confluence got its own scoped broker account -----------------------------------------------
|
||||||
const users = await must(`docker exec mesh-broker lavinmqctl list_users 2>&1`);
|
const users = await must(`docker exec mesh-broker lavinmqctl list_users 2>&1`);
|
||||||
assert.match(users, /anchor-confluence/, `the scoped account anchor-confluence is not on the broker:\n${users}`);
|
assert.match(users, /anchor-confluence/, `the scoped account anchor-confluence is not on the broker:\n${users}`);
|
||||||
|
|||||||
@@ -259,9 +259,32 @@ test("redis's own password is a secret the vault provides: it authenticates, and
|
|||||||
assert.match(issued, /scoped to what it emits and consumes/, issued);
|
assert.match(issued, /scoped to what it emits and consumes/, issued);
|
||||||
await mesh(`assign ${MACHINE} ${name}`);
|
await mesh(`assign ${MACHINE} ${name}`);
|
||||||
}
|
}
|
||||||
|
// A consumer that needs TWO values from the vault (novox/hq ADR 0094): its `secrets` entry names
|
||||||
|
// them under local names, and each is a pair of its own. It runs no code — the delivery is what
|
||||||
|
// is under test. Synthetic, so it wears no catalogue module's name.
|
||||||
|
const twoSecrets = JSON.stringify({
|
||||||
|
module: "two-secrets", version: "1", slug: "two",
|
||||||
|
requires: ["secret"],
|
||||||
|
secrets: { secret: { first: "/var/lib/two-secrets/first", second: "/var/lib/two-secrets/second" } },
|
||||||
|
resources: [{ id: "state", type: "directory", path: "/var/lib/two-secrets", mode: "0700" }],
|
||||||
|
});
|
||||||
|
await must(`printf %s ${quote(twoSecrets)} > /tmp/two-secrets.json && docker cp /tmp/two-secrets.json mesh-controller:/two-secrets.json`);
|
||||||
|
await mesh("module add /two-secrets.json");
|
||||||
|
await mesh(`assign ${MACHINE} two-secrets`);
|
||||||
await mesh(`push ${MACHINE}`);
|
await mesh(`push ${MACHINE}`);
|
||||||
await settled();
|
await settled();
|
||||||
|
|
||||||
|
// Two files, two values, and the vault holds two holders for one module — the identity with the
|
||||||
|
// local name after it.
|
||||||
|
const first = (await must(`cat /var/lib/two-secrets/first`)).replace(/\n$/, "");
|
||||||
|
const second = (await must(`cat /var/lib/two-secrets/second`)).replace(/\n$/, "");
|
||||||
|
assert.ok(first.length >= 20 && second.length >= 20, "a two-secrets value is empty or implausibly short");
|
||||||
|
assert.notEqual(first, second, "two local names were given one value");
|
||||||
|
for (const holderOf of ["mesh_anchor_two_first", "mesh_anchor_two_second"]) {
|
||||||
|
await until(`the vault holding ${holderOf}`, 90_000, async () =>
|
||||||
|
(await on(`test -s ${LEDGER}/${holderOf}.json`)).ok ? true : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
const running = await must(`docker ps --format '{{.Names}}'`);
|
const running = await must(`docker ps --format '{{.Names}}'`);
|
||||||
for (const c of ["mesh-vault", "redis", "mesh-redis"]) {
|
for (const c of ["mesh-vault", "redis", "mesh-redis"]) {
|
||||||
assert.match(running, new RegExp(`(^|\\n)${c}(\\n|$)`),
|
assert.match(running, new RegExp(`(^|\\n)${c}(\\n|$)`),
|
||||||
@@ -335,6 +358,21 @@ test("rotating the secret moves both ends: the new password works, the old one i
|
|||||||
});
|
});
|
||||||
assert.notEqual(after, before);
|
assert.notEqual(after, before);
|
||||||
|
|
||||||
|
// Both of the two-secrets consumer's values moved too, apart from each other (ADR 0094).
|
||||||
|
const firstAfter = await until("the rotated first secret", 180_000, async () => {
|
||||||
|
const now = (await must(`cat /var/lib/two-secrets/first`)).replace(/\n$/, "");
|
||||||
|
return now !== "" ? now : undefined;
|
||||||
|
});
|
||||||
|
const secondAfter = (await must(`cat /var/lib/two-secrets/second`)).replace(/\n$/, "");
|
||||||
|
assert.notEqual(firstAfter, secondAfter, "two local names were given one value after rotation");
|
||||||
|
for (const holderOf of ["mesh_anchor_two_first", "mesh_anchor_two_second"]) {
|
||||||
|
const h = await until(`the vault recording ${holderOf}'s rotation`, 90_000, async () => {
|
||||||
|
const got = JSON.parse(await must(`cat ${LEDGER}/${holderOf}.json`)) as Held;
|
||||||
|
return got.rotations >= 1 ? got : undefined;
|
||||||
|
});
|
||||||
|
assert.equal(h.rotations, 1, holderOf);
|
||||||
|
}
|
||||||
|
|
||||||
// Three logins. The new one works (redis was restarted on its config — `restart-on`), the old one
|
// Three logins. The new one works (redis was restarted on its config — `restart-on`), the old one
|
||||||
// does not: that third check is what makes it a rotation rather than an addition.
|
// does not: that third check is what makes it a rotation rather than an addition.
|
||||||
await until("redis accepting the rotated password", 180_000, async () => {
|
await until("redis accepting the rotated password", 180_000, async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user