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
105 changes: 96 additions & 9 deletions .github/workflows/python-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ on:
paths:
- "CHANGELOG.md"

permissions:
contents: write
permissions: {}

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write # create the GitHub release and attach assets
outputs:
version: ${{ steps.release.outputs.version }}
released: ${{ steps.published.outputs.released }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand Down Expand Up @@ -50,15 +54,18 @@ jobs:

- name: Check version changed
id: version-changed
env:
BEFORE_SHA: ${{ github.event.before }}
VERSION: ${{ steps.release.outputs.version }}
run: |
previous_version=$(git show "${{ github.event.before }}:pyproject.toml" | python -c '
previous_version=$(git show "${BEFORE_SHA}:pyproject.toml" | python -c '
import sys
import tomllib

print(tomllib.loads(sys.stdin.read())["project"]["version"])
'
)
if [ "$previous_version" = "${{ steps.release.outputs.version }}" ]; then
if [ "$previous_version" = "${VERSION}" ]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
Expand All @@ -69,8 +76,9 @@ jobs:
if: steps.version-changed.outputs.changed == 'true'
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.release.outputs.version }}
run: |
if gh release view "${{ steps.release.outputs.version }}" >/dev/null 2>&1; then
if gh release view "${VERSION}" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
Expand All @@ -80,6 +88,35 @@ jobs:
if: steps.version-changed.outputs.changed == 'true' && steps.existing-release.outputs.exists == 'false'
run: uv build

# Package plus locked runtime dependencies only, so the SBOM matches what
# users install from PyPI rather than the dev/test toolchain.
- name: Install runtime environment
if: steps.version-changed.outputs.changed == 'true' && steps.existing-release.outputs.exists == 'false'
run: uv sync --frozen --no-default-groups --no-editable

- uses: anchore/sbom-action/download-syft@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3
if: steps.version-changed.outputs.changed == 'true' && steps.existing-release.outputs.exists == 'false'
id: syft

# Scan site-packages only; .venv/bin links to the interpreter outside the env.
# SPDX for the release page, CycloneDX 1.6 for DependencyTrack.
- name: Generate SBOM
if: steps.version-changed.outputs.changed == 'true' && steps.existing-release.outputs.exists == 'false'
env:
SYFT: ${{ steps.syft.outputs.cmd }}
SYFT_FILE_METADATA_SELECTION: none
VERSION: ${{ steps.release.outputs.version }}
run: |
set -euo pipefail
"$SYFT" dir:.venv/lib \
--source-name django-postcode-lookup \
--source-version "${VERSION}" \
-o spdx-json=django-postcode-lookup.spdx.json \
-o cyclonedx-json@1.6=django-postcode-lookup.raw.cdx.json
# The package's own entry points at the local checkout (file://); drop it.
jq '.components |= map(if .externalReferences then .externalReferences |= map(select(.url | startswith("file://") | not)) else . end)' \
django-postcode-lookup.raw.cdx.json > django-postcode-lookup.cdx.json

- name: Publish package
if: steps.version-changed.outputs.changed == 'true' && steps.existing-release.outputs.exists == 'false'
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
Expand All @@ -91,8 +128,58 @@ jobs:
if: steps.version-changed.outputs.changed == 'true' && steps.existing-release.outputs.exists == 'false'
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.release.outputs.version }}
NOTES: ${{ steps.release.outputs.notes }}
run: |
gh release create "${VERSION}" \
--title "${VERSION}" \
--notes-file "${NOTES}" \
dist/* django-postcode-lookup.spdx.json django-postcode-lookup.cdx.json

- name: Mark release published
id: published
if: steps.version-changed.outputs.changed == 'true' && steps.existing-release.outputs.exists == 'false'
run: echo "released=true" >> "$GITHUB_OUTPUT"

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: steps.published.outputs.released == 'true'
with:
name: sbom-cyclonedx
path: django-postcode-lookup.cdx.json
retention-days: 7

# Separate job so the DT key is never present while repo code runs
# (uv build / uv sync above execute the build backend). Releases are created
# with GITHUB_TOKEN, which doesn't trigger release workflows, so this lives here.
dependencytrack:
needs: release
if: needs.release.outputs.released == 'true'
runs-on: ubuntu-latest
permissions: {}
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sbom-cyclonedx

# Each release becomes a version under the "oss/python/django-postcode-lookup"
# library container (OSS > OSS Python), which rolls up the latest version.
- name: Upload SBOM to DependencyTrack
env:
DT_API_KEY: ${{ secrets.DEPENDENCYTRACK_OSS_APIKEY }}
DT_URL: https://dependencytrack-api.labdigital.nl:8443
PROJECT: oss/python/django-postcode-lookup
VERSION: ${{ needs.release.outputs.version }}
run: |
gh release create "${{ steps.release.outputs.version }}" \
--title "${{ steps.release.outputs.version }}" \
--notes-file "${{ steps.release.outputs.notes }}" \
dist/*
set -euo pipefail
if [ -z "${DT_API_KEY}" ]; then
echo "::error::DEPENDENCYTRACK_OSS_APIKEY is not available to this repo"
exit 1
fi
curl -sS --fail-with-body -X POST "${DT_URL}/api/v1/bom" \
-H "X-Api-Key: ${DT_API_KEY}" \
-F "autoCreate=true" \
-F "projectName=${PROJECT}" \
-F "projectVersion=${VERSION}" \
-F "parentName=${PROJECT}" \
-F "isLatest=true" \
-F "bom=@django-postcode-lookup.cdx.json"
6 changes: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
Expand All @@ -38,7 +40,9 @@ jobs:
run: uv sync --group test

- name: Install Django ${{ matrix.django-version }}
run: uv pip install "Django==${{ matrix.django-version }}"
env:
DJANGO_VERSION: ${{ matrix.django-version }}
run: uv pip install "Django==${DJANGO_VERSION}"

- name: Run lint
run: uv run --group test ruff check
Expand Down
Loading
Loading