ci(release): fix publish job conditional — use vars.PYPI_PUBLISH inst… #4
Workflow file for this run
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
| name: Release (signed + provenance) | |
| # Builds the Python distribution on a version tag, generates a SHA-256 manifest, | |
| # produces a keyless SLSA build-provenance attestation (Sigstore via GitHub OIDC — | |
| # no maintainer-managed signing keys), and publishes everything to a GitHub | |
| # Release. This satisfies OpenSSF `signed_releases`: consumers can cryptographically | |
| # verify an artifact came from this repo's CI with: | |
| # | |
| # gh attestation verify <artifact> --repo Gavin-Borges/SESTRAV | |
| # | |
| # See docs/releasing.md and SECURITY.md "Release Integrity & Verification". | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: read | |
| jobs: | |
| release: | |
| name: Build, attest, and publish signed release | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # create the GitHub Release and upload assets | |
| id-token: write # OIDC token for keyless Sigstore signing | |
| attestations: write # store the build-provenance attestation | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.13" | |
| - name: Verify tag matches package version | |
| # Fail fast (before building) if the pushed tag does not match | |
| # pyproject.toml [project].version, so release artifacts can never be | |
| # mislabelled. Bump pyproject before tagging (see docs/releasing.md). | |
| run: | | |
| PKG=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])") | |
| TAG="${GITHUB_REF_NAME#v}" | |
| if [ "$PKG" != "$TAG" ]; then | |
| echo "::error::Tag '${GITHUB_REF_NAME}' (version '${TAG}') does not match pyproject.toml version '${PKG}'. Bump pyproject.toml before tagging." | |
| exit 1 | |
| fi | |
| echo "Version check OK: pyproject=${PKG} tag=${GITHUB_REF_NAME}" | |
| - name: Build sdist and wheel | |
| # setuptools build backend (see pyproject.toml [build-system]); building the | |
| # source/wheel distribution does not import the heavy ML runtime deps. | |
| # Hash-pinned (OpenSSF Scorecard Pinned-Dependencies). Regenerate with: | |
| # pip-compile --generate-hashes environments/requirements-ci-build.in \ | |
| # --output-file environments/requirements-ci-build.txt --no-emit-index-url | |
| run: | | |
| pip install --require-hashes -r environments/requirements-ci-build.txt | |
| python -m build --sdist --wheel --outdir dist/ | |
| - name: Generate SHA-256 checksum manifest | |
| run: | | |
| cd dist | |
| sha256sum * > SHA256SUMS.txt | |
| cat SHA256SUMS.txt | |
| - name: Attest build provenance (keyless SLSA) | |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 | |
| with: | |
| subject-path: "dist/*.tar.gz,dist/*.whl" | |
| - name: Publish GitHub Release with artifacts + checksums | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh release create "${GITHUB_REF_NAME}" \ | |
| dist/*.tar.gz dist/*.whl dist/SHA256SUMS.txt \ | |
| --title "SESTRAV ${GITHUB_REF_NAME}" \ | |
| --generate-notes \ | |
| --verify-tag | |
| - name: Upload dist artifacts for publish job | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: dist-${{ github.ref_name }} | |
| path: dist/ | |
| retention-days: 1 | |
| publish: | |
| name: Publish to PyPI | |
| needs: release | |
| runs-on: ubuntu-latest | |
| # Gate: set repository variable PYPI_PUBLISH=true (Settings → Variables → Actions) | |
| # once PYPI_API_TOKEN is configured as a secret. Keeping the toggle in a | |
| # non-secret variable makes the condition evaluable at job-scheduling time | |
| # (secrets context is unavailable in job-level `if` expressions). | |
| if: ${{ vars.PYPI_PUBLISH == 'true' }} | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/p/sestrav | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| - name: Download dist artifacts | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| name: dist-${{ github.ref_name }} | |
| path: dist/ | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.13" | |
| - name: Install twine | |
| run: pip install --require-hashes -r environments/requirements-ci-twine.txt | |
| - name: Publish to PyPI | |
| env: | |
| TWINE_USERNAME: __token__ | |
| TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} | |
| run: twine upload dist/*.tar.gz dist/*.whl | |
| - name: Smoke-test installation from PyPI | |
| # Wait briefly for PyPI index propagation then verify pip can resolve the new version. | |
| run: | | |
| pip install --no-deps "sestrav==${GITHUB_REF_NAME#v}" --index-url https://pypi.org/simple/ | |
| python -c "import sestrav; print('sestrav import OK')" || python -c "from src import features; print('import OK')" |