202 lines
7.4 KiB
Python
202 lines
7.4 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _load(name: str, path: Path):
|
|
spec = importlib.util.spec_from_file_location(name, path)
|
|
assert spec is not None and spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
OCI = _load("resolve_oci_platforms", ROOT / "tools/release/resolve-oci-platforms.py")
|
|
FINALIZE = _load(
|
|
"finalize_runtime_distribution",
|
|
ROOT / "tools/release/finalize-runtime-distribution.py",
|
|
)
|
|
DEPLOYER_BUILD = _load(
|
|
"build_deployer_zipapp",
|
|
ROOT / "tools/deployment/build-deployer-zipapp.py",
|
|
)
|
|
|
|
|
|
class RuntimeDistributionBuildTests(unittest.TestCase):
|
|
def test_deployment_zipapp_is_reproducible_across_source_mtimes(self) -> None:
|
|
with tempfile.TemporaryDirectory(prefix="govoplan-reproducible-zipapp-") as value:
|
|
root = Path(value)
|
|
source = root / "source"
|
|
shutil.copytree(ROOT / "tools/deployment", source)
|
|
first = root / "first.pyz"
|
|
second = root / "second.pyz"
|
|
original_root = DEPLOYER_BUILD.ROOT
|
|
try:
|
|
DEPLOYER_BUILD.ROOT = source
|
|
self.assertEqual(0, DEPLOYER_BUILD.main(["--output", str(first)]))
|
|
for path in source.rglob("*.py"):
|
|
os.utime(path, (2_000_000_000, 2_000_000_000))
|
|
self.assertEqual(0, DEPLOYER_BUILD.main(["--output", str(second)]))
|
|
finally:
|
|
DEPLOYER_BUILD.ROOT = original_root
|
|
|
|
self.assertEqual(first.read_bytes(), second.read_bytes())
|
|
|
|
def test_workflow_signs_with_the_release_environment(self) -> None:
|
|
workflow = (ROOT / ".gitea/workflows/runtime-distribution.yml").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
|
|
self.assertIn(
|
|
".runtime-build/bin/python tools/release/generate-runtime-distribution.py",
|
|
workflow,
|
|
)
|
|
self.assertNotIn(
|
|
"\n python tools/release/generate-runtime-distribution.py",
|
|
workflow,
|
|
)
|
|
|
|
def test_workflow_verifies_portable_bootstrap_artifacts_before_execution(
|
|
self,
|
|
) -> None:
|
|
workflow = (ROOT / ".gitea/workflows/runtime-distribution.yml").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
|
|
self.assertIn(
|
|
"(cd runtime-output && sha256sum govoplan-deploy.pyz > "
|
|
"govoplan-deploy.pyz.sha256)",
|
|
workflow,
|
|
)
|
|
self.assertIn("openssl pkeyutl -verify -pubin", workflow)
|
|
self.assertIn("govoplan-deploy.tampered.pyz", workflow)
|
|
self.assertLess(
|
|
workflow.index("openssl pkeyutl -verify -pubin"),
|
|
workflow.index("python runtime-output/govoplan-deploy.pyz init"),
|
|
)
|
|
|
|
def test_workflow_retains_both_platform_runtime_smoke_receipts(self) -> None:
|
|
workflow = (ROOT / ".gitea/workflows/runtime-distribution.yml").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
|
|
self.assertIn('for ARCH in amd64 arm64; do', workflow)
|
|
self.assertIn("tools/checks/runtime-image-smoke.py", workflow)
|
|
self.assertIn("runtime-smoke-amd64.json", workflow)
|
|
self.assertIn("runtime-smoke-arm64.json", workflow)
|
|
|
|
def test_resolves_platforms_and_builds_evidence_descriptor(self) -> None:
|
|
index = {
|
|
"schemaVersion": 2,
|
|
"manifests": [
|
|
{
|
|
"digest": "sha256:" + "1" * 64,
|
|
"platform": {"os": "linux", "architecture": "amd64"},
|
|
},
|
|
{
|
|
"digest": "sha256:" + "2" * 64,
|
|
"platform": {"os": "linux", "architecture": "arm64"},
|
|
},
|
|
],
|
|
}
|
|
metadata = OCI.resolve_platforms(
|
|
index,
|
|
repository="registry.example/govoplan/api",
|
|
index_digest="sha256:" + "a" * 64,
|
|
)
|
|
self.assertEqual(
|
|
"registry.example/govoplan/api@sha256:" + "1" * 64,
|
|
metadata["platforms"]["linux/amd64"],
|
|
)
|
|
|
|
with tempfile.TemporaryDirectory(prefix="govoplan-runtime-finalize-") as value:
|
|
root = Path(value)
|
|
composition = {
|
|
"schema_version": "1",
|
|
"python": {
|
|
"packages": [
|
|
{
|
|
"package": "govoplan-core",
|
|
"version": "1.2.3",
|
|
"sha256": "8" * 64,
|
|
}
|
|
],
|
|
"module_ids": ["access"],
|
|
"wheelhouse_sha256": "9" * 64,
|
|
"wheel_count": 1,
|
|
},
|
|
"web": {"sha256": "7" * 64, "file_count": 4},
|
|
}
|
|
(root / "composition.json").write_text(json.dumps(composition))
|
|
(root / "api.json").write_text(json.dumps(metadata))
|
|
web_metadata = {
|
|
"index": "registry.example/govoplan/web@sha256:" + "b" * 64,
|
|
"platforms": {
|
|
"linux/amd64": "registry.example/govoplan/web@sha256:" + "3" * 64,
|
|
"linux/arm64": "registry.example/govoplan/web@sha256:" + "4" * 64,
|
|
},
|
|
}
|
|
(root / "web.json").write_text(json.dumps(web_metadata))
|
|
deployer = root / "govoplan-deploy.pyz"
|
|
deployer.write_bytes(b"zipapp")
|
|
args = argparse.Namespace(
|
|
composition=root / "composition.json",
|
|
api_metadata=root / "api.json",
|
|
web_metadata=root / "web.json",
|
|
deployer=deployer,
|
|
deployer_url="https://downloads.example/govoplan-deploy.pyz",
|
|
artifact_base_url="https://downloads.example/runtime/v1.2.3",
|
|
source_commit="f" * 40,
|
|
version="1.2.3",
|
|
channel="stable",
|
|
sequence=1,
|
|
expires_days=30,
|
|
dependency=[
|
|
"postgres=docker.io/library/postgres@sha256:" + "5" * 64,
|
|
"redis=docker.io/library/redis@sha256:" + "6" * 64,
|
|
],
|
|
output_directory=root / "evidence",
|
|
descriptor=root / "descriptor.json",
|
|
)
|
|
|
|
descriptor = FINALIZE.finalize(args)
|
|
|
|
self.assertEqual(["access"], descriptor["composition"]["module_ids"])
|
|
self.assertEqual(
|
|
"registry.example/govoplan/api@sha256:" + "a" * 64,
|
|
descriptor["images"]["api"]["index"],
|
|
)
|
|
self.assertTrue((root / "evidence/api-sbom.cdx.json").is_file())
|
|
self.assertTrue((root / "evidence/web-provenance.json").is_file())
|
|
|
|
def test_rejects_incomplete_oci_index(self) -> None:
|
|
with self.assertRaisesRegex(ValueError, "linux/amd64 and linux/arm64"):
|
|
OCI.resolve_platforms(
|
|
{
|
|
"manifests": [
|
|
{
|
|
"digest": "sha256:" + "1" * 64,
|
|
"platform": {"os": "linux", "architecture": "amd64"},
|
|
}
|
|
]
|
|
},
|
|
repository="registry.example/govoplan/api",
|
|
index_digest="sha256:" + "a" * 64,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|