#!/usr/bin/env python3 """Capture live reef-railiance deployables for the family-declaration validator. The validator in railiance-master consumes this file via `--inventory` and must not grow a cluster dependency of its own. Substrate namespaces and Knative revision Deployments are omitted so the coverage check sees workload units, not rail machinery. """ from __future__ import annotations import argparse import json import re import subprocess import sys from datetime import datetime, timezone from typing import Any DEFAULT_REMOTE = "railiance01" DEFAULT_REEF = "reef-railiance" SOURCE = "railiance-platform" SUBSTRATE_NAMESPACES = { "cert-manager", "cert-manager-test", "cnpg-system", "default", "external-secrets", "knative-serving", "kube-node-lease", "kube-public", "kube-system", "kourier-system", "local-path-storage", } EPHEMERAL_NAMESPACE_SUFFIXES = ("-drill", "-test") KNATIVE_REVISION_DEPLOY = re.compile(r".*-\d{5}-deployment$") RESOURCE_SPECS = ( ("Deployment", "deploy"), ("StatefulSet", "sts"), ("Cluster", "cluster.postgresql.cnpg.io"), ("KnativeService", "service.serving.knative.dev"), ) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__.split("\n\n", 1)[0]) parser.add_argument( "--remote", default=DEFAULT_REMOTE, help=f"SSH host that can reach the reef API (default: {DEFAULT_REMOTE})", ) parser.add_argument( "--reef", default=DEFAULT_REEF, help=f"Reef identifier recorded in the inventory (default: {DEFAULT_REEF})", ) parser.add_argument( "-o", "--output", help="Write JSON to this path instead of stdout", ) return parser.parse_args() def remote_get(remote: str, resource: str) -> dict[str, Any]: cmd = [ "ssh", "-o", "BatchMode=yes", remote, "kubectl", "get", resource, "-A", "-o", "json", ] result = subprocess.run(cmd, check=False, capture_output=True, text=True) if result.returncode != 0: stderr = (result.stderr or "").strip() if "the server doesn't have a resource type" in stderr: return {"items": []} raise RuntimeError(f"{' '.join(cmd)} failed ({result.returncode}): {stderr}") return json.loads(result.stdout) def skip_namespace(name: str) -> bool: if name in SUBSTRATE_NAMESPACES: return True return any(name.endswith(suffix) for suffix in EPHEMERAL_NAMESPACE_SUFFIXES) def collect(remote: str) -> list[dict[str, str]]: seen: set[tuple[str, str, str]] = set() deployables: list[dict[str, str]] = [] for kind, resource in RESOURCE_SPECS: payload = remote_get(remote, resource) for item in payload.get("items") or []: meta = item.get("metadata") or {} name = meta.get("name") namespace = meta.get("namespace") or "" if not isinstance(name, str) or not name: continue if skip_namespace(namespace): continue if kind == "Deployment" and KNATIVE_REVISION_DEPLOY.fullmatch(name): continue key = (name, namespace, kind) if key in seen: continue seen.add(key) deployables.append({"name": name, "namespace": namespace, "kind": kind}) deployables.sort(key=lambda row: (row["namespace"], row["name"], row["kind"])) return deployables def main() -> int: args = parse_args() try: deployables = collect(args.remote) except (RuntimeError, json.JSONDecodeError) as exc: print(f"capture failed: {exc}", file=sys.stderr) return 1 inventory = { "source": SOURCE, "captured_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "reef": args.reef, "deployables": deployables, } text = json.dumps(inventory, indent=2) + "\n" if args.output: with open(args.output, "w", encoding="utf-8") as fh: fh.write(text) else: sys.stdout.write(text) return 0 if __name__ == "__main__": sys.exit(main())