from __future__ import annotations import json import tarfile import tempfile import unittest import argparse import sys from collections import OrderedDict from pathlib import Path sys.path.append(str(Path(__file__).resolve().parent)) from build_release import write_manifest # type: ignore import-not-found from build_offline_kit import build_offline_kit, compute_sha256 # type: ignore import-not-found class OfflineKitBuilderTests(unittest.TestCase): def setUp(self) -> None: self._temp = tempfile.TemporaryDirectory() self.base_path = Path(self._temp.name) self.out_dir = self.base_path / "out" self.release_dir = self.out_dir / "release" self.staging_dir = self.base_path / "staging" self.output_dir = self.base_path / "dist" self._create_sample_release() def tearDown(self) -> None: self._temp.cleanup() def _relative_to_out(self, path: Path) -> str: return path.relative_to(self.out_dir).as_posix() def _write_json(self, path: Path, payload: dict[str, object]) -> None: path.parent.mkdir(parents=True, exist_ok=True) with path.open("w", encoding="utf-8") as handle: json.dump(payload, handle, indent=2) handle.write("\n") def _create_sample_release(self) -> None: self.release_dir.mkdir(parents=True, exist_ok=True) sbom_path = self.release_dir / "artifacts/sboms/sample.cyclonedx.json" sbom_path.parent.mkdir(parents=True, exist_ok=True) sbom_path.write_text('{"bomFormat":"CycloneDX","specVersion":"1.5"}\n', encoding="utf-8") sbom_sha = compute_sha256(sbom_path) provenance_path = self.release_dir / "artifacts/provenance/sample.provenance.json" self._write_json( provenance_path, { "buildDefinition": {"buildType": "https://example/build"}, "runDetails": {"builder": {"id": "https://example/ci"}}, }, ) provenance_sha = compute_sha256(provenance_path) signature_path = self.release_dir / "artifacts/signatures/sample.signature" signature_path.parent.mkdir(parents=True, exist_ok=True) signature_path.write_text("signature-data\n", encoding="utf-8") signature_sha = compute_sha256(signature_path) metadata_path = self.release_dir / "artifacts/metadata/sample.metadata.json" self._write_json(metadata_path, {"digest": "sha256:1234"}) metadata_sha = compute_sha256(metadata_path) chart_path = self.release_dir / "helm/stellaops-1.0.0.tgz" chart_path.parent.mkdir(parents=True, exist_ok=True) chart_path.write_bytes(b"helm-chart-data") chart_sha = compute_sha256(chart_path) compose_path = self.release_dir.parent / "deploy/compose/docker-compose.dev.yaml" compose_path.parent.mkdir(parents=True, exist_ok=True) compose_path.write_text("services: {}\n", encoding="utf-8") compose_sha = compute_sha256(compose_path) debug_file = self.release_dir / "debug/.build-id/ab/cdef.debug" debug_file.parent.mkdir(parents=True, exist_ok=True) debug_file.write_bytes(b"\x7fELFDEBUGDATA") debug_sha = compute_sha256(debug_file) debug_manifest_path = self.release_dir / "debug/debug-manifest.json" debug_manifest = OrderedDict( ( ("generatedAt", "2025-10-26T00:00:00Z"), ("version", "1.0.0"), ("channel", "edge"), ( "artifacts", [ OrderedDict( ( ("buildId", "abcdef1234"), ("platform", "linux/amd64"), ("debugPath", "debug/.build-id/ab/cdef.debug"), ("sha256", debug_sha), ("size", debug_file.stat().st_size), ("components", ["sample"]), ("images", ["registry.example/sample@sha256:feedface"]), ("sources", ["app/sample.dll"]), ) ) ], ), ) ) self._write_json(debug_manifest_path, debug_manifest) debug_manifest_sha = compute_sha256(debug_manifest_path) (debug_manifest_path.with_suffix(debug_manifest_path.suffix + ".sha256")).write_text( f"{debug_manifest_sha} {debug_manifest_path.name}\n", encoding="utf-8", ) manifest = OrderedDict( ( ( "release", OrderedDict( ( ("version", "1.0.0"), ("channel", "edge"), ("date", "2025-10-26T00:00:00Z"), ("calendar", "2025.10"), ) ), ), ( "components", [ OrderedDict( ( ("name", "sample"), ("image", "registry.example/sample@sha256:feedface"), ("tags", ["registry.example/sample:1.0.0"]), ( "sbom", OrderedDict( ( ("path", self._relative_to_out(sbom_path)), ("sha256", sbom_sha), ) ), ), ( "provenance", OrderedDict( ( ("path", self._relative_to_out(provenance_path)), ("sha256", provenance_sha), ) ), ), ( "signature", OrderedDict( ( ("path", self._relative_to_out(signature_path)), ("sha256", signature_sha), ("ref", "sigstore://example"), ("tlogUploaded", True), ) ), ), ( "metadata", OrderedDict( ( ("path", self._relative_to_out(metadata_path)), ("sha256", metadata_sha), ) ), ), ) ) ], ), ( "charts", [ OrderedDict( ( ("name", "stellaops"), ("version", "1.0.0"), ("path", self._relative_to_out(chart_path)), ("sha256", chart_sha), ) ) ], ), ( "compose", [ OrderedDict( ( ("name", "docker-compose.dev.yaml"), ("path", compose_path.relative_to(self.out_dir).as_posix()), ("sha256", compose_sha), ) ) ], ), ( "debugStore", OrderedDict( ( ("manifest", "debug/debug-manifest.json"), ("sha256", debug_manifest_sha), ("entries", 1), ("platforms", ["linux/amd64"]), ("directory", "debug/.build-id"), ) ), ), ) ) write_manifest(manifest, self.release_dir) def test_build_offline_kit(self) -> None: args = argparse.Namespace( version="2025.10.0", channel="edge", bundle_id="bundle-001", release_dir=self.release_dir, staging_dir=self.staging_dir, output_dir=self.output_dir, cosign_key=None, cosign_password=None, cosign_identity_token=None, no_transparency=False, skip_smoke=True, ) result = build_offline_kit(args) bundle_path = Path(result["bundlePath"]) self.assertTrue(bundle_path.exists()) offline_manifest = self.output_dir.parent / "staging" / "manifest" / "offline-manifest.json" self.assertTrue(offline_manifest.exists()) with offline_manifest.open("r", encoding="utf-8") as handle: manifest_data = json.load(handle) artifacts = manifest_data["artifacts"] self.assertTrue(any(item["name"].startswith("sboms/") for item in artifacts)) metadata_path = Path(result["metadataPath"]) data = json.loads(metadata_path.read_text(encoding="utf-8")) self.assertTrue(data["bundleSha256"].startswith("sha256:")) self.assertTrue(data["manifestSha256"].startswith("sha256:")) with tarfile.open(bundle_path, "r:gz") as tar: members = tar.getnames() self.assertIn("manifest/release.yaml", members) self.assertTrue(any(name.startswith("sboms/sample-") for name in members)) if __name__ == "__main__": unittest.main()