#!/usr/bin/env python3 """ convert_service_yaml.py Converts the legacy *services.yaml* (the file you pasted) into a clean, graph‑friendly format that contains only nodes and edges. Usage: python convert_service_yaml.py [--out OUT_FILE] [--format json|yaml] """ import argparse, sys from pathlib import Path import yaml from collections import defaultdict # --------------------------------------------------------------------------- # # 1️⃣ Canonical relation names – keep them short & consistent. CANONICAL = { "depends_on": "depends_on", "connects_to": "connects_to", "publishes_to": "publishes_to", "invokes": "invokes", "belongs_to": "belongs_to", "triggers": "triggers", # you can add more if needed } # --------------------------------------------------------------------------- # def canonical_rel(rel: str) -> str: """Return the canonical relation name, falling back to lower‑case.""" return CANONICAL.get(rel.lower(), rel.replace("_", "-").lower()) # --------------------------------------------------------------------------- # def load_legacy(path: Path): with path.open() as f: data = yaml.safe_load(f) # 1. Build node map nodes = {} for svc in data.get("services", []): name = svc["name"] nodes[name] = { "id": name, "type": svc["type"], "meta": svc.get("meta") or {}, # keep meta only if present } # 2. Build edge list edges = [] def add_edge(src: str, tgt: str, rel: str): edges.append({ "source": src, "target": tgt, "relation": canonical_rel(rel), }) # a) Dependencies inside services for svc in data.get("services", []): src = svc["name"] for dep in svc.get("dependencies", []): add_edge(src, dep["name"], dep["type"]) # b) Kafka‑topic special dependencies (belongs_to / triggers) for topic in data.get("services", []): if topic["type"] == "kafka-topic": src = topic["name"] for dep in topic.get("dependencies", []): add_edge(src, dep["name"], dep["type"]) return {"services": list(nodes.values()), "dependencies": edges} # --------------------------------------------------------------------------- # def dump_output(data: dict, fmt: str) -> str: if fmt == "yaml": return yaml.safe_dump( data, sort_keys=False, default_flow_style=False, width=1000, # keep long URLs readable ) elif fmt == "json": import json return json.dumps(data, indent=2, ensure_ascii=False) else: raise ValueError(f"Unsupported format: {fmt}") # --------------------------------------------------------------------------- # def main(argv=None): parser = argparse.ArgumentParser( description="Convert legacy services.yaml to a graph‑friendly format." ) parser.add_argument("legacy", type=Path, help="path to the original YAML file") parser.add_argument( "--out", type=Path, default=None, help="where to write the new file (default: stdout)", ) parser.add_argument( "--format", choices=["yaml", "json"], default="yaml", help="output format – YAML (default) or JSON", ) args = parser.parse_args(argv) if not args.legacy.exists(): sys.exit(f"❌ File not found: {args.legacy}") result = load_legacy(args.legacy) output = dump_output(result, args.format) if args.out: args.out.write_text(output, encoding="utf-8") print(f"✅ Written to {args.out}") else: sys.stdout.write(output) if __name__ == "__main__": main()