51 lines
4.3 KiB
Bash
Executable File
51 lines
4.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
umask 077
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
IMAGE="${CONDUCTOR_TEST_IMAGE:-conductor:slice8-test}"
|
|
PROJECT="conductor-release-test-${RANDOM}"
|
|
PORT="${CONDUCTOR_TEST_PORT:-3202}"
|
|
work="$(mktemp -d)"
|
|
env_file="${work}/.env"
|
|
backup_dir="${work}/backups"
|
|
compose=(docker compose -p "$PROJECT" --env-file "$env_file" -f "$ROOT/compose.production.yml")
|
|
cleanup() { "${compose[@]}" down -v --remove-orphans >/dev/null 2>&1 || true; rm -rf -- "$work"; }
|
|
trap cleanup EXIT
|
|
|
|
if [[ "${1:-}" != "--skip-build" ]]; then docker build -f "$ROOT/Dockerfile.production" -t "$IMAGE" "$ROOT"; fi
|
|
if docker run --rm "$IMAGE" >/dev/null 2>&1; then echo "Production image started without required keys." >&2; exit 1; fi
|
|
cat > "$env_file" <<EOF
|
|
CONDUCTOR_SECRET_KEY=1111111111111111111111111111111111111111111111111111111111111111
|
|
CONDUCTOR_SESSION_KEY=22222222222222222222222222222222
|
|
CONDUCTOR_IMAGE=${IMAGE%:*}
|
|
CONDUCTOR_VERSION=${IMAGE##*:}
|
|
CONDUCTOR_BIND_ADDRESS=127.0.0.1
|
|
CONDUCTOR_PORT=${PORT}
|
|
EOF
|
|
"${compose[@]}" up -d --no-build
|
|
curl --fail --silent --show-error --retry 20 --retry-delay 1 --retry-all-errors "http://127.0.0.1:${PORT}/api/health" >/dev/null
|
|
curl --fail --silent --show-error "http://127.0.0.1:${PORT}/" | grep -qi '<!doctype html>'
|
|
curl --fail --silent --show-error "http://127.0.0.1:${PORT}/apps/test/details" | grep -qi '<!doctype html>'
|
|
api_status="$(curl --silent --output /dev/null --write-out '%{http_code}' "http://127.0.0.1:${PORT}/api/not-a-route")"
|
|
[[ "$api_status" == 404 ]] || { echo "Unknown API routes must return 404, not the SPA." >&2; exit 1; }
|
|
"${compose[@]}" exec -T conductor node -e 'const D=require("better-sqlite3");const d=new D("/data/conductor.db");d.prepare("INSERT INTO projects(name,description,project_json) VALUES(?,?,?)").run("release-restore-marker","test","{}");d.close()'
|
|
# Verify independent package bytes and metadata are both covered by recovery.
|
|
container_id="$("${compose[@]}" ps -q conductor)"
|
|
docker exec "$container_id" mkdir -m 700 /tmp/release-test-app
|
|
tar -C "$ROOT/examples/installed-app" -cf - . | docker exec -i "$container_id" tar -xf - -C /tmp/release-test-app
|
|
"${compose[@]}" exec -T conductor node dist/scripts/installedApps.js install /tmp/release-test-app </dev/null
|
|
COMPOSE_PROJECT_NAME="$PROJECT" "$ROOT/scripts/production/backup-conductor.sh" --compose-file "$ROOT/compose.production.yml" --env-file "$env_file" --backup-dir "$backup_dir" --retention-days 1 >/dev/null
|
|
archive="$(find "$backup_dir" -name 'conductor_*.tar.gz' -type f | head -n1)"
|
|
"${compose[@]}" exec -T conductor node dist/scripts/installedApps.js remove example-application
|
|
"${compose[@]}" exec -T conductor node -e 'require("fs").rmSync("/data/applications/example-application",{recursive:true,force:true})'
|
|
"${compose[@]}" exec -T conductor node -e 'const D=require("better-sqlite3");const d=new D("/data/conductor.db");d.prepare("DELETE FROM projects WHERE name=?").run("release-restore-marker");d.close()'
|
|
COMPOSE_PROJECT_NAME="$PROJECT" "$ROOT/scripts/production/restore-conductor.sh" --compose-file "$ROOT/compose.production.yml" --env-file "$env_file" --backup-dir "$backup_dir" --backup-file "$archive" --skip-safety-backup --force >/dev/null
|
|
count="$("${compose[@]}" exec -T conductor node -e 'const D=require("better-sqlite3");const d=new D("/data/conductor.db",{readonly:true});process.stdout.write(String(d.prepare("SELECT count(*) FROM projects WHERE name=?").pluck().get("release-restore-marker")));d.close()')"
|
|
[[ "$count" == 1 ]] || { echo "Restore marker was not recovered." >&2; exit 1; }
|
|
"${compose[@]}" exec -T conductor node -e 'const fs=require("fs");const D=require("better-sqlite3");const d=new D("/data/conductor.db");const r=d.prepare("SELECT digest FROM installed_packages WHERE app_id=?").get("example-application");if(!r||!fs.existsSync("/data/applications/example-application/"+r.digest+"/app.js"))process.exit(1);d.close()'
|
|
# Recreate the core container with the same persistent data, then check package CLI state.
|
|
"${compose[@]}" up -d --force-recreate --no-build
|
|
"${compose[@]}" exec -T conductor node dist/scripts/installedApps.js list | grep -q example-application
|
|
docker inspect "$("${compose[@]}" ps -q conductor)" --format '{{.Config.User}} {{.HostConfig.ReadonlyRootfs}}' | grep -q '^conductor true$'
|
|
echo '{"productionValidation":"passed"}'
|