Skip to content

Commit 828fa55

Browse files
committed
fix: address review findings for release workflow correctness
## What Moved tag creation into create_release job, removed update_major_tag job and its input, added GoReleaser config validation via yq to enforce release.disable: true, fixed -f to -F for boolean in publish_release, made go-version-file configurable, made upload globs resilient with nullglob, consolidated discussion validation into a single input check, and pinned release_image checkout to the tagged commit. ## Why The draft-first pattern changed when git tags are created, but downstream jobs hadn't been updated to account for that. Multiple review agents independently identified race conditions, a silent publish failure, and fragile assumptions that needed fixing before this is mergeable. ## Notes - Removing the `update-major-tag` input is a breaking change for callers that explicitly set it — major tag is now always pushed in create_release - The yq install adds a step to the GoReleaser job; mikefarah/yq action is SHA-pinned but is a new third-party dependency - GoReleaser config validation only checks `release.disable` not `changelog.disable` — the latter is recommended but not enforced since it won't cause conflicts Signed-off-by: jmeridth <jmeridth@gmail.com>
1 parent 76f2801 commit 828fa55

2 files changed

Lines changed: 74 additions & 61 deletions

File tree

.github/workflows/release.yaml

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ on:
1111
release-config-name:
1212
required: true
1313
type: string
14-
update-major-tag:
15-
required: false
16-
type: boolean
17-
default: true
1814
image-name:
1915
description: "Docker image name (e.g., owner/repo). Enables image build/push when set."
2016
required: false
@@ -45,6 +41,11 @@ on:
4541
required: false
4642
type: string
4743
default: ""
44+
go-version-file:
45+
description: "Path to go.mod or go.work file for Go version detection. Used by the GoReleaser job."
46+
required: false
47+
type: string
48+
default: "go.mod"
4849
secrets:
4950
github-token:
5051
required: true
@@ -94,6 +95,11 @@ jobs:
9495
uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
9596
with:
9697
egress-policy: audit
98+
- name: Checkout
99+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
100+
with:
101+
fetch-depth: 0
102+
persist-credentials: true # Required for git push of tags
97103
- name: Draft release
98104
id: release-drafter
99105
uses: release-drafter/release-drafter@139054aeaa9adc52ab36ddf67437541f039b88e2 # v7.1.1
@@ -106,40 +112,19 @@ jobs:
106112
run: |
107113
short_tag=$(echo "${{ steps.release-drafter.outputs.tag_name }}" | cut -d. -f1)
108114
echo "SHORT_TAG=$short_tag" >> "$GITHUB_OUTPUT"
109-
110-
update_major_tag:
111-
needs: create_release
112-
if: ${{ inputs.update-major-tag && needs.create_release.outputs.full-tag != '' }}
113-
runs-on: ubuntu-latest
114-
permissions:
115-
contents: write # Force-push major version tag
116-
steps:
117-
- name: Harden the runner (Audit all outbound calls)
118-
uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
119-
with:
120-
egress-policy: audit
121-
122-
- name: Checkout Repo
123-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
124-
with:
125-
fetch-depth: 0
126-
ref: ${{ needs.create_release.outputs.full-tag }}
127-
persist-credentials: true # Required for git push
128-
129-
- name: Force update major tag
115+
- name: Create and push tags
130116
run: |
131-
git tag -f "${SHORT}" "${FULL}"
132-
git push -f origin "${SHORT}"
133-
env:
134-
SHORT: ${{ needs.create_release.outputs.short-tag }}
135-
FULL: ${{ needs.create_release.outputs.full-tag }}
117+
git tag "${{ steps.release-drafter.outputs.tag_name }}"
118+
git tag -f "${{ steps.get_tag_name.outputs.SHORT_TAG }}" "${{ steps.release-drafter.outputs.tag_name }}"
119+
git push origin "${{ steps.release-drafter.outputs.tag_name }}"
120+
git push -f origin "${{ steps.get_tag_name.outputs.SHORT_TAG }}"
136121
137122
release_goreleaser:
138123
needs: create_release
139124
if: ${{ inputs.goreleaser-config-path != '' && needs.create_release.outputs.full-tag != '' }}
140125
runs-on: ubuntu-latest
141126
permissions:
142-
contents: write # Upload release assets and push tags
127+
contents: write # Upload release assets
143128
id-token: write # Federate for artifact attestation
144129
attestations: write # Generate artifact attestations
145130
steps:
@@ -152,17 +137,30 @@ jobs:
152137
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
153138
with:
154139
fetch-depth: 0
155-
persist-credentials: true # Required for git push of tags
140+
persist-credentials: false
141+
142+
- name: Install yq
143+
uses: mikefarah/yq@f71c26247e2b5278a5e07e662b2c6e3a1a72e67e # v4.45.4
144+
with:
145+
cmd: echo "yq installed"
156146

