mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-27 23:30:21 +00:00
fix(docker): require bounded e2e docker commands
This commit is contained in:
@@ -3,18 +3,29 @@
|
||||
# Shared helpers for Docker E2E scripts that keep a named container running
|
||||
# while polling readiness from the host.
|
||||
|
||||
docker_e2e_timeout_bin() {
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
printf '%s\n' timeout
|
||||
elif command -v gtimeout >/dev/null 2>&1; then
|
||||
printf '%s\n' gtimeout
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
docker_e2e_timeout_cmd() {
|
||||
local timeout_value="$1"
|
||||
shift
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
if timeout --kill-after=1s 1s true >/dev/null 2>&1; then
|
||||
timeout --kill-after=30s "$timeout_value" "$@"
|
||||
else
|
||||
timeout "$timeout_value" "$@"
|
||||
fi
|
||||
return
|
||||
local timeout_bin
|
||||
if ! timeout_bin="$(docker_e2e_timeout_bin)"; then
|
||||
echo "timeout command not found; cannot bound Docker command after ${timeout_value}" >&2
|
||||
return 127
|
||||
fi
|
||||
if "$timeout_bin" --kill-after=1s 1s true >/dev/null 2>&1; then
|
||||
"$timeout_bin" --kill-after=30s "$timeout_value" "$@"
|
||||
else
|
||||
"$timeout_bin" "$timeout_value" "$@"
|
||||
fi
|
||||
"$@"
|
||||
}
|
||||
|
||||
docker_e2e_docker_cmd() {
|
||||
|
||||
@@ -20,15 +20,22 @@ if ! declare -F docker_e2e_docker_run_cmd >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
local timeout_value="${DOCKER_COMMAND_TIMEOUT:-${OPENCLAW_DOCKER_E2E_RUN_TIMEOUT:-3600s}}"
|
||||
local timeout_bin=""
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
if timeout --kill-after=1s 1s true >/dev/null 2>&1; then
|
||||
timeout --kill-after=30s "$timeout_value" docker "$@"
|
||||
timeout_bin="timeout"
|
||||
elif command -v gtimeout >/dev/null 2>&1; then
|
||||
timeout_bin="gtimeout"
|
||||
fi
|
||||
if [ -n "$timeout_bin" ]; then
|
||||
if "$timeout_bin" --kill-after=1s 1s true >/dev/null 2>&1; then
|
||||
"$timeout_bin" --kill-after=30s "$timeout_value" docker "$@"
|
||||
else
|
||||
timeout "$timeout_value" docker "$@"
|
||||
"$timeout_bin" "$timeout_value" docker "$@"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
docker "$@"
|
||||
echo "timeout command not found; cannot bound Docker run after ${timeout_value}" >&2
|
||||
return 127
|
||||
}
|
||||
fi
|
||||
|
||||
|
||||
@@ -360,6 +360,8 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([
|
||||
"scripts/deadcode-unused-files.allowlist.mjs",
|
||||
["test/scripts/check-deadcode-unused-files.test.ts"],
|
||||
],
|
||||
["scripts/lib/docker-e2e-container.sh", ["test/scripts/docker-build-helper.test.ts"]],
|
||||
["scripts/lib/docker-e2e-package.sh", ["test/scripts/docker-build-helper.test.ts"]],
|
||||
["scripts/lib/live-docker-stage.sh", ["test/scripts/live-docker-stage.test.ts"]],
|
||||
["scripts/lib/openclaw-test-state.mjs", ["test/scripts/openclaw-test-state.test.ts"]],
|
||||
["scripts/lib/vitest-local-scheduling.mjs", ["test/scripts/vitest-local-scheduling.test.ts"]],
|
||||
@@ -420,6 +422,7 @@ const TOOLING_TEST_TARGETS = new Map([
|
||||
["test/scripts/check-deadcode-unused-files.test.ts"],
|
||||
],
|
||||
["test/scripts/ci-docker-pull-retry.test.ts", ["test/scripts/ci-docker-pull-retry.test.ts"]],
|
||||
["test/scripts/docker-build-helper.test.ts", ["test/scripts/docker-build-helper.test.ts"]],
|
||||
["test/scripts/live-docker-stage.test.ts", ["test/scripts/live-docker-stage.test.ts"]],
|
||||
["test/scripts/openclaw-test-state.test.ts", ["test/scripts/openclaw-test-state.test.ts"]],
|
||||
[
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { mkdtempSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
chmodSync,
|
||||
mkdtempSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
@@ -244,6 +252,239 @@ grep -q '^pull openclaw-reuse-image$' "$TMPDIR/docker-seen"
|
||||
}
|
||||
});
|
||||
|
||||
it("fails Docker commands fast when timeout is unavailable", () => {
|
||||
const workDir = mkdtempSync(join(tmpdir(), "openclaw-docker-timeout-required-"));
|
||||
|
||||
try {
|
||||
mkdirSync(join(workDir, "bin"));
|
||||
const rootDir = process.cwd();
|
||||
const script = `
|
||||
set -euo pipefail
|
||||
ROOT_DIR=${shellQuote(rootDir)}
|
||||
TMPDIR=${shellQuote(workDir)}
|
||||
export ROOT_DIR TMPDIR
|
||||
export PATH="$TMPDIR/bin"
|
||||
export DOCKER_COMMAND_TIMEOUT=7s
|
||||
|
||||
docker() {
|
||||
printf "%s\\n" "$*" >"$TMPDIR/docker-seen"
|
||||
}
|
||||
export -f docker
|
||||
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-container.sh"
|
||||
|
||||
set +e
|
||||
docker_e2e_docker_cmd ps 2>"$TMPDIR/stderr"
|
||||
status="$?"
|
||||
set -e
|
||||
|
||||
stderr="$(<"$TMPDIR/stderr")"
|
||||
[[ "$status" = "127" ]]
|
||||
[[ "$stderr" = *"timeout command not found; cannot bound Docker command after 7s"* ]]
|
||||
[[ ! -e "$TMPDIR/docker-seen" ]]
|
||||
`;
|
||||
|
||||
execFileSync("bash", ["-lc", script], { encoding: "utf8" });
|
||||
} finally {
|
||||
rmSync(workDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("uses plain timeout when kill-after is unsupported", () => {
|
||||
const workDir = mkdtempSync(join(tmpdir(), "openclaw-docker-plain-timeout-"));
|
||||
|
||||
try {
|
||||
const binDir = join(workDir, "bin");
|
||||
mkdirSync(binDir);
|
||||
writeFileSync(
|
||||
join(binDir, "timeout"),
|
||||
`#!/bin/bash
|
||||
set -euo pipefail
|
||||
if [[ "$1" = "--kill-after=1s" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
printf 'plain:%s|%s\\n' "$1" "\${*:2}" >>"$TMPDIR/timeout-seen"
|
||||
shift
|
||||
"$@"
|
||||
`,
|
||||
);
|
||||
chmodSync(join(binDir, "timeout"), 0o755);
|
||||
const rootDir = process.cwd();
|
||||
const script = `
|
||||
set -euo pipefail
|
||||
ROOT_DIR=${shellQuote(rootDir)}
|
||||
TMPDIR=${shellQuote(workDir)}
|
||||
export ROOT_DIR TMPDIR
|
||||
export PATH="$TMPDIR/bin:$PATH"
|
||||
export DOCKER_COMMAND_TIMEOUT=9s
|
||||
|
||||
docker() {
|
||||
printf "%s\\n" "$*" >>"$TMPDIR/docker-seen"
|
||||
}
|
||||
export -f docker
|
||||
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-container.sh"
|
||||
|
||||
docker_e2e_docker_cmd image inspect demo
|
||||
|
||||
grep -q '^plain:9s|docker image inspect demo$' "$TMPDIR/timeout-seen"
|
||||
grep -q '^image inspect demo$' "$TMPDIR/docker-seen"
|
||||
`;
|
||||
|
||||
execFileSync("bash", ["-lc", script], { encoding: "utf8" });
|
||||
} finally {
|
||||
rmSync(workDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("uses gtimeout when timeout is unavailable", () => {
|
||||
const workDir = mkdtempSync(join(tmpdir(), "openclaw-docker-gtimeout-"));
|
||||
|
||||
try {
|
||||
const binDir = join(workDir, "bin");
|
||||
mkdirSync(binDir);
|
||||
writeFileSync(
|
||||
join(binDir, "gtimeout"),
|
||||
`#!/bin/bash
|
||||
set -euo pipefail
|
||||
if [[ "$1" = "--kill-after=1s" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
printf 'gtimeout:%s %s|%s\\n' "$1" "$2" "\${*:3}" >>"$TMPDIR/timeout-seen"
|
||||
shift 2
|
||||
"$@"
|
||||
`,
|
||||
);
|
||||
chmodSync(join(binDir, "gtimeout"), 0o755);
|
||||
const rootDir = process.cwd();
|
||||
const script = `
|
||||
set -euo pipefail
|
||||
ROOT_DIR=${shellQuote(rootDir)}
|
||||
TMPDIR=${shellQuote(workDir)}
|
||||
export ROOT_DIR TMPDIR
|
||||
export PATH="$TMPDIR/bin"
|
||||
export OPENCLAW_DOCKER_E2E_RUN_TIMEOUT=13s
|
||||
|
||||
docker() {
|
||||
printf "%s\\n" "$*" >>"$TMPDIR/docker-seen"
|
||||
}
|
||||
export -f docker
|
||||
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-container.sh"
|
||||
|
||||
docker_e2e_docker_run_cmd run demo
|
||||
|
||||
[[ "$(<"$TMPDIR/timeout-seen")" = "gtimeout:--kill-after=30s 13s|docker run demo" ]]
|
||||
[[ "$(<"$TMPDIR/docker-seen")" = "run demo" ]]
|
||||
`;
|
||||
|
||||
execFileSync("bash", ["-lc", script], { encoding: "utf8" });
|
||||
} finally {
|
||||
rmSync(workDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps package-backed Docker runs bounded without the shared timeout helper", () => {
|
||||
const workDir = mkdtempSync(join(tmpdir(), "openclaw-docker-package-timeout-required-"));
|
||||
|
||||
try {
|
||||
mkdirSync(join(workDir, "bin"));
|
||||
const rootDir = process.cwd();
|
||||
const script = `
|
||||
set -euo pipefail
|
||||
ROOT_DIR=${shellQuote(rootDir)}
|
||||
TMPDIR=${shellQuote(workDir)}
|
||||
export ROOT_DIR TMPDIR
|
||||
export PATH="$TMPDIR/bin"
|
||||
export OPENCLAW_DOCKER_E2E_RUN_TIMEOUT=11s
|
||||
|
||||
dirname() {
|
||||
/usr/bin/dirname "$@"
|
||||
}
|
||||
|
||||
docker_e2e_docker_cmd() {
|
||||
return 0
|
||||
}
|
||||
|
||||
docker() {
|
||||
printf "%s\\n" "$*" >"$TMPDIR/docker-seen"
|
||||
}
|
||||
export -f docker_e2e_docker_cmd docker
|
||||
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
set +e
|
||||
docker_e2e_docker_run_cmd run demo 2>"$TMPDIR/stderr"
|
||||
status="$?"
|
||||
set -e
|
||||
|
||||
stderr="$(<"$TMPDIR/stderr")"
|
||||
[[ "$status" = "127" ]]
|
||||
[[ "$stderr" = *"timeout command not found; cannot bound Docker run after 11s"* ]]
|
||||
[[ ! -e "$TMPDIR/docker-seen" ]]
|
||||
`;
|
||||
|
||||
execFileSync("bash", ["-lc", script], { encoding: "utf8" });
|
||||
} finally {
|
||||
rmSync(workDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("uses gtimeout for package-backed Docker runs without the shared timeout helper", () => {
|
||||
const workDir = mkdtempSync(join(tmpdir(), "openclaw-docker-package-gtimeout-"));
|
||||
|
||||
try {
|
||||
const binDir = join(workDir, "bin");
|
||||
mkdirSync(binDir);
|
||||
writeFileSync(
|
||||
join(binDir, "gtimeout"),
|
||||
`#!/bin/bash
|
||||
set -euo pipefail
|
||||
if [[ "$1" = "--kill-after=1s" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
printf 'gtimeout:%s %s|%s\\n' "$1" "$2" "\${*:3}" >>"$TMPDIR/timeout-seen"
|
||||
shift 2
|
||||
"$@"
|
||||
`,
|
||||
);
|
||||
chmodSync(join(binDir, "gtimeout"), 0o755);
|
||||
const rootDir = process.cwd();
|
||||
const script = `
|
||||
set -euo pipefail
|
||||
ROOT_DIR=${shellQuote(rootDir)}
|
||||
TMPDIR=${shellQuote(workDir)}
|
||||
export ROOT_DIR TMPDIR
|
||||
export PATH="$TMPDIR/bin"
|
||||
export OPENCLAW_DOCKER_E2E_RUN_TIMEOUT=15s
|
||||
|
||||
dirname() {
|
||||
/usr/bin/dirname "$@"
|
||||
}
|
||||
|
||||
docker_e2e_docker_cmd() {
|
||||
return 0
|
||||
}
|
||||
|
||||
docker() {
|
||||
printf "%s\\n" "$*" >>"$TMPDIR/docker-seen"
|
||||
}
|
||||
export -f docker_e2e_docker_cmd docker
|
||||
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
docker_e2e_docker_run_cmd run demo
|
||||
|
||||
[[ "$(<"$TMPDIR/timeout-seen")" = "gtimeout:--kill-after=30s 15s|docker run demo" ]]
|
||||
[[ "$(<"$TMPDIR/docker-seen")" = "run demo" ]]
|
||||
`;
|
||||
|
||||
execFileSync("bash", ["-lc", script], { encoding: "utf8" });
|
||||
} finally {
|
||||
rmSync(workDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("removes functional Docker build package inputs after the build", () => {
|
||||
const workDir = mkdtempSync(join(tmpdir(), "openclaw-docker-build-cleanup-"));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user