feat(ops): add release provenance gate and audit checkout isolation
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
committed by
Hermes Agent
co-authored by
genichin
parent
a6ae800346
commit
f3a6b2c410
@@ -0,0 +1,88 @@
|
||||
"""Collect release/restart provenance and enforce audit-checkout clean gate."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
from trex_core.adapters.persistence.ops_checks import (
|
||||
collect_release_provenance,
|
||||
isolate_dirty_checkout_files,
|
||||
)
|
||||
|
||||
_DEFAULT_OUTPUT_DIR = Path("artifacts/validation/release_provenance")
|
||||
_DEFAULT_ISOLATE_DIR = Path("artifacts/validation/release_provenance_isolation")
|
||||
|
||||
|
||||
def _record_path(output_dir: Path, *, instance: str, command_class: str) -> Path:
|
||||
stamp = datetime.now(tz=UTC).strftime("%Y%m%dT%H%M%SZ")
|
||||
return output_dir / f"{stamp}--{instance}--{command_class}.json"
|
||||
|
||||
|
||||
def _parse_capture_command(raw: str) -> tuple[str, list[str]]:
|
||||
if "=" not in raw:
|
||||
raise ValueError("capture command must be KEY=command")
|
||||
key, command = raw.split("=", 1)
|
||||
key = key.strip()
|
||||
parts = [x for x in command.strip().split(" ") if x]
|
||||
if not key or not parts:
|
||||
raise ValueError("capture command must be KEY=command")
|
||||
return key, parts
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--actor", required=True)
|
||||
parser.add_argument("--reason", required=True)
|
||||
parser.add_argument("--instance", required=True)
|
||||
parser.add_argument("--command-class", required=True)
|
||||
parser.add_argument("--checkout-dir", default=".")
|
||||
parser.add_argument("--expected-oci-revision")
|
||||
parser.add_argument("--image-digest")
|
||||
parser.add_argument("--runtime-file", action="append", default=[])
|
||||
parser.add_argument("--broker-log-path", default="/tmp/trex-core.log")
|
||||
parser.add_argument("--capture-command", action="append", default=[])
|
||||
parser.add_argument("--output-dir", default=str(_DEFAULT_OUTPUT_DIR))
|
||||
parser.add_argument("--isolate-dir", default=str(_DEFAULT_ISOLATE_DIR))
|
||||
parser.add_argument("--allow-dirty", action="store_true")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
capture_commands = {}
|
||||
for raw in args.capture_command:
|
||||
key, command = _parse_capture_command(raw)
|
||||
capture_commands[key] = command
|
||||
|
||||
report = collect_release_provenance(
|
||||
actor=args.actor,
|
||||
reason=args.reason,
|
||||
instance=args.instance,
|
||||
command_class=args.command_class,
|
||||
checkout_dir=args.checkout_dir,
|
||||
expected_oci_revision=args.expected_oci_revision,
|
||||
image_digest=args.image_digest,
|
||||
runtime_files=args.runtime_file,
|
||||
capture_commands=capture_commands,
|
||||
broker_log_path=args.broker_log_path,
|
||||
)
|
||||
|
||||
output_dir = Path(args.output_dir).resolve()
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
output_path = _record_path(output_dir, instance=args.instance, command_class=args.command_class)
|
||||
|
||||
if report["checkout"]["dirty_paths"] and not args.allow_dirty:
|
||||
report["dirty_checkout_isolation"] = isolate_dirty_checkout_files(
|
||||
checkout_dir=args.checkout_dir,
|
||||
isolate_dir=args.isolate_dir,
|
||||
)
|
||||
|
||||
output_path.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
print(str(output_path))
|
||||
if report["checkout"]["dirty_paths"] and not args.allow_dirty:
|
||||
return 2
|
||||
return 0 if report["gate"]["passed"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user