217 lines
7.9 KiB
Python
217 lines
7.9 KiB
Python
"""Unit contracts for the transportable, exact RC candidate image set."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import importlib.util
|
|
import io
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import tarfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
RELEASE_BUILD = ROOT / "scripts" / "release_build.py"
|
|
|
|
|
|
def _release_build_module():
|
|
spec = importlib.util.spec_from_file_location("rc_candidate_release_build", RELEASE_BUILD)
|
|
assert spec and spec.loader
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def _write_public_source_manifest(root: Path, module, source_revision: str) -> dict[str, object]:
|
|
files = []
|
|
for relative_path in module.SOURCE_DOCKERFILES:
|
|
path = root / relative_path
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
payload = (
|
|
f'LABEL {module.OCI_SOURCE_LABEL}="{module.PUBLIC_SOURCE_REPOSITORY}"\n'
|
|
).encode()
|
|
path.write_bytes(payload)
|
|
files.append(
|
|
{
|
|
"path": relative_path,
|
|
"bytes": len(payload),
|
|
"sha256": hashlib.sha256(payload).hexdigest(),
|
|
}
|
|
)
|
|
manifest: dict[str, object] = {
|
|
"schemaVersion": 1,
|
|
"sourceRevision": source_revision,
|
|
"sourceRepository": module.PUBLIC_SOURCE_REPOSITORY,
|
|
"files": files,
|
|
}
|
|
(root / "PUBLIC_SOURCE_MANIFEST.json").write_text(json.dumps(manifest), encoding="utf-8")
|
|
return manifest
|
|
|
|
|
|
def test_release_command_failure_keeps_the_docker_error_tail(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
module = _release_build_module()
|
|
failure = subprocess.CompletedProcess(
|
|
["docker", "build"], 73, stdout="", stderr="progress\n" * 300 + "final Docker error"
|
|
)
|
|
monkeypatch.setattr(module.subprocess, "run", lambda *_args, **_kwargs: failure)
|
|
|
|
with pytest.raises(RuntimeError) as error:
|
|
module.run("docker", "build")
|
|
|
|
assert "final Docker error" in str(error.value)
|
|
assert "failed (73)" in str(error.value)
|
|
assert "progress\n" * 300 not in str(error.value)
|
|
|
|
|
|
def _add_file(archive: tarfile.TarFile, name: str, payload: bytes) -> None:
|
|
info = tarfile.TarInfo(name)
|
|
info.size = len(payload)
|
|
archive.addfile(info, io.BytesIO(payload))
|
|
|
|
|
|
def _image_records(module) -> dict[str, dict[str, object]]:
|
|
records: dict[str, dict[str, object]] = {}
|
|
for index, name in enumerate(module.IMAGES):
|
|
records[name] = {
|
|
"tag": f"{name}:1.3.0-rc.1",
|
|
"architecture": "amd64",
|
|
"labels": {
|
|
"org.opencontainers.image.version": "1.3.0-rc.1",
|
|
"org.opencontainers.image.revision": "a" * 40,
|
|
},
|
|
"rootfs_layers": [f"sha256:{index + 1:064x}"],
|
|
}
|
|
return records
|
|
|
|
|
|
def _write_bundle(path: Path, records: dict[str, dict[str, object]]) -> None:
|
|
manifest: list[dict[str, object]] = []
|
|
with tarfile.open(path, "w") as archive:
|
|
for index, (_name, record) in enumerate(records.items()):
|
|
config = f"{index:064x}.json"
|
|
layer = f"{index:064x}/layer.tar"
|
|
payload = {
|
|
"architecture": record["architecture"],
|
|
"config": {"Labels": record["labels"]},
|
|
"rootfs": {"diff_ids": record["rootfs_layers"]},
|
|
}
|
|
_add_file(archive, config, json.dumps(payload).encode())
|
|
_add_file(archive, layer, b"layer")
|
|
manifest.append({"Config": config, "RepoTags": [record["tag"]], "Layers": [layer]})
|
|
_add_file(archive, "manifest.json", json.dumps(manifest).encode())
|
|
|
|
|
|
def test_image_bundle_binds_exact_tags_labels_and_layer_sets(tmp_path: Path) -> None:
|
|
module = _release_build_module()
|
|
records = _image_records(module)
|
|
bundle = tmp_path / "candidate-images.tar"
|
|
_write_bundle(bundle, records)
|
|
|
|
verified = module.verify_image_bundle(bundle, records)
|
|
|
|
assert set(verified) == set(module.IMAGES)
|
|
assert verified["modelforge-api"]["tag"] == "modelforge-api:1.3.0-rc.1"
|
|
|
|
|
|
def test_image_bundle_rejects_a_changed_filesystem_layer_set(tmp_path: Path) -> None:
|
|
module = _release_build_module()
|
|
records = _image_records(module)
|
|
bundle = tmp_path / "candidate-images.tar"
|
|
_write_bundle(bundle, records)
|
|
records["modelforge-api"]["rootfs_layers"] = ["sha256:" + "f" * 64]
|
|
|
|
with pytest.raises(ValueError, match="layer set"):
|
|
module.verify_image_bundle(bundle, records)
|
|
|
|
|
|
def test_parentless_export_requires_an_exact_canonical_source_manifest(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
module = _release_build_module()
|
|
build_commit = "b" * 40
|
|
canonical_commit = "a" * 40
|
|
|
|
monkeypatch.setattr(module, "ROOT", tmp_path)
|
|
monkeypatch.setattr(
|
|
module,
|
|
"run",
|
|
lambda *args, **kwargs: {
|
|
("git", "rev-parse", "HEAD"): build_commit,
|
|
("git", "status", "--porcelain"): "",
|
|
("git", "describe", "--tags", "--always"): "candidate",
|
|
("git", "remote", "get-url", "origin"): "",
|
|
("git", "rev-parse", "--abbrev-ref", "HEAD"): "main",
|
|
}.get(args, ""),
|
|
)
|
|
|
|
with pytest.raises(SystemExit, match="unbound source-label override"):
|
|
module.source_identity(False, canonical_commit)
|
|
|
|
manifest = _write_public_source_manifest(tmp_path, module, canonical_commit)
|
|
|
|
with pytest.raises(SystemExit, match="requires --canonical-source-commit"):
|
|
module.source_identity(False)
|
|
|
|
identity = module.source_identity(False, canonical_commit)
|
|
|
|
assert identity["commit"] == canonical_commit
|
|
assert identity["build_commit"] == build_commit
|
|
assert identity["public_source_manifest_sha256"]
|
|
assert identity["source_repository"] == module.PUBLIC_SOURCE_REPOSITORY
|
|
|
|
manifest_path = tmp_path / "PUBLIC_SOURCE_MANIFEST.json"
|
|
manifest["sourceRepository"] = "https://attacker.invalid/forged"
|
|
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
|
with pytest.raises(SystemExit, match="approved sanitized identity"):
|
|
module.source_identity(False, canonical_commit)
|
|
|
|
manifest["sourceRepository"] = module.PUBLIC_SOURCE_REPOSITORY
|
|
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
|
dockerfile = tmp_path / module.SOURCE_DOCKERFILES[0]
|
|
dockerfile.write_text("LABEL org.opencontainers.image.source=\"https://attacker.invalid\"\n")
|
|
with pytest.raises(SystemExit, match="does not bind the current"):
|
|
module.source_identity(False, canonical_commit)
|
|
|
|
|
|
def test_private_release_identity_uses_the_canonical_https_source(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
module = _release_build_module()
|
|
commit = "b" * 40
|
|
monkeypatch.setattr(module, "ROOT", ROOT)
|
|
monkeypatch.setattr(
|
|
module,
|
|
"run",
|
|
lambda *args, **kwargs: {
|
|
("git", "rev-parse", "HEAD"): commit,
|
|
("git", "status", "--porcelain"): "",
|
|
("git", "describe", "--tags", "--always"): "candidate",
|
|
("git", "remote", "get-url", "origin"): "ssh://private.example/repo.git",
|
|
("git", "rev-parse", "--abbrev-ref", "HEAD"): "main",
|
|
}.get(args, ""),
|
|
)
|
|
|
|
identity = module.source_identity(False)
|
|
|
|
assert identity["source_repository"] == module.CANONICAL_SOURCE_REPOSITORY
|
|
|
|
|
|
def test_release_builder_checks_built_image_source_labels() -> None:
|
|
module = _release_build_module()
|
|
source = module.PUBLIC_SOURCE_REPOSITORY
|
|
records = {
|
|
"modelforge-api": {"labels": {module.OCI_SOURCE_LABEL: source}},
|
|
"modelforge-web": {"labels": {module.OCI_SOURCE_LABEL: source}},
|
|
}
|
|
|
|
module._assert_image_source_identity(records, source)
|
|
records["modelforge-web"]["labels"][module.OCI_SOURCE_LABEL] = "https://attacker.invalid"
|
|
with pytest.raises(SystemExit, match="source label does not match source identity"):
|
|
module._assert_image_source_identity(records, source)
|