Initial import from bf-network-r6.opencode — opencode migration

This commit is contained in:
2026-08-26 12:44:21 +02:00
commit 5ee0716dda
297 changed files with 72324 additions and 0 deletions

129
nautobot/ingest-queue-rule.py Executable file
View File

@@ -0,0 +1,129 @@
#!/usr/bin/env python3
"""
ComplianceRule `queue` anlegen (idempotent).
Die Luecke, die das schliesst
-----------------------------
`nautobot/mikrotik_routeros.j2` hatte bis 2026-08-23 **keinen `/queue`-Block**, und
es gab keine ComplianceRule dafuer. Eine von Hand gesetzte CAKE-Queue waere damit
**unsichtbar fuer die Compliance** gewesen (sie meldet weiter "compliant", waehrend
die Queue driftet) und beim naechsten Deploy **nicht geschuetzt**.
Das ist exakt dieselbe blinde Stelle, die **TP26** fuer `/ip firewall filter`
geschlossen hat -- dort war sie 25 Firewallregeln gross.
TP6 baut jetzt genau dorthin: CAKE auf der Senderichtung (`/queue tree` auf
`vlan100`, Type aus `/queue type`). Die Regel kommt deshalb VOR der Queue, nicht
danach -- so, wie es `tp6-praemisse-messrunbook.md` §4.1 verlangt.
Warum zwei Praefixe und nicht `/queue`
--------------------------------------
`/queue interface` traegt die Default-Zuordnung aller 26 Ports
(`only-hardware-queue`) und `bridge=no-queue`. Die stehen zwar heute in keinem
Backup (RouterOS exportiert Defaults nicht), aber ein Praefix `/queue` wuerde sie
mitfangen, sobald sich daran je etwas aendert -- und das hat mit QoS nichts zu tun.
Gematcht wird deshalb nur, was TP6 tatsaechlich setzt.
export NAUTOBOT_TOKEN=... # aus docs/nas-vlan20/stacks/210-nautobot.yml
python3 nautobot/ingest-queue-rule.py # Dry-Run
python3 nautobot/ingest-queue-rule.py --apply
"""
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
BASE = "http://10.40.20.30:8080/api"
TOKEN = os.environ.get("NAUTOBOT_TOKEN", "")
APPLY = "--apply" in sys.argv
FEATURE = "queue"
BESCHREIBUNG = ("TP6 QoS/CAKE: Queue-Types und Queue-Tree. Vor 2026-08-23 stand "
"/queue unter keiner Rule -- eine handgesetzte Queue waere unsichtbar "
"gedriftet. Analog zu TP26 fuer /ip firewall filter.")
MATCH = "/queue type\n/queue tree"
PLATTFORM = "RouterOS"
def req(method, path, data=None):
url = path if path.startswith("http") else BASE + path
body = json.dumps(data).encode() if data is not None else None
r = urllib.request.Request(url, data=body, method=method)
r.add_header("Authorization", "Token " + TOKEN)
r.add_header("Content-Type", "application/json")
r.add_header("Accept", "application/json")
try:
with urllib.request.urlopen(r, timeout=60) as resp:
raw = resp.read()
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
# 🔴 Fehlerkoerper ausgeben: Nautobot begruendet 400er praezise, und ohne den
# Text raet man an Feldnamen herum (TP22-Erfahrung).
sys.exit("!! %s %s -> %s\n %s" % (method, url, e.code, e.read().decode()[:600]))
def eins(pfad, **filter):
q = urllib.parse.urlencode(filter)
res = req("GET", "%s?%s" % (pfad, q)).get("results", [])
if len(res) > 1:
sys.exit("FEHLER: %s?%s liefert %d Treffer, erwartet 0 oder 1" % (pfad, q, len(res)))
return res[0] if res else None
def main():
if not TOKEN:
sys.exit("NAUTOBOT_TOKEN fehlt.")
print("Modus:", "APPLY" if APPLY else "DRY-RUN (nichts wird geschrieben)")
plat = eins("/dcim/platforms/", name=PLATTFORM)
if not plat:
sys.exit("Plattform %r nicht gefunden." % PLATTFORM)
print("Plattform : %s (%s)" % (plat["display"], plat["id"]))
feat = eins("/plugins/golden-config/compliance-feature/", name=FEATURE)
if feat:
print("Feature : existiert bereits (%s)" % feat["id"])
else:
print("Feature : anlegen -> %s" % FEATURE)
if APPLY:
feat = req("POST", "/plugins/golden-config/compliance-feature/",
{"name": FEATURE, "slug": FEATURE, "description": BESCHREIBUNG})
print(" angelegt (%s)" % feat["id"])
if not feat:
print("\nDry-Run: ohne Feature kann die Rule nicht geprueft werden. Mit --apply erneut.")
return
rule = eins("/plugins/golden-config/compliance-rule/",
feature=feat["id"], platform=plat["id"])
if rule:
print("Rule : existiert bereits (%s), match_config=%r"
% (rule["id"], rule.get("match_config")))
if rule.get("match_config") != MATCH:
print(" ⚠️ match_config weicht ab -- erwartet %r" % MATCH)
return
nutzlast = {
"feature": feat["id"],
"platform": plat["id"],
"description": BESCHREIBUNG,
# Reihenfolge ist hier bedeutungslos: Queue-Trees haengen ueber `parent=`
# am Namen, nicht an der Position. Anders als /ip firewall filter.
"config_ordered": False,
"config_type": "cli",
"match_config": MATCH,
"custom_compliance": False,
"config_remediation": False,
}
print("Rule : anlegen -> %s auf %s, match_config=%r" % (FEATURE, PLATTFORM, MATCH))
if APPLY:
r = req("POST", "/plugins/golden-config/compliance-rule/", nutzlast)
print(" angelegt (%s)" % r["id"])
else:
print(" Nutzlast: %s" % json.dumps(nutzlast))
if __name__ == "__main__":
main()