#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" COMPOSE_FILE="${ROOT_DIR}/docker-compose.yml" BACKEND_URL="${BACKEND_URL:-http://localhost:8000}" RUNNER_URL="${RUNNER_URL:-http://localhost:8010}" FRONTEND_URL="${FRONTEND_URL:-http://localhost:3000}" WAIT_SECONDS="${WAIT_SECONDS:-60}" PYTHON_BIN="${PYTHON_BIN:-python3}" cleanup() { docker compose -f "${COMPOSE_FILE}" down --remove-orphans >/dev/null 2>&1 || true } fetch_health() { local name="$1" local url="$2" local deadline=$((SECONDS + WAIT_SECONDS)) local body while (( SECONDS < deadline )); do if body="$(curl -fsS "${url}" 2>/dev/null)"; then printf '%s' "${body}" return 0 fi sleep 1 done echo "Timed out waiting for ${name} at ${url}" >&2 return 1 } assert_service_health() { local name="$1" local url="$2" local expected_service="$3" local body body="$(fetch_health "${name}" "${url}")" HEALTH_NAME="${name}" EXPECTED_SERVICE="${expected_service}" HEALTH_BODY="${body}" "${PYTHON_BIN}" - <<'PY' import json import os import sys name = os.environ["HEALTH_NAME"] expected_service = os.environ["EXPECTED_SERVICE"] body = os.environ["HEALTH_BODY"] try: payload = json.loads(body) except json.JSONDecodeError as error: print(f"{name}: expected JSON response, got: {body!r}", file=sys.stderr) raise SystemExit(1) from error if payload.get("service") != expected_service: print( f"{name}: expected service={expected_service!r}, got {payload.get('service')!r}", file=sys.stderr, ) raise SystemExit(1) if payload.get("status") != "ok": print(f"{name}: expected status='ok', got {payload.get('status')!r}", file=sys.stderr) raise SystemExit(1) PY echo "${name} health ok" } assert_backend_dependencies() { local body body="$(fetch_health "backend dependencies" "${BACKEND_URL}/health/dependencies")" HEALTH_BODY="${body}" "${PYTHON_BIN}" - <<'PY' import json import os import sys body = os.environ["HEALTH_BODY"] required = ("postgres", "redis", "object_storage") try: payload = json.loads(body) except json.JSONDecodeError as error: print(f"backend dependencies: expected JSON response, got: {body!r}", file=sys.stderr) raise SystemExit(1) from error if payload.get("service") != "backend": print( f"backend dependencies: expected service='backend', got {payload.get('service')!r}", file=sys.stderr, ) raise SystemExit(1) if payload.get("status") != "ok": print( f"backend dependencies: expected status='ok', got {payload.get('status')!r}", file=sys.stderr, ) raise SystemExit(1) dependencies = payload.get("dependencies") if not isinstance(dependencies, dict): print("backend dependencies: expected dependencies object", file=sys.stderr) raise SystemExit(1) failed = [name for name in required if dependencies.get(name) != "ok"] if failed: print( "backend dependencies: expected ok for " + ", ".join(failed), file=sys.stderr, ) raise SystemExit(1) PY echo "backend dependencies ok" } cd "${ROOT_DIR}" trap cleanup EXIT echo "Starting stack with docker compose..." docker compose -f "${COMPOSE_FILE}" up --build -d assert_service_health "backend" "${BACKEND_URL}/health" "backend" assert_service_health "runner" "${RUNNER_URL}/health" "runner" assert_service_health "frontend" "${FRONTEND_URL}/health" "frontend" assert_backend_dependencies echo "public health smoke ok"