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
102 changes: 100 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ on:
required: false
type: boolean
default: false
publish-existing-tag:
description: Publish an existing GitHub release tag to PyPI without creating a new release.
required: false
type: string
default: ""

permissions:
contents: read
Expand Down Expand Up @@ -78,7 +83,7 @@ jobs:

release-please:
name: release please
if: ${{ !inputs.pypi-environment-smoke }}
if: ${{ github.event_name != 'workflow_dispatch' || (!inputs.pypi-environment-smoke && inputs.publish-existing-tag == '') }}
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
Expand Down Expand Up @@ -276,7 +281,100 @@ jobs:
shell: bash
run: |
mkdir -p dist
gh release download "$RELEASE_TAG" --dir dist --pattern "*.whl" --pattern "*.tar.gz"
gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --dir dist --pattern "*.whl" --pattern "*.tar.gz"
python - <<'PY'
from pathlib import Path

artifacts = sorted(path.name for path in Path("dist").iterdir())
if not any(name.endswith(".whl") for name in artifacts):
raise SystemExit(f"release {artifacts=} does not include a wheel")
if not any(name.endswith(".tar.gz") for name in artifacts):
raise SystemExit(f"release {artifacts=} does not include a source distribution")
PY

- name: Publish distributions to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1

publish-existing-release-gate:
name: existing release gate
if: ${{ github.event_name == 'workflow_dispatch' && !inputs.pypi-environment-smoke && inputs.publish-existing-tag != '' }}
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: pypi
permissions:
contents: read

env:
RELEASE_TAG: ${{ inputs.publish-existing-tag }}
CODEBASE_GRAPH_CONFIRM_TRUSTED_PUBLISHER: ${{ vars.CODEBASE_GRAPH_CONFIRM_TRUSTED_PUBLISHER }}
CODEBASE_GRAPH_CONFIRM_PYPI_ENVIRONMENT: ${{ vars.CODEBASE_GRAPH_CONFIRM_PYPI_ENVIRONMENT }}
CODEBASE_GRAPH_CONFIRM_HOSTED_CI_GREEN: ${{ vars.CODEBASE_GRAPH_CONFIRM_HOSTED_CI_GREEN }}
CODEBASE_GRAPH_CONFIRM_PRIVATE_VULNERABILITY_REPORTING: ${{ vars.CODEBASE_GRAPH_CONFIRM_PRIVATE_VULNERABILITY_REPORTING }}
CODEBASE_GRAPH_REQUIRE_CONDA: ${{ vars.CODEBASE_GRAPH_REQUIRE_CONDA }}

steps:
- name: Validate release tag
shell: bash
run: |
python - <<'PY'
import os
import re

tag = os.environ["RELEASE_TAG"]
if re.fullmatch(r"v\d+\.\d+\.\d+", tag) is None:
raise SystemExit(f"release tag must match vX.Y.Z, got {tag!r}")
PY

- name: Check out release tag
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ env.RELEASE_TAG }}
fetch-depth: 0

- name: Run production release gate
shell: bash
run: |
args=(--production)
if [[ "${CODEBASE_GRAPH_CONFIRM_TRUSTED_PUBLISHER}" == "true" ]]; then
args+=(--confirm trusted-publisher)
fi
if [[ "${CODEBASE_GRAPH_CONFIRM_PYPI_ENVIRONMENT}" == "true" ]]; then
args+=(--confirm pypi-environment)
fi
if [[ "${CODEBASE_GRAPH_CONFIRM_HOSTED_CI_GREEN}" == "true" ]]; then
args+=(--confirm hosted-ci-green)
fi
if [[ "${CODEBASE_GRAPH_CONFIRM_PRIVATE_VULNERABILITY_REPORTING}" == "true" ]]; then
args+=(--confirm private-vulnerability-reporting)
fi
if [[ "${CODEBASE_GRAPH_REQUIRE_CONDA}" == "true" ]]; then
args+=(--require-conda)
fi
python scripts/check_release_gate.py "${args[@]}"

publish-existing-pypi:
name: publish existing release to PyPI
needs: publish-existing-release-gate
if: ${{ github.event_name == 'workflow_dispatch' && !inputs.pypi-environment-smoke && inputs.publish-existing-tag != '' }}
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: pypi
url: https://pypi.org/p/cbasegraph
permissions:
contents: read
id-token: write
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.publish-existing-tag }}

steps:
- name: Download distributions from GitHub release
shell: bash
run: |
mkdir -p dist
gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --dir dist --pattern "*.whl" --pattern "*.tar.gz"
python - <<'PY'
from pathlib import Path

Expand Down
18 changes: 16 additions & 2 deletions tests/test_release_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,21 @@ def test_release_workflow_can_smoke_test_pypi_environment_without_publishing() -
def test_release_please_is_skipped_during_pypi_environment_smoke() -> None:
text = Path(".github/workflows/release.yml").read_text(encoding="utf-8")

assert "release-please:\n name: release please\n if: ${{ !inputs.pypi-environment-smoke }}" in text
assert "release-please:\n name: release please" in text
assert "!inputs.pypi-environment-smoke" in text
assert "inputs.publish-existing-tag == ''" in text


def test_release_workflow_can_publish_existing_release_tag() -> None:
text = Path(".github/workflows/release.yml").read_text(encoding="utf-8")

assert "publish-existing-tag:" in text
assert "existing release gate" in text
assert "publish existing release to PyPI" in text
assert "inputs.publish-existing-tag != ''" in text
assert 'RELEASE_TAG: ${{ inputs.publish-existing-tag }}' in text
assert 'gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --dir dist' in text
assert "release tag must match vX.Y.Z" in text


def test_release_please_uses_strict_semver_tags() -> None:
Expand Down Expand Up @@ -175,7 +189,7 @@ def test_workflows_avoid_node20_artifact_actions() -> None:
def test_release_workflow_downloads_distributions_from_github_release() -> None:
text = Path(".github/workflows/release.yml").read_text(encoding="utf-8")

assert 'gh release download "$RELEASE_TAG" --dir dist' in text
assert 'gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --dir dist' in text
assert "release {artifacts=} does not include a wheel" in text
assert "release {artifacts=} does not include a source distribution" in text

Expand Down