name: Release Docker

on:
  push:
    tags:
      - "v[0-9]*"
  workflow_dispatch:
    inputs:
      tag:
        description: "Image tag (e.g. 1.0.0, no v prefix). Defaults to latest commit SHA."
        required: false

jobs:
  docker:
    runs-on: docker-builder
    steps:
      - uses: actions/checkout@v4

      - name: Resolve image tag
        id: meta
        run: |
          SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
          echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            TAG="${{ github.event.inputs.tag }}"
            if [ -z "$TAG" ]; then
              TAG="${{ github.sha }}"
            fi
          else
            # Docker convention: git tag v1.2.3 -> image tag 1.2.3
            TAG="${{ github.ref_name }}"
            TAG="${TAG#v}"
          fi
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"

          TAGS="lerkolabs/uptop:${TAG}"
          TAGS="${TAGS},lerkolabs/uptop:sha-${SHORT_SHA}"
          TAGS="${TAGS},ghcr.io/lerkolabs/uptop:${TAG}"
          TAGS="${TAGS},ghcr.io/lerkolabs/uptop:sha-${SHORT_SHA}"
          # :latest only for real releases — rc rehearsal tags must not move it
          if [ "${{ github.ref_type }}" = "tag" ]; then
            case "$TAG" in
              *-*) ;;
              *)
                TAGS="${TAGS},lerkolabs/uptop:latest"
                TAGS="${TAGS},ghcr.io/lerkolabs/uptop:latest"
                ;;
            esac
          fi
          echo "tags=$TAGS" >> "$GITHUB_OUTPUT"

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ secrets.GHCR_USERNAME }}
          password: ${{ secrets.GHCR_TOKEN }}

      # Scan must gate the push: build amd64 locally, scan it, and only then run
      # the multi-arch push (amd64 layers come from the builder cache, so the
      # second build only adds the arm64 work).
      - name: Build for scan (amd64, local)
        uses: docker/build-push-action@v5
        with:
          context: .
          load: true
          platforms: linux/amd64
          tags: uptop-scan:${{ steps.meta.outputs.tag }}
          build-args: |
            VERSION=${{ steps.meta.outputs.tag }}
            COMMIT=${{ github.sha }}
            BUILD_DATE=${{ github.event.head_commit.timestamp }}

      - name: Scan image for CVEs
        run: |
          curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.114.0
          grype uptop-scan:${{ steps.meta.outputs.tag }} --fail-on critical --output table

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          platforms: linux/amd64,linux/arm64
          sbom: true
          provenance: mode=max
          tags: ${{ steps.meta.outputs.tags }}
          build-args: |
            VERSION=${{ steps.meta.outputs.tag }}
            COMMIT=${{ github.sha }}
            BUILD_DATE=${{ github.event.head_commit.timestamp }}

      - name: Update Docker Hub description
        uses: peter-evans/dockerhub-description@v4
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
          repository: lerkolabs/uptop

      - name: Cleanup Docker artifacts
        if: always()
        run: |
          # the scan image is tagged, so image prune won't catch it
          docker image rm "uptop-scan:${{ steps.meta.outputs.tag }}" 2>/dev/null || true
          docker image prune -f
          docker builder prune -f --keep-storage=2GB
