Files
openclaw/scripts/lib/docker-e2e-container.sh
2026-05-27 00:19:07 -04:00

166 lines
4.4 KiB
Bash

#!/usr/bin/env bash
#
# 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
local timeout_bin
if ! timeout_bin="$(docker_e2e_timeout_bin)"; then
if command -v node >/dev/null 2>&1; then
echo "timeout command not found; using Node watchdog for Docker command timeout ${timeout_value}" >&2
node --input-type=module -e '
const [, timeoutValue, command, ...args] = process.argv;
const parseTimeoutMs = (value) => {
const match = /^([0-9]+(?:\.[0-9]+)?)(ms|s|m|h)?$/u.exec(String(value ?? "").trim());
if (!match) {
throw new Error(`unsupported timeout value: ${value}`);
}
const amount = Number(match[1]);
const unit = match[2] ?? "s";
const multiplier = unit === "ms" ? 1 : unit === "s" ? 1_000 : unit === "m" ? 60_000 : 3_600_000;
return Math.max(1, Math.ceil(amount * multiplier));
};
if (!command) {
console.error("missing command for Node watchdog");
process.exit(1);
}
const { spawn } = await import("node:child_process");
let timeoutMs;
try {
timeoutMs = parseTimeoutMs(timeoutValue);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
const child = spawn(command, args, {
detached: process.platform !== "win32",
stdio: "inherit",
});
let timedOut = false;
const killTarget = process.platform === "win32" ? child.pid : -child.pid;
const killChild = (signal) => {
if (!child.pid) {
return;
}
try {
process.kill(killTarget, signal);
} catch {
try {
child.kill(signal);
} catch {}
}
};
const timer = setTimeout(() => {
timedOut = true;
console.error(`Docker command timed out after ${timeoutValue}`);
killChild("SIGTERM");
setTimeout(() => killChild("SIGKILL"), 30_000).unref();
}, timeoutMs);
const forwardSignal = (signal) => {
killChild(signal);
};
process.once("SIGINT", forwardSignal);
process.once("SIGTERM", forwardSignal);
child.on("exit", (code, signal) => {
clearTimeout(timer);
if (timedOut) {
process.exit(124);
}
if (code !== null) {
process.exit(code);
}
if (signal) {
process.kill(process.pid, signal);
}
process.exit(1);
});
child.on("error", (error) => {
clearTimeout(timer);
console.error(error.message);
process.exit(127);
});
' "$timeout_value" "$@"
return
fi
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() {
docker_e2e_timeout_cmd "${DOCKER_COMMAND_TIMEOUT:-600s}" docker "$@"
}
docker_e2e_docker_run_cmd() {
docker_e2e_timeout_cmd "${DOCKER_COMMAND_TIMEOUT:-${OPENCLAW_DOCKER_E2E_RUN_TIMEOUT:-3600s}}" docker "$@"
}
docker_e2e_container_running() {
local container_name="$1"
[ "$(docker_e2e_docker_cmd inspect -f '{{.State.Running}}' "$container_name" 2>/dev/null || echo false)" = "true" ]
}
docker_e2e_container_exec_bash() {
local container_name="$1"
shift
docker_e2e_docker_cmd exec "$container_name" bash -lc "$*"
}
docker_e2e_wait_container_bash() {
local container_name="$1"
shift
docker_e2e_wait_container_bash_while_running "$container_name" "$container_name" "$@"
}
docker_e2e_wait_container_bash_while_running() {
local running_container_name="$1"
local exec_container_name="$2"
local attempts="$3"
local sleep_seconds="$4"
shift 4
local probe="$*"
for _ in $(seq 1 "$attempts"); do
if ! docker_e2e_container_running "$running_container_name"; then
return 1
fi
if docker_e2e_container_exec_bash "$exec_container_name" "$probe" >/dev/null 2>&1; then
return 0
fi
sleep "$sleep_seconds"
done
return 1
}
docker_e2e_tail_container_file_if_running() {
local container_name="$1"
local file_path="$2"
local lines="${3:-120}"
if docker_e2e_container_running "$container_name"; then
docker_e2e_container_exec_bash "$container_name" "tail -n $lines $file_path" || true
else
docker_e2e_docker_cmd logs "$container_name" 2>&1 | tail -n "$lines" || true
fi
}