Skip to content

Commit 0bc113c

Browse files
committed
Fail workflow for build if tests or security fail
1 parent b22b005 commit 0bc113c

File tree

1 file changed

+112
-19
lines changed

1 file changed

+112
-19
lines changed

.github/workflows/build.yml

Lines changed: 112 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ on:
77
branches:
88
- main
99
workflow_dispatch:
10+
inputs:
11+
promote:
12+
description: 'Promote to production tags after build'
13+
required: false
14+
type: boolean
15+
default: false
1016

1117
jobs:
1218
build:
13-
name: Build & Deploy
19+
name: Build Images
1420
runs-on: ubuntu-latest
1521
permissions:
1622
contents: read
@@ -49,38 +55,125 @@ jobs:
4955
username: ${{ github.actor }}
5056
password: ${{ secrets.GITHUB_TOKEN }}
5157

58+
- name: Check if image already exists
59+
id: check
60+
run: |
61+
IMAGE="ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }}"
62+
echo "Checking if ${IMAGE} already exists..."
63+
64+
if docker manifest inspect "${IMAGE}" >/dev/null 2>&1; then
65+
echo "✅ Image already exists, skipping build"
66+
echo "exists=true" >> $GITHUB_OUTPUT
67+
else
68+
echo "❌ Image does not exist, will build"
69+
echo "exists=false" >> $GITHUB_OUTPUT
70+
fi
71+
72+
- name: Extract metadata
73+
if: steps.check.outputs.exists == 'false'
74+
id: meta
75+
uses: docker/metadata-action@v5
76+
with:
77+
images: ghcr.io/${{ github.repository_owner }}/python-container-builder
78+
tags: |
79+
type=sha,prefix=${{ matrix.python_version }}-,format=long
80+
type=ref,event=pr,prefix=pr-,suffix=-${{ matrix.python_version }}
81+
5282
- name: Build and push Docker images
83+
if: steps.check.outputs.exists == 'false'
5384
uses: docker/build-push-action@v6
5485
with:
5586
context: .
5687
file: Dockerfile
5788
platforms: linux/amd64,linux/arm64
58-
push: ${{ github.event_name != 'pull_request' }}
89+
push: true
5990
build-args: |
6091
DEBIAN_VERSION=${{ matrix.debian_version }}
6192
PYTHON_VERSION=${{ matrix.python_version }}
62-
tags: |
63-
ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}
64-
${{ matrix.python_version == '3.14' && format('ghcr.io/{0}/python-container-builder:latest', github.repository_owner) || '' }}
65-
provenance: false
66-
outputs: type=image,name=python-container-builder,annotation-index.org.opencontainers.image.description=build your Python distroless containers with this
93+
tags: ${{ steps.meta.outputs.tags }}
94+
labels: ${{ steps.meta.outputs.labels }}
95+
cache-from: type=gha,scope=python-${{ matrix.python_version }}
96+
cache-to: type=gha,mode=max,scope=python-${{ matrix.python_version }}
97+
provenance: true
98+
sbom: true
99+
outputs: type=image,name=python-container-builder,annotation-index.org.opencontainers.image.description=Build your Python distroless containers with this
100+
101+
promote:
102+
name: Promote to Production
103+
runs-on: ubuntu-latest
104+
needs: [build, test, security-scan]
105+
# Promote on:
106+
# 1. Normal merge to main (not force push)
107+
# 2. Manual workflow dispatch with promote flag enabled
108+
# CRITICAL: Only runs if build, test, AND security-scan all succeed
109+
if: |
110+
(github.event_name == 'push' && github.ref == 'refs/heads/main' && !github.event.forced) ||
111+
(github.event_name == 'workflow_dispatch' && inputs.promote == true)
112+
permissions:
113+
contents: read
114+
packages: write
115+
strategy:
116+
matrix:
117+
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
118+
steps:
119+
- name: Log in to GitHub Container Registry
120+
uses: docker/login-action@v3
121+
with:
122+
registry: ghcr.io
123+
username: ${{ github.actor }}
124+
password: ${{ secrets.GITHUB_TOKEN }}
125+
126+
- name: Promote commit SHA to version tag
127+
run: |
128+
# Get the full commit SHA
129+
COMMIT_SHA="${{ github.sha }}"
130+
131+
# Source image with commit SHA
132+
SOURCE_IMAGE="ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${COMMIT_SHA}"
133+
134+
# Destination tags
135+
VERSION_TAG="ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}"
136+
137+
echo "Promoting ${SOURCE_IMAGE} to ${VERSION_TAG}"
138+
139+
# Re-tag the existing image (no rebuild)
140+
docker buildx imagetools create \
141+
"${SOURCE_IMAGE}" \
142+
--tag "${VERSION_TAG}"
143+
144+
echo "✅ Successfully promoted ${{ matrix.python_version }} to production"
145+
146+
- name: Promote latest tag
147+
if: matrix.python_version == '3.14'
148+
run: |
149+
COMMIT_SHA="${{ github.sha }}"
150+
SOURCE_IMAGE="ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${COMMIT_SHA}"
151+
LATEST_TAG="ghcr.io/${{ github.repository_owner }}/python-container-builder:latest"
152+
153+
echo "Promoting ${SOURCE_IMAGE} to ${LATEST_TAG}"
154+
155+
docker buildx imagetools create \
156+
"${SOURCE_IMAGE}" \
157+
--tag "${LATEST_TAG}"
158+
159+
echo "✅ Successfully promoted latest tag"
67160
68161
security-scan:
69162
name: Security Scan
70163
runs-on: ubuntu-latest
71164
needs: build
72-
if: github.event_name != 'pull_request'
73165
permissions:
74166
contents: read
75167
security-events: write
76168
steps:
77169
- name: Run Trivy vulnerability scanner
78170
uses: aquasecurity/trivy-action@master
79171
with:
80-
image-ref: ghcr.io/${{ github.repository_owner }}/python-container-builder:latest
172+
image-ref: ghcr.io/${{ github.repository_owner }}/python-container-builder:3.14-${{ github.sha }}
81173
format: 'sarif'
82174
output: 'trivy-results.sarif'
83175
severity: 'CRITICAL,HIGH'
176+
exit-code: '1'
84177

