import os import sys from unifi_controller_api import UnifiController from unifi_controller_api.exceptions import UnifiAuthenticationError, UnifiAPIError from pprint import pprint import pynetbox class Db(): def __init__(self): self.devices = [] self.interfaces = [] self.ips = [] class NetboxCache(): def __init__(self, nb): self.nb = nb self.device_roles = {} self.device_types = {} def get_device_role(self, slug): dt = self.device_roles.get(slug) if dt is not None: return dt dt = self.nb.dcim.device_roles.get(slug=slug) if dt is None: raise Exception(f"No such device type: {slug}") self.device_roles[slug] = dt return dt def get_device_type(self, slug): dt = self.device_types.get(slug) if dt is not None: return dt dt = self.nb.dcim.device_types.get(slug=slug) if dt is None: raise Exception(f"No such device type: {slug}") self.device_types[slug] = dt return dt def process_switch(d, db: Db, nb: NetboxCache, site, vrf): db.devices.append({ "name": d.name, "device_type": nb.get_device_type("ubiquiti-us-8-150w").id, "role": nb.get_device_role("switch").id, "mac": d.mac, "serial": d.serial, "site": site.id, }) def main(): unifi_url=os.getenv("UNIFI_URL") unifi_username=os.getenv("UNIFI_USERNAME") unifi_password=os.getenv("UNIFI_PASSWORD") unifi_site=os.getenv("UNIFI_SITE") netbox_url=os.getenv("NETBOX_URL") netbox_token=os.getenv("NETBOX_TOKEN") netbox_vrf_name=os.getenv("NETBOX_VRF") netbox_site_name=os.getenv("NETBOX_SITE") controller = controller_login(unifi_url, unifi_username, unifi_password) (nb, netbox_site, netbox_vrf) = netbox_login(netbox_url, netbox_token, netbox_site_name, netbox_vrf_name) status = nb.status() print(f"NetBox status: {status}") devices = collect_devices(controller, unifi_site) nb_cache = NetboxCache(nb) db = Db() for d in devices: pprint(d) if d.model == "US8P150": process_switch(d, db, nb_cache, netbox_site, netbox_vrf) print_db(db) sync_db(nb, db) def controller_login(url, username, password) -> UnifiController: # try: controller = UnifiController( controller_url=url, username=username, password=password, is_udm_pro=False, verify_ssl=True, ) # Just to check that there is a valid authentication controller.get_unifi_site(include_health=False, raw=False) return controller # except UnifiAuthenticationError: # print("Authentication failed - please check your UniFi Controller credentials and URL.") # except UnifiAPIError as e: # print(f"UniFi API error: {e}") # except Exception as e: # print(f"An unexpected error occurred: {e}") def collect_devices(controller: UnifiController, site_name: str) -> list[any]: try: return controller.get_unifi_site_device(site_name=site_name, detailed=True, raw=False) except UnifiAPIError as e: print(f"Error fetching device information: {e}") except Exception as e: print(f"An unexpected error occurred: {e}") devices = sorted(devices, key=lambda d: d.name) for d in devices: print(f"{d.name}", file=sys.stderr) print(f"{d}", file=sys.stderr) db.devices = devices # db.devices.append({ # "name": d.name, # "mac": d.mac, # "serial": d.serial, # }) # db.ips.append({ # "address": d.ip, # "status": "active", # "vrf": None, # "device": d.name, # "interface": "", # "is_primary": "true", # }) # db.interfaces.append({ # }) return db def print_db(db: Db): def p(xs): for x in xs: print(f"---") for k, v in x.items(): print(f"{k}: {v}") p(sorted(db.devices, key=lambda d: d["name"])) p(sorted(db.interfaces, key=lambda d: d["address"])) p(sorted(db.ips, key=lambda d: d["address"])) def sync_db(nb, db: Db): print("Synchronizing") for d in db.devices: pprint(d) remote = nb.dcim.devices.get(name = d["name"]) if remote is None: print(f"Creating new for {d['name']}") remote = nb.dcim.devices.create(d) pprint(remote.serialize()) else: pprint(remote.serialize()) remote.update(d) def netbox_login(url: str, token: str, site_name: str, vrf_name: str) -> pynetbox.core.api.Api: nb = pynetbox.api(url, token=token) site = nb.dcim.sites.get(name=site_name) if site is None: site = nb.dcim.sites.get(slug=site_name) if site is None: print(f"Could not look up site by name or slug: {site_name}", file=sys.stderr) exit(1) print(f"NetBox site {site.name}", file=sys.stderr) vrf = None vrf_id = None if vrf_name is not None: vrf = nb.ipam.vrfs.get(site=site, name=vrf_name) if vrf is None: print(f"Could not look up VRF by slug: {vrf_name}", file=sys.stderr) exit(1) vrf_id = vrf.id return nb, site, vrf if __name__ == "__main__": main()