1+ # Reusable workflow that can be referenced by repositories in their `.github/workflows/release.yaml`.
2+ # See example usage in https://github.com/bazel-contrib/rules-template/blob/main/.github/workflows/release.yaml
3+ #
4+ # This workflow calls `.github/workflows/release_prep.sh` as the command to prepare the release.
5+ # Release notes are expected to be outputted to stdout from the release prep command.
6+ #
7+ # This workflow uses https://github.com/bazel-contrib/setup-bazel to prepare the cache folders.
8+ # Caching may be disabled by setting `mount_bazel_caches` to false.
9+ #
10+ # The workflow requires the following permissions to be set on the invoking job:
11+ #
12+ # permissions:
13+ # id-token: write # Needed to attest provenance
14+ # attestations: write # Needed to attest provenance
15+ # contents: write # Needed to upload release files
16+
17+ permissions : {}
18+
19+ on :
20+ # Make this workflow reusable, see
21+ # https://github.blog/2022-02-10-using-reusable-workflows-github-actions
22+ workflow_call :
23+ inputs :
24+ release_files :
25+ required : true
26+ description : |
27+ Newline-delimited globs of paths to assets to upload for release.
28+ relative to the module repository. The paths should include any files
29+ such as a release archive created by the release_prep script`.
30+
31+ See https://github.com/softprops/action-gh-release#inputs.
32+ type : string
33+ # TODO: there's a security design problem here:
34+ # Users of a workflow_dispatch trigger could fill in something via the GH Web UI
35+ # that would cause the release to use an arbitrary script.
36+ # That change wouldn't be reflected in the sources in the repo, and therefore
37+ # would not be verifiable by the attestation.
38+ # For now, we force this path to be hard-coded.
39+ #
40+ # release_prep_command:
41+ # default: .github/workflows/release_prep.sh
42+ # description: |
43+ # Command to run to prepare the release and generate release notes.
44+ # Release notes are expected to be outputted to stdout.
45+ # type: string
46+ bazel_test_command :
47+ default : " bazel test //..."
48+ description : |
49+ Bazel test command that may be overridden to set custom flags and targets.
50+ The --disk_cache=~/.cache/bazel-disk-cache --repository_cache=~/.cache/bazel-repository-cache flags are
51+ automatically appended to the command.
52+ type : string
53+ mount_bazel_caches :
54+ default : true
55+ description : |
56+ Whether to enable caching in the bazel-contrib/setup-bazel action.
57+ type : boolean
58+ prerelease :
59+ default : true
60+ description : Indicator of whether or not this is a prerelease.
61+ type : boolean
62+ draft :
63+ default : false
64+ description : |
65+ Whether the release should be created as a draft or published immediately.
66+ type : boolean
67+ tag_name :
68+ description : |
69+ The tag which is being released.
70+ By default, https://github.com/softprops/action-gh-release will use `github.ref_name`.
71+ type : string
72+
73+ jobs :
74+ build :
75+ outputs :
76+ release-files-artifact-id : ${{ steps.upload-release-files.outputs.artifact-id }}
77+ release-notes-artifact-id : ${{ steps.upload-release-notes.outputs.artifact-id }}
78+ runs-on : self-hosted
79+ steps :
80+ - name : Checkout
81+ uses : actions/checkout@v4
82+ with :
83+ ref : ${{ inputs.tag_name }}
84+
85+ - uses : bazel-contrib/setup-bazel@0.14.0
86+ with :
87+ disk-cache : ${{ inputs.mount_bazel_caches }}
88+ repository-cache : ${{ inputs.mount_bazel_caches }}
89+
90+ - name : Test
91+ run : ${{ inputs.bazel_test_command }} --disk_cache=~/.cache/bazel-disk-cache --repository_cache=~/.cache/bazel-repository-cache
92+
93+ # Fetch built artifacts (if any) from earlier jobs, which the release script may want to read.
94+ # Extract into ${GITHUB_WORKSPACE}/artifacts/*
95+ - uses : actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
96+
97+ - name : Build release artifacts and prepare release notes
98+ run : |
99+ if [ ! -f ".github/workflows/release_prep.sh" ]; then
100+ echo "ERROR: create a .github/workflows/release_prep.sh script"
101+ exit 1
102+ fi
103+ .github/workflows/release_prep.sh ${{ inputs.tag_name || github.ref_name }} > release_notes.txt
104+
105+ - uses : actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
106+ id : upload-release-files
107+ with :
108+ name : release_files
109+ path : ${{ inputs.release_files }}
110+
111+ - uses : actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
112+ id : upload-release-notes
113+ with :
114+ name : release_notes
115+ path : release_notes.txt
116+
117+ attest :
118+ needs : build
119+ outputs :
120+ attestations-artifact-id : ${{ steps.upload-attestations.outputs.artifact-id }}
121+ permissions :
122+ id-token : write
123+ attestations : write
124+ runs-on : ubuntu-latest
125+ steps :
126+ # actions/download-artifact@v4 does not yet support downloading via the immutable artifact-id,
127+ # but the Javascript library does. See: https://github.com/actions/download-artifact/issues/349
128+ - run : npm install @actions/artifact@2.1.9
129+ - name : download-release-files
130+ uses : actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
131+ env :
132+ ARTIFACT_ID : ${{ needs.build.outputs.release-files-artifact-id }}
133+ with :
134+ script : |
135+ const {default: artifactClient} = require('@actions/artifact')
136+ const { ARTIFACT_ID } = process.env
137+ await artifactClient.downloadArtifact(ARTIFACT_ID, { path: 'release_files/'})
138+
139+ # https://github.com/actions/attest-build-provenance
140+ - name : Attest release files
141+ id : attest_release
142+ uses : actions/attest-build-provenance@v2
143+ with :
144+ subject-path : release_files/**/*
145+
146+ # The Bazel Central Registry requires an attestation per release archive, but the
147+ # actions/attest-build-provenance action only produces a single attestation for a
148+ # list of subjects. Copy the combined attestations into individually named
149+ # .intoto.jsonl files.
150+ - name : Write release archive attestations into intoto.jsonl
151+ id : write_release_archive_attestation
152+ run : |
153+ # https://bazel.build/rules/lib/repo/http#http_archive
154+ RELEASE_ARCHIVE_REGEX="(\.zip|\.jar|\.war|\.aar|\.tar|\.tar\.gz|\.tgz|\.tar\.xz|\.txz|\.tar\.xzt|\.tzst|\.tar\.bz2|\.ar|\.deb)$"
155+
156+ ATTESTATIONS_DIR=$(mktemp --directory)
157+ for filename in $(find release_files/ -type f -printf "%f\n"); do
158+ if [[ "${filename}" =~ $RELEASE_ARCHIVE_REGEX ]]; then
159+ ATTESTATION_FILE="$(basename "${filename}").intoto.jsonl"
160+ echo "Writing attestation to ${ATTESTATION_FILE}"
161+ cat ${{ steps.attest_release.outputs.bundle-path }} | jq --compact-output > "${ATTESTATIONS_DIR}/${ATTESTATION_FILE}"
162+ fi
163+ done
164+ echo "release_archive_attestations_dir=${ATTESTATIONS_DIR}" >> $GITHUB_OUTPUT
165+ - uses : actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
166+ id : upload-attestations
167+ with :
168+ name : attestations
169+ path : ${{ steps.write_release_archive_attestation.outputs.release_archive_attestations_dir }}/*
170+
171+ release :
172+ needs : [build, attest]
173+ permissions :
174+ contents : write
175+ runs-on : ubuntu-latest
176+ steps :
177+ # actions/download-artifact@v4 does not yet support downloading via the immutable artifact-id,
178+ # but the Javascript library does. See: https://github.com/actions/download-artifact/issues/349
179+ - run : npm install @actions/artifact@2.1.9
180+ - name : download-artifacts
181+ uses : actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
182+ env :
183+ RELEASE_FILES_ARTIFACT_ID : ${{ needs.build.outputs.release-files-artifact-id }}
184+ RELEASE_NOTES_ARTIFACT_ID : ${{ needs.build.outputs.release-notes-artifact-id }}
185+ ATTESTATIONS_ARTIFACT_ID : ${{ needs.attest.outputs.attestations-artifact-id }}
186+ with :
187+ script : |
188+ const {default: artifactClient} = require('@actions/artifact')
189+ const { RELEASE_FILES_ARTIFACT_ID, RELEASE_NOTES_ARTIFACT_ID, ATTESTATIONS_ARTIFACT_ID } = process.env
190+ await Promise.all([
191+ artifactClient.downloadArtifact(RELEASE_FILES_ARTIFACT_ID, { path: 'release_files/'}),
192+ artifactClient.downloadArtifact(RELEASE_NOTES_ARTIFACT_ID, { path: 'release_notes/'}),
193+ artifactClient.downloadArtifact(ATTESTATIONS_ARTIFACT_ID, { path: 'attestations/'})
194+ ])
195+
196+ - name : Release
197+ uses : softprops/action-gh-release@v2
198+ with :
199+ prerelease : ${{ inputs.prerelease }}
200+ draft : ${{ inputs.draft }}
201+ # Use GH feature to populate the changelog automatically
202+ generate_release_notes : true
203+ body_path : release_notes/release_notes.txt
204+ fail_on_unmatched_files : true
205+ tag_name : ${{ inputs.tag_name }}
206+ files : |
207+ release_files/**/*
208+ attestations/*
0 commit comments