Close RAILIANCE-WP-0015-T02 platform rapp schema convergence
Converge the S3 platform-service pattern onto ADR-0007, emit the reef-railiance live deployable inventory for the family coverage check, and mark T02 done. Declaration edits land in rapp-openbao and rapp-postgres.
This commit is contained in:
parent
deabcde73c
commit
ccb5972b42
5 changed files with 430 additions and 20 deletions
143
scripts/capture-reef-deployables.py
Executable file
143
scripts/capture-reef-deployables.py
Executable file
|
|
@ -0,0 +1,143 @@
|
|||
#!/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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue