diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index cc737af..ec446be 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -48,18 +48,20 @@ jobs: # Note: This repository only builds and pushes images. # FluxCD Image Automation in another repository performs deployment. - # Stable tags (vX.Y.Z) may become :latest. Pre-release tags never do. - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + # Stable tags (vX.Y.Z) also publish :latest. + # Pre-release tags (e.g. vX.Y.Z-rc1) also publish :prerelease-tag. + if printf '%s' "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then docker buildx build \ --platform linux/amd64 \ --push \ - --tag $REGISTRY/$IMAGE_NAME:$TAG \ - --tag $REGISTRY/$IMAGE_NAME:latest \ + --tag "$REGISTRY/$IMAGE_NAME:$TAG" \ + --tag "$REGISTRY/$IMAGE_NAME:latest" \ . else docker buildx build \ --platform linux/amd64 \ --push \ - --tag $REGISTRY/$IMAGE_NAME:$TAG \ + --tag "$REGISTRY/$IMAGE_NAME:$TAG" \ + --tag "$REGISTRY/$IMAGE_NAME:prerelease-tag" \ . fi diff --git a/README.md b/README.md index 28c5ed2..fe66d66 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,47 @@ docker run --rm -p 8080:8080 \ gitea-mcp-auth-proxy:dev ``` +## Registry-Image lokal testen + +Fuer einen End-to-End-Test gegen die veroeffentlichte Gitea-Container-Registry gibt es das Script `scripts/test-registry-image.sh`. + +Es orientiert sich am Gitea-Flow aus der Container-Registry-Doku: + +- Login gegen `gitea.nehmer.net` +- Pull von `gitea.nehmer.net/torben/gitea-mcp-auth-proxy:` +- lokaler Start des gezogenen Images mit den eingecheckten Demo-Hashes +- HTTP-Pruefungen fuer `200` und `401` + +Default-Verhalten: + +- User: `torben` +- Tag: `latest` +- Runtime: automatisch `docker`, sonst `podman` + +Der Registry-Login fragt das Passwort oder einen PAT interaktiv und unsichtbar ab. + +Beispiel: + +```bash +./scripts/test-registry-image.sh +``` + +Bestimmten Tag testen: + +```bash +./scripts/test-registry-image.sh --tag v1.2.3 +``` + +Optional auf vorhandene Image-Labels pruefen: + +```bash +./scripts/test-registry-image.sh \ + --require-label org.opencontainers.image.source \ + --require-label org.opencontainers.image.title=gitea-mcp-auth-proxy +``` + +Hinweis: Das Script validiert optionale Labels erst nach dem Pull. Aktuell definiert dieses Repo selbst noch keine OCI-Image-Labels im Build. + ## CI/CD (Gitea Actions) - Build-Workflow: `.gitea/workflows/build.yaml` diff --git a/scripts/.gitkeep b/scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/test-registry-image.sh b/scripts/test-registry-image.sh new file mode 100755 index 0000000..6bdd68d --- /dev/null +++ b/scripts/test-registry-image.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REGISTRY_HOST="gitea.nehmer.net" +OWNER="torben" +IMAGE_NAME="gitea-mcp-auth-proxy" +TAG="latest" +USERNAME="torben" +RUNTIME="" +REQUIRED_LABELS=() + +usage() { + cat <<'EOF' +Usage: scripts/test-registry-image.sh [options] + +Pulls the published container image from the Gitea registry, starts it locally +with the committed demo hashes, and verifies expected HTTP responses. + +Options: + --tag Image tag to test (default: latest) + --runtime Container runtime to use (auto-detect by default) + --username Registry username (default: torben) + --require-label Require image label to exist + --require-label Require image label to equal a specific value + -h, --help Show this help + +Examples: + scripts/test-registry-image.sh + scripts/test-registry-image.sh --tag v1.2.3 + scripts/test-registry-image.sh --require-label org.opencontainers.image.source +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --tag) + TAG="${2:-}" + shift 2 + ;; + --runtime) + RUNTIME="${2:-}" + shift 2 + ;; + --username) + USERNAME="${2:-}" + shift 2 + ;; + --require-label) + REQUIRED_LABELS+=("${2:-}") + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ -z "$TAG" ]]; then + echo "Tag must not be empty" >&2 + exit 1 +fi + +if [[ -z "$RUNTIME" ]]; then + if command -v docker >/dev/null 2>&1; then + RUNTIME="docker" + elif command -v podman >/dev/null 2>&1; then + RUNTIME="podman" + else + echo "Neither docker nor podman is available" >&2 + exit 1 + fi +fi + +if ! command -v "$RUNTIME" >/dev/null 2>&1; then + echo "Container runtime not found: $RUNTIME" >&2 + exit 1 +fi + +if ! command -v curl >/dev/null 2>&1; then + echo "curl is required" >&2 + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "$script_dir/.." && pwd)" +hash_dir="$repo_root/demo/token-hashes" + +if [[ ! -d "$hash_dir" ]]; then + echo "Demo hash directory not found: $hash_dir" >&2 + exit 1 +fi + +image_ref="$REGISTRY_HOST/$OWNER/$IMAGE_NAME:$TAG" +container_name="authproxy-registry-test-$$" +container_id="" +host_port="" + +cleanup() { + local exit_code=$? + if [[ -n "$container_id" ]]; then + "$RUNTIME" rm -f "$container_id" >/dev/null 2>&1 || true + fi + exit "$exit_code" +} +trap cleanup EXIT + +printf 'Registry password for %s@%s: ' "$USERNAME" "$REGISTRY_HOST" >&2 +stty -echo +IFS= read -r registry_password +stty echo +printf '\n' >&2 + +if [[ -z "$registry_password" ]]; then + echo "Empty password/token is not allowed" >&2 + exit 1 +fi + +printf '%s' "$registry_password" | "$RUNTIME" login "$REGISTRY_HOST" --username "$USERNAME" --password-stdin >/dev/null +unset registry_password + +echo "Pulling $image_ref" +"$RUNTIME" pull "$image_ref" >/dev/null + +for required_label in "${REQUIRED_LABELS[@]}"; do + label_key="$required_label" + label_expected="" + if [[ "$required_label" == *=* ]]; then + label_key="${required_label%%=*}" + label_expected="${required_label#*=}" + fi + + label_value="$("$RUNTIME" image inspect "$image_ref" --format "{{ index .Config.Labels \"$label_key\" }}" 2>/dev/null || true)" + if [[ -z "$label_value" || "$label_value" == "" ]]; then + echo "Required image label missing: $label_key" >&2 + exit 1 + fi + if [[ -n "$label_expected" && "$label_value" != "$label_expected" ]]; then + echo "Image label mismatch for $label_key: expected $label_expected, got $label_value" >&2 + exit 1 + fi +done + +echo "Starting container from $image_ref" +container_id="$($RUNTIME run -d \ + --name "$container_name" \ + -p 127.0.0.1::8080 \ + -e AUTH_PROXY_LISTEN_ADDR=:8080 \ + -e AUTH_PROXY_TOKEN_HASHES_DIR=/token-hashes \ + -e AUTH_PROXY_LOG_LEVEL=debug \ + -v "$hash_dir:/token-hashes:ro" \ + "$image_ref")" + +host_port="$($RUNTIME port "$container_id" 8080/tcp | awk -F: 'NR==1 {print $NF}')" +if [[ -z "$host_port" ]]; then + echo "Failed to determine mapped host port" >&2 + "$RUNTIME" logs "$container_id" >&2 || true + exit 1 +fi + +base_url="http://127.0.0.1:$host_port" + +wait_for_health() { + local attempt http_code + for attempt in $(seq 1 30); do + http_code="$(curl -s -o /dev/null -w '%{http_code}' "$base_url/healthz" || true)" + if [[ "$http_code" == "200" ]]; then + return 0 + fi + sleep 1 + done + return 1 +} + +assert_status() { + local expected=$1 + local description=$2 + shift 2 + local http_code + http_code="$(curl -s -o /dev/null -w '%{http_code}' "$@")" + if [[ "$http_code" != "$expected" ]]; then + echo "FAIL: $description expected $expected, got $http_code" >&2 + "$RUNTIME" logs "$container_id" >&2 || true + exit 1 + fi + echo "OK: $description -> $http_code" +} + +if ! wait_for_health; then + echo "Container did not become healthy: $image_ref" >&2 + "$RUNTIME" logs "$container_id" >&2 || true + exit 1 +fi + +assert_status 200 "healthz" "$base_url/healthz" +assert_status 401 "missing token" "$base_url/" +assert_status 401 "wrong token" -H "Authorization: Bearer wrong" "$base_url/" +assert_status 200 "demo-token-1" -H "Authorization: Bearer demo-token-1" "$base_url/" + +echo "Registry image test succeeded: $image_ref" \ No newline at end of file