Skip to content

Release Pipeline

Release Pipeline #14

Workflow file for this run

name: Release Pipeline
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g., 1.0.0)'
required: true
type: string
permissions:
contents: read
packages: write
pages: write
concurrency:
group: production-release-${{ github.event.release.tag_name || inputs.version }}
cancel-in-progress: false
jobs:
config:
name: Get Configuration
runs-on: ubuntu-latest
outputs:
python-versions: ${{ steps.config.outputs.python-versions }}
default-python-version: ${{ steps.config.outputs.default-python-version }}
container-registry: ${{ steps.config.outputs.container-registry }}
container-image: ${{ steps.config.outputs.container-image }}
package-version: ${{ steps.config.outputs.package-version }}
is-release: ${{ steps.config.outputs.is-release }}
pypi-name: ${{ steps.config.outputs.pypi-name }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Get project configuration
id: config
uses: ./.github/actions/get-config
prod-containers:
name: Build Production Containers
needs: config
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ${{ fromJSON(needs.config.outputs.python-versions) }}
env:
SCAN_LEVEL: ${{ matrix.python-version == needs.config.outputs.default-python-version && 'full' || 'basic' }}
PUSH_TO_REGISTRY: true
permissions:
contents: read
packages: write
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
with:
platforms: linux/amd64,linux/arm64
- name: Log in to Container Registry
uses: docker/login-action@v4
with:
registry: ${{ needs.config.outputs.container-registry }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Python and UV
uses: ./.github/actions/setup-uv-cached
with:
cache-key: prod-${{ needs.config.outputs.package-version }}-py${{ matrix.python-version }}
fail-on-cache-miss: false
- name: Build package wheel
run: make build
- name: Build and scan container image
env:
REGISTRY: ${{ needs.config.outputs.container-registry }}
IMAGE_NAME: ${{ needs.config.outputs.container-image }}
VERSION: ${{ needs.config.outputs.package-version }}
PYTHON_VERSION: ${{ matrix.python-version }}
DEFAULT_PYTHON: ${{ needs.config.outputs.default-python-version }}
run: |
make container single push \
REGISTRY="$REGISTRY" \
IMAGE="$IMAGE_NAME" \
VERSION="$VERSION" \
PYTHON_VERSION="$PYTHON_VERSION"
prod-manifests:
name: Create Production Manifests
needs: [config, prod-containers]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Log in to Container Registry
uses: docker/login-action@v4
with:
registry: ${{ needs.config.outputs.container-registry }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create production tag manifests
env:
REGISTRY: ${{ needs.config.outputs.container-registry }}
IMAGE: ${{ needs.config.outputs.container-image }}
VERSION: ${{ needs.config.outputs.package-version }}
DEFAULT_PYTHON: ${{ needs.config.outputs.default-python-version }}
IS_RELEASE: "true"
run: |
# Get tags from Makefile target
eval "$(make get-container-tags-env)"
# Create primary tags (latest, version)
if [[ -n "$primary_tags" ]]; then
IFS=',' read -ra TAGS <<< "$primary_tags"
for tag in "${TAGS[@]}"; do
echo "Creating primary tag: $tag -> $VERSION-python$DEFAULT_PYTHON"
docker buildx imagetools create \
-t "$REGISTRY/$IMAGE:$tag" \
"$REGISTRY/$IMAGE:$VERSION-python$DEFAULT_PYTHON"
done
fi
# Create Python variant tags
if [[ -n "$python_tags" ]]; then
IFS=',' read -ra TAGS <<< "$python_tags"
for tag in "${TAGS[@]}"; do
py_ver=${tag#python}
echo "Creating Python variant tag: $tag -> $VERSION-python$py_ver"
docker buildx imagetools create \
-t "$REGISTRY/$IMAGE:$tag" \
"$REGISTRY/$IMAGE:$VERSION-python$py_ver"
done
fi
prod-pypi:
name: Publish to PyPI
needs: config
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Python and UV
uses: ./.github/actions/setup-uv-cached
with:
cache-key: prod-pypi-${{ needs.config.outputs.package-version }}
fail-on-cache-miss: false
- name: Build package
run: make build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
print-hash: true
verbose: true
prod-docs:
name: Deploy Production Documentation
needs: config
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
permissions:
contents: read
pages: write
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Python and UV
uses: ./.github/actions/setup-uv-cached
with:
cache-key: prod-docs-${{ needs.config.outputs.package-version }}
fail-on-cache-miss: false
- name: Build documentation
run: make ci-docs-build-for-pages
- name: Upload Pages artifact
id: upload
uses: actions/upload-pages-artifact@v4
with:
path: docs/site
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
container-cleanup:
name: Cleanup Old Container Images
needs: [config, prod-manifests]
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Delete untagged container images
uses: actions/delete-package-versions@v5
with:
package-name: ${{ github.event.repository.name }}
package-type: container
min-versions-to-keep: 0
delete-only-untagged-versions: true
github-assets:
name: Upload GitHub Release Assets
needs: [config, prod-containers, prod-pypi]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Python and UV
uses: ./.github/actions/setup-uv-cached
with:
cache-key: prod-assets-${{ needs.config.outputs.package-version }}
fail-on-cache-miss: false
- name: Build package
run: make build
- name: Generate SBOM
run: make sbom
- name: Upload release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ needs.config.outputs.package-version }}"
RELEASE_TAG="v$VERSION"
# Upload wheel and source distribution
gh release upload "$RELEASE_TAG" dist/*.whl dist/*.tar.gz --clobber
# Upload SBOM if it exists
if [[ -f "sbom.json" ]]; then
gh release upload "$RELEASE_TAG" sbom.json --clobber
fi