v1.8.0 #19
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
| timeout-minutes: 10 | |
| 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 }} | |
| build-platforms: ${{ steps.config.outputs.build-platforms }} | |
| short-name: ${{ steps.config.outputs.short-name }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Get project configuration | |
| id: config | |
| uses: ./.github/actions/get-config | |
| # Build the wheel and UI bundle exactly once so prod-containers, prod-pypi, | |
| # and github-assets all publish the identical artefact. | |
| build-wheel: | |
| name: Build Release Wheel | |
| needs: config | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Setup Python and UV | |
| uses: ./.github/actions/setup-uv-cached | |
| with: | |
| cache-key: prod-wheel-${{ needs.config.outputs.package-version }} | |
| fail-on-cache-miss: false | |
| - name: Setup Bun (for UI static bundle) | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: "1.2.15" # pinned; update intentionally to avoid surprise breakage | |
| # The SPA bundle is built automatically by the setuptools build hook during | |
| # `make build` (python -m build). No separate step needed. | |
| - name: Build package wheel | |
| run: make build | |
| - name: Upload wheel artifact | |
| uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4 | |
| with: | |
| name: release-wheel-${{ needs.config.outputs.package-version }} | |
| path: dist/ | |
| retention-days: 1 | |
| prod-containers: | |
| name: Build Production Containers | |
| needs: [config, build-wheel] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 | |
| with: | |
| platforms: ${{ needs.config.outputs.build-platforms }} | |
| - name: Log in to Container Registry | |
| uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4 | |
| with: | |
| registry: ${{ needs.config.outputs.container-registry }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Download release wheel | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: release-wheel-${{ needs.config.outputs.package-version }} | |
| path: dist/ | |
| - name: Build and push 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 }} | |
| BUILD_PLATFORMS: ${{ needs.config.outputs.build-platforms }} | |
| PACKAGE_NAME_SHORT: ${{ needs.config.outputs.short-name }} | |
| run: | | |
| docker buildx build \ | |
| --platform "$BUILD_PLATFORMS" \ | |
| --build-arg "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ | |
| --build-arg "VERSION=$VERSION" \ | |
| --build-arg "VCS_REF=$(git rev-parse --short HEAD)" \ | |
| --build-arg "PYTHON_VERSION=$PYTHON_VERSION" \ | |
| --build-arg "PACKAGE_NAME_SHORT=$PACKAGE_NAME_SHORT" \ | |
| --cache-from type=gha \ | |
| --cache-to type=gha,mode=max \ | |
| --push \ | |
| -t "$REGISTRY/$IMAGE_NAME:$VERSION-python$PYTHON_VERSION" \ | |
| . | |
| prod-manifests: | |
| name: Create Production Manifests | |
| needs: [config, prod-containers] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Log in to Container Registry | |
| uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4 | |
| with: | |
| registry: ${{ needs.config.outputs.container-registry }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create production tag manifests | |
| shell: bash | |
| 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: | | |
| # Parse Makefile container tag output into shell variables in the same step. | |
| # GITHUB_ENV writes are only visible to SUBSEQUENT steps, so we assign | |
| # directly into the current shell environment instead. | |
| # get-container-tags-env emits "export KEY=VALUE" lines; strip the prefix and | |
| # validate the KEY=VALUE format before assigning (rejects subcommands etc.). | |
| while IFS= read -r line; do | |
| # Strip leading "export " if present | |
| kv="${line#export }" | |
| # Validate KEY=VALUE format before assigning (reject lines with subcommands etc.) | |
| if [[ "$kv" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then | |
| declare "${BASH_REMATCH[1]}=${BASH_REMATCH[2]}" | |
| fi | |
| done < <(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, build-wheel] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| environment: pypi | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - 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: Download release wheel | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: release-wheel-${{ needs.config.outputs.package-version }} | |
| path: dist/ | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 | |
| with: | |
| print-hash: true | |
| verbose: true | |
| attestations: true | |
| prod-docs: | |
| name: Deploy Production Documentation | |
| needs: config | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| permissions: | |
| contents: write | |
| pages: write | |
| id-token: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| - 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: Compute minor version | |
| id: version | |
| run: | | |
| VERSION_MINOR=$(echo "${{ needs.config.outputs.package-version }}" | cut -d. -f1,2) | |
| echo "minor=${VERSION_MINOR}" >> "$GITHUB_OUTPUT" | |
| - name: Deploy versioned docs via mike | |
| run: bash dev-tools/docs/mike_deploy.sh release "${{ steps.version.outputs.minor }}" --push | |
| - name: Upload Pages artifact | |
| id: upload | |
| uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 | |
| with: | |
| path: docs/site | |
| - name: Deploy to GitHub Pages | |
| id: deployment | |
| uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 | |
| - name: Smoke-check deployed site | |
| run: bash dev-tools/docs/smoke_check_pages.sh "${{ steps.deployment.outputs.page_url }}" | |
| container-cleanup: | |
| name: Cleanup Old Container Images | |
| needs: [config, prod-manifests] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| packages: write | |
| steps: | |
| - name: Delete untagged container images | |
| uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # 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, build-wheel, prod-containers, prod-pypi] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - 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: Download release wheel | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: release-wheel-${{ needs.config.outputs.package-version }} | |
| path: dist/ | |
| - name: Generate SBOM | |
| run: make sbom-generate | |
| - name: Upload release assets | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.config.outputs.package-version }}" | |
| RELEASE_TAG="v$VERSION" | |
| gh release upload "$RELEASE_TAG" dist/*.whl dist/*.tar.gz --clobber | |
| for f in python-sbom-cyclonedx.json python-sbom.json; do | |
| if [[ -f "$f" ]]; then | |
| gh release upload "$RELEASE_TAG" "$f" --clobber | |
| fi | |
| done |