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+ external-cache : ${{ inputs.mount_bazel_caches }}
89+ repository-cache : ${{ inputs.mount_bazel_caches }}
90+
91+ - name : Test
92+ run : ${{ inputs.bazel_test_command }} --disk_cache=~/.cache/bazel-disk-cache --repository_cache=~/.cache/bazel-repository-cache
93+
94+ # Fetch built artifacts (if any) from earlier jobs, which the release script may want to read.
95+ # Extract into ${GITHUB_WORKSPACE}/artifacts/*
96+ - uses : actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
97+
98+ - name : Build release artifacts and prepare release notes
99+ run : |
100+ if [ ! -f ".github/workflows/release_prep.sh" ]; then
101+ echo "ERROR: create a .github/workflows/release_prep.sh script"
102+ exit 1
103+ fi
104+ .github/workflows/release_prep.sh ${{ inputs.tag_name || github.ref_name }} > release_notes.txt
105+
106+ - uses : actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
107+ id : upload-release-files
108+ with :
109+ name : release_files
110+ path : ${{ inputs.release_files }}
111+
112+ - uses : actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
113+ id : upload-release-notes
114+ with :
115+ name : release_notes
116+ path : release_notes.txt
117+
118+ attest :
119+ needs : build
120+ outputs :
121+ attestations-artifact-id : ${{ steps.upload-attestations.outputs.artifact-id }}
122+ permissions :
123+ id-token : write
124+ attestations : write
125+ runs-on : ubuntu-latest
126+ steps :
127+ # actions/download-artifact@v4 does not yet support downloading via the immutable artifact-id,
128+ # but the Javascript library does. See: https://github.com/actions/download-artifact/issues/349
129+ - run : npm install @actions/artifact@2.1.9
130+ - name : download-release-files
131+ uses : actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
132+ env :
133+ ARTIFACT_ID : ${{ needs.build.outputs.release-files-artifact-id }}
134+ with :
135+ script : |
136+ const {default: artifactClient} = require('@actions/artifact')
137+ const { ARTIFACT_ID } = process.env
138+ await artifactClient.downloadArtifact(ARTIFACT_ID, { path: 'release_files/'})
139+
140+ # https://github.com/actions/attest-build-provenance
141+ - name : Attest release files
142+ id : attest_release
143+ uses : actions/attest-build-provenance@v2
144+ with :
145+ subject-path : release_files/**/*
146+
147+ # The Bazel Central Registry requires an attestation per release archive, but the
148+ # actions/attest-build-provenance action only produces a single attestation for a
149+ # list of subjects. Copy the combined attestations into individually named
150+ # .intoto.jsonl files.
151+ - name : Write release archive attestations into intoto.jsonl
152+ id : write_release_archive_attestation
153+ run : |
154+ # https://bazel.build/rules/lib/repo/http#http_archive
155+ RELEASE_ARCHIVE_REGEX="(\.zip|\.jar|\.war|\.aar|\.tar|\.tar\.gz|\.tgz|\.tar\.xz|\.txz|\.tar\.xzt|\.tzst|\.tar\.bz2|\.ar|\.deb)$"
156+
157+ ATTESTATIONS_DIR=$(mktemp --directory)
158+ for filename in $(find release_files/ -type f -printf "%f\n"); do
159+ if [[ "${filename}" =~ $RELEASE_ARCHIVE_REGEX ]]; then
160+ ATTESTATION_FILE="$(basename "${filename}").intoto.jsonl"
161+ echo "Writing attestation to ${ATTESTATION_FILE}"
162+ cat ${{ steps.attest_release.outputs.bundle-path }} | jq --compact-output > "${ATTESTATIONS_DIR}/${ATTESTATION_FILE}"
163+ fi
164+ done
165+ echo "release_archive_attestations_dir=${ATTESTATIONS_DIR}" >> $GITHUB_OUTPUT
166+ - uses : actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
167+ id : upload-attestations
168+ with :
169+ name : attestations
170+ path : ${{ steps.write_release_archive_attestation.outputs.release_archive_attestations_dir }}/*
171+
172+ release :
173+ needs : [build, attest]
174+ permissions :
175+ contents : write
176+ runs-on : ubuntu-latest
177+ steps :
178+ # actions/download-artifact@v4 does not yet support downloading via the immutable artifact-id,
179+ # but the Javascript library does. See: https://github.com/actions/download-artifact/issues/349
180+ - run : npm install @actions/artifact@2.1.9
181+ - name : download-artifacts
182+ uses : actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
183+ env :
184+ RELEASE_FILES_ARTIFACT_ID : ${{ needs.build.outputs.release-files-artifact-id }}
185+ RELEASE_NOTES_ARTIFACT_ID : ${{ needs.build.outputs.release-notes-artifact-id }}
186+ ATTESTATIONS_ARTIFACT_ID : ${{ needs.attest.outputs.attestations-artifact-id }}
187+ with :
188+ script : |
189+ const {default: artifactClient} = require('@actions/artifact')
190+ const { RELEASE_FILES_ARTIFACT_ID, RELEASE_NOTES_ARTIFACT_ID, ATTESTATIONS_ARTIFACT_ID } = process.env
191+ await Promise.all([
192+ artifactClient.downloadArtifact(RELEASE_FILES_ARTIFACT_ID, { path: 'release_files/'}),
193+ artifactClient.downloadArtifact(RELEASE_NOTES_ARTIFACT_ID, { path: 'release_notes/'}),
194+ artifactClient.downloadArtifact(ATTESTATIONS_ARTIFACT_ID, { path: 'attestations/'})
195+ ])
196+
197+ - name : Release
198+ uses : softprops/action-gh-release@v2
199+ with :
200+ prerelease : ${{ inputs.prerelease }}
201+ draft : ${{ inputs.draft }}
202+ # Use GH feature to populate the changelog automatically
203+ generate_release_notes : true
204+ body_path : release_notes/release_notes.txt
205+ fail_on_unmatched_files : true
206+ tag_name : ${{ inputs.tag_name }}
207+ files : |
208+ release_files/**/*
209+ attestations/*
0 commit comments