|
| 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 build //..." |
| 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.15.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