Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ jobs:
contents: write # Upload release assets
id-token: write # Federate for artifact attestation
attestations: write # Generate artifact attestations
outputs:
sbom_matrix: ${{ steps.sbom-matrix.outputs.matrix }}
is_public: ${{ steps.repo-visibility.outputs.is_public }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2
Expand Down Expand Up @@ -227,6 +230,55 @@ jobs:
run: |
echo "::warning::Artifact attestation skipped — not available for private user-owned repositories. Make this repository public to enable attestation."

- name: Generate SBOM attestation matrix
id: sbom-matrix
if: ${{ inputs.create-attestation && steps.repo-visibility.outputs.is_public == 'true' }}
run: |
shopt -s nullglob
sboms=(dist/*.spdx.json)
if [ ${#sboms[@]} -eq 0 ]; then
echo "matrix=" >> "$GITHUB_OUTPUT"
exit 0
fi
matrix=$(printf '%s\n' "${sboms[@]}" | jq -R '{"sbom": ., "archive": sub("\\.spdx\\.json$"; "")}' | jq -s -c '{"include": .}')
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"

- name: Upload dist for SBOM attestation
if: ${{ inputs.create-attestation && steps.repo-visibility.outputs.is_public == 'true' && steps.sbom-matrix.outputs.matrix != '' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-dist
path: dist
retention-days: 1

attest_sboms:
needs: release_goreleaser
if: ${{ inputs.create-attestation && needs.release_goreleaser.outputs.is_public == 'true' && needs.release_goreleaser.outputs.sbom_matrix != '' }}
runs-on: ubuntu-latest
permissions:
id-token: write # Federate for SBOM attestation
attestations: write # Generate SBOM attestations
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.release_goreleaser.outputs.sbom_matrix) }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2
with:
egress-policy: audit

- name: Download dist
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-dist
path: dist

- name: Attest SBOM
uses: actions/attest-sbom@4651f806c01d8637787e274ac3bdf724ef169f34 # v4.1.0
with:
subject-path: "${{ matrix.archive }}"
sbom-path: "${{ matrix.sbom }}"

release_image:
needs: create_release
if: ${{ inputs.image-name != '' && needs.create_release.outputs.full-tag != '' }}
Expand Down Expand Up @@ -306,12 +358,13 @@ jobs:
echo "::warning::Artifact attestation skipped — not available for private user-owned repositories. Make this repository public to enable attestation."

publish_release:
needs: [create_release, release_goreleaser, release_image]
needs: [create_release, release_goreleaser, attest_sboms, release_image]
if: >
always() &&
inputs.publish &&
needs.create_release.result == 'success' &&
(needs.release_goreleaser.result == 'success' || needs.release_goreleaser.result == 'skipped') &&
(needs.attest_sboms.result == 'success' || needs.attest_sboms.result == 'skipped') &&
(needs.release_image.result == 'success' || needs.release_image.result == 'skipped')
runs-on: ubuntu-latest
permissions:
Expand Down
9 changes: 6 additions & 3 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ The workflow runs up to six jobs:

1. **create_release** - Always runs. Creates a draft release via release-drafter, then creates and pushes the full and major version git tags.
2. **release_goreleaser** - Runs when `goreleaser-config-path` is set. Builds Go binaries, uploads artifacts to the draft release, and optionally creates attestations.
3. **release_image** - Runs when `image-name` is set. Builds and pushes a multi-platform Docker image, and optionally creates attestations.
4. **publish_release** - Runs when `publish` is true and all preceding jobs succeed (or are skipped). Publishes the draft release.
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.
3. **attest_sboms** - Runs when `create-attestation: true`, the repository is public, and GoReleaser produced `*.spdx.json` files. Creates an SBOM attestation linking each archive to its corresponding SBOM via `actions/attest-sbom`. Fans out across archive/SBOM pairs via a matrix.
4. **release_image** - Runs when `image-name` is set. Builds and pushes a multi-platform Docker image, and optionally creates attestations.
5. **publish_release** - Runs when `publish` is true and all preceding jobs succeed (or are skipped). Publishes the draft release.
6. **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.

## GoReleaser Configuration

Expand All @@ -103,6 +104,8 @@ Without these settings, GoReleaser will attempt to create its own GitHub release

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.

When `create-attestation: true` and SBOMs are produced, the `attest_sboms` job additionally runs `actions/attest-sbom` per (archive, SBOM) pair. This creates an SBOM attestation linking each archive to its corresponding `.spdx.json`, on top of the build provenance attestations generated by `release_goreleaser`. SBOM linkage requires GoReleaser to emit one SBOM per archive using the default `${artifact}.spdx.json` naming pattern (or any naming that strips `.spdx.json` to yield the matching archive path).

## Notes

- 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.
Expand Down
Loading