Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
release-manifest-verify / verify (push) Has been cancelled
- Implement comprehensive tests for PackRunAttestationService, covering attestation generation, verification, and event emission. - Add tests for SealedInstallEnforcer to validate sealed install requirements and enforcement logic. - Introduce a MonacoLoaderService stub for testing purposes to prevent Monaco workers/styles from loading during Karma runs.
90 lines
2.6 KiB
Python
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()
|