TP44 Etappe 2 Teil A: Seeder für die zwei fehlenden IPAM-Prefixe
nautobot/tp44-ipam-prefixes.py — idempotenter get-or-create für 10.40.0.0/16 (container, Route bf-core-mesh) und 10.101.0.0/24 (network, Route fm.emsenhuber.net). Dry-Run default, --commit schreibt. Nach dem Anlegen zieht tp44-cf-estate-seed.py Schritt [7] die zwei cloud-network-prefix-assignments automatisch ein. Muster: ingest-baseline.py / tp37-substrat-seed.py. Noch nicht gegen Nautobot ausgeführt (nur Dry-Run verifiziert). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
119
nautobot/tp44-ipam-prefixes.py
Normal file
119
nautobot/tp44-ipam-prefixes.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user