rickub/clipublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/rickub/cli.git
git clone ssh://git@rickub.com/rickub/cli.git
Initial import of the rickub CLI as a standalone public project 1a1d430Unverified · on main · Olivier Girardot · 7h ago
release.yml · 211 lines · 7.7 KBYAML Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
name: Release

run-name: Release v${{ github.event.inputs.version }} from ${{ github.ref_name }}

# THE ONLY workflow in this repository that declares workflow_dispatch.
#
# The rickub dispatch API (POST /api/v1/repos/{owner}/{repo}/actions/dispatch,
# and `rickub run dispatch`) triggers EVERY workflow_dispatch workflow on the
# ref, so keeping this trigger unique means a dispatch can never accidentally
# fan out into other pipelines. Cut releases from the web UI ("Run workflow"),
# which can target a single workflow. See RELEASING.md.
on:
  workflow_dispatch:
    inputs:
      version:
        description: "Version to release, without the leading v (e.g. 1.2.3)"
        required: true
        type: string
      dry_run:
        description: "Build and verify everything, but do not publish the release"
        required: false
        default: false
        type: boolean

# Creating a release requires write access; the default token is read-only.
permissions:
  contents: write

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

jobs:
  release:
    name: build and publish release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # Full history + tags so the "tag already exists" check is meaningful.
          fetch-depth: 0

      - name: Validate inputs
        id: check
        env:
          VERSION_INPUT: ${{ github.event.inputs.version }}
          DRY_RUN_INPUT: ${{ github.event.inputs.dry_run }}
        run: |
          set -euo pipefail

          version="$(printf '%s' "${VERSION_INPUT}" | tr -d ' \t\r\n')"
          if [ -z "${version}" ]; then
            echo "::error::version input is empty"
            exit 1
          fi
          if [ "${version#v}" != "${version}" ]; then
            echo "::error::Do not include the leading 'v'. Pass '${version#v}', not '${version}'."
            exit 1
          fi
          if ! printf '%s' "${version}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
            echo "::error::version must be MAJOR.MINOR.PATCH (e.g. 1.2.3), got '${version}'"
            exit 1
          fi
          tag="v${version}"

          # Normalise the boolean input to an unambiguous string output;
          # step outputs are always strings, so `!= 'true'` is safe downstream.
          dry_run=false
          case "$(printf '%s' "${DRY_RUN_INPUT}" | tr '[:upper:]' '[:lower:]')" in
            true|1|yes|on) dry_run=true ;;
          esac

          # Cross-check the VERSION file. A mismatch is usually a forgotten bump
          # PR; it is a warning, not a hard failure, so hotfixes stay possible.
          if [ -f VERSION ]; then
            base="$(tr -d ' \t\r\n' < VERSION)"
            if [ "${base}" != "${version}" ]; then
              echo "::warning::VERSION file says '${base}' but you are releasing '${version}'." \
                   "Open a PR bumping VERSION to '${version}' so future RCs are numbered from it."
            fi
          else
            echo "::warning::No VERSION file at the repository root."
          fi

          {
            echo "version=${version}"
            echo "tag=${tag}"
            echo "dry_run=${dry_run}"
          } >> "$GITHUB_OUTPUT"

          echo "Releasing ${tag} at ${GITHUB_SHA} (dry_run=${dry_run})"

      - name: Refuse to overwrite an existing release
        env:
          TAG: ${{ steps.check.outputs.tag }}
          GH_TOKEN: ${{ github.token }}
        run: |
          set -euo pipefail

          # 1. Local/remote git tag — authoritative and always available.
          git fetch --tags --force --quiet || true
          if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null 2>&1; then
            echo "::error::Tag ${TAG} already exists. Bump the version instead of re-releasing."
            exit 1
          fi
          if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
            echo "::error::Tag ${TAG} already exists on the remote."
            exit 1
          fi

          # 2. Release API — best effort. $GITHUB_API_URL already carries the
          #    /gh prefix of the rickub REST shim. Anything other than a clear
          #    200 is treated as "cannot tell" so a shim/proxy hiccup does not
          #    block a legitimate release (the git tag check above is the gate).
          code="$(curl -sS -o /tmp/release-probe.json -w '%{http_code}' \
            -H "Authorization: Bearer ${GH_TOKEN}" \
            -H "Accept: application/vnd.github+json" \
            "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" || echo 000)"
          case "${code}" in
            200)
              echo "::error::A release for ${TAG} already exists (API returned 200)."
              exit 1
              ;;
            404)
              echo "No existing release for ${TAG}."
              ;;
            *)
              echo "::warning::Could not check for an existing ${TAG} release (HTTP ${code});" \
                   "relying on the git tag check. A 403 here means the CI proxy still blocks" \
                   "/gh paths — see RELEASING.md."
              ;;
          esac

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version-file: go.mod
          cache: true

      - name: go vet
        run: go vet ./...

      - name: go test
        run: go test ./... -count=1

      - name: Build distribution
        run: bash scripts/build-dist.sh "${{ steps.check.outputs.version }}"

      - name: Release notes
        id: notes
        env:
          TAG: ${{ steps.check.outputs.tag }}
        run: |
          set -euo pipefail
          {
            echo "## rickub ${TAG}"
            echo
            echo "- Commit: \`${GITHUB_SHA}\` (\`${GITHUB_REF_NAME}\`)"
            echo "- Built by \`${GITHUB_WORKFLOW}\` run #${GITHUB_RUN_NUMBER}"
            echo
            echo '### Install'
            echo
            echo 'Download the archive for your platform, verify it, and drop the binary on your PATH:'
            echo
            echo '```sh'
            echo "tar -xzf rickub_${TAG#v}_\$(uname -s | tr '[:upper:]' '[:lower:]')_amd64.tar.gz"
            echo 'sha256sum -c SHA256SUMS --ignore-missing'
            echo 'install -m 0755 rickub /usr/local/bin/rickub'
            echo '```'
            echo
            echo '### Checksums'
            echo
            echo '```'
            cat dist/SHA256SUMS
            echo '```'
          } > "${RUNNER_TEMP}/release-notes.md"
          echo "path=${RUNNER_TEMP}/release-notes.md" >> "$GITHUB_OUTPUT"

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: rickub-${{ steps.check.outputs.version }}
          path: dist/
          if-no-files-found: error
          retention-days: 90

      - name: Publish release
        if: steps.check.outputs.dry_run != 'true'
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ steps.check.outputs.tag }}
          name: ${{ steps.check.outputs.tag }}
          body_path: ${{ steps.notes.outputs.path }}
          # The tag does not exist yet: the rickub release shim creates it at
          # target_commitish, so the dispatched ref's commit is what gets tagged.
          target_commitish: ${{ github.sha }}
          draft: false
          prerelease: false
          files: |
            dist/*.tar.gz
            dist/SHA256SUMS
          fail_on_unmatched_files: true

      - name: Dry run summary
        if: steps.check.outputs.dry_run == 'true'
        run: |
          set -euo pipefail
          echo "::notice::Dry run — ${{ steps.check.outputs.tag }} was NOT published."
          echo "Artifacts that would have been released:"
          ls -l dist/