191 lines
6.2 KiB
Bash
191 lines
6.2 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
IMAGE="${1:-toolctrl-worker:smoke}"
|
|
CONTAINER_NAME="${DOCKER_WORKER_SMOKE_CONTAINER:-toolctrl-worker-runtime-smoke-$$}"
|
|
DB_CONTAINER_NAME="${DOCKER_WORKER_SMOKE_DB_CONTAINER:-toolctrl-worker-runtime-smoke-db-$$}"
|
|
REDIS_CONTAINER_NAME="${DOCKER_WORKER_SMOKE_REDIS_CONTAINER:-toolctrl-worker-runtime-smoke-redis-$$}"
|
|
NETWORK_NAME="${DOCKER_WORKER_SMOKE_NETWORK:-toolctrl-worker-runtime-smoke-$$}"
|
|
SMOKE_REVISION="${DOCKER_WORKER_SMOKE_REVISION:-}"
|
|
DB_PASSWORD="toolctrl"
|
|
DB_NAME="toolctrl"
|
|
DB_USER="toolctrl"
|
|
|
|
cleanup() {
|
|
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
docker rm -f "$DB_CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
docker rm -f "$REDIS_CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
docker network rm "$NETWORK_NAME" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
resolve_smoke_revision() {
|
|
if [[ -n "$SMOKE_REVISION" ]]; then
|
|
printf "%s" "$SMOKE_REVISION"
|
|
return
|
|
fi
|
|
|
|
docker image inspect -f '{{ index .Config.Labels "org.opencontainers.image.revision" }}' "$IMAGE"
|
|
}
|
|
|
|
trap cleanup EXIT
|
|
cleanup
|
|
SMOKE_REVISION="$(resolve_smoke_revision)"
|
|
if [[ -z "$SMOKE_REVISION" || "$SMOKE_REVISION" == "<no value>" || "$SMOKE_REVISION" == "unknown" ]]; then
|
|
echo "Image ${IMAGE} does not expose a concrete org.opencontainers.image.revision; build with --build-arg APP_REVISION or set DOCKER_WORKER_SMOKE_REVISION explicitly" >&2
|
|
exit 1
|
|
fi
|
|
|
|
worker_env=(
|
|
-e NODE_ENV=production
|
|
-e APP_NAME=ToolCTRL
|
|
-e APP_BASE_DOMAIN=localhost
|
|
)
|
|
|
|
expect_worker_health() {
|
|
local output="$1"
|
|
local expected_status="$2"
|
|
local expected_enabled="$3"
|
|
local expected_group="${4:-}"
|
|
local expected_queue="${5:-}"
|
|
|
|
node -e '
|
|
const [raw, expectedStatus, expectedEnabled, expectedRevision, expectedGroup, expectedQueue] = process.argv.slice(1);
|
|
const report = JSON.parse(raw);
|
|
if (report.status !== expectedStatus) {
|
|
console.error(`Expected worker status ${expectedStatus}, got ${report.status}`);
|
|
process.exit(1);
|
|
}
|
|
if (String(report.workerEnabled) !== expectedEnabled) {
|
|
console.error(`Expected workerEnabled ${expectedEnabled}, got ${report.workerEnabled}`);
|
|
process.exit(1);
|
|
}
|
|
if (report.revision !== expectedRevision) {
|
|
console.error(`Expected revision ${expectedRevision}, got ${report.revision || "<empty>"}`);
|
|
process.exit(1);
|
|
}
|
|
if (expectedGroup && report.workerGroup !== expectedGroup) {
|
|
console.error(`Expected workerGroup ${expectedGroup}, got ${report.workerGroup || "<empty>"}`);
|
|
process.exit(1);
|
|
}
|
|
if (expectedQueue && !Array.isArray(report.queues)) {
|
|
console.error("Expected worker health queues to be an array");
|
|
process.exit(1);
|
|
}
|
|
if (expectedQueue && !report.queues.includes(expectedQueue)) {
|
|
console.error(`Expected worker queues to include ${expectedQueue}, got ${report.queues?.join(",") || "<empty>"}`);
|
|
process.exit(1);
|
|
}
|
|
if (expectedStatus === "ready" && (report.databaseStatus !== "ready" || report.redisStatus !== "ready")) {
|
|
console.error(`Expected ready DB/Redis, got database=${report.databaseStatus} redis=${report.redisStatus}`);
|
|
process.exit(1);
|
|
}
|
|
' "$output" "$expected_status" "$expected_enabled" "$SMOKE_REVISION" "$expected_group" "$expected_queue"
|
|
}
|
|
|
|
docker network create "$NETWORK_NAME" >/dev/null
|
|
|
|
health_output="$(
|
|
docker run --rm \
|
|
"${worker_env[@]}" \
|
|
-e WORKER_ENABLED=false \
|
|
"$IMAGE" node apps/worker/dist/health.js
|
|
)"
|
|
|
|
expect_worker_health "$health_output" disabled false
|
|
|
|
docker run -d --name "$DB_CONTAINER_NAME" --network "$NETWORK_NAME" --network-alias db \
|
|
-e POSTGRES_USER="$DB_USER" \
|
|
-e POSTGRES_PASSWORD="$DB_PASSWORD" \
|
|
-e POSTGRES_DB="$DB_NAME" \
|
|
postgres:16-alpine >/dev/null
|
|
|
|
docker run -d --name "$REDIS_CONTAINER_NAME" --network "$NETWORK_NAME" --network-alias redis redis:7-alpine >/dev/null
|
|
|
|
CONTAINER_DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}?schema=public"
|
|
CONTAINER_REDIS_URL="redis://redis:6379/0"
|
|
|
|
for attempt in $(seq 1 30); do
|
|
if docker exec "$DB_CONTAINER_NAME" pg_isready -U "$DB_USER" -d "$DB_NAME" >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
|
|
if [[ "$attempt" == "30" ]]; then
|
|
echo "Postgres smoke database did not become ready" >&2
|
|
docker logs "$DB_CONTAINER_NAME" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
sleep 1
|
|
done
|
|
|
|
for attempt in $(seq 1 30); do
|
|
if docker exec "$REDIS_CONTAINER_NAME" redis-cli ping >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
|
|
if [[ "$attempt" == "30" ]]; then
|
|
echo "Redis smoke broker did not become ready" >&2
|
|
docker logs "$REDIS_CONTAINER_NAME" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
sleep 1
|
|
done
|
|
|
|
docker run --rm --network "$NETWORK_NAME" \
|
|
-e NODE_ENV=production \
|
|
-e DATABASE_URL="$CONTAINER_DATABASE_URL" \
|
|
--entrypoint /bin/sh \
|
|
"$IMAGE" -lc 'cd /myapp && pnpm exec prisma migrate deploy --config prisma.config.ts' >/dev/null
|
|
|
|
enabled_health_output="$(
|
|
docker run --rm --network "$NETWORK_NAME" \
|
|
"${worker_env[@]}" \
|
|
-e WORKER_ENABLED=true \
|
|
-e WORKER_QUEUE_GROUP=general \
|
|
-e DATABASE_URL="$CONTAINER_DATABASE_URL" \
|
|
-e BULLMQ_REDIS_URL="$CONTAINER_REDIS_URL" \
|
|
"$IMAGE" node apps/worker/dist/health.js
|
|
)"
|
|
expect_worker_health "$enabled_health_output" ready true general toolctrl.notifications
|
|
|
|
mail_health_output="$(
|
|
docker run --rm --network "$NETWORK_NAME" \
|
|
"${worker_env[@]}" \
|
|
-e WORKER_ENABLED=true \
|
|
-e WORKER_QUEUE_GROUP=mail \
|
|
-e DATABASE_URL="$CONTAINER_DATABASE_URL" \
|
|
-e BULLMQ_REDIS_URL="$CONTAINER_REDIS_URL" \
|
|
"$IMAGE" node apps/worker/dist/health.js
|
|
)"
|
|
expect_worker_health "$mail_health_output" ready true mail toolctrl.email
|
|
|
|
docker run -d --name "$CONTAINER_NAME" --network "$NETWORK_NAME" \
|
|
"${worker_env[@]}" \
|
|
-e WORKER_ENABLED=true \
|
|
-e WORKER_QUEUE_GROUP=general \
|
|
-e DATABASE_URL="$CONTAINER_DATABASE_URL" \
|
|
-e BULLMQ_REDIS_URL="$CONTAINER_REDIS_URL" \
|
|
"$IMAGE" >/tmp/toolctrl-worker-runtime-smoke.log
|
|
|
|
for attempt in $(seq 1 10); do
|
|
if [[ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" 2>/dev/null || true)" == "true" ]]; then
|
|
break
|
|
fi
|
|
|
|
if [[ "$attempt" == "10" ]]; then
|
|
echo "Worker container did not stay running" >&2
|
|
docker logs "$CONTAINER_NAME" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
sleep 1
|
|
done
|
|
|
|
running_health_output="$(
|
|
docker exec "$CONTAINER_NAME" node apps/worker/dist/health.js
|
|
)"
|
|
expect_worker_health "$running_health_output" ready true general toolctrl.notifications
|
|
|
|
echo "Docker worker runtime smoke passed for ${IMAGE}"
|