diff --git a/build.sh b/build.sh index 9b9181c..2078bf6 100755 --- a/build.sh +++ b/build.sh @@ -50,12 +50,21 @@ Execution modes: Not reproducible; uses whatever Ruby/Node is installed. With neither a container engine nor DOCS_TOOLBOX_LOCAL=1, the task aborts. -Override the image with DOCS_TOOLBOX_IMAGE. +Override the image with DOCS_TOOLBOX_IMAGE, and the engine with +DOCS_TOOLBOX_ENGINE (podman or docker) when detection picks a broken one. USAGE } +# DOCS_TOOLBOX_ENGINE forces an engine and skips detection. It exists because +# `podman info` only reports that the engine is *configured* -- it says nothing +# about whether the OCI runtime can start a container, and that gap is real: on +# some hosts `podman info` succeeds while `podman run` dies in crun. When the +# preferred engine is broken in a way detection cannot see, naming the other one +# is the only way out. find_engine() { - if command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1; then + if [ -n "${DOCS_TOOLBOX_ENGINE:-}" ]; then + printf '%s\n' "$DOCS_TOOLBOX_ENGINE" + elif command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1; then printf '%s\n' "podman" elif command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then printf '%s\n' "docker" diff --git a/templates/scripts/build.sh b/templates/scripts/build.sh index 7767d61..955e45c 100755 --- a/templates/scripts/build.sh +++ b/templates/scripts/build.sh @@ -55,13 +55,23 @@ Execution modes: Not reproducible; uses whatever Ruby/Node is installed. With neither a container engine nor DOCS_TOOLBOX_LOCAL=1, the task aborts. -Override the image with DOCS_TOOLBOX_IMAGE. Application code is built with its -own tooling, not this script (docs-toolbox carries only the docs toolchain). +Override the image with DOCS_TOOLBOX_IMAGE, and the engine with +DOCS_TOOLBOX_ENGINE (podman or docker) when detection picks a broken one. +Application code is built with its own tooling, not this script (docs-toolbox +carries only the docs toolchain). USAGE } +# DOCS_TOOLBOX_ENGINE forces an engine and skips detection. It exists because +# `podman info` only reports that the engine is *configured* -- it says nothing +# about whether the OCI runtime can start a container, and that gap is real: on +# some hosts `podman info` succeeds while `podman run` dies in crun. When the +# preferred engine is broken in a way detection cannot see, naming the other one +# is the only way out. find_engine() { - if command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1; then + if [ -n "${DOCS_TOOLBOX_ENGINE:-}" ]; then + printf '%s\n' "$DOCS_TOOLBOX_ENGINE" + elif command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1; then printf '%s\n' "podman" elif command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then printf '%s\n' "docker" diff --git a/test/build-sh-template.test.mjs b/test/build-sh-template.test.mjs index 40340a1..04a3a00 100644 --- a/test/build-sh-template.test.mjs +++ b/test/build-sh-template.test.mjs @@ -74,3 +74,56 @@ test("local-mode check-adapters runs the adapter checker", (t) => { assert.equal(result.status, 0, result.stderr); assert.match(invocations, /node scripts\/check-agent-adapters\.js/); }); + +// Container-mode engine selection. Both engines are stubbed so nothing is +// actually pulled: each records the command it was given and exits 0, which is +// enough to see which one the runner reached for. +function runContainerTaskWithEngineStubs(t, env) { + const parent = fs.mkdtempSync(path.join(os.tmpdir(), "akt-buildsh-engine-")); + t.after(() => fs.rmSync(parent, { recursive: true, force: true })); + + fs.copyFileSync(templateBuildSh, path.join(parent, "build.sh")); + + const binDir = path.join(parent, "stub-bin"); + fs.mkdirSync(binDir); + const log = path.join(parent, "invocations.log"); + for (const engine of ["podman", "docker"]) { + const stub = path.join(binDir, engine); + fs.writeFileSync(stub, `#!/bin/sh\necho "${engine} $1" >> "$STUB_LOG"\n`); + fs.chmodSync(stub, 0o755); + } + + const result = spawnSync("sh", ["build.sh", "validate"], { + cwd: parent, + encoding: "utf8", + env: { + ...process.env, + PATH: `${binDir}:${process.env.PATH}`, + STUB_LOG: log, + DOCS_TOOLBOX_LOCAL: "", + ...env, + }, + }); + + return { + result, + invocations: fs.existsSync(log) ? fs.readFileSync(log, "utf8") : "", + }; +} + +test("container mode prefers podman when nothing is forced", (t) => { + const { invocations } = runContainerTaskWithEngineStubs(t, {}); + assert.match(invocations, /^podman run$/m); + assert.doesNotMatch(invocations, /^docker run$/m); +}); + +test("DOCS_TOOLBOX_ENGINE overrides detection", (t) => { + // podman is on PATH and its `info` probe succeeds, so detection would pick it. + // The override has to win anyway -- that is the whole point: `podman info` + // cannot see an OCI runtime that fails only at container start. + const { invocations } = runContainerTaskWithEngineStubs(t, { + DOCS_TOOLBOX_ENGINE: "docker", + }); + assert.match(invocations, /^docker run$/m); + assert.doesNotMatch(invocations, /^podman run$/m); +});