Skip to content

Commit 3286a62

Browse files
authored
Add a "release-gate" step to the release workflow (#24365)
Mirrors astral-sh/uv#18804 You can see the environment policies I'll apply following merge at https://github.com/astral-sh/github-policies/tree/main/environments Also updates the Docker workflow to avoid using release secrets when not pushing.
1 parent 5f88756 commit 3286a62

File tree

3 files changed

+27
-3
lines changed

3 files changed

+27
-3
lines changed

.github/workflows/build-docker.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
name: Build Docker image (ghcr.io/astral-sh/ruff) for ${{ matrix.platform }}
3232
runs-on: ubuntu-latest
3333
environment:
34-
name: release
34+
name: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit && 'release' || '' }}
3535
strategy:
3636
fail-fast: false
3737
matrix:
@@ -47,6 +47,7 @@ jobs:
4747
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
4848

4949
- uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
50+
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
5051
with:
5152
registry: ghcr.io
5253
username: ${{ github.repository_owner }}

.github/workflows/release.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ env:
5353
CARGO_DIST_CHECKSUM: "cd355dab0b4c02fb59038fef87655550021d07f45f1d82f947a34ef98560abb8"
5454

5555
jobs:
56+
release-gate:
57+
# N.B. This name should not change, it is used for downstream checks.
58+
name: release-gate
59+
if: ${{ inputs.tag != 'dry-run' }}
60+
runs-on: ubuntu-latest
61+
# This environment requires a 2-factor approval, i.e., the workflow must be approved by another
62+
# team member. GitHub fires approval events on every job that deploys to an environment, so we
63+
# have a dedicated environment for this purpose instead of using the `release` environment.
64+
# We use a GitHub App with a deployment protection rule webhook to ensure that the `release`
65+
# environment is only approved when the `release-gate` job succeeds.
66+
environment:
67+
name: release-gate
68+
deployment: false
69+
steps:
70+
- run: echo "Release approved"
71+
5672
# Run 'dist plan' (or host) to determine what tasks we need to do
5773
plan:
5874
runs-on: "depot-ubuntu-latest-4"
@@ -109,7 +125,8 @@ jobs:
109125
custom-build-docker:
110126
needs:
111127
- plan
112-
if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }}
128+
- release-gate
129+
if: ${{ always() && needs.plan.result == 'success' && (needs.release-gate.result == 'success' || needs.release-gate.result == 'skipped') && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run') }}
113130
uses: ./.github/workflows/build-docker.yml
114131
with:
115132
plan: ${{ needs.plan.outputs.val }}
@@ -229,6 +246,7 @@ jobs:
229246
needs:
230247
- plan
231248
- host
249+
- release-gate
232250
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
233251
uses: ./.github/workflows/publish-pypi.yml
234252
with:
@@ -243,6 +261,7 @@ jobs:
243261
needs:
244262
- plan
245263
- host
264+
- release-gate
246265
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
247266
uses: ./.github/workflows/publish-wasm.yml
248267
with:
@@ -259,12 +278,13 @@ jobs:
259278
needs:
260279
- plan
261280
- host
281+
- release-gate
262282
- custom-publish-pypi
263283
- custom-publish-wasm
264284
# use "always() && ..." to allow us to wait for all publish jobs while
265285
# still allowing individual publish jobs to skip themselves (for prereleases).
266286
# "host" however must run to completion, no skipping allowed!
267-
if: ${{ always() && needs.host.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') && (needs.custom-publish-wasm.result == 'skipped' || needs.custom-publish-wasm.result == 'success') }}
287+
if: ${{ always() && needs.host.result == 'success' && needs.release-gate.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') && (needs.custom-publish-wasm.result == 'skipped' || needs.custom-publish-wasm.result == 'success') }}
268288
runs-on: "depot-ubuntu-latest-4"
269289
permissions:
270290
"attestations": "write"

CONTRIBUTING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,12 +416,15 @@ Commit each step of this process separately for easier review.
416416
417417
- The new version number (without starting `v`)
418418
419+
1. Request a deployment approval from another team member
420+
419421
1. The release workflow will do the following:
420422
421423
1. Build all the assets. If this fails (even though we tested in step 4), we haven't tagged or
422424
uploaded anything, you can restart after pushing a fix. If you just need to rerun the build,
423425
make sure you're [re-running all the failed
424426
jobs](https://docs.github.com/en/actions/managing-workflow-runs/re-running-workflows-and-jobs#re-running-failed-jobs-in-a-workflow) and not just a single failed job.
427+
1. Wait for aforementioned approval
425428
1. Upload to PyPI.
426429
1. Create and push the Git tag (as extracted from `pyproject.toml`). We create the Git tag only
427430
after building the wheels and uploading to PyPI, since we can't delete or modify the tag ([#4468](https://github.com/astral-sh/ruff/issues/4468)).

0 commit comments

Comments
 (0)