128 lines
4.4 KiB
Bash
Executable File
128 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
IMAGE="${1:-hivegrid-mcp:smoke}"
|
|
PORT="${DOCKER_MCP_SMOKE_PORT:-}"
|
|
CONTAINER_NAME="${DOCKER_MCP_SMOKE_CONTAINER:-hivegrid-mcp-runtime-smoke-$$}"
|
|
TRANSPORT="${DOCKER_MCP_SMOKE_TRANSPORT:-host}"
|
|
SMOKE_REVISION="${DOCKER_MCP_SMOKE_REVISION:-}"
|
|
HEALTH_ATTEMPTS="${DOCKER_MCP_SMOKE_HEALTH_ATTEMPTS:-120}"
|
|
|
|
cleanup() {
|
|
docker rm -f "$CONTAINER_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 method="${2:-GET}"
|
|
local code
|
|
|
|
if [[ "$TRANSPORT" == "exec" ]]; then
|
|
code="$(docker exec "$CONTAINER_NAME" node -e 'const http = require("node:http"); const [path, method] = process.argv.slice(1); const request = http.request({ hostname: "127.0.0.1", port: 8080, path, method, headers: { Host: "mcp.hivegrid.de" } }, (response) => { process.stdout.write(String(response.statusCode ?? "000")); response.resume(); }); request.on("error", () => process.stdout.write("000")); request.end();' "$path" "$method" || true)"
|
|
else
|
|
code="$(curl -s -o /dev/null -w "%{http_code}" -X "$method" -H "Host: mcp.hivegrid.de" "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: "mcp.hivegrid.de" } }, (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: mcp.hivegrid.de" "http://127.0.0.1:${PORT}${path}"
|
|
fi
|
|
}
|
|
|
|
expect_status() {
|
|
local path="$1"
|
|
local expected="$2"
|
|
local method="${3:-GET}"
|
|
local actual
|
|
|
|
actual="$(http_status "$path" "$method")"
|
|
if [[ "$actual" != "$expected" ]]; then
|
|
echo "Expected ${method} ${path} to return ${expected}, got ${actual}" >&2
|
|
docker logs "$CONTAINER_NAME" >&2 || true
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
expect_health_revision() {
|
|
local body
|
|
local actual
|
|
local service
|
|
|
|
body="$(http_body /healthz)"
|
|
actual="$(printf "%s" "$body" | node -e 'const fs = require("node:fs"); const payload = JSON.parse(fs.readFileSync(0, "utf8")); process.stdout.write(payload.revision || "");')"
|
|
service="$(printf "%s" "$body" | node -e 'const fs = require("node:fs"); const payload = JSON.parse(fs.readFileSync(0, "utf8")); process.stdout.write(payload.service || "");')"
|
|
if [[ "$actual" != "$SMOKE_REVISION" || "$service" != "mcp" ]]; then
|
|
echo "Expected /healthz service=mcp revision=${SMOKE_REVISION}, got service=${service:-<empty>} revision=${actual:-<empty>}" >&2
|
|
docker logs "$CONTAINER_NAME" >&2 || true
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
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_MCP_SMOKE_REVISION explicitly" >&2
|
|
exit 1
|
|
fi
|
|
|
|
publish_args=(-p 127.0.0.1::8080)
|
|
if [[ -n "$PORT" ]]; then
|
|
publish_args=(-p "${PORT}:8080")
|
|
fi
|
|
|
|
docker run -d --name "$CONTAINER_NAME" "${publish_args[@]}" \
|
|
-e NODE_ENV=production \
|
|
-e PORT=8080 \
|
|
-e MCP_PORT=8080 \
|
|
-e APP_REVISION="$SMOKE_REVISION" \
|
|
-e MCP_OAUTH_TOKEN_HASH_SECRET="smoke-only-mcp-token-hash-secret-not-real" \
|
|
"$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 "MCP 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
|
|
expect_status /readyz 200
|
|
expect_status /mcp 404 POST
|
|
expect_status / 401 POST
|
|
|
|
echo "Docker MCP runtime smoke passed for ${IMAGE}"
|