#!/usr/bin/env bash set -euo pipefail cd "$(dirname "$0")/.." tmp=$(mktemp -d) project="thothii-task5-$(date +%s)-$$" expected_pi_version=$(sed -n 's/^ARG PI_VERSION=//p' docker/core.Dockerfile) trap 'docker compose --project-name "$project" -f compose.yaml -f deploy/compose.local.yaml down --volumes --remove-orphans >/dev/null 2>&1 || true; rm -rf "$tmp"' EXIT HUP INT TERM test -n "$expected_pi_version" printf '{}\n' >"$tmp/pi-auth.json" chmod 0600 "$tmp/pi-auth.json" export PI_AUTH_FILE="$tmp/pi-auth.json" export THT_WORKSPACE_GIT_REMOTE="https://git.example.invalid/thothii/workspaces.git" # Let Docker assign loopback ports so this isolated contract test never collides with an operator stack. export THOTH_CORE_HTTP_PORT=0 export THOTH_HTTP_PORT=0 context_check="$tmp/build-context" mkdir -p "$context_check/deploy" cp .dockerignore "$context_check/.dockerignore" printf '%s\n' 'task-5-context-sentinel' >"$context_check/deploy/thothii.env" cat >"$context_check/Dockerfile" <<'EOF' FROM scratch COPY deploy/thothii.env /sentinel EOF if docker build --quiet -f "$context_check/Dockerfile" "$context_check" >"$tmp/context-check.out" 2>&1; then echo "deploy/thothii.env entered the Docker build context" >&2 exit 1 fi if ! awk ' $1 == "FROM" && $2 !~ /^[^@]+@sha256:[0-9a-f]{64}$/ { print FILENAME ":" FNR ": unpinned base image: " $0 > "/dev/stderr" bad = 1 } END { exit bad } ' docker/core.Dockerfile docker/frontend.Dockerfile; then echo "every production FROM reference must use tag@sha256" >&2 exit 1 fi while IFS= read -r base_image; do manifest=$(docker buildx imagetools inspect "$base_image") printf '%s\n' "$manifest" | grep -Eq 'Platform:[[:space:]]+linux/amd64' printf '%s\n' "$manifest" | grep -Eq 'Platform:[[:space:]]+linux/arm64' done < <(awk '$1 == "FROM" { print $2 }' docker/core.Dockerfile docker/frontend.Dockerfile | sort -u) docker compose --project-name "$project" -f compose.yaml -f deploy/compose.local.yaml build --pull core_label=$(docker image inspect --format '{{ index .Config.Labels "io.thothii.pi.version" }}' thothii-core:local) test "$core_label" = "$expected_pi_version" test "$(docker image inspect --format '{{ index .Config.Labels "org.opencontainers.image.title" }}' thothii-core:local)" = "thothii-core" test "$(docker image inspect --format '{{ index .Config.Labels "org.opencontainers.image.title" }}' thothii-frontend:local)" = "thothii-frontend" docker compose --project-name "$project" -f compose.yaml -f deploy/compose.local.yaml up --detach --wait --wait-timeout 90 docker compose --project-name "$project" -f compose.yaml -f deploy/compose.local.yaml exec -T core sh -ceu ' test "$(id -u)" = 10001 test "$(pi --version)" = "$PI_VERSION" command -v pi >/dev/null test ! -e /var/run/docker.sock touch /data/.task5-writable rm /data/.task5-writable if find /app /home /data -xdev \( -iname "*chirone*" -o -iname "*omics*portal*" \) -print -quit | grep -q .; then echo "portal or Chirone path found in core image" >&2 exit 1 fi ' if docker compose --project-name "$project" -f compose.yaml -f deploy/compose.local.yaml config | grep -Eqi 'docker\.sock|/var/run/docker|docker[-_]?daemon'; then echo "Compose must not mount a Docker socket or daemon" >&2 exit 1 fi frontend_address=$(docker compose --project-name "$project" -f compose.yaml -f deploy/compose.local.yaml port frontend 8080 | head -n 1) curl --fail --silent --show-error "http://$frontend_address/" >/dev/null curl --fail --silent --show-error "http://$frontend_address/api/health" >/dev/null echo "Task 5 container deployment contract passed."