#!/usr/bin/env python3 """deploy-plan.py — map changed paths to deployable services. Thin wrapper over scripts/service-map.py, which owns the path->service semantics in the shared inventory (shared/services.yml): per-service path rules, the quadlet netns/network coupling graph, host scoping (a host's directory can only select that host's services), and canonical restart order. This file keeps the deployer's CLI contract only. Usage: deploy-plan.py --target NAME --repo DIR --base REV --head REV deploy-plan.py --target NAME [paths...] (paths also on stdin) Prints one service name per line (canonical restart order); exit 0 with no output when nothing relevant changed. """ import os import subprocess import sys SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) SERVICE_MAP = os.path.join(SCRIPT_DIR, "service-map.py") def git_paths(repo: str, base: str, head: str) -> list[str]: proc = subprocess.run( ["git", "-C", repo, "diff", "--name-only", base, head], text=True, capture_output=True) if proc.returncode != 0: sys.stderr.write(f"deploy-plan: git diff failed: {proc.stderr}") raise SystemExit(1) return [line for line in proc.stdout.splitlines() if line] def main() -> int: args = sys.argv[1:] if not args or args[0] != "--target": print(__doc__, file=sys.stderr) return 2 if len(args) < 2: print("missing target name after --target", file=sys.stderr) return 2 target = args[1] # `--paths` is the historic seam keyword: a flag, not an option — the # paths are the positional arguments that follow it. rest = [a for a in args[2:] if a != "--paths"] if "--repo" in rest: try: repo = rest[rest.index("--repo") + 1] base = rest[rest.index("--base") + 1] head = rest[rest.index("--head") + 1] except (ValueError, IndexError): print("--repo, --base and --head must appear together", file=sys.stderr) return 2 paths = git_paths(repo, base, head) else: paths = rest or [line.strip() for line in sys.stdin if line.strip()] if not paths: return 0 proc = subprocess.run([sys.executable, SERVICE_MAP, "plan", "--host", target] + paths, text=True, capture_output=True) if proc.returncode != 0: sys.stderr.write(proc.stderr) return proc.returncode sys.stdout.write(proc.stdout) return 0 if __name__ == "__main__": sys.exit(main())