ci: harden release workflow with version-exists gate, success() guard, and Sigstore attestations#411
Open
erichare wants to merge 8 commits into
Open
ci: harden release workflow with version-exists gate, success() guard, and Sigstore attestations#411erichare wants to merge 8 commits into
erichare wants to merge 8 commits into
Conversation
…or to uv_setup The explicit `if: needs.build.outputs.version-exists == 'false'` added to the release.yml downstream jobs replaced GitHub Actions' implicit success() check on `needs`, so a failed test-pypi-publish no longer blocked pre-release-checks, pre-release-unit-lowest-python, publish, or mark-release. A transient TestPyPI failure could therefore lead to a production PyPI publish that skipped the test-publish gate. Re-add success() to every gated job (`if: success() && needs.build.outputs. version-exists == 'false'`). success() automatically requires all of each job's `needs` to have succeeded, restoring the prior safety semantics while keeping the skip-when-already-published behavior, and stays correct if dependencies are added later. Also migrate codecov_aggregator.yml off the legacy setup-python + pipx + make venv pattern onto the shared ./.github/actions/uv_setup composite action + `uv sync --dev`, matching lint/local/main/unit.
…ow' into feat-release-workflow
Revert the uv_setup composite-action migration of the lint, local, main, unit, and codecov_aggregator workflows from this branch; that mechanical migration now lives in its own PR (branch ci-uv-setup-migration). This PR is narrowed to the release-workflow hardening only: - release.yml: check-pypi version-exists gate + success() guard + attestations - _test_release.yml: attestations
3 tasks
Coverage reportfor commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the PyPI release pipeline (
release.yml+ the reusable_test_release.yml) safer and more robust. Three independent hardening changes:success()guard — restore GitHub's implicit needs-success gating that the newif:conditions would otherwise silently drop.attestations: truefor both prod and Test PyPI publishes.This PR contains only the release-robustness portion of #411. The unrelated CI-tooling migration (shared
uv_setupcomposite action +uv sync --devacross lint/local/main/unit/codecov workflows) is split into a separate PR.Changes
1. Version-exists gate (
release.yml)buildjob gains a newcheck-pypistep (shell: python) that querieshttps://pypi.org/pypi/{pkg}/{version}/json:version-exists=trueversion-exists=false(proceed)version-exists.test-pypi-publish,pre-release-checks,pre-release-unit-lowest-python,publish,mark-release) now carriesif: success() && needs.build.outputs.version-exists == 'false', so re-running the workflow against an already-released version is a clean no-op instead of a failed double-publish attempt.publishjob noting that legacy PyPI API tokens for astrapy should be revoked once trusted publishing is verified against real releases.2.
success()guard (release.yml) — why it mattersAdding an explicit
if:to a job replaces GitHub's default condition (success()), which otherwise implicitly requires every entry inneeds:to have succeeded. Without re-addingsuccess(), anif: needs.build.outputs.version-exists == 'false'would evaluate to true even when an upstream needed job failed, because the bare expression only checks the version output and no longer waits on needs-success.Concretely:
publish(production PyPI) liststest-pypi-publishin itsneeds:. Iftest-pypi-publishfails, the implicit success gate is what stops production publish from running. Replacing that gate with a version-only condition would let a failed Test PyPI publish slip through and still push to production PyPI. Prependingsuccess() &&restores the AND with needs-success, so a failedtest-pypi-publish(or any upstream job) blocks production publish while still honoring the version-exists short-circuit.3. Re-enable Sigstore attestations
release.ymlpublishstep:attestations: false→attestations: true._test_release.ymlpublishstep:attestations: false→attestations: true(TODO comment removed).id-token: writepermission required for attestations is already present on both publish jobs (no permission changes needed). Generates signed provenance/attestations viapypa/gh-action-pypi-publishfor both prod and Test PyPI uploads.Verification
python3 -c "import yaml; yaml.safe_load(...)".SC2001shellcheck style suggestion atrelease.yml:98inside the unchangedpre-release-checksjob — it is present onmainand is not introduced by this PR. The newcheck-pypiPython step and all four addedif: success() && ...conditions lint clean.id-token: writeis present on both publish jobs (attestations prerequisite) and that every short-circuited downstream job now ANDssuccess()with the version check.Notes / follow-ups