"""US08-02: the image's build contract, its entrypoint, and its health check. Building the image needs a Docker daemon and the network, which is what ``tests/e2e/test_container_runtime.py`` does. Everything that can be checked without either is checked here, because the parts most likely to rot silently — a pin that stopped being a pin, a build context that started including the library, a health check pointed at liveness instead of readiness — are all readable from the files. The entrypoint and health check are shell, so they are exercised as shell: run with a stubbed ``id`` and ``python`` on ``PATH``, which is enough to prove the refusal, the role marker, and the argument pass-through without a container. """ from __future__ import annotations import json import os import re import shutil import stat import subprocess import sys from pathlib import Path import pytest from photo_pipeline.config import Config from photo_pipeline.services import diagnostics REPO = Path(__file__).resolve().parents[2] DOCKERFILE = (REPO / "Dockerfile").read_text() DOCKERIGNORE = (REPO / ".dockerignore").read_text() ENTRYPOINT = REPO / "docker" / "entrypoint.sh" HEALTHCHECK = REPO / "docker" / "healthcheck.sh" SHA256 = re.compile(r"^[0-9a-f]{64}$") def instructions(text: str) -> list[str]: """The lines that do something: comments explain, they do not build.""" return [line.strip() for line in text.splitlines() if line.strip() and not line.startswith("#")] def build_args() -> dict[str, str]: """Every ``ARG name=default`` in the Dockerfile — the pins, in other words.""" found = {} for match in re.finditer(r"^ARG\s+([A-Z0-9_]+)=(.+)$", DOCKERFILE, re.MULTILINE): found[match.group(1)] = match.group(2).strip() return found # ── pins ───────────────────────────────────────────────────────────────────── def test_the_base_image_is_pinned_by_version_and_digest(): base = build_args()["PYTHON_IMAGE"] assert base.startswith("python:3.12.") assert "@sha256:" in base, "a tag can be moved; a digest cannot" assert ":latest" not in DOCKERFILE # Both stages build from the same pinned base, so the tool that was verified in one # is the tool that ships in the other. assert DOCKERFILE.count("FROM ${PYTHON_IMAGE}") == 2 def test_exiftool_and_the_uploader_are_pinned_and_verified(): args = build_args() assert re.match(r"^\d+\.\d+", args["EXIFTOOL_VERSION"]) assert re.match(r"^\d+\.\d+\.\d+$", args["IMMICH_GO_VERSION"]) for arch in ("AMD64", "ARM64"): assert SHA256.match(args[f"IMMICH_GO_SHA256_{arch}"]), arch # The pinned exiftool package is installed by version, not by name alone. assert 'libimage-exiftool-perl=${EXIFTOOL_VERSION}"' in DOCKERFILE # And the build fails if what got installed is not what was pinned. assert "is not the pinned" in DOCKERFILE and "is not pinned" in DOCKERFILE def test_the_uploader_download_refuses_a_mismatching_checksum(tmp_path): """The verification is the point of pinning a URL, so it is run, not read.""" script = REPO / "docker" / "fetch-immich-go.py" result = subprocess.run( [ sys.executable, str(script), "--version", "0.0.0-does-not-exist", "--sha256-amd64", "0" * 64, "--sha256-arm64", "0" * 64, "--into", str(tmp_path), ], capture_output=True, text=True, ) assert result.returncode != 0 assert not list(tmp_path.iterdir()), "nothing is written before it is verified" def test_the_recorded_versions_are_reported_by_diagnostics(tmp_path, monkeypatch): """What the image records is what `diagnostics` answers with (acceptance criterion 2).""" recorded = tmp_path / "versions.json" recorded.write_text(json.dumps({"exiftool": "13.25", "immich-go": "0.32.0"})) monkeypatch.setattr(diagnostics, "IMAGE_VERSIONS_FILE", recorded) monkeypatch.setattr(diagnostics.exiftool, "version", lambda: "13.25") monkeypatch.setattr(diagnostics, "_uploader_version", lambda _binary: "immich-go 0.32.0") config = Config(data_dir=tmp_path / "data") reported = {tool["name"]: tool for tool in diagnostics.tools(config)} assert reported["exiftool"]["pinned"] == "13.25" assert reported["immich-go"]["pinned"] == "0.32.0" assert "0.32.0" in reported["immich-go"]["version"] assert diagnostics.report(config)["tools"] == list(reported.values()) assert "tool_version_drift" not in {w["code"] for w in diagnostics.report(config)["warnings"]} def test_a_replaced_tool_is_reported_as_drift(tmp_path, monkeypatch): recorded = tmp_path / "versions.json" recorded.write_text(json.dumps({"exiftool": "13.25"})) monkeypatch.setattr(diagnostics, "IMAGE_VERSIONS_FILE", recorded) monkeypatch.setattr(diagnostics.exiftool, "version", lambda: "12.57") report = diagnostics.report(Config(data_dir=tmp_path / "data")) drift = [w for w in report["warnings"] if w["code"] == "tool_version_drift"] assert drift and "13.25" in drift[0]["message"] and "12.57" in drift[0]["message"] def test_versions_are_absent_rather_than_invented_outside_a_container(tmp_path, monkeypatch): monkeypatch.setattr(diagnostics, "IMAGE_VERSIONS_FILE", tmp_path / "nothing.json") for tool in diagnostics.tools(Config(data_dir=tmp_path / "data")): assert tool["pinned"] is None # ── the final layer ────────────────────────────────────────────────────────── def test_no_test_or_build_tooling_is_installed_in_the_image(): runtime = "\n".join(instructions(DOCKERFILE.split("AS runtime", 1)[1])) for unwanted in ("[test]", "pytest", "playwright", "build-essential", "gcc"): assert unwanted not in runtime, unwanted assert "pip install --no-cache-dir -e ." in runtime def test_neither_secrets_nor_library_data_can_enter_the_build_context(): lines = instructions(DOCKERIGNORE) assert lines[0] == "*", "the context is deny-by-default" allowed = {line[1:] for line in lines if line.startswith("!")} # Everything the Dockerfile copies has to be allowed, and nothing else is. copied = { source for match in re.finditer(r"^COPY (?!--from)(.+)$", DOCKERFILE, re.MULTILINE) for source in match.group(1).split()[:-1] } assert {Path(source).parts[0] for source in copied} <= allowed assert not {"data", ".git", ".env", "tests", ".venv"} & allowed for generated in ("**/*.env", "**/*.db", "**/*.log", "**/__pycache__"): assert generated in lines, generated def test_the_image_runs_as_a_non_root_user_whose_ids_are_build_arguments(): args = build_args() assert args["UID"] == "1000" and args["GID"] == "1000" assert "USER ${UID}:${GID}" in DOCKERFILE assert re.search(r"^USER (root|0)", DOCKERFILE, re.MULTILINE) is None assert 'useradd --uid "${UID}" --gid "${GID}"' in DOCKERFILE def test_the_health_check_is_readiness_and_the_default_role_is_serve(): assert "HEALTHCHECK" in DOCKERFILE assert "/usr/local/bin/healthcheck.sh" in DOCKERFILE assert 'CMD ["serve"]' in DOCKERFILE assert 'ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]' in DOCKERFILE assert "/api/v1/health/ready" in HEALTHCHECK.read_text() assert "/api/v1/health/live" not in HEALTHCHECK.read_text() # No supervisor: one role per container (acceptance criterion 4). for supervisor in ("supervisord", "s6-overlay", "runit"): assert supervisor not in DOCKERFILE @pytest.mark.parametrize("script", [ENTRYPOINT, HEALTHCHECK]) def test_the_scripts_are_executable(script): assert script.stat().st_mode & stat.S_IXUSR, f"{script.name} must be executable in git" # ── the entrypoint, run as shell ───────────────────────────────────────────── @pytest.fixture def stubs(tmp_path): """A PATH where ``python`` records its arguments and ``id`` can be told a UID.""" bin_dir = tmp_path / "bin" bin_dir.mkdir() recorded = tmp_path / "argv" python = bin_dir / "python" python.write_text(f'#!/bin/sh\nprintf "%s\\n" "$@" > {recorded}\nexit 0\n') python.chmod(0o755) (bin_dir / "id").write_text('#!/bin/sh\nprintf "%s" "${STUB_UID:-1000}"\n') (bin_dir / "id").chmod(0o755) return bin_dir, recorded, tmp_path / "role" def run_script(script: Path, *args, stubs, env=None): bin_dir, recorded, role_file = stubs result = subprocess.run( ["/bin/sh", str(script), *args], capture_output=True, text=True, env={ "PATH": f"{bin_dir}:{os.environ['PATH']}", "PHOTO_PIPELINE_ROLE_FILE": str(role_file), **(env or {}), }, ) argv = recorded.read_text().splitlines() if recorded.exists() else [] return result, argv def test_the_container_refuses_to_run_as_root(stubs): result, argv = run_script(ENTRYPOINT, "serve", stubs=stubs, env={"STUB_UID": "0"}) assert result.returncode == 1 assert "refusing to run as root" in result.stderr assert argv == [], "the application is never started as root" assert not stubs[2].exists(), "not even the role marker is written" @pytest.mark.parametrize( "given,expected", [ (["serve"], ["-m", "photo_pipeline", "serve"]), (["worker", "--id", "worker-2"], ["-m", "photo_pipeline", "worker", "--id", "worker-2"]), # Every other management command stays reachable: operating the container is # operating the same CLI. (["diagnostics"], ["-m", "photo_pipeline", "diagnostics"]), ([], ["-m", "photo_pipeline"]), ], ) def test_the_role_selects_the_command_and_arguments_pass_through(given, expected, stubs): result, argv = run_script(ENTRYPOINT, *given, stubs=stubs) assert result.returncode == 0, result.stderr assert argv == expected def test_the_role_is_recorded_for_the_health_check(stubs): run_script(ENTRYPOINT, "worker", stubs=stubs) assert stubs[2].read_text() == "worker" def test_the_health_check_only_probes_the_serving_role(stubs): stubs[2].write_text("worker") result, argv = run_script(HEALTHCHECK, stubs=stubs) assert result.returncode == 0 and argv == [], "a worker has no endpoint to probe" stubs[2].write_text("serve") result, argv = run_script(HEALTHCHECK, stubs=stubs, env={"PHOTO_PIPELINE_PORT": "9123"}) assert result.returncode == 0, result.stderr assert argv == ["-", "9123"], "the configured port is the one probed" def test_the_health_check_fails_while_the_api_is_not_ready(stubs): """No python stub: the real interpreter probes a port nothing is listening on.""" stubs[2].write_text("serve") result = subprocess.run( ["/bin/sh", str(HEALTHCHECK)], capture_output=True, text=True, env={ "PATH": os.path.dirname(sys.executable) + os.pathsep + os.environ["PATH"], "PHOTO_PIPELINE_ROLE_FILE": str(stubs[2]), "PHOTO_PIPELINE_PORT": "1", }, ) assert result.returncode == 1 assert "not ready" in result.stderr def test_the_scripts_are_posix_shell(): """They run in the image's /bin/sh, which is dash — not bash.""" shells = ["/bin/sh"] + ([dash] if (dash := shutil.which("dash")) else []) for shell in shells: for script in (ENTRYPOINT, HEALTHCHECK): checked = subprocess.run([shell, "-n", str(script)], capture_output=True, text=True) assert checked.returncode == 0, f"{shell} {script.name}: {checked.stderr}"