Skip to content

Commit e92cb60

Browse files
authored
fix(ci): order discussion after publish + auto-install syft for SBOMs (#138)
* fix(release): create discussion only after publish succeeds ## What Move the release_discussion job to run after publish_release succeeds instead of in parallel with the build jobs. The publish_release job no longer depends on release_discussion. ## Why Previously, the discussion was created before the draft release was published. If publish_release failed (or publish was disabled), subscribers were still notified about a release that did not actually exist as a published artifact. Gating discussion creation on a successful publish ensures announcements only fire for releases users can actually consume. ## Notes - The redundant inputs.publish check on release_discussion was removed; gating now flows through publish_release, which itself requires inputs.publish. - Total wall-clock time for a full release increases slightly because the discussion job no longer runs in parallel with goreleaser/image builds — it now runs after publish. - Behavior when create-discussion is false or discussion secrets are unset is unchanged. Signed-off-by: jmeridth <jmeridth@gmail.com> * feat(release): auto-install syft when GoReleaser config uses sboms ## What In `release_goreleaser`, detect a `sboms:` block in the user's GoReleaser config (via yq, already installed for the existing release.disable check) and conditionally install `syft` before running GoReleaser. Generated `dist/*.spdx.json` files are now included in both the draft-release upload and the build provenance attestation subject paths. ## Why GoReleaser configs that declare `sboms.cmd: syft` previously failed inside the reusable workflow with `exec: "syft": executable file not found in $PATH`, because the `release_goreleaser` job did not install syft. Consumers had to either drop SBOM generation from their GoReleaser config or maintain a separate workflow job to install syft, which defeats the purpose of consolidating into this reusable workflow. ## Notes - The new `Install Syft for SBOM generation` step only runs when the config has a `sboms:` key, so consumers without SBOMs see no behavior change. - syft is pinned via `anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0`. If a newer release is preferred I am happy to bump it before merge. - SBOM `.spdx.json` files are included in `attest-build-provenance` subject paths so attestation covers the same set of artifacts the user sees in the published release. - The `Detect SBOM generation` step is intentionally placed after the existing `Validate GoReleaser config has release disabled` step, so the validate-first behavior is preserved. Signed-off-by: jmeridth <jmeridth@gmail.com> * ci: allow github and release scopes in PR titles ## What Add `github` and `release` to the allowed PR-title scope list in test-pr-title.yaml. ## Why PRs that touch GitHub-platform-specific config (workflows, issue templates, labels) and the release pipeline previously had to use a less specific scope or no scope at all, since `fix(release): ...` and `chore(github): ...` would fail scope validation. This PR's own title uses `release` as its scope. ## Notes - `requireScope` remains `false`, so scope is still optional; this only widens the allowed set when one is used. Signed-off-by: jmeridth <jmeridth@gmail.com> --------- Signed-off-by: jmeridth <jmeridth@gmail.com>
1 parent 592067a commit e92cb60

3 files changed

Lines changed: 53 additions & 33 deletions

File tree

.github/workflows/release.yaml

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,20 @@ jobs:
162162
exit 1
163163
fi
164164
165+
- name: Detect SBOM generation in GoReleaser config
166+
id: detect-sbom
167+
run: |
168+
sboms=$(yq '.sboms // ""' "${{ inputs.goreleaser-config-path }}")
169+
if [ -n "$sboms" ] && [ "$sboms" != "null" ]; then
170+
echo "needs_syft=true" >> "$GITHUB_OUTPUT"
171+
else
172+
echo "needs_syft=false" >> "$GITHUB_OUTPUT"
173+
fi
174+
175+
- name: Install Syft for SBOM generation
176+
if: steps.detect-sbom.outputs.needs_syft == 'true'
177+
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
178+
165179
- name: Set up Go
166180
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
167181
with:
@@ -179,7 +193,7 @@ jobs:
179193
- name: Upload artifacts to draft release
180194
run: |
181195
shopt -s nullglob
182-
files=(dist/*.tar.gz dist/*.zip dist/checksums.txt)
196+
files=(dist/*.tar.gz dist/*.zip dist/checksums.txt dist/*.spdx.json)
183197
if [ ${#files[@]} -eq 0 ]; then
184198
echo "::error::No artifacts found in dist/ to upload"
185199
exit 1
@@ -206,6 +220,7 @@ jobs:
206220
dist/*.tar.gz
207221
dist/*.zip
208222
dist/checksums.txt
223+
dist/*.spdx.json
209224
210225
- name: Skip attestation notice
211226
if: ${{ inputs.create-attestation && steps.repo-visibility.outputs.is_public != 'true' }}
@@ -290,9 +305,35 @@ jobs:
290305
run: |
291306
echo "::warning::Artifact attestation skipped — not available for private user-owned repositories. Make this repository public to enable attestation."
292307
308+
publish_release:
309+
needs: [create_release, release_goreleaser, release_image]
310+
if: >
311+
always() &&
312+
inputs.publish &&
313+
needs.create_release.result == 'success' &&
314+
(needs.release_goreleaser.result == 'success' || needs.release_goreleaser.result == 'skipped') &&
315+
(needs.release_image.result == 'success' || needs.release_image.result == 'skipped')
316+
runs-on: ubuntu-latest
317+
permissions:
318+
contents: write # Publish draft release
319+
steps:
320+
- name: Harden the runner (Audit all outbound calls)
321+
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
322+
with:
323+
egress-policy: audit
324+
325+
- name: Publish draft release
326+
run: |
327+
gh api \
328+
--method PATCH \
329+
"/repos/${{ github.repository }}/releases/${{ needs.create_release.outputs.release-id }}" \
330+
-F draft=false
331+
env:
332+
GH_TOKEN: ${{ secrets.github-token }}
333+
293334
release_discussion:
294-
needs: create_release
295-
if: ${{ inputs.create-discussion && inputs.publish && needs.create_release.outputs.full-tag != '' }}
335+
needs: [create_release, publish_release]
336+
if: ${{ inputs.create-discussion && needs.publish_release.result == 'success' && needs.create_release.outputs.full-tag != '' }}
296337
runs-on: ubuntu-latest
297338
permissions:
298339
contents: read # Required by harden-runner
@@ -344,30 +385,3 @@ jobs:
344385
repository-id: ${{ env.DISCUSSION_REPOSITORY_ID }}
345386
category-id: ${{ env.DISCUSSION_CATEGORY_ID }}
346387
github-token: ${{ secrets.github-token }}
347-
348-
publish_release:
349-
needs: [create_release, release_goreleaser, release_image, release_discussion]
350-
if: >
351-
always() &&
352-
inputs.publish &&
353-
needs.create_release.result == 'success' &&
354-
(needs.release_goreleaser.result == 'success' || needs.release_goreleaser.result == 'skipped') &&
355-
(needs.release_image.result == 'success' || needs.release_image.result == 'skipped') &&
356-
(needs.release_discussion.result == 'success' || needs.release_discussion.result == 'skipped')
357-
runs-on: ubuntu-latest
358-
permissions:
359-
contents: write # Publish draft release
360-
steps:
361-
- name: Harden the runner (Audit all outbound calls)
362-
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
363-
with:
364-
egress-policy: audit
365-
366-
- name: Publish draft release
367-
run: |
368-
gh api \
369-
--method PATCH \
370-
"/repos/${{ github.repository }}/releases/${{ needs.create_release.outputs.release-id }}" \
371-
-F draft=false
372-
env:
373-
GH_TOKEN: ${{ secrets.github-token }}

.github/workflows/test-pr-title.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ jobs:
3131
ci
3232
docs
3333
deps
34+
github
35+
release
3436
requireScope: false
3537
secrets:
3638
github-token: ${{ secrets.GITHUB_TOKEN }}

docs/release.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Release Reusable Workflow
22

3-
Consolidated release workflow that creates a draft release, optionally builds artifacts (GoReleaser, Docker images), creates GitHub Discussions announcements, and publishes the release after all jobs succeed. This draft-first pattern supports repositories with immutable releases enabled.
3+
Consolidated release workflow that creates a draft release, optionally builds artifacts (GoReleaser, Docker images), publishes the release after all build jobs succeed, and then creates a GitHub Discussions announcement. This draft-first pattern supports repositories with immutable releases enabled and ensures announcements only fire for releases that publish successfully.
44

55
## Inputs
66

@@ -82,8 +82,8 @@ The workflow runs up to six jobs:
8282
1. **create_release** - Always runs. Creates a draft release via release-drafter, then creates and pushes the full and major version git tags.
8383
2. **release_goreleaser** - Runs when `goreleaser-config-path` is set. Builds Go binaries, uploads artifacts to the draft release, and optionally creates attestations.
8484
3. **release_image** - Runs when `image-name` is set. Builds and pushes a multi-platform Docker image, and optionally creates attestations.
85-
4. **release_discussion** - Runs when `create-discussion` is set. Both `discussion-category-id` and `discussion-repository-id` secrets are required if so. Creates a GitHub Discussions announcement.
86-
5. **publish_release** - Runs when `publish` is true and all preceding jobs succeed (or are skipped). Publishes the draft release.
85+
4. **publish_release** - Runs when `publish` is true and all preceding jobs succeed (or are skipped). Publishes the draft release.
86+
5. **release_discussion** - Runs after `publish_release` succeeds and when `create-discussion` is set. Both `discussion-category-id` and `discussion-repository-id` secrets are required if so. Creates a GitHub Discussions announcement only after the release is successfully published.
8787

8888
## GoReleaser Configuration
8989

@@ -99,6 +99,10 @@ changelog:
9999

100100
Without these settings, GoReleaser will attempt to create its own GitHub release, conflicting with the draft release created by release-drafter.
101101

102+
### SBOM generation
103+
104+
If your GoReleaser config includes an `sboms:` block that calls `syft`, the workflow detects it via `yq` and installs syft automatically before running GoReleaser. Generated `*.spdx.json` files are uploaded alongside the archives and included in the build provenance attestation when `create-attestation: true`. No additional configuration is needed beyond declaring `sboms:` in your GoReleaser config.
105+
102106
## Notes
103107

104108
- The draft-first pattern supports repositories with **immutable releases** enabled. The release is created as a draft, artifacts are uploaded, and only then is it published.

0 commit comments

Comments
 (0)