Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 57 additions & 33 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,39 +32,55 @@ concurrency:
cancel-in-progress: false

# Every job below with an effect outside this run declares a deployment
# ``environment:``, which is what holds it at "Waiting" until a required reviewer
# approves. Four jobs qualify, and they get four environments rather than one
# shared ``release``:
# ``environment:``, but as of #887 only one of them -- ``github-release`` --
# carries a required reviewer. Four jobs still get four environments rather than
# one shared ``release``, for credential and blast-radius separation, not for
# approval count:
#
# github -> github-release the public GitHub Release, and the ``v*`` tag that
# ``action-gh-release``'s ``tag_name`` creates with it
# tag -> conda-tag a commit pushed to ``main``, and the ``conda-*`` tag
# pypi -> pypi an upload to PyPI, and to TestPyPI
# conda -> anaconda an upload to anaconda.org/jarryshaw
#
# One environment per distinct credential and blast radius, because approval is
# granted to an *environment* and not to a job: a single ``release`` approved once
# would release PyPI and Anaconda together, and those are different credentials
# (OIDC trusted publishing versus ``ANACONDA_TOKEN``) reaching different indexes.
# PyPI is the one that has to be answerable alone -- a version number it has
# accepted cannot be reused, and ``skip-existing: true`` below means the re-upload
# then *succeeds* having published nothing, so an unintended publish consumes the
# number the intended release wanted. ``github`` and ``tag`` are split on the same
# principle: ``github`` spends the run's own ``GITHUB_TOKEN``, whereas ``tag``
# spends a long-lived PAT and a deploy key to write to the default branch.
# Approving the GitHub Release is now the release's *only* approval.
# ``tag`` and ``pypi`` both carry ``needs: [ github, ... ]``, and ``conda`` needs
# ``tag`` and ``github`` too, so nothing downstream of ``github`` can start before
# that approval lands, and rejecting it leaves nothing tagged and nothing
# published. See :file:`docs/source/releasing.rst` for the full pipeline and the
# reasoning behind one approval being enough.
#
# The cost is two approval waves, not four: ``github`` and ``tag`` need only
# ``version_check`` so they arrive together, and ``pypi`` and ``conda`` then arrive
# together too, each wave answerable in one visit to "Review pending deployments".
# The other three environments keep their names -- ``pypi``'s matters because
# PyPI trusted publishing matches its OIDC claim against it -- and keep their
# secret scoping, they just no longer hold a required reviewer of their own. Do
# not add one back to any of them without re-examining the ``needs:`` chain: a
# reviewer on ``conda-tag`` with its old ``needs: [ version_check ]`` is exactly
# the bug #887 fixed, because it let the commit-to-``main`` and ``conda-*`` tag
# run *before* the GitHub Release was ever approved.
# ``tests/project/test_release_gates.py``'s ``TestGatedJobsDependOnGithub``
# asserts that every gated job's ``needs:`` closure includes ``github``, which
# is the checkable half of this paragraph -- reverting the line above to
# ``needs: [ version_check ]`` fails only that one test.
#
# One environment per distinct credential and blast radius regardless of who
# gates it: a single ``release`` approved once would release PyPI and Anaconda
# together, and those are different credentials (OIDC trusted publishing versus
# ``ANACONDA_TOKEN``) reaching different indexes. PyPI in particular cannot afford
# an unintended publish -- a version number it has accepted cannot be reused, and
# ``skip-existing: true`` below means a re-upload then *succeeds* having published
# nothing, so the number the intended release wanted is gone regardless. ``github``
# and ``tag`` were split on the same principle: ``github`` spends the run's own
# ``GITHUB_TOKEN``, whereas ``tag`` spends a long-lived PAT and a deploy key to
# write to the default branch.
#
# Two things about this that are not self-enforcing, and #641 is the first of them:
#
# * An ``environment:`` naming an environment that does not exist is created
# implicitly, with no protection rules, and the job proceeds unapproved. So
# these four lines are inert until each environment exists under
# Settings -> Environments with a required reviewer on it. This repository
# already ships the worked example of that failure: ``github-pages`` has
# existed since 2021 with ``"protection_rules": []``.
# the ``github-release`` line above is inert until that environment exists
# under Settings -> Environments with a required reviewer on it. This
# repository already ships the worked example of that failure:
# ``github-pages`` has existed since 2021 with ``"protection_rules": []``.
# * Leave each one's "Deployment branches and tags" as "No restriction". This
# workflow's ``push`` trigger is ``tags: v*``, and a branch policy that does
# not list that tag pattern does not pause the job for approval -- it fails it
Expand Down Expand Up @@ -132,7 +148,8 @@ jobs:
name: GitHub Release
runs-on: ubuntu-latest
# Gated: publishes the Release, and creates the ``v*`` tag that every job
# below then checks out. See the note above ``jobs:``.
# below then checks out. Since #887 this is the release's only approval --
# see the note above ``jobs:``.
environment: github-release
permissions:
contents: write
Expand Down Expand Up @@ -222,13 +239,16 @@ jobs:
# two check rows reading `Conda Tag` with nothing to tell them apart.
name: Conda Tag (release)
runs-on: ubuntu-latest
# Gated: pushes a commit to ``main`` and the ``conda-*`` tag. ``permissions: {}``
# Pushes a commit to ``main`` and the ``conda-*`` tag. ``permissions: {}``
# does not constrain it -- the write goes through the deploy key and PAT below,
# not the run's token -- which is why it needs an approval of its own. See the
# note above ``jobs:``.
# not the run's token. It carried its own required reviewer until #887; now it
# depends on ``github`` rather than on ``version_check`` alone, precisely so
# the single ``github-release`` approval still gates it -- without that
# ``needs:``, this job would start the moment ``version_check`` finished,
# ahead of any approval. See the note above ``jobs:``.
environment: conda-tag
permissions: {}
needs: [ version_check ]
needs: [ github, version_check ]
if: ${{ startsWith(github.ref_name, 'v') || needs.version_check.outputs.PCAPKIT_TAG_EXISTS == 'false' }}
steps:
- name: Load maintenance deploy key
Expand Down Expand Up @@ -283,11 +303,14 @@ jobs:
pypi:
name: PyPI distribution for Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
# Gated, and named ``pypi`` rather than the upstream snippet's ``release``: see
# the note above ``jobs:`` for why each target gets its own. #641 was that this
# line stayed commented while the rest of the same snippet did not -- the
# Named ``pypi`` rather than the upstream snippet's ``release``: see the note
# above ``jobs:`` for why each target gets its own. #641 was that this line
# stayed commented while the rest of the same snippet did not -- the
# ``id-token: write`` that *enables* trusted publishing was re-added below, so
# the block read as disabled as a unit when only its gate still was.
# the block read as disabled as a unit when only its gate still was. #887
# removed this environment's own required reviewer; it is gated only
# transitively now, through ``needs: [ github, version_check ]`` below, and the
# name still matters because PyPI's trusted-publishing claim match is keyed on it.
environment: pypi
permissions:
contents: write
Expand Down Expand Up @@ -372,10 +395,11 @@ jobs:
conda:
name: Conda deployment (release) on ${{ matrix.os }} with Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
# Gated: uploads to anaconda.org/jarryshaw with ``ANACONDA_TOKEN``. This job
# never had an ``environment:`` at all, not even a commented one, so it was the
# half of #641 that was not written down as a thing to turn on. See the note
# above ``jobs:``.
# Uploads to anaconda.org/jarryshaw with ``ANACONDA_TOKEN``. This job never had
# an ``environment:`` at all, not even a commented one, so it was the half of
# #641 that was not written down as a thing to turn on. #887 later removed
# this environment's own required reviewer; gating is transitive now, through
# ``needs: [ tag, github, version_check ]`` below. See the note above ``jobs:``.
environment: anaconda
permissions:
contents: write
Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ construction and analysis library.
demo
testing
conventions
releasing
pep
changelog

Expand Down
Loading
Loading