Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE="${1:-toolctrl-api:smoke}"
|
||||
PORT="${DOCKER_API_SMOKE_PORT:-}"
|
||||
CONTAINER_NAME="${DOCKER_API_SMOKE_CONTAINER:-toolctrl-api-runtime-smoke-$$}"
|
||||
DB_CONTAINER_NAME="${DOCKER_API_SMOKE_DB_CONTAINER:-toolctrl-api-runtime-smoke-db-$$}"
|
||||
NETWORK_NAME="${DOCKER_API_SMOKE_NETWORK:-toolctrl-api-runtime-smoke-$$}"
|
||||
TRANSPORT="${DOCKER_API_SMOKE_TRANSPORT:-host}"
|
||||
SMOKE_REVISION="${DOCKER_API_SMOKE_REVISION:-}"
|
||||
HEALTH_ATTEMPTS="${DOCKER_API_SMOKE_HEALTH_ATTEMPTS:-300}"
|
||||
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 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"
|
||||
}
|
||||
|
||||
http_status() {
|
||||
local path="$1"
|
||||
local code
|
||||
|
||||
if [[ "$TRANSPORT" == "exec" ]]; then
|
||||
code="$(docker exec "$CONTAINER_NAME" node -e 'const http = require("node:http"); const [path] = process.argv.slice(1); const request = http.request({ hostname: "127.0.0.1", port: 8080, path, headers: { Host: "api.localhost" } }, (response) => { process.stdout.write(String(response.statusCode ?? "000")); response.resume(); }); request.on("error", () => process.stdout.write("000")); request.end();' "$path" || true)"
|
||||
else
|
||||
code="$(curl -s -o /dev/null -w "%{http_code}" -H "Host: api.localhost" "http://127.0.0.1:${PORT}${path}" || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "$code" ]]; then
|
||||
printf "000"
|
||||
return
|
||||
fi
|
||||
|
||||
printf "%s" "$code"
|
||||
}
|
||||
|
||||
http_body() {
|
||||
local path="$1"
|
||||
|
||||
if [[ "$TRANSPORT" == "exec" ]]; then
|
||||
docker exec "$CONTAINER_NAME" node -e 'const http = require("node:http"); const [path] = process.argv.slice(1); const request = http.request({ hostname: "127.0.0.1", port: 8080, path, headers: { Host: "api.localhost" } }, (response) => { let body = ""; response.setEncoding("utf8"); response.on("data", (chunk) => { body += chunk; }); response.on("end", () => process.stdout.write(body)); }); request.on("error", () => process.exit(1)); request.end();' "$path"
|
||||
else
|
||||
curl --fail --silent --show-error -H "Host: api.localhost" "http://127.0.0.1:${PORT}${path}"
|
||||
fi
|
||||
}
|
||||
|
||||
expect_status() {
|
||||
local path="$1"
|
||||
local expected="$2"
|
||||
local actual
|
||||
|
||||
actual="$(http_status "$path")"
|
||||
if [[ "$actual" != "$expected" ]]; then
|
||||
echo "Expected ${path} to return ${expected}, got ${actual}" >&2
|
||||
docker logs "$CONTAINER_NAME" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expect_health_revision() {
|
||||
local path="$1"
|
||||
local body
|
||||
local actual
|
||||
|
||||
body="$(http_body "$path")"
|
||||
actual="$(printf "%s" "$body" | node -e 'const fs = require("node:fs"); try { const payload = JSON.parse(fs.readFileSync(0, "utf8")); process.stdout.write(payload.revision || ""); } catch { process.exit(1); }')"
|
||||
if [[ "$actual" != "$SMOKE_REVISION" ]]; then
|
||||
echo "Expected ${path} revision ${SMOKE_REVISION}, got ${actual:-<empty>}" >&2
|
||||
docker logs "$CONTAINER_NAME" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expect_openapi_json() {
|
||||
local path="$1"
|
||||
local body
|
||||
|
||||
body="$(http_body "$path")"
|
||||
printf "%s" "$body" | node -e '
|
||||
const fs = require("node:fs");
|
||||
const payload = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
if (typeof payload.openapi !== "string") throw new Error("missing openapi version");
|
||||
if (!payload.paths || !payload.paths["/v1/health"] || !payload.paths["/v1/entities/{entity}/rows"]) {
|
||||
throw new Error("missing expected OpenAPI paths");
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
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_API_SMOKE_REVISION explicitly" >&2
|
||||
exit 1
|
||||
fi
|
||||
docker network create "$NETWORK_NAME" >/dev/null
|
||||
|
||||
docker run -d --name "$DB_CONTAINER_NAME" --network "$NETWORK_NAME" --network-alias db -p 127.0.0.1::5432 \
|
||||
-e POSTGRES_USER="$DB_USER" \
|
||||
-e POSTGRES_PASSWORD="$DB_PASSWORD" \
|
||||
-e POSTGRES_DB="$DB_NAME" \
|
||||
postgres:16-alpine >/dev/null
|
||||
|
||||
CONTAINER_DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}?schema=public"
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
publish_args=(-p 127.0.0.1::8080)
|
||||
if [[ -n "$PORT" ]]; then
|
||||
publish_args=(-p "${PORT}:8080")
|
||||
fi
|
||||
|
||||
docker run -d --name "$CONTAINER_NAME" --network "$NETWORK_NAME" "${publish_args[@]}" \
|
||||
-e NODE_ENV=production \
|
||||
-e PORT=8080 \
|
||||
-e API_PORT=8080 \
|
||||
-e APP_NAME=ToolCTRL \
|
||||
-e APP_BASE_DOMAIN=localhost \
|
||||
-e WEB_APP_HOST=app.localhost \
|
||||
-e ADMIN_APP_HOST=admin.localhost \
|
||||
-e API_HOST=api.localhost \
|
||||
-e APP_PROTOCOL=http \
|
||||
-e DATABASE_URL="$CONTAINER_DATABASE_URL" \
|
||||
-e SESSION_SECRET='deploy-smoke-session-secret-minimum-32' \
|
||||
-e CRYPTO_SECRET='deploy-smoke-crypto-secret-minimum-32' \
|
||||
-e JWT_SECRET='deploy-smoke-jwt-secret-minimum-32' \
|
||||
-e API_ACCESS_TOKEN='deploy-smoke-api-access-token-minimum-32' \
|
||||
-e ANALYTICS_TOKEN='deploy-smoke-analytics-token-minimum-32' \
|
||||
"$IMAGE" >/dev/null
|
||||
|
||||
if [[ -z "$PORT" ]]; then
|
||||
PORT="$(docker port "$CONTAINER_NAME" 8080/tcp | awk -F: 'NR == 1 { print $NF }')"
|
||||
fi
|
||||
|
||||
for attempt in $(seq 1 "$HEALTH_ATTEMPTS"); do
|
||||
if [[ "$(http_status /healthz)" == "200" ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
if [[ "$attempt" == "$HEALTH_ATTEMPTS" ]]; then
|
||||
echo "API container did not become healthy on port ${PORT} after ${HEALTH_ATTEMPTS}s" >&2
|
||||
docker logs "$CONTAINER_NAME" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
expect_status /healthz 200
|
||||
expect_health_revision /healthz
|
||||
expect_status /v1/docs 200
|
||||
expect_status /v1/openapi.json 200
|
||||
expect_openapi_json /v1/openapi.json
|
||||
expect_status /readyz 200
|
||||
|
||||
echo "Docker API runtime smoke passed for ${IMAGE}"
|
||||
Reference in new issue
Block a user