# On a v* tag (or on demand), publish the "try it with no Rust" demo image to ghcr.io and
# cut the GitHub Release with notes taken from the CHANGELOG. The image is the Dockerfile at
# the repo root -- the same thing `make docker-demo` builds locally.
name: release

on:
  push:
    tags: ['v*']
  workflow_dispatch:
    inputs:
      tag:
        description: the tag to release (e.g. v0.2.1)
        required: true

permissions:
  contents: write     # create the GitHub Release
  packages: write     # push the image to ghcr.io
  actions: read       # the gate below reads verify.yml's runs with `gh run list`

concurrency:
  group: release-${{ github.event.inputs.tag || github.ref_name }}
  cancel-in-progress: false

jobs:
  release:
    runs-on: ubuntu-24.04
    timeout-minutes: 40
    steps:
      - name: resolve the tag
        id: tag
        run: |
          TAG="${{ github.event.inputs.tag || github.ref_name }}"
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
          echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"

      # On a tag push, checkout defaults to the tag. On workflow_dispatch it checks out the
      # branch it was run from (the default branch) -- which is what we want when releasing a
      # tag made before this Dockerfile existed: the image is built from current code, whose
      # extension is identical to the tagged one.
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false   # keep the release token (contents/packages: write) out of .git/config, where docker build and the demo run could read it

      # Do not publish anything the attack suite did not pass on this exact commit. On a tag
      # push verify runs in parallel, so wait for it; on workflow_dispatch it has already run.
      - name: require the verify workflow to pass for this commit
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          SHA="${{ github.sha }}"
          for i in $(seq 1 40); do
            CONC=$(gh run list -R "${{ github.repository }}" --workflow verify.yml --commit "$SHA" \
                   --json status,conclusion --jq '[.[] | select(.status=="completed")][0].conclusion // ""')
            case "$CONC" in
              success) echo "verify passed for $SHA"; exit 0 ;;
              "")      echo "waiting for verify on $SHA ($i/40)"; sleep 30 ;;
              *)       echo "verify did not pass for $SHA: $CONC"; exit 1 ;;
            esac
          done
          echo "timed out waiting for verify on $SHA"; exit 1

      - name: the tag matches the extension version
        run: |
          WANT="${{ steps.tag.outputs.version }}"
          CARGO=$(sed -nE 's/^version = "([^"]+)".*/\1/p' Cargo.toml | head -1)
          META_TOP=$(jq -r '.version' META.json)
          META_PROV=$(jq -r '.provides.pg_agent_gate.version' META.json)
          PROV_FILE=$(jq -r '.provides.pg_agent_gate.file' META.json)
          for pair in "Cargo.toml:$CARGO" "META.json:$META_TOP" "META.json/provides:$META_PROV"; do
            [ "${pair#*:}" = "$WANT" ] || { echo "tag ${{ steps.tag.outputs.tag }} does not match ${pair%%:*} version ${pair#*:}"; exit 1; }
          done
          [ -f "$PROV_FILE" ] || { echo "META.json provides.file '$PROV_FILE' does not exist"; exit 1; }
          echo "version $WANT matches Cargo.toml and both META.json versions; provides.file ($PROV_FILE) present"

      - name: build the demo image
        run: |
          OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
          echo "IMAGE=ghcr.io/$OWNER/pg_agent_gate-demo" >> "$GITHUB_ENV"
          docker build \
            --label org.opencontainers.image.source=https://github.com/${{ github.repository }} \
            --label org.opencontainers.image.version=${{ steps.tag.outputs.version }} \
            --label org.opencontainers.image.revision=${{ github.sha }} \
            -t "ghcr.io/$OWNER/pg_agent_gate-demo:${{ steps.tag.outputs.version }}" \
            -t "ghcr.io/$OWNER/pg_agent_gate-demo:latest" .

      # Publish only if the gate actually did its job in the image: these markers are the
      # refusals and limits in the demo output, not merely that it exited 0.
      - name: run the demo in the image before publishing it
        run: |
          docker run --rm "$IMAGE:${{ steps.tag.outputs.version }}" | tee demo-output.txt
          for m in "it proposes, it does not execute" "refused at propose" \
                   "aborted" "TABLE GONE" "ana@new.example"; do
            grep -qF "$m" demo-output.txt || { echo "smoke test: the demo is missing: $m"; exit 1; }
          done

      - name: push to ghcr.io
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
          docker push "$IMAGE:${{ steps.tag.outputs.version }}"
          docker push "$IMAGE:latest"

      - name: release notes from the changelog
        run: |
          awk -v v="${{ steps.tag.outputs.version }}" '
            $0 ~ "^## " v " " { f = 1; next }
            f && /^## / { exit }
            f { print }
          ' CHANGELOG.md > notes.md
          [ -s notes.md ] || echo "See CHANGELOG.md." > notes.md
          OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
          printf '\n---\nDemo image: `docker run --rm ghcr.io/%s/pg_agent_gate-demo:%s` (built from commit %s).\n' \
            "$OWNER" "${{ steps.tag.outputs.version }}" "${{ github.sha }}" >> notes.md

      - name: create or update the GitHub release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          TAG="${{ steps.tag.outputs.tag }}"
          if gh release view "$TAG" >/dev/null 2>&1; then
            gh release edit "$TAG" --notes-file notes.md
          else
            gh release create "$TAG" --title "$TAG" --notes-file notes.md
          fi
