267 lines
8.3 KiB
Bash
Executable File
267 lines
8.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# shellcheck source=scripts/lib/docker-runtime-smoke.sh
|
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" && pwd)/docker-runtime-smoke.sh"
|
|
|
|
TARGET="${1:-all}"
|
|
DOCKER_SMOKE_RUN_ID="${DOCKER_SMOKE_RUN_ID:-local-$$}"
|
|
DOCKER_SMOKE_REVISION="${DOCKER_SMOKE_REVISION:-}"
|
|
DOCKER_SMOKE_BUILD_TIMEOUT_SECONDS="${DOCKER_SMOKE_BUILD_TIMEOUT_SECONDS:-1200}"
|
|
DOCKER_SMOKE_WEB_RUNTIME_TIMEOUT_SECONDS="${DOCKER_SMOKE_WEB_RUNTIME_TIMEOUT_SECONDS:-720}"
|
|
DOCKER_SMOKE_SERVICE_RUNTIME_TIMEOUT_SECONDS="${DOCKER_SMOKE_SERVICE_RUNTIME_TIMEOUT_SECONDS:-420}"
|
|
DOCKER_SMOKE_ARTIFACT_TIMEOUT_SECONDS="${DOCKER_SMOKE_ARTIFACT_TIMEOUT_SECONDS:-60}"
|
|
DOCKER_SMOKE_COMPOSE_TIMEOUT_SECONDS="${DOCKER_SMOKE_COMPOSE_TIMEOUT_SECONDS:-60}"
|
|
DOCKER_SMOKE_RUNTIME_GRACE_SECONDS="${DOCKER_SMOKE_RUNTIME_GRACE_SECONDS:-30}"
|
|
|
|
if [[ -z "$DOCKER_SMOKE_RUN_ID" || ${#DOCKER_SMOKE_RUN_ID} -gt 80 || ! "$DOCKER_SMOKE_RUN_ID" =~ ^[A-Za-z0-9_.-]+$ ]]; then
|
|
echo "DOCKER_SMOKE_RUN_ID must contain 1-80 Docker-safe characters" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ ! "$DOCKER_SMOKE_REVISION" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
|
echo "DOCKER_SMOKE_REVISION must be a concrete 40-character Git SHA" >&2
|
|
exit 2
|
|
fi
|
|
|
|
case "$TARGET" in
|
|
all | web | worker | api | mcp | hocuspocus | portal-domain-controller) ;;
|
|
*)
|
|
echo "Docker smoke target must be one of: all, web, worker, api, mcp, hocuspocus, portal-domain-controller" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
WEB_IMAGE="hivegrid-web:smoke-${DOCKER_SMOKE_RUN_ID}"
|
|
WORKER_IMAGE="hivegrid-worker:smoke-${DOCKER_SMOKE_RUN_ID}"
|
|
API_IMAGE="hivegrid-api:smoke-${DOCKER_SMOKE_RUN_ID}"
|
|
MCP_IMAGE="hivegrid-mcp:smoke-${DOCKER_SMOKE_RUN_ID}"
|
|
HOCUSPOCUS_IMAGE="hivegrid-hocuspocus:smoke-${DOCKER_SMOKE_RUN_ID}"
|
|
PORTAL_CONTROLLER_IMAGE="hivegrid-portal-domain-controller:smoke-${DOCKER_SMOKE_RUN_ID}"
|
|
WEB_ARTIFACT_CONTAINER_NAME="hivegrid-web-artifact-smoke-${DOCKER_SMOKE_RUN_ID}"
|
|
|
|
export DOCKER_SMOKE_RUN_ID
|
|
export DOCKER_SMOKE_REVISION
|
|
export DOCKER_SMOKE_MIGRATOR_IMAGE="$WEB_IMAGE"
|
|
export DOCKER_WEB_SMOKE_TRANSPORT="${DOCKER_WEB_SMOKE_TRANSPORT:-exec}"
|
|
export DOCKER_API_SMOKE_TRANSPORT="${DOCKER_API_SMOKE_TRANSPORT:-exec}"
|
|
export DOCKER_MCP_SMOKE_TRANSPORT="${DOCKER_MCP_SMOKE_TRANSPORT:-exec}"
|
|
export DOCKER_PORTAL_DOMAIN_CONTROLLER_SMOKE_REVISION="$DOCKER_SMOKE_REVISION"
|
|
|
|
cleanup() {
|
|
docker_smoke_cleanup_container "suite:cleanup:web-artifact" "$WEB_ARTIFACT_CONTAINER_NAME"
|
|
}
|
|
|
|
docker_smoke_install_cleanup_traps cleanup
|
|
cleanup
|
|
|
|
run_parallel_wave() {
|
|
local wave="$1"
|
|
local task
|
|
local index
|
|
local child_status
|
|
local status=0
|
|
local -a tasks=()
|
|
local -a pids=()
|
|
shift
|
|
|
|
if [[ "$#" -eq 0 ]]; then
|
|
echo "[docker-smoke] FAIL stage=wave:${wave} reason=no_tasks exit_code=2" >&2
|
|
return 2
|
|
fi
|
|
|
|
echo "[docker-smoke] WAVE_START wave=${wave} tasks=$#" >&2
|
|
for task in "$@"; do
|
|
tasks+=("$task")
|
|
"$task" &
|
|
pids+=("$!")
|
|
done
|
|
|
|
for index in "${!pids[@]}"; do
|
|
if wait "${pids[$index]}"; then
|
|
echo "[docker-smoke] WAVE_PASS wave=${wave} task=${tasks[$index]}" >&2
|
|
continue
|
|
else
|
|
child_status=$?
|
|
fi
|
|
|
|
echo "[docker-smoke] WAVE_FAIL wave=${wave} task=${tasks[$index]} exit_code=${child_status}" >&2
|
|
if [[ "$status" -eq 0 ]]; then
|
|
status="$child_status"
|
|
fi
|
|
done
|
|
|
|
if [[ "$status" -ne 0 ]]; then
|
|
return "$status"
|
|
fi
|
|
echo "[docker-smoke] WAVE_PASS wave=${wave} tasks=${#tasks[@]}" >&2
|
|
}
|
|
|
|
build_root_target() {
|
|
local target="$1"
|
|
local image="$2"
|
|
local build_args
|
|
|
|
build_args=(
|
|
docker build
|
|
--target "$target"
|
|
--build-arg "APP_REVISION=${DOCKER_SMOKE_REVISION}"
|
|
)
|
|
if [[ "$target" == "web" ]]; then
|
|
build_args+=(--build-arg VITE_HOCUSPOCUS_URL=wss://collab.invalid)
|
|
fi
|
|
build_args+=(--tag "$image" .)
|
|
|
|
docker_smoke_run "build:${target}" "$DOCKER_SMOKE_BUILD_TIMEOUT_SECONDS" "${build_args[@]}"
|
|
}
|
|
|
|
build_hocuspocus() {
|
|
docker_smoke_run "build:hocuspocus" "$DOCKER_SMOKE_BUILD_TIMEOUT_SECONDS" \
|
|
docker build \
|
|
--file apps/hocuspocus/Dockerfile \
|
|
--target hocuspocus \
|
|
--build-arg "APP_REVISION=${DOCKER_SMOKE_REVISION}" \
|
|
--tag "$HOCUSPOCUS_IMAGE" \
|
|
.
|
|
}
|
|
|
|
image_matches_revision() {
|
|
local image="$1"
|
|
local actual_revision=""
|
|
|
|
if ! docker_smoke_capture actual_revision "image:inspect" "$DOCKER_SMOKE_PROBE_TIMEOUT_SECONDS" \
|
|
docker image inspect -f '{{ index .Config.Labels "org.opencontainers.image.revision" }}' "$image"; then
|
|
return 1
|
|
fi
|
|
[[ "$actual_revision" == "$DOCKER_SMOKE_REVISION" ]]
|
|
}
|
|
|
|
ensure_web_migrator_image() {
|
|
if image_matches_revision "$WEB_IMAGE"; then
|
|
echo "[docker-smoke] REUSE stage=image:web revision=${DOCKER_SMOKE_REVISION}" >&2
|
|
return
|
|
fi
|
|
|
|
build_root_target web "$WEB_IMAGE"
|
|
}
|
|
|
|
validate_web() {
|
|
docker_smoke_run "artifacts:web" "$DOCKER_SMOKE_ARTIFACT_TIMEOUT_SECONDS" \
|
|
docker run --rm --name "$WEB_ARTIFACT_CONTAINER_NAME" --entrypoint /bin/sh "$WEB_IMAGE" -lc \
|
|
'test -f /myapp/apps/web/build/server/index.js && test -d /myapp/apps/web/build/client && test -f /myapp/apps/web/build-portal/server/index.js && test -d /myapp/apps/web/build-portal/client && test -f /myapp/build/server-runtime/server.js'
|
|
docker_smoke_run_with_grace "runtime:web" "$DOCKER_SMOKE_WEB_RUNTIME_TIMEOUT_SECONDS" "$DOCKER_SMOKE_RUNTIME_GRACE_SECONDS" \
|
|
bash scripts/docker-web-runtime-smoke.sh "$WEB_IMAGE"
|
|
}
|
|
|
|
build_web() {
|
|
build_root_target web "$WEB_IMAGE"
|
|
}
|
|
|
|
build_worker() {
|
|
build_root_target worker "$WORKER_IMAGE"
|
|
}
|
|
|
|
validate_worker() {
|
|
docker_smoke_run_with_grace "runtime:worker" "$DOCKER_SMOKE_SERVICE_RUNTIME_TIMEOUT_SECONDS" "$DOCKER_SMOKE_RUNTIME_GRACE_SECONDS" \
|
|
bash scripts/docker-worker-runtime-smoke.sh "$WORKER_IMAGE"
|
|
}
|
|
|
|
build_and_validate_worker() {
|
|
build_worker
|
|
validate_worker
|
|
}
|
|
|
|
build_api() {
|
|
build_root_target api "$API_IMAGE"
|
|
}
|
|
|
|
validate_api() {
|
|
docker_smoke_run_with_grace "runtime:api" "$DOCKER_SMOKE_SERVICE_RUNTIME_TIMEOUT_SECONDS" "$DOCKER_SMOKE_RUNTIME_GRACE_SECONDS" \
|
|
bash scripts/docker-api-runtime-smoke.sh "$API_IMAGE"
|
|
}
|
|
|
|
build_and_validate_api() {
|
|
build_api
|
|
validate_api
|
|
}
|
|
|
|
build_mcp() {
|
|
build_root_target mcp "$MCP_IMAGE"
|
|
}
|
|
|
|
validate_mcp() {
|
|
docker_smoke_run_with_grace "runtime:mcp" "$DOCKER_SMOKE_SERVICE_RUNTIME_TIMEOUT_SECONDS" "$DOCKER_SMOKE_RUNTIME_GRACE_SECONDS" \
|
|
bash scripts/docker-mcp-runtime-smoke.sh "$MCP_IMAGE"
|
|
}
|
|
|
|
build_and_validate_mcp() {
|
|
build_mcp
|
|
validate_mcp
|
|
}
|
|
|
|
validate_hocuspocus() {
|
|
docker_smoke_run_with_grace "runtime:hocuspocus" "$DOCKER_SMOKE_SERVICE_RUNTIME_TIMEOUT_SECONDS" "$DOCKER_SMOKE_RUNTIME_GRACE_SECONDS" \
|
|
bash scripts/docker-hocuspocus-runtime-smoke.sh "$HOCUSPOCUS_IMAGE"
|
|
}
|
|
|
|
build_and_validate_hocuspocus() {
|
|
build_hocuspocus
|
|
validate_hocuspocus
|
|
}
|
|
|
|
build_portal_controller() {
|
|
build_root_target portal-domain-controller "$PORTAL_CONTROLLER_IMAGE"
|
|
}
|
|
|
|
validate_portal_controller() {
|
|
docker_smoke_run_with_grace "runtime:portal-domain-controller" "$DOCKER_SMOKE_SERVICE_RUNTIME_TIMEOUT_SECONDS" "$DOCKER_SMOKE_RUNTIME_GRACE_SECONDS" \
|
|
bash scripts/docker-portal-domain-controller-runtime-smoke.sh "$PORTAL_CONTROLLER_IMAGE"
|
|
}
|
|
|
|
build_and_validate_portal_controller() {
|
|
build_portal_controller
|
|
validate_portal_controller
|
|
}
|
|
|
|
# Validate interpolation against a committed non-secret fixture. `--quiet`
|
|
# prevents Compose from echoing resolved developer or CI credentials.
|
|
docker_smoke_run "compose:config" "$DOCKER_SMOKE_COMPOSE_TIMEOUT_SECONDS" \
|
|
docker compose --env-file .env.test config --quiet
|
|
|
|
case "$TARGET" in
|
|
all)
|
|
# All images are complete before any service consumes the web migrator image.
|
|
run_parallel_wave builds \
|
|
build_web build_worker build_api build_mcp build_hocuspocus build_portal_controller
|
|
# Runtime resources are service- and run-scoped; their databases and Redis instances
|
|
# intentionally remain isolated while the six unchanged validations run concurrently.
|
|
run_parallel_wave runtimes \
|
|
validate_web validate_worker validate_api validate_mcp validate_hocuspocus validate_portal_controller
|
|
;;
|
|
web)
|
|
build_web
|
|
validate_web
|
|
;;
|
|
worker)
|
|
ensure_web_migrator_image
|
|
build_and_validate_worker
|
|
;;
|
|
api)
|
|
ensure_web_migrator_image
|
|
build_and_validate_api
|
|
;;
|
|
mcp)
|
|
ensure_web_migrator_image
|
|
build_and_validate_mcp
|
|
;;
|
|
hocuspocus)
|
|
ensure_web_migrator_image
|
|
build_and_validate_hocuspocus
|
|
;;
|
|
portal-domain-controller)
|
|
build_and_validate_portal_controller
|
|
;;
|
|
esac
|
|
|
|
echo "Docker smoke suite passed for target=${TARGET} revision=${DOCKER_SMOKE_REVISION} run=${DOCKER_SMOKE_RUN_ID}"
|