Files
git.stella-ops.org/scripts/mirror/verify_oci_layout.py
StellaOps Bot cce96f3596
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
blockers 2
2025-11-23 14:54:17 +02:00

78 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""
Verify OCI layout emitted by make-thin-v1.sh when OCI=1.
Checks:
1) oci-layout exists and version is 1.0.0
2) index.json manifest digest/size match manifest.json hash/size
3) manifest.json references config/layers present in blobs with matching sha256 and size
Usage:
python scripts/mirror/verify_oci_layout.py out/mirror/thin/oci
Exit 0 on success, non-zero on failure with message.
"""
import hashlib, json, pathlib, sys
def sha256(path: pathlib.Path) -> str:
h = hashlib.sha256()
with path.open('rb') as f:
for chunk in iter(lambda: f.read(8192), b''):
h.update(chunk)
return h.hexdigest()
def main():
if len(sys.argv) != 2:
print(__doc__)
sys.exit(2)
root = pathlib.Path(sys.argv[1])
layout = root / "oci-layout"
index = root / "index.json"
manifest = root / "manifest.json"
if not layout.exists() or not index.exists() or not manifest.exists():
raise SystemExit("missing oci-layout/index.json/manifest.json")
layout_obj = json.loads(layout.read_text())
if layout_obj.get("imageLayoutVersion") != "1.0.0":
raise SystemExit("oci-layout version not 1.0.0")
idx_obj = json.loads(index.read_text())
if not idx_obj.get("manifests"):
raise SystemExit("index.json manifests empty")
man_digest = idx_obj["manifests"][0]["digest"]
man_size = idx_obj["manifests"][0]["size"]
actual_man_sha = sha256(manifest)
if man_digest != f"sha256:{actual_man_sha}":
raise SystemExit(f"manifest digest mismatch: {man_digest} vs sha256:{actual_man_sha}")
if man_size != manifest.stat().st_size:
raise SystemExit("manifest size mismatch")
man_obj = json.loads(manifest.read_text())
blobs = root / "blobs" / "sha256"
# config
cfg_digest = man_obj["config"]["digest"].split(":",1)[1]
cfg_size = man_obj["config"]["size"]
cfg_path = blobs / cfg_digest
if not cfg_path.exists():
raise SystemExit(f"config blob missing: {cfg_path}")
if cfg_path.stat().st_size != cfg_size:
raise SystemExit("config size mismatch")
if sha256(cfg_path) != cfg_digest:
raise SystemExit("config digest mismatch")
for layer in man_obj.get("layers", []):
ldigest = layer["digest"].split(":",1)[1]
lsize = layer["size"]
lpath = blobs / ldigest
if not lpath.exists():
raise SystemExit(f"layer blob missing: {lpath}")
if lpath.stat().st_size != lsize:
raise SystemExit("layer size mismatch")
if sha256(lpath) != ldigest:
raise SystemExit("layer digest mismatch")
print("OK: OCI layout verified")
if __name__ == "__main__":
main()