diff --git a/.github/workflows/release_ruleset.yaml b/.github/workflows/release_ruleset.yaml index 5eacd2f..6d13185 100644 --- a/.github/workflows/release_ruleset.yaml +++ b/.github/workflows/release_ruleset.yaml @@ -2,28 +2,47 @@ # See example usage in https://github.com/bazel-contrib/rules-template/blob/main/.github/workflows/release.yaml # # By default this workflows calls `.github/workflows/release_prep.sh` as the command to prepare -# the release. This can be customized with the `release_prep_command` attribute. Release notes are -# expected to be outputted to stdout from the release prep command. +# the release. This can be customized with the `release_prep_command` attribute. See documentation +# for the `release_prep_command` property below for inputs and outputs of the script. # # This workflow uses https://github.com/bazel-contrib/setup-bazel to prepare the cache folders. # Caching may be disabled by setting `mount_bazel_caches` to false. +# +# The workflow requires the following permissions to be set on the invoking job: +# +# permissions: +# contents: write # Needed for uploading release files + on: # Make this workflow reusable, see # https://github.blog/2022-02-10-using-reusable-workflows-github-actions workflow_call: inputs: - release_files: - required: true - description: | - Newline-delimited globs of paths to assets to upload for release. - See https://github.com/softprops/action-gh-release#inputs - type: string release_prep_command: default: .github/workflows/release_prep.sh description: | Command to run to prepare the release and generate release notes. - Release notes are expected to be outputted to stdout. + + The script will be provided with the following env vars: + + ARTIFACTS_DIR: Path to a pre-created directory where all artifacts to be + uploaded to the GitHub release must be placed. + + RELEASE_NOTES: Path to a pre-created file where release notes content should + be written. + + The following arguments are passed to the script: + + The tag supplied as an input to this workflows, or ${{ github.ref_name }}. + + The script must output a JSON blob with paths to release archives. The path can + be absolute or relative to the ARTIFACTS_DIR. For example: + + { + "release_archives": ["my-ruleset-vX.Y.Z.tar.gz"] + } + type: string bazel_test_command: default: "bazel test //..." @@ -55,23 +74,66 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ inputs.tag_name }} + path: this - - uses: bazel-contrib/setup-bazel@0.8.0 + - uses: bazel-contrib/setup-bazel@0.14.0 with: + module-root: this disk-cache: ${{ inputs.mount_bazel_caches }} external-cache: ${{ inputs.mount_bazel_caches }} repository-cache: ${{ inputs.mount_bazel_caches }} - name: Test + working-directory: this run: ${{ inputs.bazel_test_command }} --disk_cache=~/.cache/bazel-disk-cache --repository_cache=~/.cache/bazel-repository-cache - - name: Build release artifacts and prepare release notes + - name: Release preparation + id: release_prep + working-directory: this run: | if [ ! -f "${{ inputs.release_prep_command }}" ]; then echo "ERROR: create a ${{ inputs.release_prep_command }} release prep script or configure a different release prep command with the release_prep_command attribute" exit 1 fi - ${{ inputs.release_prep_command }} ${{ inputs.tag_name || github.ref_name }} > release_notes.txt + + export ARTIFACTS_DIR=$(mktemp --directory) + export RELEASE_NOTES=$(mktemp) + OUTPUT=$(${{ inputs.release_prep_command }} ${{ inputs.tag_name || github.ref_name }} | jq --compact-output .) + + echo "Release prep output:" + echo "${OUTPUT}" + + # Parse the the release archives, making the paths absolute + # Store a comma-delimited list which is the format required by the `subject-path` + # input in actions/attest-build-provenance. + RELEASE_ARCHIVES=$(echo "${OUTPUT}" | jq --raw-output --arg artifactsDir "${ARTIFACTS_DIR}/" '.release_archives[] |= sub("^(?[^/].*)";"\($artifactsDir)\(.path)") | .release_archives[]' | tr '\n' ',' | sed '$s/,$//') + + echo "artifacts_dir=$ARTIFACTS_DIR" >> $GITHUB_OUTPUT + echo "release_notes=$RELEASE_NOTES" >> $GITHUB_OUTPUT + echo "release_archives=$RELEASE_ARCHIVES" >> $GITHUB_OUTPUT + + # The actions/attest-build-provenance action can only produce a single attestation with multiple + # subjects, rather than an attestation per subject, which we want. Create a single attestation + # with all release archives, then copy the same attestation to multiple files below. + - name: Attest release archive(s) provenance + id: attest_release_archive + uses: actions/attest-build-provenance@v2 + with: + subject-path: ${{ steps.release_prep.outputs.release_archives }} + + - name: Write release archive(s) attestation into intoto.jsonl + id: write_release_archive_attestation + run: | + ATTESTATIONS_DIR=$(mktemp --directory) + RELEASE_ARCHIVES="${{ steps.release_prep.outputs.release_archives }}" + SPACE_DELIMITED="${RELEASE_ARCHIVES//,/ }" + for ARCHIVE in $SPACE_DELIMITED; do + echo "${ARCHIVE}" + ATTESTATION_FILE="$(basename "${ARCHIVE}").intoto.jsonl" + echo "Writing attestation to ${ATTESTATION_FILE}" + cat ${{ steps.attest_release_archive.outputs.bundle-path }} | jq --compact-output > "${ATTESTATIONS_DIR}/${ATTESTATION_FILE}" + done + echo "release_archive_attestations_dir=${ATTESTATIONS_DIR}" >> $GITHUB_OUTPUT - name: Release uses: softprops/action-gh-release@v1 @@ -79,7 +141,9 @@ jobs: prerelease: ${{ inputs.prerelease }} # Use GH feature to populate the changelog automatically generate_release_notes: true - body_path: release_notes.txt + body_path: ${{ steps.release_prep.outputs.release_notes }} fail_on_unmatched_files: true - files: ${{ inputs.release_files }} - tag_name: ${{ inputs.tag_name }} + files: | + ${{ steps.release_prep.outputs.artifacts_dir }}/* + ${{ steps.write_release_archive_attestation.outputs.release_archive_attestations_dir }}/* + tag_name: ${{ inputs.tag_name }} \ No newline at end of file