name: Release

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'The version being released'
        required: true
        type: string
      update-changelog:
        description: 'Update CHANGELOG'
        required: true
        type: boolean
        default: false
      notification-channel:
        description: 'Slack channel to use for notifications'
        required: false
        type: string
        default: 'CUYKT2A73'

env:
  GO_TAGS: "release"

jobs:
  prepare-release:
    runs-on: ${{ endsWith(github.repository, '-enterprise') && fromJSON('["self-hosted", "ondemand", "linux", "type=m7a.2xlarge;m6a.2xlarge"]') || 'ubuntu-20.04' }}
    outputs:
      build-ref: ${{ steps.commit-change-push.outputs.build-ref }}
    steps:
      - name: Prevent running from main
        if: ${{ github.ref_name == 'main' }}
        run: |-
          echo "::error::Workflow not allowed to run from ${{ github.ref_name }}"
          exit 1

      - name: Print release info
        run: |-
          echo "::notice::Release v${{ github.event.inputs.version }} from branch ${{ github.ref_name }}"

      - name: Install semver CLI
        run: |-
          local_bin="${HOME}/.local/bin"
          mkdir -p "${local_bin}"
          curl -L --output "${local_bin}/semver" \
            https://raw.githubusercontent.com/fsaintjacques/semver-tool/3.3.0/src/semver
          chmod +x "${local_bin}/semver"
          echo "${local_bin}" >> "$GITHUB_PATH"

      - name: Validate release version
        run: |-
          if [ "$(semver validate ${{ github.event.inputs.version }})" == "invalid" ]; then
            echo "::error::Version ${{ github.event.inputs.version }} is invalid"
            exit 1
          fi
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          persist-credentials: false
      - name: Retrieve Vault-hosted Secrets
        if: endsWith(github.repository, '-enterprise')
        id: vault
        uses: hashicorp/vault-action@a1b77a09293a4366e48a5067a86692ac6e94fdc0 # v3.1.0
        with:
          url: ${{ vars.CI_VAULT_URL }}
          method: ${{ vars.CI_VAULT_METHOD }}
          path: ${{ vars.CI_VAULT_PATH }}
          jwtGithubAudience: ${{ vars.CI_VAULT_AUD }}
          secrets: |-
            kv/data/github/hashicorp/nomad-enterprise/gha ELEVATED_GITHUB_TOKEN ;
      - name: Git config token
        run: git config --global url.'https://${{ env.ELEVATED_GITHUB_TOKEN || secrets.ELEVATED_GITHUB_TOKEN }}@github.com'.insteadOf 'https://github.com'
      - name: Git config user/name
        run: |-
          git config --global user.email "github-team-nomad-core@hashicorp.com"
          git config --global user.name "hc-github-team-nomad-core"

      - name: Determine Go version
        id: get-go-version
        # We use .go-version as our source of truth for current Go
        # version, because "goenv" can react to it automatically.
        run: |
          echo "Building with Go $(cat .go-version)"
          echo "go-version=$(cat .go-version)" >> "$GITHUB_OUTPUT"

      - name: Setup go
        uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
        with:
          go-version: ${{ steps.get-go-version.outputs.go-version }}

      - name: Setup node and yarn
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          node-version: "20"
          cache-dependency-path: "ui/yarn.lock"

      - name: Install Yarn
        run: |
          npm install -g yarn

      - name: Install dependencies
        run: |
          make deps

      - name: Update notification channel
        id: notification-channel
        if: ${{ github.event.inputs.notification-channel != '' }}
        run: |
          sed -i.bak -e 's|\(notification_channel * = *"\)[^"]*|\1${{ github.event.inputs.notification-channel }}|g' .release/ci.hcl
          rm -rf .release/ci.hcl.bak
          git diff --color=always .release/ci.hcl

      - name: Update version file
        run: |
          NOMAD_VERSION="${{ github.event.inputs.version }}"
          NOMAD_MAIN_VERSION=$(semver get release "$NOMAD_VERSION")
          NOMAD_PRERELEASE_VERSION=$(semver get prerel "$NOMAD_VERSION")

          echo "updating version to ${NOMAD_MAIN_VERSION}-${NOMAD_PRERELEASE_VERSION}"

          sed -i.bak -e "s|\(Version * = *\"\)[^\"]*|\1${NOMAD_MAIN_VERSION}|g" version/version.go
          sed -i.bak -e "s|\(VersionPrerelease * = *\"\)[^\"]*|\1${NOMAD_PRERELEASE_VERSION}|g" version/version.go
          rm -rf version/version.go.bak
          git diff --color=always version/version.go

      - name: Update changelog
        if: ${{ github.event.inputs.update-changelog == 'true' }}
        run: |
          echo "::group::Fetch all git repo"
          git fetch --unshallow
          echo "::endgroup::"

          echo -e "## ${{ github.event.inputs.version }} ($(date '+%B %d, %Y'))\n$(make changelog)\n\n$(cat CHANGELOG.md)" > CHANGELOG.md
          git diff --color=always CHANGELOG.md

      - name: Generate static assets
        id: generate-static-assets
        run: |
          make prerelease

      - name: Commit and push changes
        id: commit-change-push
        run: |
          git add -A .
          find . -name '*.generated.go' -not -path './vendor/*' -exec git add -f '{}' \;
          if ! git diff-index --quiet HEAD --; then
            git commit --message "Generate files for ${{ github.event.inputs.version }} release"
            git push origin "$(git rev-parse --abbrev-ref HEAD)"
            echo "committing generated files"
          else
            echo "no files were updated"
          fi
          echo "build-ref=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

      - name: Invoke build workflow
        id: invoke-build
        env:
          GH_TOKEN: ${{ env.ELEVATED_GITHUB_TOKEN || secrets.ELEVATED_GITHUB_TOKEN }}
        run: |
          gh workflow run build.yml --ref ${{ github.ref_name }} --field build-ref=${{ steps.commit-change-push.outputs.build-ref }} --field make-prerelease=false

      - name: Revert notification channel
        if: ${{ github.event.inputs.notification-channel != '' }}
        run: |
          git reset ${{ github.sha }} -- .release/ci.hcl

          # git reset will place the original file content in the staging area
          # and leave the changes since then unstaged, so call git restore to
          # discard these changes and use --cached to display the diff in the
          # staging area.
          git restore .release/ci.hcl
          git diff --cached --color=always .release/ci.hcl

      - name: Update version file
        run: |
          # Only bump the Version value if this is not a pre-release.
          # For final releases we want `nomad -version` to display the next
          # version to indicate that the current release is done.
          if [ -z "$(semver get prerel ${{ github.event.inputs.version }})" ]; then
            next_version=$(semver bump patch ${{ github.event.inputs.version }})
            sed -i.bak -e "s|\(Version * = *\"\)[^\"]*|\1${next_version}|g" version/version.go
          fi
          # Set the VersionPrerelease variable back to dev.
          sed -i.bak -e "s|\(VersionPrerelease * = *\"\)[^\"]*|\1dev|g" version/version.go
          rm -rf version/version.go.bak
          git diff --color=always version/version.go

      - name: Update LAST_RELEASE
        run: |
          # LAST_RELEASE is used to generate the new CHANGELOG entries, so it's
          # only updated for final releases.
          if [ -z "$(semver get prerel ${{ github.event.inputs.version }})" ]; then
            sed -i.bak -re "s|^(LAST_RELEASE\s+\?=\s).*$|\1${{ github.event.inputs.version }}|g" GNUmakefile
            rm -fr GNUmakefile.bak
            git diff --color=always GNUmakefile
          else
            echo "Version ${{ github.event.inputs.version }} is a prerelease, skipping update of LAST_RELEASE"
          fi

      - name: Remove generated files
        run: |
          # These generated files are only needed when building the final
          # binary and should be not be present in the repository afterwards.
          find . -name '*.generated.go' -print0 | xargs -0 git rm
          git status

      - name: Commit post-release changes
        run: |
          # Display staged and unstaged diffs, skipping deleted files to avoid
          # cluttering the output with the generated files.
          git diff --diff-filter=d --color=always HEAD
          git add -A .
          if ! git diff-index --quiet HEAD --; then
            git commit --message 'Prepare for next release'
            git push origin "$(git rev-parse --abbrev-ref HEAD)"
          else
            echo "no files were updated"
          fi

permissions:
  contents: read
  id-token: write
