#!/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()