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
52 changes: 29 additions & 23 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ jobs:
validate-release:
runs-on: ubuntu-latest
permissions:
contents: read
# Draft releases are visible only to tokens with push access. Keep that
# access isolated from the jobs that receive registry credentials.
contents: write
outputs:
version: ${{ steps.release.outputs.version }}
tag: ${{ steps.release.outputs.tag }}
source_commit: ${{ steps.bundle.outputs.source_commit }}
manifest_sha256: ${{ steps.bundle.outputs.manifest_sha256 }}
bundle_name: ${{ steps.bundle.outputs.bundle_name }}
is_draft: ${{ steps.release.outputs.is_draft }}
steps:
- name: Validate selected draft release
Expand Down Expand Up @@ -121,8 +124,23 @@ jobs:
--version "${RELEASE_VERSION}" \
--tag "${RELEASE_TAG}" \
--source-commit "${SOURCE_COMMIT}"
BUNDLE_NAME="release-promotion-${RELEASE_VERSION}-${SOURCE_COMMIT}-${GITHUB_RUN_ID}-attempt-${GITHUB_RUN_ATTEMPT}"
echo "source_commit=${SOURCE_COMMIT}" >> "$GITHUB_OUTPUT"
echo "manifest_sha256=${MANIFEST_SHA256}" >> "$GITHUB_OUTPUT"
echo "bundle_name=${BUNDLE_NAME}" >> "$GITHUB_OUTPUT"

- name: Preserve verified release bundle
uses: actions/upload-artifact@v4
with:
name: ${{ steps.bundle.outputs.bundle_name }}
path: |
${{ runner.temp }}/release-bundle/cisco_sccfm_devkit-${{ steps.release.outputs.version }}-py3-none-any.whl
${{ runner.temp }}/release-bundle/cisco_sccfm_devkit-${{ steps.release.outputs.version }}.tar.gz
${{ runner.temp }}/release-bundle/cisco-sccfm-${{ steps.release.outputs.version }}.tar.gz
${{ runner.temp }}/release-bundle/release-manifest.json
if-no-files-found: error
compression-level: 0
retention-days: 30

publish-to-pypi:
needs: validate-release
Expand All @@ -148,17 +166,11 @@ jobs:
python -m pip install --upgrade pip
python -m pip install twine==6.2.0

- name: Download exact draft release bundle
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ needs.validate-release.outputs.tag }}
BUNDLE_DIR: ${{ runner.temp }}/release-bundle
run: |
set -euo pipefail
mkdir -p "${BUNDLE_DIR}"
gh release download "${RELEASE_TAG}" \
--repo "${GITHUB_REPOSITORY}" \
--dir "${BUNDLE_DIR}"
- name: Download verified release bundle
uses: actions/download-artifact@v4
with:
name: ${{ needs.validate-release.outputs.bundle_name }}
path: ${{ runner.temp }}/release-bundle

- name: Verify bundle and inspect PyPI state
id: pypi
Expand Down Expand Up @@ -300,17 +312,11 @@ jobs:
python -m pip install --upgrade pip
python -m pip install "ansible-core>=2.20,<2.22"

- name: Download exact draft release bundle
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ needs.validate-release.outputs.tag }}
BUNDLE_DIR: ${{ runner.temp }}/release-bundle
run: |
set -euo pipefail
mkdir -p "${BUNDLE_DIR}"
gh release download "${RELEASE_TAG}" \
--repo "${GITHUB_REPOSITORY}" \
--dir "${BUNDLE_DIR}"
- name: Download verified release bundle
uses: actions/download-artifact@v4
with:
name: ${{ needs.validate-release.outputs.bundle_name }}
path: ${{ runner.temp }}/release-bundle

- name: Verify bundle and inspect Galaxy state
id: galaxy
Expand Down
8 changes: 8 additions & 0 deletions sccfm-ansible/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ Cisco SCCFM Collection Release Notes

.. contents:: Topics

v0.39.4
========

Bugfixes
--------

- Fixed manual release promotion so verified draft assets are handed off to read-only PyPI and Galaxy publication jobs.

v0.39.3
========

Expand Down
7 changes: 7 additions & 0 deletions sccfm-ansible/changelogs/changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
ancestor: null
# sccfm-release-retarget-seed: 0.39.0
releases:
0.39.4:
changes:
bugfixes:
- Fixed manual release promotion so verified draft assets are handed off to
read-only PyPI and Galaxy publication jobs.
fragments: []
release_date: '2026-08-19'
0.39.3:
changes:
bugfixes:
Expand Down
33 changes: 30 additions & 3 deletions tests/test_release_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,19 @@ def test_workflows_separate_automatic_preparation_from_manual_deployment() -> No
assert 'git rev-parse "refs/tags/${RELEASE_TAG}^{commit}"' in validation
assert "release-manifest-sha256:" in validation
assert "release_artifacts verify" in validation
assert "contents: write" in validation
assert "actions/upload-artifact@v4" in validation
assert "bundle_name: ${{ steps.bundle.outputs.bundle_name }}" in validation
assert "name: ${{ steps.bundle.outputs.bundle_name }}" in validation
assert "GITHUB_RUN_ID" in validation
assert "GITHUB_RUN_ATTEMPT" in validation
assert "if-no-files-found: error" in validation
assert "overwrite:" not in validation
assert "secrets.PYPI_API_TOKEN" not in validation
assert "secrets.GALAXY_API_KEY" not in validation
assert validation.index("release_artifacts verify") < validation.index(
"actions/upload-artifact@v4"
)

prohibited_deploy_commands = (
"poetry build",
Expand All @@ -260,17 +273,31 @@ def test_workflows_separate_automatic_preparation_from_manual_deployment() -> No
"git commit ",
"git tag ",
"git push ",
"actions/upload-artifact",
"actions/download-artifact",
"SCCFM_CI_DEPLOY_KEY",
)
for command in prohibited_deploy_commands:
assert command not in release

for job in (validation, pypi, galaxy, finalizer):
for job in (validation, finalizer):
assert 'gh release download "${RELEASE_TAG}"' in job
assert "release_artifacts verify" in job

for job in (pypi, galaxy):
assert "contents: read" in job
assert "contents: write" not in job
assert job.count("actions/download-artifact@v4") == 1
assert "name: ${{ needs.validate-release.outputs.bundle_name }}" in job
assert 'gh release download "${RELEASE_TAG}"' not in job
assert "GH_TOKEN:" not in job
assert "secrets.GITHUB_TOKEN" not in job
assert "release_artifacts verify" in job
assert "EXPECTED_MANIFEST_SHA256" in job
assert job.index("actions/download-artifact@v4") < job.index("release_artifacts verify")

assert "contents: write" in finalizer
assert release.count("actions/upload-artifact@v4") == 1
assert release.count("actions/download-artifact@v4") == 2

assert "pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33" in pypi
assert "pypa/gh-action-pypi-publish@release/v1" not in pypi
assert "secrets.PYPI_API_TOKEN" in pypi
Expand Down