aboutsummaryrefslogtreecommitdiffstats
path: root/tools/netbox/scripts/netbox2gondul/netbox2gondul.py
diff options
context:
space:
mode:
authorHåkon Solbjørg <hakon@solbj.org>2023-03-13 20:51:27 +0100
committerHåkon Solbjørg <hakon@solbj.org>2023-03-13 21:06:52 +0100
commit258f43b4bb18f679229f0389ca33c14ffd55da10 (patch)
tree6c311cd16cecd17ee647a06e14f4b60f71f731a5 /tools/netbox/scripts/netbox2gondul/netbox2gondul.py
parent7058e48c38aa48843894e121a2fabec9e7814d40 (diff)
feat(netbox2gondul): Initial implementation of netbox2gondul as a netbox script
Converted from tg19/netbox_tools/netbox2gondul.py
Diffstat (limited to 'tools/netbox/scripts/netbox2gondul/netbox2gondul.py')
-rw-r--r--tools/netbox/scripts/netbox2gondul/netbox2gondul.py166
1 files changed, 166 insertions, 0 deletions
diff --git a/tools/netbox/scripts/netbox2gondul/netbox2gondul.py b/tools/netbox/scripts/netbox2gondul/netbox2gondul.py
new file mode 100644
index 0000000..81d41de
--- /dev/null
+++ b/tools/netbox/scripts/netbox2gondul/netbox2gondul.py
@@ -0,0 +1,166 @@
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import F
+from django.utils.text import slugify
+
+from dcim.choices import DeviceStatusChoices, InterfaceModeChoices, InterfaceTypeChoices, SiteStatusChoices
+from dcim.models import Cable, CableTermination, Device, DeviceRole, DeviceType, Interface, Manufacturer, Site
+from extras.scripts import *
+from ipam.models import IPAddress, Prefix, VLAN
+from ipam.lookups import NetContainsOrEquals
+
+import ipaddress
+import json
+import requests
+from requests.models import HTTPBasicAuth
+
+GONDUL_URL = ""
+GONDUL_USERNAME = ""
+GONDUL_PASSWORD = ""
+
+def find_prefix_for_device(device) -> Prefix:
+ pass
+
+
+class Netbox2Gondul(Script):
+
+ class Meta:
+ name = "Sync NetBox to Gondul"
+ description = """
+ Can be done for a single network/device or a full sync. Note that this will not do 'renames' of devices, so it is best used for updating device information.
+ If a device is selected, it will also sync the required networks as long as they are set up correctly (Primary IP addresses for the Switch & VLAN configured for the Prefix of those IP Addresses).
+ """
+ field_order = ['site_name', 'switch_count', 'switch_model']
+
+ switch = ObjectVar(
+ description="Switch",
+ model=Device,
+ required=True,
+ )
+ """
+ vlan = ObjectVar(
+ description="VLAN",
+ model=VLAN,
+ required=False,
+ )
+ prefix_v4 = ObjectVar(
+ description="IPv4 Prefix",
+ model=Prefix,
+ query_params={
+ 'family': 4,
+ 'vlan_id': '$vlan'
+ },
+ required=False,
+ )
+ prefix_v6 = ObjectVar(
+ description="IPv6 Prefix",
+ model=Prefix,
+ query_params={
+ 'family': 6,
+ 'vlan_id': '$vlan'
+ },
+ required=False,
+ )
+ """
+
+ def network_to_gondul(self, vlan: VLAN, prefix_v4: Prefix, prefix_v6: Prefix):
+ self.log_info(f"Posting {vlan.name} to Gondul")
+
+ gondul_auth = HTTPBasicAuth(GONDUL_USERNAME, GONDUL_PASSWORD)
+
+ subnet4 = None
+ subnet6 = None
+ gw4 = None
+ gw6 = None
+ router = None
+
+ if prefix_v4:
+ subnet4 = str(prefix_v4.prefix)
+ gw4 = ipaddress.IPv4Network(prefix_v4.prefix)[1].exploded
+ else:
+ self.log_warning(f'Network for VLAN <a href="{vlan.get_absolute_url()}">{vlan.name}</a> is missing IPv4 Prefix')
+
+ if prefix_v6:
+ subnet6 = str(prefix_v6.prefix)
+ gw6 = ipaddress.IPv6Network(prefix_v6.prefix)[1].exploded
+ else:
+ self.log_warning(f'Network for VLAN <a href="{vlan.get_absolute_url()}">{vlan.name}</a> is missing IPv6 Prefix')
+
+ try:
+ router = IPAddress.objects.get(address=gw4)
+ except IPAddress.DoesNotExist:
+ self.log_warning(f'Router not found for VLAN <a href="{vlan.get_absolute_url()}">{vlan.name}</a>')
+
+ data = json.dumps([{
+ "name": vlan.name,
+ "subnet4": subnet4,
+ "subnet6": subnet6,
+ "gw4": gw4,
+ "gw6": gw6,
+ "router": router,
+ "vlan": vlan.vid,
+ }])
+
+ req = requests.post(
+ f"{GONDUL_URL}/api/write/networks",
+ data=data,
+ headers={'content-type': 'application/json'},
+ auth=gondul_auth,
+ )
+
+ if req.ok:
+ self.log_success(f"Gondul said (HTTP {req.status_code}): {req.text}")
+ else:
+ self.log_failure(f"Gondul said HTTP {req.status_code} and {req.text}")
+
+ def device_to_gondul(self, device: Device):
+ self.log_info(f"Posting {device.name} to Gondul")
+
+ gondul_auth = HTTPBasicAuth(GONDUL_USERNAME, GONDUL_PASSWORD)
+
+ data = json.dumps([
+ ])
+
+ req = requests.post(
+ f"{GONDUL_URL}/api/write/switches",
+ data=data,
+ headers={'content-type': 'application/json'},
+ auth=gondul_auth,
+ )
+
+ if req.ok:
+ self.log_success(f"Gondul said (HTTP {req.status_code}): {req.text}")
+ else:
+ self.log_failure(f"Gondul said HTTP {req.status_code} and {req.text}")
+
+ def run(self, data, commit):
+
+ switch: Device = data['switch']
+ """
+ vlan: VLAN = data['vlan']
+ prefix_v4: Prefix = data['prefix_v4']
+ prefix_v6: Prefix = data['prefix_v6']
+ """
+
+ """
+ if prefix_v4 is None:
+ self.log_info(f"v4 not provided, default")
+ """
+
+ if not (switch.primary_ip4 or switch.primary_ip6):
+ self.log_failure(f'Switch <a href="{switch.get_absolute_url()}">{switch.name}</a> is missing primary IPv4 or IPv6 address.')
+ return
+
+ prefix_v4 = Prefix.objects.get(NetContainsOrEquals(F('prefix'), str(switch.primary_ip4.address)))
+ prefix_v6 = Prefix.objects.get(NetContainsOrEquals(F('prefix'), str(switch.primary_ip6.address)))
+
+ vlan = prefix_v6.vlan
+
+ if prefix_v4.vlan != prefix_v6.vlan:
+ self.log_failure(f'VLANs differ for the IPv4 and IPv6 addresses.')
+ return
+
+ self.network_to_gondul(vlan, prefix_v4, prefix_v6)
+
+ self.log_success("All good, sending to Gondul")
+
+ self.device_to_gondul(switch)