157-
- name: Create and push tag
147+
- name: Validate GoReleaser config has release disabled
158148
run: |
159-
git tag "${{ needs.create_release.outputs.full-tag }}"
160-
git push origin "${{ needs.create_release.outputs.full-tag }}"
149+
config="${{ inputs.goreleaser-config-path }}"
150+
if ! [ -f "$config" ]; then
151+
echo "::error::GoReleaser config file not found: $config"
152+
exit 1
153+
fi
154+
release_disabled=$(yq '.release.disable' "$config")
155+
if [ "$release_disabled" != "true" ]; then
156+
echo "::error::GoReleaser config must have 'release: disable: true' to prevent conflicting with the draft release created by this workflow. See https://github.com/github-community-projects/ospo-reusable-workflows/blob/main/docs/release.md#goreleaser-configuration"
157+
exit 1
158+
fi
161159
162160
- name: Set up Go
163161
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
164162
with:
165-
go-version-file: go.mod
163+
go-version-file: ${{ inputs.go-version-file }}
166164

167165
- name: Build with GoReleaser
168166
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
@@ -175,10 +173,14 @@ jobs:
175173

176174
- name: Upload artifacts to draft release
177175
run: |
176+
shopt -s nullglob
177+
files=(dist/*.tar.gz dist/*.zip dist/checksums.txt)
178+
if [ ${#files[@]} -eq 0 ]; then
179+
echo "::error::No artifacts found in dist/ to upload"
180+
exit 1
181+
fi
178182
gh release upload "${{ needs.create_release.outputs.full-tag }}" \
179-
dist/*.tar.gz \
180-
dist/*.zip \
181-
dist/checksums.txt \
183+
"${files[@]}" \
182184
--clobber
183185
env:
184186
GH_TOKEN: ${{ secrets.github-token }}
@@ -232,6 +234,7 @@ jobs:
232234
233235
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
234236
with:
237+
ref: ${{ needs.create_release.outputs.full-tag }}
235238
persist-credentials: false
236239

237240
- name: Set up Docker Buildx
@@ -298,20 +301,18 @@ jobs:
298301
with:
299302
egress-policy: audit
300303

301-
- name: Validate discussion repository ID
302-
if: ${{ env.DISCUSSION_REPOSITORY_ID == '' }}
303-
run: |
304-
echo "::notice::discussion-repository-id secret is not set, skipping discussion creation"
305-
exit 0
306-
307-
- name: Validate discussion category ID
308-
if: ${{ env.DISCUSSION_CATEGORY_ID == '' }}
304+
- name: Check discussion inputs
305+
id: check-inputs
309306
run: |
310-
echo "::notice::discussion-category-id secret is not set, skipping discussion creation"
311-
exit 0
307+
if [ -z "${DISCUSSION_REPOSITORY_ID}" ] || [ -z "${DISCUSSION_CATEGORY_ID}" ]; then
308+
echo "::notice::discussion-repository-id and/or discussion-category-id secrets are not set, skipping discussion creation"
309+
echo "skip=true" >> "$GITHUB_OUTPUT"
310+
else
311+
echo "skip=false" >> "$GITHUB_OUTPUT"
312+
fi
312313
313314
- name: Create an Announcement Discussion for Release
314-
if: ${{ env.DISCUSSION_REPOSITORY_ID != '' && env.DISCUSSION_CATEGORY_ID != '' }}
315+
if: ${{ steps.check-inputs.outputs.skip == 'false' }}
315316
uses: abirismyname/create-discussion@c2b7c825241769dda523865ae444a879f6bbd0e0
316317
with:
317318
title: ${{ needs.create_release.outputs.full-tag }}
@@ -321,12 +322,11 @@ jobs:
321322
github-token: ${{ secrets.github-token }}
322323

323324
publish_release:
324-
needs: [create_release, update_major_tag, release_goreleaser, release_image, release_discussion]
325+
needs: [create_release, release_goreleaser, release_image, release_discussion]
325326
if: >
326327
always() &&
327328
inputs.publish &&
328329
needs.create_release.result == 'success' &&
329-
(needs.update_major_tag.result == 'success' || needs.update_major_tag.result == 'skipped') &&
330330
(needs.release_goreleaser.result == 'success' || needs.release_goreleaser.result == 'skipped') &&
331331
(needs.release_image.result == 'success' || needs.release_image.result == 'skipped') &&
332332
(needs.release_discussion.result == 'success' || needs.release_discussion.result == 'skipped')
@@ -344,6 +344,6 @@ jobs:
344344
gh api \
345345
--method PATCH \
346346
"/repos/${{ github.repository }}/releases/${{ needs.create_release.outputs.release-id }}" \
347-
-f draft=false
347+
-F draft=false
348348
env:
349349
GH_TOKEN: ${{ secrets.github-token }}

docs/release.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ Consolidated release workflow that creates a draft release, optionally builds ar
2020
# The name of the configuration file to use
2121
# from the release-drafter/release-drafter GitHub Action
2222
release-config-name: release-drafter.yml
23-
# Boolean flag whether to update major tag to latest full semver tag, default is true
24-
update-major-tag: true
2523

2624
# --- Optional: GoReleaser build/upload ---
2725
# Setting goreleaser-config-path enables the GoReleaser job
2826
# Path to GoReleaser config file (e.g., .goreleaser.yaml)
2927
goreleaser-config-path: .goreleaser.yaml
28+
# Path to go.mod or go.work file for Go version detection, default is go.mod
29+
go-version-file: go.mod
3030

3131
# --- Optional: Docker image build/push ---
3232
# Setting image-name enables the image build/push job
@@ -78,12 +78,25 @@ jobs:
7878
7979
The workflow runs up to six jobs:
8080
81-
1. **create_release** - Always runs. Creates a draft release via release-drafter.
82-
2. **update_major_tag** - Runs when `update-major-tag` is true. Force-updates the major version tag.
83-
3. **release_goreleaser** - Runs when `goreleaser-config-path` is set. Builds Go binaries, uploads artifacts to the draft release, and optionally creates attestations.
84-
4. **release_image** - Runs when `image-name` is set. Builds and pushes a multi-platform Docker image, and optionally creates attestations.
85-
5. **release_discussion** - Runs when both `discussion-category-id` and `discussion-repository-id` secrets are set. Creates a GitHub Discussions announcement.
86-
6. **publish_release** - Runs when `publish` is true and all preceding jobs succeed (or are skipped). Publishes the draft release.
81+
1. **create_release** - Always runs. Creates a draft release via release-drafter, then creates and pushes the full and major version git tags.
82+
2. **release_goreleaser** - Runs when `goreleaser-config-path` is set. Builds Go binaries, uploads artifacts to the draft release, and optionally creates attestations.
83+
3. **release_image** - Runs when `image-name` is set. Builds and pushes a multi-platform Docker image, and optionally creates attestations.
84+
4. **release_discussion** - Runs when both `discussion-category-id` and `discussion-repository-id` secrets are set. Creates a GitHub Discussions announcement.
85+
5. **publish_release** - Runs when `publish` is true and all preceding jobs succeed (or are skipped). Publishes the draft release.
86+
87+
## GoReleaser Configuration
88+
89+
When using the `goreleaser-config-path` input, your GoReleaser config **must** disable release and changelog management since this workflow handles both via release-drafter:
90+
91+
```yaml
92+
release:
93+
disable: true
94+
95+
changelog:
96+
disable: true
97+
```
98+
99+
Without these settings, GoReleaser will attempt to create its own GitHub release, conflicting with the draft release created by release-drafter.
87100

88101
## Notes
89102

0 commit comments

Comments
 (0)