85178
- name: Upload Trivy results to GitHub Security
86179
uses: github/codeql-action/upload-sarif@v4
@@ -92,15 +185,15 @@ jobs:
92185
uses: aquasecurity/trivy-action@master
93186
if: always()
94187
with:
95-
image-ref: ghcr.io/${{ github.repository_owner }}/python-container-builder:latest
188+
image-ref: ghcr.io/${{ github.repository_owner }}/python-container-builder:3.14-${{ github.sha }}
96189
format: 'table'
97190
severity: 'CRITICAL,HIGH'
191+
exit-code: '1'
98192

99193
test:
100194
name: Test Images
101195
runs-on: ubuntu-latest
102196
needs: build
103-
if: github.event_name != 'pull_request'
104197
permissions:
105198
contents: read
106199
strategy:
@@ -109,32 +202,32 @@ jobs:
109202
steps:
110203
- name: Test Python version
111204
run: |
112-
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }} python --version
205+
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} python --version
113206
114207
- name: Test uv is installed
115208
run: |
116-
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }} uv --version
209+
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} uv --version
117210
118211
- name: Test poetry is installed
119212
run: |
120-
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }} poetry --version
213+
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} poetry --version
121214
122215
- name: Test pipenv is installed
123216
run: |
124-
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }} pipenv --version
217+
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} pipenv --version
125218
126219
- name: Test pdm is installed
127220
run: |
128-
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }} pdm --version
221+
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} pdm --version
129222
130223
- name: Test venv is created
131224
run: |
132-
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }} sh -c 'test -d /.venv && echo "venv exists"'
225+
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} sh -c 'test -d /.venv && echo "venv exists"'
133226
134227
- name: Test package installation with uv
135228
run: |
136-
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }} sh -c 'uv pip install requests && python -c "import requests; print(f\"requests {requests.__version__} imported successfully\")"'
229+
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} sh -c 'uv pip install requests && python -c "import requests; print(f\"requests {requests.__version__} imported successfully\")"'
137230
138231
- name: Test package installation with pip
139232
run: |
140-
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }} sh -c 'pip install click && python -c "import click; print(f\"click {click.__version__} imported successfully\")"'
233+
docker run --rm ghcr.io/${{ github.repository_owner }}/python-container-builder:${{ matrix.python_version }}-${{ github.sha }} sh -c 'pip install click && python -c "import click; print(f\"click {click.__version__} imported successfully\")"'

0 commit comments

Comments
 (0)