scripts/check-image.sh
Rama main · réplica de lectura del repositorio original.
#!/bin/bash
#
# Runs the production image the way ECS does — migrations, then the web and
# worker processes — and fails on anything the image is missing.
#
# Usage: scripts/check-image.sh <image>
#
# Postgres, Redis and RabbitMQ are throwaway containers on a private network, so
# this runs the same in CI and next to a local dev stack, which it never
# touches.
set -euo pipefail
image="${1:?usage: scripts/check-image.sh <image>}"
prefix="argos-check-image-$$"
cleanup() {
docker rm -f "$prefix-postgres" "$prefix-redis" "$prefix-rabbitmq" \
"$prefix-web" "$prefix-worker" >/dev/null 2>&1 || true
docker network rm "$prefix" >/dev/null 2>&1 || true
}
trap cleanup EXIT
# fail <message> [container]
fail() {
echo "check-image: $1" >&2
if [ -n "${2:-}" ]; then
docker logs "$prefix-$2" 2>&1 | tail -50 >&2
fi
exit 1
}
# wait_for <seconds> <command...>
wait_for() {
local seconds=$1
shift
for ((i = 0; i < seconds; i++)); do
if "$@" >/dev/null 2>&1; then
return 0
fi
sleep 1
done
return 1
}
running() {
[ "$(docker inspect -f '{{.State.Running}}' "$prefix-$1")" = true ]
}
# Same versions as docker-compose.yml.
docker network create "$prefix" >/dev/null
docker run -d --name "$prefix-postgres" --network "$prefix" \
-e POSTGRES_HOST_AUTH_METHOD=trust postgres:18-alpine >/dev/null
docker run -d --name "$prefix-redis" --network "$prefix" \
redis:6-alpine >/dev/null
docker run -d --name "$prefix-rabbitmq" --network "$prefix" \
rabbitmq:4.2-alpine >/dev/null
# Placeholders for the secrets production injects that a process refuses to
# start without.
app=(
--network "$prefix"
-e NODE_ENV=production
-e DATABASE_URL="postgresql://postgres@$prefix-postgres/postgres"
-e REDIS_URL="redis://$prefix-redis:6379"
-e AMQP_URL="amqp://$prefix-rabbitmq"
-e ENCRYPTION_KEY=1111111111111111111111111111111111111111111111111111111111111111
-e RESEND_API_KEY=re_placeholder
)
# Over TCP: during initdb the server only listens on its socket, then restarts.
wait_for 60 docker exec "$prefix-postgres" pg_isready -h 127.0.0.1 ||
fail "Postgres did not start" postgres
echo "Migrations"
docker run --rm "${app[@]}" "$image" scripts/release.sh ||
fail "scripts/release.sh failed"
echo "Web process"
docker run -d --name "$prefix-web" "${app[@]}" -p 127.0.0.1::4001 \
"$image" ./apps/backend/bin/start.sh web >/dev/null
web="http://$(docker port "$prefix-web" 4001 | head -1)"
wait_for 60 curl -fsS "$web/health" || fail "web did not answer /health" web
for path in / /robots.txt; do
curl -fsS -o /dev/null -H "Host: app.argos-ci.com" \
-H "X-Forwarded-Proto: https" "$web$path" ||
fail "web did not serve $path" web
done
curl -fsS -H "Host: app.argos-ci.com" -H "X-Forwarded-Proto: https" \
-H "Content-Type: application/json" -H "apollo-require-preflight: true" \
--data '{"query":"{ me { id } }"}' "$web/graphql" | grep -q '"data"' ||
fail "web did not answer a GraphQL query" web
echo "Worker process"
wait_for 60 docker exec "$prefix-rabbitmq" \
rabbitmq-diagnostics -q check_port_connectivity ||
fail "RabbitMQ did not start" rabbitmq
docker run -d --name "$prefix-worker" "${app[@]}" \
"$image" ./apps/backend/bin/start.sh worker >/dev/null
# Every job logs "Initialize consuming" at startup and "Consuming queue" once
# its consumer is attached, so equal counts mean every queue is served.
consuming() {
local logs initialized consuming
logs=$(docker logs "$prefix-worker" 2>&1)
initialized=$(grep -c '"msg":"Initialize consuming"' <<<"$logs" || true)
consuming=$(grep -c '"msg":"Consuming queue"' <<<"$logs" || true)
[ "$initialized" -gt 0 ] && [ "$consuming" -eq "$initialized" ]
}
wait_for 60 consuming || fail "worker did not consume every queue" worker
running web || fail "web exited" web
running worker || fail "worker exited" worker
# Diffing only loads its native binaries when a job runs, so nothing above
# reaches them.
echo "Diff binaries"
docker run --rm -w /app/apps/backend "$image" node --input-type=module -e '
import { fingerprintDiff } from "@argos-ci/mask-fingerprint";
import { compare } from "odiff-bin";
import sharp from "sharp";
const image = (background) =>
sharp({ create: { width: 8, height: 8, channels: 3, background } }).png();
await image("#f00").toFile("/tmp/base.png");
await image("#00f").toFile("/tmp/head.png");
const result = await compare("/tmp/base.png", "/tmp/head.png", "/tmp/diff.png");
if (result.match || result.diffCount !== 64) {
throw new Error(`unexpected odiff result: ${JSON.stringify(result)}`);
}
await fingerprintDiff("/tmp/diff.png");
' || fail "diff binaries failed"
echo "check-image: $image OK"