63 lines
2.4 KiB
Python
63 lines
2.4 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Export a validated, public CCR as JSON for the stdlib node delivery helper.
|
||
|
|
|
||
|
|
Uses the platform's existing PyYAML validator. No credential lookup or live call.
|
||
|
|
After CCR status updates, YAML remains the source of truth; export and review its
|
||
|
|
new JSON hash before an attended delivery. Draft exports remain non-executable.
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import hashlib
|
||
|
|
import importlib.util
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
from pathlib import Path
|
||
|
|
import sys
|
||
|
|
|
||
|
|
from application_backup_credentials import PROFILES, contract
|
||
|
|
|
||
|
|
ROOT = Path(__file__).resolve().parents[1]
|
||
|
|
|
||
|
|
|
||
|
|
def render(source, application):
|
||
|
|
spec = importlib.util.spec_from_file_location("backup_ccr_validator", ROOT / "scripts/credential-change.py")
|
||
|
|
validator = importlib.util.module_from_spec(spec)
|
||
|
|
spec.loader.exec_module(validator)
|
||
|
|
ccr, errors, _ = validator.validate_ccr(source)
|
||
|
|
if errors or validator.applier_policy_violations(ccr):
|
||
|
|
raise ValueError("invalid_reader_contract")
|
||
|
|
policy = validator.policy_artifact_diff(ccr)
|
||
|
|
if not policy["matches"]:
|
||
|
|
raise ValueError("policy_source_mismatch")
|
||
|
|
ccr["source_ccr_sha256"] = hashlib.sha256(source.read_bytes()).hexdigest()
|
||
|
|
ccr["policy_sha256"] = hashlib.sha256(validator.generated_policy_hcl(ccr).encode()).hexdigest()
|
||
|
|
raw = (json.dumps(ccr, indent=2, sort_keys=True) + "\n").encode()
|
||
|
|
contract(raw, application)
|
||
|
|
return raw
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
||
|
|
parser.add_argument("--ccr", type=Path, required=True)
|
||
|
|
parser.add_argument("--application", choices=PROFILES, required=True)
|
||
|
|
parser.add_argument("--output", type=Path, required=True)
|
||
|
|
args = parser.parse_args()
|
||
|
|
try:
|
||
|
|
raw = render(args.ccr, args.application)
|
||
|
|
fd = os.open(args.output, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
|
||
|
|
with os.fdopen(fd, "wb") as stream:
|
||
|
|
stream.write(raw)
|
||
|
|
stream.flush()
|
||
|
|
os.fsync(stream.fileno())
|
||
|
|
print(json.dumps({"status": "rendered", "application": args.application,
|
||
|
|
"ccr_sha256": hashlib.sha256(raw).hexdigest(), "effects": ["public_contract_export"]}))
|
||
|
|
return 0
|
||
|
|
except Exception: # noqa: BLE001 - metadata-only fixed diagnostics
|
||
|
|
print('{"status":"failed","code":"contract_export_failed"}', file=sys.stderr)
|
||
|
|
return 1
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(main())
|