name: Publish ArgoCD Release
on:
  push:
    tags:
      - 'v*'
      - '!v2.4*'
      - '!v2.5*'
      - '!v2.6*'

permissions: {}

env:
  # renovate: datasource=golang-version packageName=golang
  GOLANG_VERSION: '1.26.2' # Note: go-version must also be set in job argocd-image.with.go-version  
  # a workaround to disable harden runner
  STEP_SECURITY_HARDEN_RUNNER: ${{ vars.disable_harden_runner }}
  
jobs:
  argocd-image:
    needs: [setup-variables]
    permissions:
      contents: read
      id-token: write # for creating OIDC tokens for signing.
      packages: write # used to push images to `ghcr.io` if used.
    if: github.repository == 'argoproj/argo-cd' || needs.setup-variables.outputs.allow_fork_release == 'true'
    uses: ./.github/workflows/image-reuse.yaml
    with:
      quay_image_name: ${{ needs.setup-variables.outputs.quay_image_name }}
      # Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
      # renovate: datasource=golang-version packageName=golang
      go-version: 1.26.2
      platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
      push: true
    secrets:
      quay_username: ${{ secrets.RELEASE_QUAY_USERNAME }}
      quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }}

  setup-variables:
    name: Setup Release Variables
    if: github.repository == 'argoproj/argo-cd' || (github.repository_owner != 'argoproj' && vars.ENABLE_FORK_RELEASES == 'true' && vars.IMAGE_NAMESPACE && vars.IMAGE_NAMESPACE != 'argoproj')
    runs-on: ubuntu-24.04
    outputs:
      is_pre_release: ${{ steps.var.outputs.is_pre_release }}
      is_latest_release: ${{ steps.var.outputs.is_latest_release }}
      enable_fork_releases: ${{ steps.var.outputs.enable_fork_releases }}
      image_namespace: ${{ steps.var.outputs.image_namespace }}
      image_repository: ${{ steps.var.outputs.image_repository }}
      quay_image_name: ${{ steps.var.outputs.quay_image_name }}
      provenance_image: ${{ steps.var.outputs.provenance_image }}
      allow_fork_release: ${{ steps.var.outputs.allow_fork_release }}
    steps:
      - name: Harden the runner (Audit all outbound calls)
        if: ${{ vars.disable_harden_runner != 'true' }}
        uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df # v2.18.0
        with:
          egress-policy: audit
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}
      - name: Setup variables
        id: var
        run: |
          set -xue
          # Fetch all tag information
          git fetch --prune --tags --force

          LATEST_RELEASE_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | grep -v '-' | tail -n1)

          PRE_RELEASE=false
          # Check if latest tag is a pre-release
          if echo ${{ github.ref_name }} | grep -E -- '-rc[0-9]+$';then
            PRE_RELEASE=true
          fi

          IS_LATEST=false
          # Ensure latest release tag matches github.ref_name
          if [[ $LATEST_RELEASE_TAG == ${{ github.ref_name }} ]];then
            IS_LATEST=true
          fi
          echo "is_pre_release=$PRE_RELEASE" >> $GITHUB_OUTPUT
          echo "is_latest_release=$IS_LATEST" >> $GITHUB_OUTPUT
          
          # Calculate configuration with defaults
          ENABLE_FORK_RELEASES="${{ vars.ENABLE_FORK_RELEASES || 'false' }}"
          IMAGE_NAMESPACE="${{ vars.IMAGE_NAMESPACE || 'argoproj' }}"
          IMAGE_REPOSITORY="${{ vars.IMAGE_REPOSITORY || 'argocd' }}"
          
          echo "enable_fork_releases=$ENABLE_FORK_RELEASES" >> $GITHUB_OUTPUT
          
          echo "image_namespace=$IMAGE_NAMESPACE" >> $GITHUB_OUTPUT
          echo "image_repository=$IMAGE_REPOSITORY" >> $GITHUB_OUTPUT
          echo "quay_image_name=quay.io/$IMAGE_NAMESPACE/$IMAGE_REPOSITORY:${{ github.ref_name }}" >> $GITHUB_OUTPUT
          echo "provenance_image=quay.io/$IMAGE_NAMESPACE/$IMAGE_REPOSITORY" >> $GITHUB_OUTPUT
          
          ALLOW_FORK_RELEASE=false
          if [[ "${{ github.repository_owner }}" != "argoproj" && "$ENABLE_FORK_RELEASES" == "true" && "$IMAGE_NAMESPACE" != "argoproj" && "${{ github.ref }}" == refs/tags/* ]]; then
            ALLOW_FORK_RELEASE=true
          fi
          echo "allow_fork_release=$ALLOW_FORK_RELEASE" >> $GITHUB_OUTPUT

  argocd-image-provenance:
    needs: [setup-variables, argocd-image]
    permissions:
      actions: read # for detecting the Github Actions environment.
      id-token: write # for creating OIDC tokens for signing.
      packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
    # Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
    if: github.repository == 'argoproj/argo-cd' || needs.setup-variables.outputs.allow_fork_release == 'true'
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
    with:
      image: ${{ needs.setup-variables.outputs.provenance_image }}
      digest: ${{ needs.argocd-image.outputs.image-digest }}
    secrets:
      registry-username: ${{ secrets.RELEASE_QUAY_USERNAME }}
      registry-password: ${{ secrets.RELEASE_QUAY_TOKEN }}

  goreleaser:
    needs:
      - setup-variables
      - argocd-image
      - argocd-image-provenance
    permissions:
      contents: write # used for uploading assets
    if: github.repository == 'argoproj/argo-cd' || needs.setup-variables.outputs.allow_fork_release == 'true'
    runs-on: ubuntu-24.04
    env:
      GORELEASER_MAKE_LATEST: ${{ needs.setup-variables.outputs.is_latest_release }}
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Fetch all tags
        run: git fetch --force --tags

      - name: Setup Golang
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
        with:
          go-version: ${{ env.GOLANG_VERSION }}
          cache: false

      - name: Set GORELEASER_PREVIOUS_TAG # Workaround, GoReleaser uses 'git-describe' to determine a previous tag. Our tags are created in release branches.
        run: |
          set -xue
          GORELEASER_PREVIOUS_TAG=$(go run hack/get-previous-release/get-previous-version-for-release-notes.go ${{ github.ref_name }}) || exit 1
          echo "GORELEASER_PREVIOUS_TAG=$GORELEASER_PREVIOUS_TAG" >> $GITHUB_ENV

      - name: Set environment variables for ldflags
        id: set_ldflag
        run: |
          echo "KUBECTL_VERSION=$(go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev)" >> $GITHUB_ENV
          echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV

      - name: Free Disk Space (Ubuntu)
        uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
        with:
          large-packages: false
          docker-images: false
          swap-storage: false
          tool-cache: false

      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@e24998b8b67b290c2fa8b7c14fcfa7de2c5c9b8c # v7.1.0
        id: run-goreleaser
        with:
          version: v2.14.3
          args: release --clean --timeout 55m
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          KUBECTL_VERSION: ${{ env.KUBECTL_VERSION }}
          GIT_TREE_STATE: ${{ env.GIT_TREE_STATE }}
          # Used to determine the current repository in the goreleaser config to display correct manifest links
          GORELEASER_CURRENT_REPOSITORY: ${{ github.repository }}

      - name: Generate subject for provenance
        id: hash
        env:
          ARTIFACTS: '${{ steps.run-goreleaser.outputs.artifacts }}'
        run: |
          set -euo pipefail

          hashes=$(echo $ARTIFACTS | jq --raw-output '.[] | {name, "digest": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join("  ") | sub("^sha256:";"")' | base64 -w0)
          if test "$hashes" = ""; then # goreleaser < v1.13.0
            checksum_file=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Checksum") | .path')
            hashes=$(cat $checksum_file | base64 -w0)
          fi
          echo "hashes=$hashes" >> $GITHUB_OUTPUT

  goreleaser-provenance:
    needs: [goreleaser, setup-variables]
    permissions:
      actions: read # for detecting the Github Actions environment
      id-token: write # Needed for provenance signing and ID
      contents: write #  Needed for release uploads
    if: github.repository == 'argoproj/argo-cd' || needs.setup-variables.outputs.allow_fork_release == 'true'
    # Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
    with:
      base64-subjects: '${{ needs.goreleaser.outputs.hashes }}'
      provenance-name: 'argocd-cli.intoto.jsonl'
      upload-assets: true

  generate-sbom:
    name: Create SBOM and generate hash
    needs:
      - argocd-image
      - goreleaser
      - setup-variables
    permissions:
      contents: write # Needed for release uploads
    outputs:
      hashes: ${{ steps.sbom-hash.outputs.hashes }}
    if: github.repository == 'argoproj/argo-cd' || needs.setup-variables.outputs.allow_fork_release == 'true'
    runs-on: ubuntu-24.04
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Install pnpm
        uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
        with:
          package_json_file: ui/package.json

      - name: Setup Golang
        uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
        with:
          go-version: ${{ env.GOLANG_VERSION }}
          cache: false

      - name: Generate SBOM (spdx)
        id: spdx-builder
        env:
          # defines the spdx/spdx-sbom-generator version to use.
          SPDX_GEN_VERSION: v0.0.13
          # defines the sigs.k8s.io/bom version to use.
          SIGS_BOM_VERSION: v0.2.1
          # full qualified name of the docker image to be inspected
          DOCKER_IMAGE: ${{ needs.setup-variables.outputs.quay_image_name }}
        run: |
          set -euo pipefail
          pnpm install --dir ./ui --frozen-lockfile
          go install github.com/spdx/spdx-sbom-generator/cmd/generator@$SPDX_GEN_VERSION
          go install sigs.k8s.io/bom/cmd/bom@$SIGS_BOM_VERSION

          generator -p . -o /tmp

          # When ui/ should use in-repo pnpm for `pnpm sbom` (11+):
          #   1. In ui/package.json set "packageManager" to a pnpm 11+ release (e.g. pnpm@11.0.0), then from ./ui run
          #      `pnpm install` and commit the resulting ui/pnpm-lock.yaml so release CI's pnpm/action-setup matches.
          #   2. Delete hack/generate-ui-pnpm-sbom.sh and remove the ./hack/generate-ui-pnpm-sbom.sh line below.
          #   3. Uncomment:
          # pnpm --dir ./ui sbom --sbom-format spdx --prod > /tmp/bom-ui-pnpm.spdx.json
          ./hack/generate-ui-pnpm-sbom.sh --write /tmp/bom-ui-pnpm.spdx.json

          if [[ -n "${DOCKER_IMAGE:-}" ]]; then
            bom generate -o /tmp/bom-docker-image.spdx -i "${DOCKER_IMAGE}"
          fi

          cd /tmp
          shopt -s nullglob
          spdx_files=( *.spdx )
          shopt -u nullglob
          if [[ ${#spdx_files[@]} -eq 0 ]]; then
            echo "No .spdx files produced under /tmp"
            exit 1
          fi
          tar -zcf sbom.tar.gz "${spdx_files[@]}" bom-ui-pnpm.spdx.json

      - name: Generate SBOM hash
        shell: bash
        id: sbom-hash
        run: |
          # sha256sum generates sha256 hash for sbom.
          # base64 -w0 encodes to base64 and outputs on a single line.
          # sha256sum /tmp/sbom.tar.gz ... | base64 -w0
          echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"

      - name: Upload SBOM
        uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          files: |
            /tmp/sbom.tar.gz

  sbom-provenance:
    needs: [generate-sbom, setup-variables]
    permissions:
      actions: read # for detecting the Github Actions environment
      id-token: write # Needed for provenance signing and ID
      contents: write #  Needed for release uploads
    if: github.repository == 'argoproj/argo-cd' || needs.setup-variables.outputs.allow_fork_release == 'true'
    # Must be referenced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
    with:
      base64-subjects: '${{ needs.generate-sbom.outputs.hashes }}'
      provenance-name: 'argocd-sbom.intoto.jsonl'
      upload-assets: true

  post-release:
    needs:
      - setup-variables
      - argocd-image
      - goreleaser
      - generate-sbom
    permissions:
      contents: write # Needed to push commit to update stable tag
      pull-requests: write # Needed to create PR for VERSION update.
    if: github.repository == 'argoproj/argo-cd' || needs.setup-variables.outputs.allow_fork_release == 'true'
    runs-on: ubuntu-24.04
    env:
      TAG_STABLE: ${{ needs.setup-variables.outputs.is_latest_release }}
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup Git author information
        run: |
          set -ue
          git config --global user.email 'ci@argoproj.com'
          git config --global user.name 'CI'

      - name: Update stable tag to latest version
        run: |
          git tag -f stable ${{ github.ref_name }}
          git push -f origin stable
        if: ${{ env.TAG_STABLE == 'true' }}

      - name: Check to see if VERSION should be updated on master branch
        run: |
          set -xue
          SOURCE_TAG=${{ github.ref_name }}
          VERSION_REF="${SOURCE_TAG#*v}"
          COMMIT_HASH=$(git rev-parse HEAD)
          if echo "$VERSION_REF" | grep -E -- '^[0-9]+\.[0-9]+\.0-rc1';then
            VERSION=$(awk 'BEGIN {FS=OFS="."} {$2++; print}' <<< "${VERSION_REF%-rc1}")
            echo "Updating VERSION to: $VERSION"
            echo "UPDATE_VERSION=true" >> $GITHUB_ENV
            echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV
            echo "COMMIT_HASH=$COMMIT_HASH" >> $GITHUB_ENV
          else
            echo "Not updating VERSION"
            echo "UPDATE_VERSION=false" >> $GITHUB_ENV
          fi

      - name: Update VERSION on master branch
        run: |
          echo ${{ env.NEW_VERSION }} > VERSION
          # Replace the 'project-release: vX.X.X-rcX' line in SECURITY-INSIGHTS.yml
          sed -i "s/project-release: v.*$/project-release: v${{ env.NEW_VERSION }}/" SECURITY-INSIGHTS.yml
          # Update the 'commit-hash: XXXXXXX' line in SECURITY-INSIGHTS.yml
          sed -i "s/commit-hash: .*/commit-hash: ${{ env.COMMIT_HASH }}/" SECURITY-INSIGHTS.yml
        if: ${{ env.UPDATE_VERSION == 'true' }}

      - name: Create PR to update VERSION on master branch
        uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
        with:
          commit-message: Bump version in master
          title: 'chore: Bump version in master'
          body: All images built from master should indicate which version we are on track for.
          signoff: true
          branch: update-version
          branch-suffix: random
          base: master
        if: ${{ env.UPDATE_VERSION == 'true' }}
