Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/scripts/lib/docker-runtime-smoke.sh
T

363 lines
10 KiB
Bash

#!/usr/bin/env bash
# Shared, Bash 3.2-compatible execution boundaries for Docker runtime smokes.
# The Node runner deliberately logs stage metadata only; command arguments and
# environment values must never be copied into wrapper diagnostics.
_DOCKER_SMOKE_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export DOCKER_SMOKE_NODE_BIN="${DOCKER_SMOKE_NODE_BIN:-node}"
export DOCKER_SMOKE_TIMEOUT_RUNNER="${DOCKER_SMOKE_TIMEOUT_RUNNER:-${_DOCKER_SMOKE_LIB_DIR}/run-command-with-timeout.mjs}"
export DOCKER_SMOKE_COMMAND_TIMEOUT_SECONDS="${DOCKER_SMOKE_COMMAND_TIMEOUT_SECONDS:-30}"
export DOCKER_SMOKE_PROBE_TIMEOUT_SECONDS="${DOCKER_SMOKE_PROBE_TIMEOUT_SECONDS:-5}"
export DOCKER_SMOKE_LOG_TIMEOUT_SECONDS="${DOCKER_SMOKE_LOG_TIMEOUT_SECONDS:-10}"
export DOCKER_SMOKE_CLEANUP_TIMEOUT_SECONDS="${DOCKER_SMOKE_CLEANUP_TIMEOUT_SECONDS:-15}"
export DOCKER_SMOKE_DATABASE_READY_TIMEOUT_SECONDS="${DOCKER_SMOKE_DATABASE_READY_TIMEOUT_SECONDS:-90}"
export DOCKER_SMOKE_REDIS_READY_TIMEOUT_SECONDS="${DOCKER_SMOKE_REDIS_READY_TIMEOUT_SECONDS:-60}"
export DOCKER_SMOKE_MIGRATION_TIMEOUT_SECONDS="${DOCKER_SMOKE_MIGRATION_TIMEOUT_SECONDS:-300}"
export DOCKER_SMOKE_ROLE_PROVISION_TIMEOUT_SECONDS="${DOCKER_SMOKE_ROLE_PROVISION_TIMEOUT_SECONDS:-30}"
export DOCKER_SMOKE_TIMEOUT_GRACE_SECONDS="${DOCKER_SMOKE_TIMEOUT_GRACE_SECONDS:-2}"
export DOCKER_SMOKE_LOG_TAIL="${DOCKER_SMOKE_LOG_TAIL:-200}"
DOCKER_SMOKE_EXPECTED_REJECTION_MARKER="hivegrid-smoke-expected-rejection-v1"
_DOCKER_SMOKE_CLEANUP_CALLBACK=""
_DOCKER_SMOKE_CLEANUP_ACTIVE=0
docker_smoke_run() {
if [[ "$#" -lt 3 ]]; then
printf '%s\n' '[docker-smoke] FAIL stage=helper reason=invalid_arguments exit_code=2' >&2
return 2
fi
local stage="$1"
local timeout_seconds="${2:-$DOCKER_SMOKE_COMMAND_TIMEOUT_SECONDS}"
shift 2
docker_smoke_run_with_grace "$stage" "$timeout_seconds" "$DOCKER_SMOKE_TIMEOUT_GRACE_SECONDS" "$@"
}
docker_smoke_run_with_grace() {
if [[ "$#" -lt 4 ]]; then
printf '%s\n' '[docker-smoke] FAIL stage=helper reason=invalid_arguments exit_code=2' >&2
return 2
fi
local stage="$1"
local timeout_seconds="${2:-$DOCKER_SMOKE_COMMAND_TIMEOUT_SECONDS}"
local grace_seconds="${3:-$DOCKER_SMOKE_TIMEOUT_GRACE_SECONDS}"
shift 3
"$DOCKER_SMOKE_NODE_BIN" "$DOCKER_SMOKE_TIMEOUT_RUNNER" \
--stage "$stage" \
--timeout-seconds "$timeout_seconds" \
--grace-seconds "$grace_seconds" \
-- "$@"
}
docker_smoke_expect_docker_exec_rejection() {
if [[ "$#" -lt 4 ]]; then
printf '%s\n' '[docker-smoke] FAIL stage=helper reason=invalid_arguments exit_code=2' >&2
return 2
fi
local stage="$1"
local timeout_seconds="${2:-$DOCKER_SMOKE_COMMAND_TIMEOUT_SECONDS}"
local container_name="$3"
local observed_output=""
local command_status
shift 3
if docker_smoke_capture observed_output "$stage" "$timeout_seconds" \
docker exec "$container_name" sh -c '
marker="$1"
shift
"$@"
command_status=$?
if [ "$command_status" -eq 1 ]; then
printf "%s" "$marker"
exit 0
fi
if [ "$command_status" -eq 0 ]; then
exit 64
fi
exit "$command_status"
' docker-smoke-rejection "$DOCKER_SMOKE_EXPECTED_REJECTION_MARKER" "$@"; then
command_status=0
else
command_status=$?
fi
if [[ "$command_status" -ne 0 ]]; then
return "$command_status"
fi
if [[ "$observed_output" != "$DOCKER_SMOKE_EXPECTED_REJECTION_MARKER" ]]; then
printf '%s\n' "[docker-smoke] FAIL stage=${stage} reason=missing_expected_rejection_marker exit_code=1" >&2
return 1
fi
printf '%s\n' "[docker-smoke] ASSERT stage=${stage} outcome=expected_rejection" >&2
}
docker_smoke_capture() {
if [[ "$#" -lt 4 ]]; then
printf '%s\n' '[docker-smoke] FAIL stage=helper reason=invalid_arguments exit_code=2' >&2
return 2
fi
local output_variable="$1"
local stage="$2"
local timeout_seconds="${3:-$DOCKER_SMOKE_COMMAND_TIMEOUT_SECONDS}"
local _docker_smoke_captured_value
local command_status
shift 3
if [[ ! "$output_variable" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
printf '%s\n' '[docker-smoke] FAIL stage=helper reason=invalid_output_variable exit_code=2' >&2
return 2
fi
if _docker_smoke_captured_value="$(docker_smoke_run "$stage" "$timeout_seconds" "$@")"; then
command_status=0
else
command_status=$?
fi
if [[ "$command_status" -ne 0 ]]; then
if [[ -n "$_docker_smoke_captured_value" ]]; then
printf '%s\n' "$_docker_smoke_captured_value" >&2
fi
return "$command_status"
fi
printf -v "$output_variable" '%s' "$_docker_smoke_captured_value"
}
docker_smoke_probe() {
if [[ "$#" -lt 2 ]]; then
printf '%s\n' '[docker-smoke] FAIL stage=helper reason=invalid_arguments exit_code=2' >&2
return 2
fi
local stage="$1"
shift
docker_smoke_run "$stage" "$DOCKER_SMOKE_PROBE_TIMEOUT_SECONDS" "$@"
}
docker_smoke_wait_for_postgres() {
if [[ "$#" -ne 4 ]]; then
printf '%s\n' '[docker-smoke] FAIL stage=postgres-readiness reason=invalid_arguments exit_code=2' >&2
return 2
fi
local stage="$1"
local container_name="$2"
local database_user="$3"
local database_name="$4"
local deadline_seconds=$((SECONDS + DOCKER_SMOKE_DATABASE_READY_TIMEOUT_SECONDS))
while [[ "$SECONDS" -lt "$deadline_seconds" ]]; do
if docker_smoke_probe "$stage" docker exec "$container_name" \
pg_isready -h 127.0.0.1 -U "$database_user" -d "$database_name" >/dev/null 2>&1; then
return 0
fi
sleep 1
done
return 1
}
docker_smoke_logs() {
if [[ "$#" -ne 2 ]]; then
printf '%s\n' '[docker-smoke] FAIL stage=helper reason=invalid_arguments exit_code=2' >&2
return 2
fi
local stage="$1"
local container_name="$2"
docker_smoke_run "$stage" "$DOCKER_SMOKE_LOG_TIMEOUT_SECONDS" \
docker logs --tail "$DOCKER_SMOKE_LOG_TAIL" "$container_name"
}
docker_smoke_cleanup_run() {
if [[ "$#" -lt 2 ]]; then
return 0
fi
local stage="$1"
local cleanup_status
shift
if docker_smoke_run "$stage" "$DOCKER_SMOKE_CLEANUP_TIMEOUT_SECONDS" "$@" >/dev/null; then
return 0
else
cleanup_status=$?
fi
printf '%s\n' "[docker-smoke] WARN stage=${stage} reason=cleanup_incomplete exit_code=${cleanup_status}" >&2
return 0
}
_docker_smoke_cleanup_resources() {
if [[ "$#" -lt 3 ]]; then
return 0
fi
local resource_kind="$1"
local stage="$2"
shift 2
docker_smoke_cleanup_run "$stage" bash -c '
set -u
resource_kind="$1"
shift
removal_status=0
removal_output=""
case "$resource_kind" in
container)
removal_output="$(docker container rm -f "$@" 2>&1 >/dev/null)" || removal_status=$?
;;
network|volume)
removal_output="$(docker "$resource_kind" rm "$@" 2>&1 >/dev/null)" || removal_status=$?
;;
*)
exit 2
;;
esac
cleanup_incomplete=0
for resource_name in "$@"; do
inspect_status=0
resource_absent=0
inspect_output="$(docker "$resource_kind" inspect "$resource_name" 2>&1 >/dev/null)" || inspect_status=$?
if [[ "$inspect_status" -eq 0 ]]; then
printf "[docker-smoke] RETAINED kind=%s name=%s\n" "$resource_kind" "$resource_name" >&2
cleanup_incomplete=1
continue
fi
case "$resource_kind" in
container)
case "$inspect_output" in
*"No such object: $resource_name"*|*"No such container: $resource_name"*) resource_absent=1 ;;
esac
;;
network)
case "$inspect_output" in
*"No such network: $resource_name"*|*"network $resource_name not found"*) resource_absent=1 ;;
esac
;;
volume)
case "$inspect_output" in
*"No such volume: $resource_name"*|*"no such volume: $resource_name"*|*"get $resource_name: no such volume"*) resource_absent=1 ;;
esac
;;
esac
if [[ "$resource_absent" -ne 1 ]]; then
if [[ -n "$inspect_output" ]]; then
printf "%s\n" "$inspect_output" >&2
fi
printf "[docker-smoke] CLEANUP_CHECK_FAILED kind=%s name=%s\n" "$resource_kind" "$resource_name" >&2
cleanup_incomplete=1
fi
done
if [[ "$cleanup_incomplete" -eq 1 ]]; then
if [[ -n "$removal_output" ]]; then
printf "%s\n" "$removal_output" >&2
fi
if [[ "$removal_status" -eq 0 ]]; then
removal_status=1
fi
exit "$removal_status"
fi
' docker-smoke-cleanup "$resource_kind" "$@"
}
docker_smoke_cleanup_container() {
if [[ "$#" -lt 2 ]]; then
return 0
fi
local stage="$1"
shift
_docker_smoke_cleanup_resources container "$stage" "$@"
}
docker_smoke_cleanup_network() {
if [[ "$#" -lt 2 ]]; then
return 0
fi
local stage="$1"
shift
_docker_smoke_cleanup_resources network "$stage" "$@"
}
docker_smoke_cleanup_volume() {
if [[ "$#" -lt 2 ]]; then
return 0
fi
local stage="$1"
shift
_docker_smoke_cleanup_resources volume "$stage" "$@"
}
_docker_smoke_invoke_cleanup() {
if [[ "$_DOCKER_SMOKE_CLEANUP_ACTIVE" -eq 1 || -z "$_DOCKER_SMOKE_CLEANUP_CALLBACK" ]]; then
return 0
fi
_DOCKER_SMOKE_CLEANUP_ACTIVE=1
if "$_DOCKER_SMOKE_CLEANUP_CALLBACK"; then
:
else
:
fi
_DOCKER_SMOKE_CLEANUP_ACTIVE=0
return 0
}
_docker_smoke_handle_exit() {
local original_status="$1"
trap - EXIT INT TERM
_docker_smoke_invoke_cleanup
exit "$original_status"
}
_docker_smoke_handle_signal() {
local signal_name="$1"
local signal_status
case "$signal_name" in
INT)
signal_status=130
;;
TERM)
signal_status=143
;;
*)
signal_status=1
;;
esac
trap - EXIT INT TERM
_docker_smoke_invoke_cleanup
exit "$signal_status"
}
docker_smoke_install_cleanup_traps() {
if [[ "$#" -ne 1 || -z "$1" ]] || ! declare -F "$1" >/dev/null 2>&1; then
printf '%s\n' '[docker-smoke] FAIL stage=helper reason=invalid_cleanup_callback exit_code=2' >&2
return 2
fi
_DOCKER_SMOKE_CLEANUP_CALLBACK="$1"
trap '_docker_smoke_handle_exit "$?"' EXIT
trap '_docker_smoke_handle_signal INT' INT
trap '_docker_smoke_handle_signal TERM' TERM
}
unset _DOCKER_SMOKE_LIB_DIR