diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 3c1caddee..5bf71ee9f 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/ty) 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 c0ff7f31d..5eb7195e7 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: ${{ github.event_name == 'workflow_dispatch' && 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 }} @@ -179,6 +196,8 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} runs-on: "depot-ubuntu-latest-4" + environment: + name: release outputs: val: ${{ steps.host.outputs.manifest }} steps: @@ -218,6 +237,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: @@ -233,12 +253,15 @@ jobs: needs: - plan - host + - release-gate - custom-publish-pypi # 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') }} + if: ${{ always() && needs.host.result == 'success' && needs.release-gate.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') }} runs-on: "depot-ubuntu-latest-4" + environment: + name: release permissions: "attestations": "write" "contents": "write" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 58fae8bed..f7dd437f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -168,6 +168,10 @@ Preparation for the release is automated. When running the release workflow for pre-release versions, use the Cargo version format (not PEP 440), e.g. `0.0.1-alpha.5` (not `0.0.1a5`). For stable releases, these formats are identical. +1. Request a deployment approval from another team member. + + The release workflow will pause at the `release-gate` job until this approval is granted. + The release will automatically be created on GitHub after the distributions are published. 1. Run `uv run --no-project ./scripts/update_schemastore.py` diff --git a/ruff b/ruff index 39c3636bc..34998be22 160000 --- a/ruff +++ b/ruff @@ -1 +1 @@ -Subproject commit 39c3636bc9c37db2652a0123848949a459e02988 +Subproject commit 34998be22ec3a77d398bbd55234ef8740f768329