#!/usr/bin/env python3 import requests import json from urllib.parse import urljoin class NetBoxUpdater: def __init__(self, url, token): self.base_url = url.rstrip('/') self.api_url = urljoin(self.base_url + '/', 'api/') self.headers = { 'Authorization': f'Token {token}', 'Content-Type': 'application/json', 'Accept': 'application/json' } self.session = requests.Session() self.session.headers.update(self.headers) def post(self, endpoint, data): """Make POST request to NetBox API""" url = urljoin(self.api_url, endpoint.lstrip('/')) response = self.session.post(url, json=data) if response.status_code not in [200, 201]: print(f"Error creating {endpoint}: {response.status_code}") print(f"Response: {response.text}") response.raise_for_status() return response.json() def patch(self, endpoint, data): """Make PATCH request to NetBox API""" url = urljoin(self.api_url, endpoint.lstrip('/')) response = self.session.patch(url, json=data) if response.status_code not in [200, 201]: print(f"Error updating {endpoint}: {response.status_code}") print(f"Response: {response.text}") response.raise_for_status() return response.json() def get(self, endpoint, params=None): """Make GET request to NetBox API""" url = urljoin(self.api_url, endpoint.lstrip('/')) response = self.session.get(url, params=params) response.raise_for_status() return response.json() def main(): # NetBox connection import os api_token = os.environ.get('NETBOX_KEY', 'e50298f7fd20f7fd6f1931f635511b34f6e8cfde') nb = NetBoxUpdater('https://netbox.vntx.net/', api_token) # Router subnet data with interface mappings router_data = [ { 'prefix': '10.250.1.24/29', 'ip': '10.250.1.25/29', 'interface': 'ether3-climax-11ghz', 'description': 'Climax 11GHz backhaul link', 'role': 'infrastructure' }, { 'prefix': '10.254.254.101/32', 'ip': '10.254.254.101/32', 'interface': 'loopback', 'description': 'Verona router loopback', 'role': 'loopback' }, { 'prefix': '100.64.0.0/22', 'ip': '100.64.3.254/22', 'interface': 'verona', 'description': 'Verona main CGNAT subnet', 'role': 'customer' }, { 'prefix': '204.110.188.224/27', 'ip': '204.110.188.254/27', 'interface': 'verona', 'description': 'Verona public IP subnet', 'role': 'customer' }, { 'prefix': '100.64.12.0/22', 'ip': '100.64.15.254/22', 'interface': 'vlan_19_ether6', 'description': 'Verona VLAN 19 CGNAT', 'role': 'customer' }, { 'prefix': '10.10.80.0/20', 'ip': '10.10.95.254/20', 'interface': 'vlan_10_ether6', 'description': 'Verona CPE management subnet 1', 'role': 'management' }, { 'prefix': '10.10.0.0/20', 'ip': '10.10.15.254/20', 'interface': 'vlan_10_ether6', 'description': 'Verona CPE management subnet 2', 'role': 'management' }, { 'prefix': '10.0.101.0/24', 'ip': '10.0.101.254/24', 'interface': 'sfp-sfpplus1-verona-tower-switch', 'description': 'Verona tower switch connection', 'role': 'infrastructure' }, { 'prefix': '10.250.1.144/29', 'ip': '10.250.1.145/29', 'interface': 'ether6-switch', 'description': 'Verona switch interconnect', 'role': 'infrastructure' }, { 'prefix': '204.110.191.0/27', 'ip': '204.110.191.30/27', 'interface': 'vlan9_sfpplus1', 'description': 'Verona VLAN 9 public subnet', 'role': 'customer' }, { 'prefix': '10.25.1.0/24', 'ip': '10.25.1.254/24', 'interface': 'ether1', 'description': 'Verona ether1 management', 'role': 'management' }, { 'prefix': '192.168.99.0/24', 'ip': '192.168.99.254/24', 'interface': 'ether10-powerswitch', 'description': 'Verona power switch management', 'role': 'management' } ] # Get site ID site_response = nb.get('dcim/sites/', params={'name': 'Verona'}) if site_response['count'] == 0: print("Error: Verona site not found") return site_id = site_response['results'][0]['id'] print(f"Found Verona site with ID: {site_id}") # Get or create prefix roles role_mapping = { 'infrastructure': 'Infrastructure', 'loopback': 'Loopback', 'customer': 'Customer', 'management': 'Management' } role_ids = {} for key, name in role_mapping.items(): role_response = nb.get('ipam/roles/', params={'name': name}) if role_response['count'] > 0: role_ids[key] = role_response['results'][0]['id'] else: # Create role if it doesn't exist role_data = { 'name': name, 'slug': key } try: created_role = nb.post('ipam/roles/', role_data) role_ids[key] = created_role['id'] print(f"Created role: {name}") except: print(f"Could not create role: {name}") role_ids[key] = None # Process each subnet print("\n=== Creating/Updating Prefixes ===") created_prefixes = 0 failed_prefixes = 0 for item in router_data: # Check if prefix already exists prefix_response = nb.get('ipam/prefixes/', params={'prefix': item['prefix']}) prefix_data = { 'prefix': item['prefix'], 'site': site_id, 'status': 'active', 'description': item['description'], 'is_pool': False } if role_ids.get(item['role']): prefix_data['role'] = role_ids[item['role']] try: if prefix_response['count'] == 0: # Create new prefix created_prefix = nb.post('ipam/prefixes/', prefix_data) print(f"Created prefix: {item['prefix']} - {item['description']}") created_prefixes += 1 else: # Update existing prefix existing_id = prefix_response['results'][0]['id'] updated_prefix = nb.patch(f'ipam/prefixes/{existing_id}/', prefix_data) print(f"Updated prefix: {item['prefix']} - {item['description']}") created_prefixes += 1 except Exception as e: print(f"Failed to create/update prefix {item['prefix']}: {e}") failed_prefixes += 1 print(f"\nPrefix Summary: {created_prefixes} successful, {failed_prefixes} failed") # Update IP addresses with interface information print("\n=== Updating IP Address Interface Assignments ===") updated_ips = 0 failed_ips = 0 # Get the device for Verona device_response = nb.get('dcim/devices/', params={'site_id': site_id}) if device_response['count'] == 0: print("Warning: No device found for Verona site") device_id = None else: device_id = device_response['results'][0]['id'] device_name = device_response['results'][0]['name'] print(f"Found device: {device_name} (ID: {device_id})") # Create a mapping of IP to interface ip_to_interface = {item['ip']: item['interface'] for item in router_data} ip_to_description = {item['ip']: item['description'] for item in router_data} # Get all IPs for the site ips_response = nb.get('ipam/ip-addresses/', params={'site_id': site_id}) for ip_obj in ips_response['results']: ip_address = ip_obj['address'] if ip_address in ip_to_interface: interface_name = ip_to_interface[ip_address] description = ip_to_description[ip_address] # Update IP with description update_data = { 'description': f"{interface_name} - {description}" } # If we have a device, try to find or create the interface if device_id: # Check if interface exists interface_response = nb.get('dcim/interfaces/', params={ 'device_id': device_id, 'name': interface_name }) if interface_response['count'] == 0: # Create interface interface_data = { 'device': device_id, 'name': interface_name, 'type': 'virtual', # Using virtual for now 'enabled': True } try: created_interface = nb.post('dcim/interfaces/', interface_data) interface_id = created_interface['id'] print(f"Created interface: {interface_name}") # Assign IP to interface update_data['assigned_object_type'] = 'dcim.interface' update_data['assigned_object_id'] = interface_id except Exception as e: print(f"Failed to create interface {interface_name}: {e}") else: # Use existing interface interface_id = interface_response['results'][0]['id'] update_data['assigned_object_type'] = 'dcim.interface' update_data['assigned_object_id'] = interface_id # Update the IP address try: updated_ip = nb.patch(f"ipam/ip-addresses/{ip_obj['id']}/", update_data) print(f"Updated IP {ip_address} with interface {interface_name}") updated_ips += 1 except Exception as e: print(f"Failed to update IP {ip_address}: {e}") failed_ips += 1 print(f"\nIP Update Summary: {updated_ips} successful, {failed_ips} failed") print("\n=== Update Complete ===") print(f"Total prefixes processed: {created_prefixes + failed_prefixes}") print(f"Total IPs processed: {updated_ips + failed_ips}") if __name__ == "__main__": main()