mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-15 07:35:58 +08:00
406 lines
12 KiB
Bash
406 lines
12 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# Shared package helpers for Docker E2E scripts.
|
|
# Builds or resolves one OpenClaw npm tarball and exposes mount/build-context
|
|
# helpers so Docker lanes test the package artifact instead of repo sources.
|
|
|
|
DOCKER_E2E_PACKAGE_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ROOT_DIR="${ROOT_DIR:-$(cd "$DOCKER_E2E_PACKAGE_LIB_DIR/../.." && pwd)}"
|
|
|
|
if ! declare -F run_logged >/dev/null 2>&1; then
|
|
source "$DOCKER_E2E_PACKAGE_LIB_DIR/docker-e2e-logs.sh"
|
|
fi
|
|
if ! declare -F docker_e2e_docker_cmd >/dev/null 2>&1; then
|
|
source "$DOCKER_E2E_PACKAGE_LIB_DIR/docker-e2e-container.sh"
|
|
fi
|
|
if ! declare -F docker_e2e_docker_run_resource_args >/dev/null 2>&1; then
|
|
docker_e2e_resource_limits_disabled() {
|
|
case "${OPENCLAW_DOCKER_E2E_DISABLE_RESOURCE_LIMITS:-}" in
|
|
1 | true | TRUE | yes | YES | on | ON)
|
|
return 0
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
docker_e2e_resource_value_disabled() {
|
|
case "${1:-}" in
|
|
"" | 0 | none | NONE | off | OFF | false | FALSE)
|
|
return 0
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
docker_e2e_detect_available_cpus() {
|
|
if [ -n "${OPENCLAW_DOCKER_E2E_AVAILABLE_CPUS:-}" ]; then
|
|
printf '%s\n' "$OPENCLAW_DOCKER_E2E_AVAILABLE_CPUS"
|
|
return 0
|
|
fi
|
|
if command -v nproc >/dev/null 2>&1; then
|
|
nproc
|
|
return 0
|
|
fi
|
|
if command -v getconf >/dev/null 2>&1; then
|
|
getconf _NPROCESSORS_ONLN
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
docker_e2e_resolve_cpus() {
|
|
local requested="$1"
|
|
local available=""
|
|
available="$(docker_e2e_detect_available_cpus 2>/dev/null || true)"
|
|
if [[ "$requested" =~ ^[0-9]+$ ]] && [[ "$available" =~ ^[0-9]+$ ]] && [ "$requested" -gt "$available" ]; then
|
|
printf '%s\n' "$available"
|
|
return 0
|
|
fi
|
|
printf '%s\n' "$requested"
|
|
}
|
|
|
|
docker_e2e_run_arg_present() {
|
|
local option="$1"
|
|
shift
|
|
local arg
|
|
for arg in "$@"; do
|
|
if [ "$arg" = "$option" ] || [[ "$arg" == "$option="* ]]; then
|
|
return 0
|
|
fi
|
|
case "$option:$arg" in
|
|
--memory:-m | --memory:-m=*)
|
|
return 0
|
|
;;
|
|
esac
|
|
done
|
|
return 1
|
|
}
|
|
|
|
docker_e2e_docker_run_resource_args() {
|
|
DOCKER_E2E_RUN_RESOURCE_ARGS=()
|
|
if docker_e2e_resource_limits_disabled; then
|
|
return 0
|
|
fi
|
|
|
|
local memory="${OPENCLAW_DOCKER_E2E_MEMORY:-8g}"
|
|
local cpus="${OPENCLAW_DOCKER_E2E_CPUS:-16}"
|
|
local pids_limit="${OPENCLAW_DOCKER_E2E_PIDS_LIMIT:-2048}"
|
|
cpus="$(docker_e2e_resolve_cpus "$cpus")"
|
|
|
|
if ! docker_e2e_resource_value_disabled "$memory" && ! docker_e2e_run_arg_present --memory "$@"; then
|
|
DOCKER_E2E_RUN_RESOURCE_ARGS+=(--memory "$memory")
|
|
fi
|
|
if ! docker_e2e_resource_value_disabled "$cpus" && ! docker_e2e_run_arg_present --cpus "$@"; then
|
|
DOCKER_E2E_RUN_RESOURCE_ARGS+=(--cpus "$cpus")
|
|
fi
|
|
if ! docker_e2e_resource_value_disabled "$pids_limit" && ! docker_e2e_run_arg_present --pids-limit "$@"; then
|
|
DOCKER_E2E_RUN_RESOURCE_ARGS+=(--pids-limit "$pids_limit")
|
|
fi
|
|
}
|
|
fi
|
|
if ! declare -F docker_e2e_docker_run_cmd >/dev/null 2>&1; then
|
|
docker_e2e_docker_run_cmd() {
|
|
if [ "${1:-}" = "run" ]; then
|
|
shift
|
|
docker_e2e_docker_run_resource_args "$@"
|
|
if declare -F docker_e2e_timeout_cmd >/dev/null 2>&1; then
|
|
if [ "${#DOCKER_E2E_RUN_RESOURCE_ARGS[@]}" -gt 0 ]; then
|
|
docker_e2e_timeout_cmd "${DOCKER_COMMAND_TIMEOUT:-${OPENCLAW_DOCKER_E2E_RUN_TIMEOUT:-3600s}}" docker run "${DOCKER_E2E_RUN_RESOURCE_ARGS[@]}" "$@"
|
|
else
|
|
docker_e2e_timeout_cmd "${DOCKER_COMMAND_TIMEOUT:-${OPENCLAW_DOCKER_E2E_RUN_TIMEOUT:-3600s}}" docker run "$@"
|
|
fi
|
|
return
|
|
fi
|
|
if [ "${#DOCKER_E2E_RUN_RESOURCE_ARGS[@]}" -gt 0 ]; then
|
|
set -- run "${DOCKER_E2E_RUN_RESOURCE_ARGS[@]}" "$@"
|
|
else
|
|
set -- run "$@"
|
|
fi
|
|
fi
|
|
if declare -F docker_e2e_timeout_cmd >/dev/null 2>&1; then
|
|
docker_e2e_timeout_cmd "${DOCKER_COMMAND_TIMEOUT:-${OPENCLAW_DOCKER_E2E_RUN_TIMEOUT:-3600s}}" docker "$@"
|
|
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
|
|
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_bin" "$timeout_value" docker "$@"
|
|
fi
|
|
return
|
|
fi
|
|
echo "timeout command not found; cannot bound Docker run after ${timeout_value}" >&2
|
|
return 127
|
|
}
|
|
fi
|
|
|
|
docker_e2e_abs_path() {
|
|
local file="$1"
|
|
(cd "$(dirname "$file")" && printf '%s/%s\n' "$(pwd)" "$(basename "$file")")
|
|
}
|
|
|
|
docker_e2e_prepare_package_tgz() {
|
|
local label="$1"
|
|
local package_tgz="${2:-${OPENCLAW_CURRENT_PACKAGE_TGZ:-}}"
|
|
|
|
if [ -n "$package_tgz" ]; then
|
|
if [ ! -f "$package_tgz" ]; then
|
|
echo "OpenClaw package tarball does not exist: $package_tgz" >&2
|
|
return 1
|
|
fi
|
|
docker_e2e_abs_path "$package_tgz"
|
|
return 0
|
|
fi
|
|
|
|
local pack_dir
|
|
pack_dir="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-docker-e2e-pack.XXXXXX")"
|
|
local pack_status=0
|
|
package_tgz="$(
|
|
node "$ROOT_DIR/scripts/package-openclaw-for-docker.mjs" \
|
|
--output-dir "$pack_dir" \
|
|
--output-name openclaw-current.tgz
|
|
)" || pack_status="$?"
|
|
if [ "$pack_status" -ne 0 ]; then
|
|
rm -rf "$pack_dir"
|
|
return "$pack_status"
|
|
fi
|
|
if [ -z "$package_tgz" ]; then
|
|
echo "missing packed OpenClaw tarball" >&2
|
|
rm -rf "$pack_dir"
|
|
return 1
|
|
fi
|
|
touch "$pack_dir/.openclaw-docker-e2e-generated-package"
|
|
docker_e2e_abs_path "$package_tgz"
|
|
}
|
|
|
|
docker_e2e_prepare_package_context() {
|
|
local package_tgz="$1"
|
|
local context_dir
|
|
context_dir="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-docker-e2e-package-context.XXXXXX")"
|
|
# BuildKit named contexts must be directories, so expose the tarball as a
|
|
# stable filename inside a tiny temporary context.
|
|
local copy_status=0
|
|
cp "$package_tgz" "$context_dir/openclaw-current.tgz" || copy_status="$?"
|
|
if [ "$copy_status" -ne 0 ]; then
|
|
rm -rf "$context_dir"
|
|
return "$copy_status"
|
|
fi
|
|
printf '%s\n' "$context_dir"
|
|
}
|
|
|
|
docker_e2e_package_mount_args() {
|
|
local package_tgz="$1"
|
|
local target="${2:-/tmp/openclaw-current.tgz}"
|
|
DOCKER_E2E_PACKAGE_ARGS=(-v "$package_tgz:$target:ro" -e "OPENCLAW_CURRENT_PACKAGE_TGZ=$target")
|
|
if [ -n "${OPENCLAW_E2E_NPM_INSTALL_TIMEOUT:-}" ]; then
|
|
DOCKER_E2E_PACKAGE_ARGS+=(-e "OPENCLAW_E2E_NPM_INSTALL_TIMEOUT=$OPENCLAW_E2E_NPM_INSTALL_TIMEOUT")
|
|
fi
|
|
if [ -n "${OPENCLAW_E2E_COMMAND_TIMEOUT:-}" ]; then
|
|
DOCKER_E2E_PACKAGE_ARGS+=(-e "OPENCLAW_E2E_COMMAND_TIMEOUT=$OPENCLAW_E2E_COMMAND_TIMEOUT")
|
|
fi
|
|
}
|
|
|
|
docker_e2e_cleanup_package_tgz() {
|
|
local package_tgz="${1:-}"
|
|
[ -n "$package_tgz" ] || return 0
|
|
[ "$(basename "$package_tgz")" = "openclaw-current.tgz" ] || return 0
|
|
|
|
local pack_dir
|
|
pack_dir="$(dirname "$package_tgz")"
|
|
if [ -f "$pack_dir/.openclaw-docker-e2e-generated-package" ]; then
|
|
rm -rf "$pack_dir"
|
|
fi
|
|
}
|
|
|
|
docker_e2e_cleanup_package_mount_args() {
|
|
local expect_volume_path=0
|
|
local arg
|
|
for arg in "${DOCKER_E2E_PACKAGE_ARGS[@]:-}"; do
|
|
if [ "$expect_volume_path" = "1" ]; then
|
|
docker_e2e_cleanup_package_tgz "${arg%%:*}"
|
|
expect_volume_path=0
|
|
continue
|
|
fi
|
|
if [ "$arg" = "-v" ]; then
|
|
expect_volume_path=1
|
|
fi
|
|
done
|
|
}
|
|
|
|
docker_e2e_cleanup_container_cidfile() {
|
|
local cidfile="${1:-}"
|
|
[ -n "$cidfile" ] || return 0
|
|
if [ -f "$cidfile" ]; then
|
|
local container_id
|
|
container_id="$(head -n 1 "$cidfile" 2>/dev/null || true)"
|
|
if [ -n "$container_id" ]; then
|
|
docker_e2e_docker_cmd rm -f "$container_id" >/dev/null 2>&1 || true
|
|
fi
|
|
rm -f "$cidfile"
|
|
fi
|
|
}
|
|
|
|
docker_e2e_harness_mount_args() {
|
|
DOCKER_E2E_HARNESS_ARGS=(
|
|
-v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro"
|
|
-v "$ROOT_DIR/scripts/lib:/app/scripts/lib:ro"
|
|
-v "$ROOT_DIR/scripts/windows-cmd-helpers.mjs:/app/scripts/windows-cmd-helpers.mjs:ro"
|
|
)
|
|
}
|
|
|
|
docker_e2e_run_with_harness() {
|
|
docker_e2e_harness_mount_args
|
|
local run_status=0
|
|
local cid_dir
|
|
local cidfile
|
|
local docker_run_pid=""
|
|
local harness_stdin_fd=""
|
|
local cleanup_done=0
|
|
local previous_int_trap
|
|
local previous_term_trap
|
|
local previous_hup_trap
|
|
cid_dir="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-docker-e2e-container.XXXXXX")"
|
|
cidfile="$cid_dir/container.cid"
|
|
previous_int_trap="$(trap -p INT || true)"
|
|
previous_term_trap="$(trap -p TERM || true)"
|
|
previous_hup_trap="$(trap -p HUP || true)"
|
|
restore_harness_traps() {
|
|
if [ -n "$previous_int_trap" ]; then
|
|
eval "$previous_int_trap"
|
|
else
|
|
trap - INT
|
|
fi
|
|
if [ -n "$previous_term_trap" ]; then
|
|
eval "$previous_term_trap"
|
|
else
|
|
trap - TERM
|
|
fi
|
|
if [ -n "$previous_hup_trap" ]; then
|
|
eval "$previous_hup_trap"
|
|
else
|
|
trap - HUP
|
|
fi
|
|
}
|
|
docker_e2e_harness_descendant_pids() {
|
|
local parent_pid="$1"
|
|
local child_pid
|
|
for child_pid in $(pgrep -P "$parent_pid" 2>/dev/null || true); do
|
|
docker_e2e_harness_descendant_pids "$child_pid"
|
|
printf '%s\n' "$child_pid"
|
|
done
|
|
}
|
|
terminate_harness_docker_run() {
|
|
[ -n "$docker_run_pid" ] || return 0
|
|
kill -0 "$docker_run_pid" 2>/dev/null || return 0
|
|
local descendant_pids
|
|
descendant_pids="$(docker_e2e_harness_descendant_pids "$docker_run_pid")"
|
|
if [ -n "$descendant_pids" ]; then
|
|
kill -TERM $descendant_pids 2>/dev/null || true
|
|
fi
|
|
kill -TERM "$docker_run_pid" 2>/dev/null || true
|
|
local grace_seconds="${OPENCLAW_DOCKER_E2E_CONTAINER_TERM_GRACE_SECONDS:-10}"
|
|
if ! [[ "$grace_seconds" =~ ^[0-9]+$ ]] || [ "$grace_seconds" -lt 1 ]; then
|
|
grace_seconds="10"
|
|
else
|
|
grace_seconds="$((10#$grace_seconds))"
|
|
fi
|
|
local wait_attempt
|
|
for wait_attempt in $(seq 1 "$((grace_seconds * 10))"); do
|
|
if ! kill -0 "$docker_run_pid" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
/bin/sleep 0.1
|
|
done
|
|
descendant_pids="$(docker_e2e_harness_descendant_pids "$docker_run_pid")"
|
|
if [ -n "$descendant_pids" ]; then
|
|
kill -KILL $descendant_pids 2>/dev/null || true
|
|
fi
|
|
kill -KILL "$docker_run_pid" 2>/dev/null || true
|
|
}
|
|
cleanup_harness_run() {
|
|
local cleanup_status="${1:-$?}"
|
|
local exit_after_cleanup="${2:-0}"
|
|
if [ "$cleanup_done" = "1" ]; then
|
|
if [ "$exit_after_cleanup" = "1" ]; then
|
|
exit "$cleanup_status"
|
|
fi
|
|
return "$cleanup_status"
|
|
fi
|
|
cleanup_done=1
|
|
trap - INT TERM HUP
|
|
terminate_harness_docker_run
|
|
wait "$docker_run_pid" 2>/dev/null || true
|
|
docker_e2e_cleanup_container_cidfile "$cidfile"
|
|
rmdir "$cid_dir" 2>/dev/null || true
|
|
docker_e2e_cleanup_package_mount_args
|
|
if [ -n "$harness_stdin_fd" ]; then
|
|
eval "exec ${harness_stdin_fd}<&-"
|
|
fi
|
|
restore_harness_traps
|
|
if [ "$exit_after_cleanup" = "1" ]; then
|
|
exit "$cleanup_status"
|
|
fi
|
|
return "$cleanup_status"
|
|
}
|
|
trap 'cleanup_harness_run 130 1' INT
|
|
trap 'cleanup_harness_run 143 1' TERM
|
|
trap 'cleanup_harness_run 129 1' HUP
|
|
local candidate_fd
|
|
for candidate_fd in 19 18 17 16 15 14 13 12 11 10; do
|
|
if ! eval "true <&${candidate_fd}" 2>/dev/null; then
|
|
harness_stdin_fd="$candidate_fd"
|
|
break
|
|
fi
|
|
done
|
|
if [ -z "$harness_stdin_fd" ]; then
|
|
echo "no free file descriptor available for Docker harness stdin" >&2
|
|
cleanup_harness_run 1
|
|
return 1
|
|
fi
|
|
eval "exec ${harness_stdin_fd}<&0"
|
|
docker_e2e_docker_run_cmd run --rm --cidfile "$cidfile" "${DOCKER_E2E_HARNESS_ARGS[@]}" "$@" <&$harness_stdin_fd &
|
|
docker_run_pid="$!"
|
|
local had_errexit=0
|
|
case "$-" in
|
|
*e*)
|
|
had_errexit=1
|
|
;;
|
|
esac
|
|
set +e
|
|
wait "$docker_run_pid"
|
|
run_status="$?"
|
|
if [ "$had_errexit" = "1" ]; then
|
|
set -e
|
|
fi
|
|
cleanup_harness_run 0
|
|
return "$run_status"
|
|
}
|
|
|
|
docker_e2e_run_detached_with_harness() {
|
|
docker_e2e_harness_mount_args
|
|
docker_e2e_docker_cmd run -d "${DOCKER_E2E_HARNESS_ARGS[@]}" "$@"
|
|
}
|
|
|
|
docker_e2e_run_logged_with_harness() {
|
|
local label="$1"
|
|
shift
|
|
run_logged "$label" docker_e2e_run_with_harness "$@"
|
|
}
|
|
|
|
docker_e2e_run_logged_print_with_harness() {
|
|
local label="$1"
|
|
shift
|
|
run_logged_print_heartbeat \
|
|
"$label" \
|
|
"${OPENCLAW_DOCKER_E2E_LOG_HEARTBEAT_SECONDS:-30}" \
|
|
docker_e2e_run_with_harness \
|
|
"$@"
|
|
}
|