#!/usr/bin/env python3 """Generate inspectable release provenance for the images built by CI.""" from __future__ import annotations import hashlib import json import os import subprocess from pathlib import Path IMAGES = { "api": "mobilityops-api-release", "web": "mobilityops-web-release", "backup_tools": "mobilityops-backup-tools-release", } def sha256(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() def main() -> None: revision = os.environ.get("GITHUB_SHA", "") if len(revision) != 40: raise SystemExit("GITHUB_SHA must contain the full release revision") images: dict[str, dict[str, str]] = {} for name, image in IMAGES.items(): details = json.loads( subprocess.check_output(["docker", "image", "inspect", image], text=True) )[0] labels = details.get("Config", {}).get("Labels", {}) or {} if labels.get("org.opencontainers.image.revision") != revision: raise SystemExit(f"revision label mismatch for {image}") sbom = Path(f"mobilityops-{name.replace('_', '-')}-sbom.cdx.json") images[name] = { "reference": image, "local_image_id": details["Id"], "revision": labels["org.opencontainers.image.revision"], "sbom": sbom.name, "sbom_sha256": sha256(sbom), } provenance = { "schema": "mobilityops.release-provenance.v1", "revision": revision, "repository": os.environ.get("GITHUB_REPOSITORY", "MobilityOps"), "ref": os.environ.get("GITHUB_REF", ""), "workflow_run": os.environ.get("GITHUB_RUN_ID", ""), "images": images, } Path("release-provenance.json").write_text( json.dumps(provenance, indent=2, sort_keys=True) + "\n", encoding="utf-8" ) if __name__ == "__main__": main()