---
-name: PyPI release
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: 2025 The Linux Foundation
+
+# Runs on a new pull request, performs build and runs tests
+name: 'Python Build/Test/Release'
# yamllint disable-line rule:truthy
-on: push
+on:
+ # Trigger on tag push events
+ push:
+ tags:
+ - '**'
permissions: {}
jobs:
- publish:
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
- runs-on: ubuntu-latest
+ tag-validate:
+ name: 'Validate Tag Push'
+ runs-on: 'ubuntu-latest'
+ permissions:
+ contents: read
+ timeout-minutes: 1
+ outputs:
+ tag: "${{ steps.tag-validate.outputs.tag }}"
+ steps:
+ # Harden the runner used by this workflow
+ # yamllint disable-line rule:line-length
+ - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
+ with:
+ egress-policy: 'audit'
+
+ - name: 'Verify Pushed Tag'
+ id: 'tag-validate'
+ # yamllint disable-line rule:line-length
+ uses: lfreleng-actions/tag-push-verify-action@80e2bdbbb9ee7b67557a31705892b75e75d2859e # v0.1.1
+ with:
+ versioning: 'semver'
+
+ - name: 'Reject Development Tags'
+ if: steps.tag-validate.outputs.dev_version == 'true'
+ shell: bash
+ run: |
+ # Reject Development Tags
+ echo "Development tag pushed; aborting release workflow 🛑"
+ echo "Development tag pushed; aborting release workflow 🛑" \
+ >> "$GITHUB_STEP_SUMMARY"
+ exit 1
+
+ python-build:
+ name: 'Python Build'
+ needs: 'tag-validate'
+ runs-on: 'ubuntu-latest'
+ outputs:
+ matrix_json: "${{ steps.python-build.outputs.matrix_json }}"
+ artefact_name: "${{ steps.python-build.outputs.artefact_name }}"
+ artefact_path: "${{ steps.python-build.outputs.artefact_path }}"
+ permissions:
+ contents: read
+ id-token: write # Needed for attestations
+ attestations: write # Needed for attestations
+ timeout-minutes: 12
env:
GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+ steps:
+ # Harden the runner used by this workflow
+ # yamllint disable-line rule:line-length
+ - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
+ with:
+ egress-policy: 'audit'
+
+ # yamllint disable-line rule:line-length
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+
+ - name: 'Build Python project'
+ id: 'python-build'
+ # yamllint disable-line rule:line-length
+ uses: lfreleng-actions/python-build-action@48381cece78a990a6ba93bd5924bcd40bf0d1a7d # v0.1.20
+ with:
+ sigstore_sign: true
+ attestations: true
+
+ python-tests:
+ name: 'Python Tests'
+ runs-on: 'ubuntu-latest'
+ needs: 'python-build'
+ # Matrix job
+ strategy:
+ fail-fast: false
+ matrix: "${{ fromJson(needs.python-build.outputs.matrix_json) }}"
permissions:
- contents: write
+ contents: read
+ timeout-minutes: 12
steps:
- - name: Checkout repository
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- - name: Configure Python
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- with:
- python-version: "3.8"
- - name: Build package distribution files
- run: >-
- pipx run tox -e clean,build
- - name: Configure TWINE
- run: |
- echo "TWINE_USERNAME=__token__" >> "$GITHUB_ENV"
- echo "TWINE_PASSWORD=${{ secrets.PYPI_API_TOKEN }}" >> "$GITHUB_ENV"
- - name: Publish to PyPI
- run: >-
- pipx run tox -e publish -- --repository pypi
- - name: Get tag
- id: tag
- uses: devops-actions/action-get-tag@v1.0.2
- - name: Convert tag
- run: |
- VER=$(echo "${{ steps.tag.outputs.tag }}" | tr . -)
- echo "ANCHOR=${VER}" >> "$GITHUB_ENV"
- - name: Generate reno report
- run: |
- # fetch last 30 changes
- git fetch --depth=30
- pipx run tox -e reno -- report \
- --version "${{ steps.tag.outputs.tag }}" \
- 2>/dev/null > reno-notes.md || true
- if grep -q 'reno: FAIL' reno-notes.md
- then
- touch modified-reno-notes.md
- else
- sed '/^\.pkg.*$/d' reno-notes.md |
- sed '/^reno:.*$/d' |
- sed '/^\.\. .*$/d' |
- sed '$d' |
- sed '$d' |
- sed '1d' > modified-reno-notes.md
- fi
- - name: Create Release Notes
- run: |
- gh release create ${{ steps.tag.outputs.tag }} --generate-notes \
- -F modified-reno-notes.md
+ # Harden the runner used by this workflow
+ # yamllint disable-line rule:line-length
+ - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
+ with:
+ egress-policy: 'audit'
+
+ # yamllint disable-line rule:line-length
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+
+ - name: 'Test Python project [PYTEST]'
+ # yamllint disable-line rule:line-length
+ uses: lfreleng-actions/python-test-action@bdde9e4e6221e858359f9036bd4f41ab3b1af90e # v0.1.11
+ with:
+ python_version: "${{ matrix.python-version }}"
+
+ python-audit:
+ name: 'Python Audit'
+ runs-on: 'ubuntu-latest'
+ needs: 'python-build'
+ # Matrix job
+ strategy:
+ fail-fast: false
+ matrix: "${{ fromJson(needs.python-build.outputs.matrix_json) }}"
+ permissions:
+ contents: read
+ timeout-minutes: 10
+ steps:
+ # Harden the runner used by this workflow
+ # yamllint disable-line rule:line-length
+ - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
+ with:
+ egress-policy: 'audit'
+
+ # yamllint disable-line rule:line-length
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+
+ - name: 'Audit Python project'
+ # yamllint disable-line rule:line-length
+ uses: lfreleng-actions/python-audit-action@bab5316468c108870eb759ef0de622bae9239aad # v0.2.2
+ with:
+ python_version: "${{ matrix.python-version }}"
+
+ test-pypi:
+ name: 'Test PyPI Publishing'
+ runs-on: 'ubuntu-latest'
+ needs:
+ - 'tag-validate'
+ - 'python-tests'
+ - 'python-audit'
+ environment:
+ name: 'development'
+ permissions:
+ contents: read
+ id-token: write # IMPORTANT: mandatory for trusted publishing
+ timeout-minutes: 5
+ steps:
+ # Harden the runner used by this workflow
+ # yamllint disable-line rule:line-length
+ - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
+ with:
+ egress-policy: 'audit'
+
+ # yamllint disable-line rule:line-length
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+
+ - name: 'Test PyPI publishing'
+ # yamllint disable-line rule:line-length
+ uses: lfreleng-actions/pypi-publish-action@81a056957ed050f8305760055b1fd8103a916989 # v0.1.1
+ with:
+ environment: 'development'
+ tag: "${{ needs.tag-validate.outputs.tag }}"
+
+ pypi:
+ name: 'Release PyPI Package'
+ runs-on: 'ubuntu-latest'
+ needs:
+ - 'tag-validate'
+ - 'test-pypi'
+ environment:
+ name: 'production'
+ permissions:
+ contents: read
+ id-token: write # IMPORTANT: mandatory for trusted publishing
+ timeout-minutes: 5
+ steps:
+ # Harden the runner used by this workflow
+ # yamllint disable-line rule:line-length
+ - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
+ with:
+ egress-policy: 'audit'
+
+ # yamllint disable-line rule:line-length
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+
+ - name: 'PyPI release'
+ # yamllint disable-line rule:line-length
+ uses: lfreleng-actions/pypi-publish-action@81a056957ed050f8305760055b1fd8103a916989 # v0.1.1
+ with:
+ environment: 'production'
+ attestations: true
+ tag: "${{ needs.tag-validate.outputs.tag }}"
+
+ promote-release:
+ name: 'Promote Draft Release'
+ # yamllint disable-line rule:line-length
+ if: startsWith(github.ref, 'refs/tags/')
+ needs:
+ - 'tag-validate'
+ - 'pypi'
+ runs-on: 'ubuntu-latest'
+ permissions:
+ contents: write # IMPORTANT: needed to edit a draft release and promote it
+ timeout-minutes: 2
+ outputs:
+ release_url: "${{ steps.promote-release.outputs.release_url }}"
+ steps:
+ # Harden the runner used by this workflow
+ # yamllint disable-line rule:line-length
+ - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
+ with:
+ egress-policy: 'audit'
+
+ # yamllint disable-line rule:line-length
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+
+ - name: 'Promote draft release'
+ id: 'promote-release'
+ # yamllint disable-line rule:line-length
+ uses: lfreleng-actions/draft-release-promote-action@d7e7df12e32fa26b28dbc2f18a12766482785399 # v0.1.2
+ with:
+ token: "${{ secrets.GITHUB_TOKEN }}"
+ tag: "${{ needs.tag-validate.outputs.tag }}"
+ latest: true
+
+ # Need to attach build artefacts to the release
+ # This step could potentially be moved
+ # (May be better to when/where the release is still in draft state)
+ attach-artefacts:
+ name: 'Attach Artefacts to Release'
+ runs-on: 'ubuntu-latest'
+ needs:
+ - 'tag-validate'
+ - 'python-build'
+ - 'promote-release'
+ permissions:
+ contents: write # IMPORTANT: needed to edit release, attach artefacts
+ timeout-minutes: 5
+ steps:
+ # Harden the runner used by this workflow
+ # yamllint disable-line rule:line-length
+ - uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
+ with:
+ egress-policy: 'audit'
+
+ # Note: no need for a checkout step in this job
+
+ - name: '⬇ Download build artefacts'
+ # yamllint disable-line rule:line-length
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
+ with:
+ name: "${{ needs.python-build.outputs.artefact_name }}"
+ path: "${{ needs.python-build.outputs.artefact_path }}"
+
+ - name: 'Attach build artefacts to release'
+ # yamllint disable-line rule:line-length
+ uses: alexellis/upload-assets@13926a61cdb2cb35f5fdef1c06b8b591523236d3 # 0.4.1
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: "${{ github.token }}"
+ with:
+ asset_paths: '["${{ needs.python-build.outputs.artefact_path }}/**"]'