Skip to content

Commit 8fd611d

Browse files
committed
ci: add batch image validation and publishing
1 parent 7d15f76 commit 8fd611d

3 files changed

Lines changed: 173 additions & 1 deletion

File tree

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
name: Batch service image
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "batch-service/**"
7+
- ".github/workflows/batch-service-image.yaml"
8+
push:
9+
branches:
10+
- main
11+
- dev
12+
paths:
13+
- "batch-service/**"
14+
- ".github/workflows/batch-service-image.yaml"
15+
16+
concurrency:
17+
group: batch-service-image-${{ github.workflow }}-${{ github.ref }}
18+
cancel-in-progress: true
19+
20+
permissions:
21+
contents: read
22+
23+
env:
24+
IMAGE_NAME: ghcr.io/phoenixvc/sluice-batch-service
25+
TEST_IMAGE: sluice-batch-service:contract-test
26+
27+
jobs:
28+
validate:
29+
name: Test, build, and smoke image
30+
runs-on: ubuntu-latest
31+
timeout-minutes: 15
32+
33+
steps:
34+
- name: Check out repository
35+
uses: actions/checkout@v4
36+
37+
- name: Set up Python
38+
uses: actions/setup-python@v5
39+
with:
40+
python-version: "3.13"
41+
cache: pip
42+
cache-dependency-path: batch-service/requirements-test.txt
43+
44+
- name: Install test dependencies
45+
working-directory: batch-service
46+
run: python -m pip install --requirement requirements-test.txt
47+
48+
- name: Run mocked contract tests
49+
working-directory: batch-service
50+
env:
51+
BATCH_JOB_ID_SIGNING_KEY: contract-tests-only
52+
run: python -m pytest -q
53+
54+
- name: Build image
55+
run: docker build --tag "${TEST_IMAGE}" batch-service
56+
57+
- name: Verify non-root runtime
58+
shell: bash
59+
run: |
60+
runtime_uid="$(docker run --rm "${TEST_IMAGE}" id -u)"
61+
if [[ "${runtime_uid}" == "0" ]]; then
62+
echo "::error::Batch service image runs as root"
63+
exit 1
64+
fi
65+
echo "Runtime UID: ${runtime_uid}"
66+
67+
- name: Start image
68+
run: |
69+
docker run \
70+
--detach \
71+
--name sluice-batch-service-smoke \
72+
--env BATCH_JOB_ID_SIGNING_KEY=local-image-smoke-only \
73+
--publish 127.0.0.1:8080:8080 \
74+
"${TEST_IMAGE}"
75+
76+
- name: Verify signed-job health
77+
shell: bash
78+
run: |
79+
for attempt in {1..30}; do
80+
if response="$(curl --fail --silent http://127.0.0.1:8080/healthz)"; then
81+
if python -c \
82+
'import json,sys; data=json.load(sys.stdin); sys.exit(0 if data == {"status":"ok","job_id_signing_configured":True} else 1)' \
83+
<<< "${response}"; then
84+
echo "Batch service health check passed"
85+
exit 0
86+
fi
87+
fi
88+
sleep 1
89+
done
90+
91+
docker logs sluice-batch-service-smoke
92+
echo "::error::Batch service did not report signing configured"
93+
exit 1
94+
95+
- name: Stop image
96+
if: always()
97+
run: docker rm --force sluice-batch-service-smoke || true
98+
99+
publish:
100+
name: Publish immutable GHCR image
101+
if: github.event_name == 'push'
102+
needs: validate
103+
runs-on: ubuntu-latest
104+
timeout-minutes: 20
105+
permissions:
106+
contents: read
107+
packages: write
108+
attestations: write
109+
id-token: write
110+
111+
steps:
112+
- name: Check out repository
113+
uses: actions/checkout@v4
114+
115+
- name: Set up Docker Buildx
116+
uses: docker/setup-buildx-action@v3
117+
118+
- name: Log in to GHCR
119+
uses: docker/login-action@v3
120+
with:
121+
registry: ghcr.io
122+
username: ${{ github.actor }}
123+
password: ${{ secrets.GITHUB_TOKEN }}
124+
125+
- name: Extract OCI metadata
126+
id: meta
127+
uses: docker/metadata-action@v5
128+
with:
129+
images: ${{ env.IMAGE_NAME }}
130+
tags: |
131+
type=raw,value=${{ github.sha }}
132+
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
133+
134+
- name: Build and publish
135+
id: build
136+
uses: docker/build-push-action@v6
137+
with:
138+
context: batch-service
139+
file: batch-service/Dockerfile
140+
platforms: linux/amd64
141+
push: true
142+
tags: ${{ steps.meta.outputs.tags }}
143+
labels: ${{ steps.meta.outputs.labels }}
144+
provenance: mode=max
145+
sbom: true
146+
cache-from: type=gha,scope=batch-service
147+
cache-to: type=gha,mode=max,scope=batch-service
148+
149+
- name: Write image summary
150+
shell: bash
151+
run: |
152+
{
153+
echo "## Batch service image published"
154+
echo ""
155+
echo "| Reference | Value |"
156+
echo "| --- | --- |"
157+
echo "| SHA tag | \`${IMAGE_NAME}:${GITHUB_SHA}\` |"
158+
echo "| Digest pin | \`${IMAGE_NAME}@${{ steps.build.outputs.digest }}\` |"
159+
} >> "${GITHUB_STEP_SUMMARY}"

batch-service/.dockerignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.pytest_cache/
2+
.venv/
3+
**/__pycache__/
4+
**/*.pyc
5+
tests/

batch-service/Dockerfile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
FROM python:3.13-slim
22

3+
ENV PYTHONDONTWRITEBYTECODE=1 \
4+
PYTHONUNBUFFERED=1
5+
36
WORKDIR /app
47

8+
RUN addgroup --system app \
9+
&& adduser --system --ingroup app app
10+
511
COPY requirements.txt ./
612
RUN pip install --no-cache-dir -r requirements.txt
713

8-
COPY batch_service ./batch_service
14+
COPY --chown=app:app batch_service ./batch_service
15+
16+
USER app
917

1018
EXPOSE 8080
1119

0 commit comments

Comments
 (0)