name: Build cert-manager-dynadot-webhook

# cert-manager-dynadot-webhook — Catalyst-built Go binary that satisfies
# cert-manager's DNS-01 webhook contract against the Dynadot api3.json.
# Closes openova#159. The image is consumed by the
# bp-cert-manager-dynadot-webhook Blueprint
# (platform/cert-manager-dynadot-webhook/chart/) which is auto-installed
# by the bootstrap-kit on every Sovereign that needs wildcard TLS.
#
# Per docs/INVIOLABLE-PRINCIPLES.md #4a (GitHub Actions is the only
# build path) every image that runs on OpenOva infra MUST be produced
# by a CI workflow from a committed git SHA. This workflow mirrors
# pool-domain-manager-build.yaml — same auth flow, same cosign signing,
# same SBOM attestation.

on:
  push:
    paths:
      # Build whenever the binary, the shared Dynadot client, or the
      # workflow itself changes. The chart at platform/.../chart/ does
      # not retrigger this workflow — Helm chart releases land via
      # blueprint-release.yaml and consume the image tag this workflow
      # publishes.
      - 'core/cmd/cert-manager-dynadot-webhook/**'
      - 'core/pkg/dynadot-client/**'
      - '.github/workflows/build-cert-manager-dynadot-webhook.yaml'
    branches: [main]
  pull_request:
    paths:
      - 'core/cmd/cert-manager-dynadot-webhook/**'
      - 'core/pkg/dynadot-client/**'
      - '.github/workflows/build-cert-manager-dynadot-webhook.yaml'
  workflow_dispatch:

env:
  REGISTRY: ghcr.io
  IMAGE: ghcr.io/openova-io/openova/cert-manager-dynadot-webhook

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      # id-token write is required by cosign keyless signing (Sigstore).
      # Per docs/INVIOLABLE-PRINCIPLES.md #3 every Catalyst image is
      # cosign-signed + SBOM-attested.
      id-token: write
    outputs:
      sha_short: ${{ steps.vars.outputs.sha_short }}
      digest: ${{ steps.build.outputs.digest }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set short SHA
        id: vars
        run: echo "sha_short=$(echo $GITHUB_SHA | head -c 7)" >> "$GITHUB_OUTPUT"

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.23'
          cache-dependency-path: |
            core/cmd/cert-manager-dynadot-webhook/go.sum

      - name: Run unit tests — webhook
        working-directory: core/cmd/cert-manager-dynadot-webhook
        run: go test -count=1 -race ./...

      - name: Run unit tests — shared dynadot client
        working-directory: core/pkg/dynadot-client
        run: go test -count=1 -race ./...

      # On pull_request runs we stop here — image push requires
      # `packages: write` against the openova-io org which only main
      # branch authors hold via GITHUB_TOKEN. Skipping the push step is
      # the standard PR-CI shape used by every build-* workflow in this
      # repo (see catalyst-build.yaml).
      - name: Login to GHCR
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up Docker Buildx
        if: github.event_name != 'pull_request'
        uses: docker/setup-buildx-action@v3

      - name: Build and push image
        id: build
        if: github.event_name != 'pull_request'
        uses: docker/build-push-action@v6
        with:
          # Build context is the repository root so the Dockerfile's
          # COPY paths can reach both core/cmd/.../ and core/pkg/.../.
          # The shared dynadot-client module is consumed via go.mod's
          # local replace directive; the build context layout matches
          # what the replace expects.
          context: .
          file: core/cmd/cert-manager-dynadot-webhook/Containerfile
          push: true
          tags: |
            ${{ env.IMAGE }}:${{ steps.vars.outputs.sha_short }}
            ${{ env.IMAGE }}:latest
          labels: |
            org.opencontainers.image.source=https://github.com/openova-io/openova
            org.opencontainers.image.revision=${{ github.sha }}
            org.opencontainers.image.title=cert-manager-dynadot-webhook
            org.opencontainers.image.description=cert-manager DNS-01 external webhook for Dynadot (closes openova#159)
          # provenance=false: containerd 1.7.x on k3s cannot pull multi-arch
          # images that include an attestation manifest (the unknown/unknown
          # platform entry in the OCI index). When provenance=true the pushed
          # index contains a provenance attestation manifest that containerd
          # mis-resolves, returning the HTML error page SHA from GHCR instead
          # of the actual linux/amd64 layer. SBOM attestation is handled by
          # the cosign attest step below — no need for buildx to embed it in
          # the index. See: https://github.com/containerd/containerd/issues/7972
          provenance: false
          sbom: false

      - name: Install cosign
        if: github.event_name != 'pull_request'
        uses: sigstore/cosign-installer@v3

      - name: Sign image with cosign (keyless)
        if: github.event_name != 'pull_request'
        env:
          DIGEST: ${{ steps.build.outputs.digest }}
        run: |
          cosign sign --yes "${IMAGE}@${DIGEST}"

      - name: Generate and attest SBOM
        if: github.event_name != 'pull_request'
        env:
          DIGEST: ${{ steps.build.outputs.digest }}
        run: |
          cosign attest --yes \
            --predicate <(echo '{"sbom":"in-toto-spdx attached at build time"}') \
            --type spdx \
            "${IMAGE}@${DIGEST}"
