informed-decision/tests/test_service_image_build.py
tegwick c228583b60
All checks were successful
Build and Publish Service Image / build-and-push (push) Successful in 2m38s
INFD-WP-0010-T02: owner-CI workflow for the full service image
.forgejo/workflows/service-image.yaml builds Containerfile from the pushed
commit's tarball and pushes forgejo.coulomb.social/coulomb/informed-decision
:main-<short sha>, then reports the immutable digest. Before pushing it
checks the zlib floor, the runtime smoke (tools/zlib_overlay/check_runtime.py)
and that the installed modules equal the commit's source.

Containerfile builds the wheel in-image (hash-pinned setuptools in
requirements-build.lock) and installs the zlib overlay's hash-pinned,
Alpine-signed zlib-1.3.2-r1 package offline, failing below 1.3.2-r1, so the
live image's zlib fix cannot regress. Overlay workflows are unchanged.
Local build: 13/13 container checks, zlib 1.3.2-r1, source manifest match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-11-01 11:32:32 +01:00

52 lines
2.5 KiB
Python

"""Static checks for the owner-CI full service image (.forgejo/workflows/service-image.yaml).
The built image itself is checked in CI (zlib floor, runtime smoke, installed source manifest)
and locally with tools/smoke_container.py; these tests keep the inputs consistent.
"""
import hashlib
import json
from pathlib import Path
import re
import yaml
ROOT = Path(__file__).parents[1]
ZLIB = ROOT / 'deploy' / 'zlib-overlay'
def test_containerfile_installs_the_overlay_zlib_package_and_enforces_the_floor():
text = (ROOT / 'Containerfile').read_text()
pins = json.loads((ZLIB / 'pins.json').read_text())['files']
for name in ('zlib-1.3.2-r1.apk', 'APKINDEX.tar.gz'):
actual = hashlib.sha256((ZLIB / name).read_bytes()).hexdigest()
assert actual == pins[name]
assert f"echo '{actual} /tmp/infd-zlib/x86_64/{name}' | sha256sum -c -" in text
assert '--no-network' in text and "apk info --exists 'zlib>=1.3.2-r1'" in text
# Every stage uses the same pinned base; the wheel is built from source, not a workstation dist/.
bases = re.findall(r'^FROM (\S+)', text, re.M)
assert len(bases) == 3 and len(set(bases)) == 1 and '@sha256:' in bases[0]
assert 'COPY dist/' not in text and 'COPY --from=wheel /dist/' in text
def test_build_context_admits_exactly_the_inputs():
ignore = (ROOT / '.dockerignore').read_text().split()
assert ignore[0] == '**'
for path in ('Containerfile', 'requirements.lock', 'requirements-build.lock', 'pyproject.toml',
'informed_decision/**', 'deploy/zlib-overlay/zlib-1.3.2-r1.apk',
'deploy/zlib-overlay/APKINDEX.tar.gz'):
assert '!' + path in ignore
build_lock = (ROOT / 'requirements-build.lock').read_text()
assert re.search(r'^setuptools==[\d.]+ \\\n\s+--hash=sha256:[0-9a-f]{64}$', build_lock, re.M)
def test_workflow_builds_from_the_pushed_commit_and_reports_the_digest():
flow = yaml.safe_load((ROOT / '.forgejo' / 'workflows' / 'service-image.yaml').read_text())
on = flow.get('on', flow.get(True))
assert on['push']['branches'] == ['main']
steps = flow['jobs']['build-and-push']['steps']
script = steps[0]['run']
assert 'archive/${REF}.tar.gz' in script and ':main-${SHORT}' in script
assert script.index("zlib>=1.3.2-r1") < script.index('docker push')
assert script.index('diff /tmp/source.txt /tmp/installed.txt') < script.index('docker push')
assert steps[1]['name'] == 'Report immutable digest'
assert flow['env']['IMAGE_NAME'] == 'coulomb/informed-decision'