Merge pull request #15 from jski/bugfix/remove-security-scan-step #12
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: Build & Deploy | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| inputs: | |
| promote: | |
| description: 'Promote to production tags after build' | |
| required: false | |
| type: boolean | |
| default: false | |
| jobs: | |
| build: | |
| name: Build Images | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - python_version: "3.9" | |
| debian_version: "bullseye" | |
| - python_version: "3.10" | |
| debian_version: "bullseye" | |
| - python_version: "3.11" | |
| debian_version: "bookworm" | |
| - python_version: "3.12" | |
| debian_version: "bookworm" | |
| - python_version: "3.13" | |
| debian_version: "bookworm" | |
| - python_version: "3.14" | |
| debian_version: "bookworm" | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| with: | |
| platforms: all | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Check if image already exists or can be re-tagged | |
| id: check | |
| run: | | |
| CURRENT_IMAGE="ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }}" | |
| echo "=== Image Existence Check ===" | |
| echo "Checking if ${CURRENT_IMAGE} already exists..." | |
| # Check if image with current commit SHA exists | |
| if docker manifest inspect "${CURRENT_IMAGE}" >/dev/null 2>&1; then | |
| echo "✅ Image already exists for current commit, skipping build" | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "❌ Image does not exist for current commit: ${{ github.sha }}" | |
| # If this is a push to main (likely a PR merge), try to find and re-tag the PR image | |
| if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == "refs/heads/main" ]; then | |
| echo "" | |
| echo "=== Attempting PR Image Re-tag (Squash Merge Handling) ===" | |
| # Method 1: Extract PR number from commit message | |
| # Squash merges typically include "#123" in the commit message | |
| PR_NUM=$(git log -1 --pretty=%B | grep -oP '#\K\d+' | head -1 || echo "") | |
| if [ -n "$PR_NUM" ]; then | |
| echo "Found PR #${PR_NUM} in commit message" | |
| PR_IMAGE="ghcr.io/${{ github.repository_owner }}/python-container-builder:pr-${PR_NUM}-${{ matrix.python_version }}" | |
| echo "Checking if PR image exists: ${PR_IMAGE}" | |
| if docker manifest inspect "${PR_IMAGE}" >/dev/null 2>&1; then | |
| echo "✅ Found PR image, re-tagging to new commit SHA..." | |
| # Re-tag the PR image with the new commit SHA (no rebuild!) | |
| docker buildx imagetools create \ | |
| "${PR_IMAGE}" \ | |
| --tag "${CURRENT_IMAGE}" | |
| if [ $? -eq 0 ]; then | |
| echo "✅ Successfully re-tagged PR image to commit SHA" | |
| echo " Source: ${PR_IMAGE}" | |
| echo " Target: ${CURRENT_IMAGE}" | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| else | |
| echo "❌ Re-tagging failed, will build from scratch" | |
| fi | |
| else | |
| echo "⚠️ PR image not found: ${PR_IMAGE}" | |
| fi | |
| else | |
| echo "⚠️ Could not extract PR number from commit message" | |
| fi | |
| # Method 2: Use GitHub API to find PR by merge commit SHA | |
| echo "" | |
| echo "Trying GitHub API to find PR by commit SHA..." | |
| PR_NUM=$(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ | |
| "https://api.github.com/repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \ | |
| | jq -r '.[0].number // empty') | |
| if [ -n "$PR_NUM" ]; then | |
| echo "Found PR #${PR_NUM} via GitHub API" | |
| PR_IMAGE="ghcr.io/${{ github.repository_owner }}/python-container-builder:pr-${PR_NUM}-${{ matrix.python_version }}" | |
| echo "Checking if PR image exists: ${PR_IMAGE}" | |
| if docker manifest inspect "${PR_IMAGE}" >/dev/null 2>&1; then | |
| echo "✅ Found PR image, re-tagging to new commit SHA..." | |
| docker buildx imagetools create \ | |
| "${PR_IMAGE}" \ | |
| --tag "${CURRENT_IMAGE}" | |
| if [ $? -eq 0 ]; then | |
| echo "✅ Successfully re-tagged PR image to commit SHA" | |
| echo " Source: ${PR_IMAGE}" | |
| echo " Target: ${CURRENT_IMAGE}" | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| else | |
| echo "❌ Re-tagging failed, will build from scratch" | |
| fi | |
| else | |
| echo "⚠️ PR image not found: ${PR_IMAGE}" | |
| fi | |
| else | |
| echo "⚠️ Could not find PR via GitHub API" | |
| fi | |
| fi | |
| echo "" | |
| echo "=== Final Decision ===" | |
| echo "❌ No existing image found, will build from scratch" | |
| echo "exists=false" >> $GITHUB_OUTPUT | |
| - name: Extract metadata | |
| if: steps.check.outputs.exists == 'false' | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/python-container-builder | |
| tags: | | |
| type=sha,prefix=${{ matrix.python_version }}-,format=long | |
| type=ref,event=pr,prefix=pr-,suffix=-${{ matrix.python_version }} | |
| - name: Build and push Docker images | |
| if: steps.check.outputs.exists == 'false' | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: Dockerfile | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| build-args: | | |
| DEBIAN_VERSION=${{ matrix.debian_version }} | |
| PYTHON_VERSION=${{ matrix.python_version }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha,scope=python-${{ matrix.python_version }} | |
| cache-to: type=gha,mode=max,scope=python-${{ matrix.python_version }} | |
| provenance: true | |
| sbom: true | |
| outputs: type=image,name=python-container-builder,annotation-index.org.opencontainers.image.description=Build your Python distroless containers with this | |
| promote: | |
| name: Promote to Production | |
| runs-on: ubuntu-latest | |
| needs: [build, test] | |
| # Promote on: | |
| # 1. Normal merge to main (not force push) | |
| # 2. Manual workflow dispatch with promote flag enabled | |
| # CRITICAL: Only runs if build AND test all succeed | |
| if: | | |
| (github.event_name == 'push' && github.ref == 'refs/heads/main' && !github.event.forced) || | |
| (github.event_name == 'workflow_dispatch' && inputs.promote == true) | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python_version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] | |
| steps: | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Promote commit SHA to version tag | |
| run: | | |
| # Get the full commit SHA | |
| COMMIT_SHA="${{ github.sha }}" | |
| # Source image with commit SHA | |
| SOURCE_IMAGE="ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${COMMIT_SHA}" | |
| # Destination tags | |
| VERSION_TAG="ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}" | |
| echo "Promoting ${SOURCE_IMAGE} to ${VERSION_TAG}" | |
| # Re-tag the existing image (no rebuild) | |
| docker buildx imagetools create \ | |
| "${SOURCE_IMAGE}" \ | |
| --tag "${VERSION_TAG}" | |
| echo "✅ Successfully promoted ${{ matrix.python_version }} to production" | |
| - name: Promote latest tag | |
| if: matrix.python_version == '3.14' | |
| run: | | |
| COMMIT_SHA="${{ github.sha }}" | |
| SOURCE_IMAGE="ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${COMMIT_SHA}" | |
| LATEST_TAG="ghcr.io/${{ github.repository_owner }}/python-container-builder:latest" | |
| echo "Promoting ${SOURCE_IMAGE} to ${LATEST_TAG}" | |
| docker buildx imagetools create \ | |
| "${SOURCE_IMAGE}" \ | |
| --tag "${LATEST_TAG}" | |
| echo "✅ Successfully promoted latest tag" | |
| test: | |
| name: Test Images | |
| runs-on: ubuntu-latest | |
| needs: build | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python_version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] | |
| steps: | |
| - name: Test Python version | |
| run: | | |
| docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} python --version | |
| - name: Test uv is installed | |
| run: | | |
| docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} uv --version | |
| - name: Test poetry is installed | |
| run: | | |
| docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} poetry --version | |
| - name: Test pipenv is installed | |
| run: | | |
| docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} pipenv --version | |
| - name: Test pdm is installed | |
| run: | | |
| docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} pdm --version | |
| - name: Test pip is installed in venv | |
| run: | | |
| docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} /.venv/bin/pip --version | |
| - name: Test venv is created | |
| run: | | |
| docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} sh -c 'test -d /.venv && echo "venv exists"' | |
| - name: Test package installation with uv | |
| run: | | |
| docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} sh -c 'uv pip install requests && /.venv/bin/python -c "import requests; print(f\"requests {requests.__version__} imported successfully\")"' | |
| - name: Test package installation with pip | |
| run: | | |
| docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} sh -c '/.venv/bin/pip install click && /.venv/bin/python -c "import click; print(f\"click {click.__version__} imported successfully\")"' |