diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 543b510153c6e4..c79cb1e1be926d 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -31,7 +31,7 @@ jobs: name: Build Docker image (ghcr.io/astral-sh/ruff) for ${{ matrix.platform }} runs-on: ubuntu-latest environment: - name: release + name: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit && 'release' || '' }} strategy: fail-fast: false matrix: @@ -47,6 +47,7 @@ jobs: - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }} with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 878a1ecea9efaa..1e44415f1b7281 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,6 +53,22 @@ env: CARGO_DIST_CHECKSUM: "cd355dab0b4c02fb59038fef87655550021d07f45f1d82f947a34ef98560abb8" jobs: + release-gate: + # N.B. This name should not change, it is used for downstream checks. + name: release-gate + if: ${{ inputs.tag != 'dry-run' }} + runs-on: ubuntu-latest + # This environment requires a 2-factor approval, i.e., the workflow must be approved by another + # team member. GitHub fires approval events on every job that deploys to an environment, so we + # have a dedicated environment for this purpose instead of using the `release` environment. + # We use a GitHub App with a deployment protection rule webhook to ensure that the `release` + # environment is only approved when the `release-gate` job succeeds. + environment: + name: release-gate + deployment: false + steps: + - run: echo "Release approved" + # Run 'dist plan' (or host) to determine what tasks we need to do plan: runs-on: "depot-ubuntu-latest-4" @@ -109,7 +125,8 @@ jobs: custom-build-docker: needs: - plan - if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }} + - release-gate + 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') }} uses: ./.github/workflows/build-docker.yml with: plan: ${{ needs.plan.outputs.val }} @@ -229,6 +246,7 @@ jobs: needs: - plan - host + - release-gate if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} uses: ./.github/workflows/publish-pypi.yml with: @@ -243,6 +261,7 @@ jobs: needs: - plan - host + - release-gate if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} uses: ./.github/workflows/publish-wasm.yml with: @@ -259,12 +278,13 @@ jobs: needs: - plan - host + - release-gate - custom-publish-pypi - custom-publish-wasm # use "always() && ..." to allow us to wait for all publish jobs while # still allowing individual publish jobs to skip themselves (for prereleases). # "host" however must run to completion, no skipping allowed! - 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') }} + 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') }} runs-on: "depot-ubuntu-latest-4" permissions: "attestations": "write" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5bdf9c92d64e98..507de7119d69dd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -416,12 +416,15 @@ Commit each step of this process separately for easier review. - The new version number (without starting `v`) +1. Request a deployment approval from another team member + 1. The release workflow will do the following: 1. Build all the assets. If this fails (even though we tested in step 4), we haven't tagged or uploaded anything, you can restart after pushing a fix. If you just need to rerun the build, make sure you're [re-running all the failed 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. + 1. Wait for aforementioned approval 1. Upload to PyPI. 1. Create and push the Git tag (as extracted from `pyproject.toml`). We create the Git tag only 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)).