diff --git a/nautobot/tp44-ipam-prefixes.py b/nautobot/tp44-ipam-prefixes.py new file mode 100644 index 0000000..02cac2d --- /dev/null +++ b/nautobot/tp44-ipam-prefixes.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +""" +TP44 Etappe 2 Teil A — die zwei fehlenden IPAM-Prefixe der CF-CloudNetworks anlegen. + +Zwei CloudNetworks (tp44-cf-estate-seed.py) tragen eine Route, die der Seeder als +cloud-network-prefix-assignment verknuepfen wuerde -- aber nur, wenn der Prefix in +IPAM existiert (dort Schritt [7], sonst "~ Prefix ... nicht in IPAM"): + + 10.40.0.0/16 -> bf-core-mesh (WARP-Connector auf crs326-core, TP16.1) [container] + 10.101.0.0/24 -> fm.emsenhuber.net (Standort FM, cloudflared-Tunnel) [network] + +Idempotenter get-or-create (Dry-Run default). Nach dem --commit den Seeder erneut +laufen lassen -- er haengt die zwei Assignments dann automatisch ein. + + 10.40.0.0/16 als 'container', weil die VLAN-/24 (10.40.10/20/30/...) aus + ingest-baseline.py bereits als Prefixe existieren; Nautobot nistet sie + automatisch darunter (rein hierarchisch, nicht-destruktiv). + +Token-Quelle: docs/nas-vlan20/stacks/210-nautobot.yml (gitignored), +Schluessel NAUTOBOT_SUPERUSER_API_TOKEN. + +Aufruf: + python3 nautobot/tp44-ipam-prefixes.py # Dry-Run (default) + python3 nautobot/tp44-ipam-prefixes.py --commit # legt an +""" +import json +import os +import re +import sys +import urllib.request +import urllib.parse +import urllib.error +from pathlib import Path + +NB = os.environ.get("NAUTOBOT_URL", "http://10.40.20.30:8080") +REPO = Path(__file__).resolve().parent.parent +YML = REPO / "docs" / "nas-vlan20" / "stacks" / "210-nautobot.yml" +COMMIT = "--commit" in sys.argv + + +def load_token() -> str: + for line in YML.read_text().splitlines(): + m = re.search(r"NAUTOBOT_SUPERUSER_API_TOKEN[=:]\s*([^\s\"']+)", line) + if m: + return m.group(1) + sys.exit(f"NAUTOBOT_SUPERUSER_API_TOKEN nicht in {YML} gefunden") + + +TOKEN = load_token() +HDRS = {"Authorization": f"Token {TOKEN}", "Accept": "application/json", + "Content-Type": "application/json"} + + +def req(method: str, path: str, body=None): + url = f"{NB}{path}" + data = json.dumps(body).encode() if body is not None else None + r = urllib.request.Request(url, data=data, headers=HDRS, method=method) + try: + with urllib.request.urlopen(r, timeout=20) as resp: + return resp.status, json.loads(resp.read().decode() or "{}") + except urllib.error.HTTPError as e: + return e.code, json.loads(e.read().decode() or "{}") + + +def find(endpoint: str, params: dict, match_key: str, match_val: str): + q = urllib.parse.urlencode(dict(params, depth=0)) + st, d = req("GET", f"/api/{endpoint}/?{q}") + for row in d.get("results", []): + if row.get(match_key) == match_val: + return row + return None + + +def status_id(name: str): + return find("extras/statuses", {"name": name}, "name", name)["id"] + + +def namespace_id(name: str): + return find("ipam/namespaces", {"name": name}, "name", name)["id"] + + +# ---------------------------------------------------------------- Inventar +# (prefix, type, description) +PREFIXES = [ + ("10.40.0.0/16", "container", + "BF-Supernet; Route des CF WARP-Connectors bf-core-mesh (crs326-core, TP16.1)"), + ("10.101.0.0/24", "network", + "Standort FM; Route des CF cloudflared-Tunnels fm.emsenhuber.net"), +] + + +def main(): + print(f"== TP44 IPAM-Prefixe ({'COMMIT' if COMMIT else 'DRY-RUN'}) @ {NB} ==") + st_active = status_id("Active") + ns_global = namespace_id("Global") + + for prefix, ptype, desc in PREFIXES: + existing = find("ipam/prefixes", {"prefix": prefix}, "prefix", prefix) + if existing: + print(f" = Prefix: '{prefix}' existiert ({existing['id']})") + continue + if not COMMIT: + print(f" + Prefix: '{prefix}' [{ptype}] [DRY-RUN]") + continue + payload = {"prefix": prefix, "status": st_active, "namespace": ns_global, + "type": ptype, "description": desc} + st, d = req("POST", "/api/ipam/prefixes/", payload) + if st in (200, 201): + print(f" + Prefix: '{prefix}' [{ptype}] angelegt ({d.get('id')})") + else: + print(f" ! Prefix: '{prefix}' FEHLER {st}: {json.dumps(d)[:300]}") + + print(f"\n== fertig ({'COMMIT' if COMMIT else 'DRY-RUN — nichts geschrieben'}) ==") + if not COMMIT: + print("Danach: python3 nautobot/tp44-cf-estate-seed.py --commit (haengt die 2 Assignments ein)") + + +if __name__ == "__main__": + main()