Files
git.stella-ops.org/devops/release/check_release_manifest.py
2025-12-26 18:11:06 +02:00

90 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""
Fail-fast validator for release manifests and downloads manifest.
Checks presence of required components and expected fields so release pipelines
can surface missing artefacts early (instead of blocking deploy tasks later).
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import yaml
REQUIRED_COMPONENTS = [
"orchestrator",
"policy-registry",
"vex-lens",
"issuer-directory",
"findings-ledger",
"vuln-explorer-api",
"packs-registry",
"task-runner",
"web-ui",
]
def load_yaml(path: Path):
try:
return yaml.safe_load(path.read_text())
except Exception as exc:
raise SystemExit(f"ERROR: failed to parse {path}: {exc}")
def check_manifest(manifest_path: Path) -> list[str]:
data = load_yaml(manifest_path)
comps = {c.get("name") for c in data.get("release", {}).get("components", [])}
missing = [c for c in REQUIRED_COMPONENTS if c not in comps]
return missing
def check_downloads(downloads_path: Path) -> list[str]:
missing = []
try:
data = json.loads(downloads_path.read_text())
except Exception as exc:
return [f"{downloads_path}: invalid JSON ({exc})"]
items = data.get("items", [])
if not items:
missing.append(f"{downloads_path}: no items found")
for idx, item in enumerate(items):
for field in ("name", "type"):
if field not in item:
missing.append(f"{downloads_path}: item {idx} missing '{field}'")
if item.get("type") == "container" and "image" not in item:
missing.append(f"{downloads_path}: item {idx} missing 'image'")
if item.get("type") == "archive" and "sha256" not in item:
missing.append(f"{downloads_path}: item {idx} missing 'sha256'")
return missing
def main():
manifest = Path("deploy/releases/2025.09-stable.yaml")
airgap = Path("deploy/releases/2025.09-airgap.yaml")
downloads = Path("deploy/downloads/manifest.json")
errors: list[str] = []
for path in (manifest, airgap):
if not path.exists():
errors.append(f"{path}: file missing")
continue
missing = check_manifest(path)
if missing:
errors.append(f"{path}: missing components -> {', '.join(missing)}")
if downloads.exists():
errors.extend(check_downloads(downloads))
else:
errors.append(f"{downloads}: file missing")
if errors:
print("FAIL\n" + "\n".join(f"- {e}" for e in errors))
sys.exit(1)
print("OK: required components present and downloads manifest is well-formed.")
if __name__ == "__main__":
main()