Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
89 lines
3.3 KiB
Python
89 lines
3.3 KiB
Python
"""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())
|