#!/usr/bin/env python3 """ NetBox DHCP IP Reservation Script Marks individual IPs .100-.150 in each subnet as reserved for DHCP """ import requests import ipaddress import sys import os # Configuration NETBOX_URL = "https://netbox.pdmarf.co.uk" NETBOX_TOKEN = os.environ.get("NETBOX_TOKEN") or sys.argv[1] if len(sys.argv) > 1 else None if not NETBOX_TOKEN: print("Error: NetBox API token required") print("Usage: NETBOX_TOKEN='your-token' python3 sys-apps-netbox-reserve-dhcp-ips.py") print(" or: python3 sys-apps-netbox-reserve-dhcp-ips.py 'your-token'") sys.exit(1) # API headers headers = { "Authorization": f"Token {NETBOX_TOKEN}", "Content-Type": "application/json", "Accept": "application/json" } def get_all_prefixes(): """Get all IPv4 prefixes from NetBox""" url = f"{NETBOX_URL}/api/ipam/prefixes/" params = {"family": 4, "limit": 1000} try: response = requests.get(url, headers=headers, params=params, verify=True) response.raise_for_status() return response.json()["results"] except requests.exceptions.RequestException as e: print(f"Error fetching prefixes: {e}") sys.exit(1) def create_dhcp_ip(ip_address, prefix_id): """Create or update individual IP as DHCP reserved""" url = f"{NETBOX_URL}/api/ipam/ip-addresses/" data = { "address": f"{ip_address}/24", # CIDR notation "status": "reserved", "dns_name": "", "description": "DHCP Pool - Reserved for dynamic assignment" } try: response = requests.post(url, headers=headers, json=data, verify=True) if response.status_code == 201: return "created" elif response.status_code == 400: error_detail = response.json() if "already exists" in str(error_detail).lower() or "duplicate" in str(error_detail).lower(): return "exists" else: return f"error: {error_detail}" else: return f"error: HTTP {response.status_code}" except Exception as e: return f"error: {e}" def reserve_dhcp_range(prefix_str, prefix_id): """Reserve IPs .100-.150 in a prefix""" try: network = ipaddress.ip_network(prefix_str, strict=False) # Calculate .100 and .150 addresses base_ip = str(network.network_address) octets = base_ip.split('.') created = 0 exists = 0 errors = 0 # Create IPs from .100 to .150 for i in range(100, 151): ip_octets = octets[:-1] + [str(i)] ip_address = '.'.join(ip_octets) # Validate IP is within network ip_addr = ipaddress.ip_address(ip_address) if ip_addr not in network: continue result = create_dhcp_ip(ip_address, prefix_id) if result == "created": created += 1 if created % 10 == 0: # Progress indicator print(f" ... {created} IPs created so far") elif result == "exists": exists += 1 else: errors += 1 if errors <= 3: # Show first 3 errors only print(f" ✗ {ip_address}: {result}") return created, exists, errors except Exception as e: print(f" ✗ Error processing {prefix_str}: {e}") return 0, 0, 0 def main(): print(f"\nConnecting to NetBox: {NETBOX_URL}") print("=" * 60) print("This will create individual IP objects for .100-.150 in each subnet") print("Status: Reserved | Description: DHCP Pool") print("=" * 60) # Get all prefixes print("\nFetching prefixes...") prefixes = get_all_prefixes() # Filter to /24 subnets only (skip /16) prefixes = [p for p in prefixes if '/24' in p['prefix']] print(f"Found {len(prefixes)} /24 prefixes\n") # Process each prefix total_created = 0 total_exists = 0 total_errors = 0 for prefix in prefixes: prefix_str = prefix["prefix"] prefix_id = prefix["id"] vlan_name = prefix.get("vlan", {}).get("name", "No VLAN") if prefix.get("vlan") else "No VLAN" print(f"Processing: {prefix_str} ({vlan_name})") created, exists, errors = reserve_dhcp_range(prefix_str, prefix_id) print(f" ✓ Created: {created}, Already exists: {exists}, Errors: {errors}") total_created += created total_exists += exists total_errors += errors # Summary print("\n" + "=" * 60) print("Summary:") print(f" ✓ Created: {total_created}") print(f" ℹ️ Already existed: {total_exists}") print(f" ✗ Errors: {total_errors}") print("=" * 60) print("\n✓ DHCP pool IPs reserved in NetBox!") print(" IPs .100-.150 are now marked as 'Reserved' status") print(" They will no longer show as 'Available' in the IP list") if __name__ == "__main__": main()