diff --git a/README.md b/README.md index 20e6693..959a2fb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Netbox to Zabbix synchronization +# NetBox to Zabbix synchronization -A script to create, update and delete Zabbix hosts using Netbox device objects. +A script to create, update and delete Zabbix hosts using NetBox device objects. ## Installation via Docker @@ -81,16 +81,16 @@ password. In that case `ZABBIX_USER` and `ZABBIX_PASS` will be ignored. ZABBIX_TOKEN=othersecrettoken ``` -If you are using custom SSL certificates for Netbox and/or Zabbix, you can set +If you are using custom SSL certificates for NetBox and/or Zabbix, you can set the following environment variable to the path of your CA bundle file: ```bash REQUEST_CA_BUNDLE=/path/to/your/ca-bundle.crt ``` -### Netbox custom fields +### NetBox custom fields -Use the following custom fields in Netbox (if you are using config context for +Use the following custom fields in NetBox (if you are using config context for the template information then the zabbix_template field is not required): ``` @@ -118,7 +118,7 @@ manually change the ID. For example to re-run a sync. ## Virtual Machine (VM) Syncing In order to use VM syncing, make sure that the `zabbix_id` custom field is also -present on Virtual machine objects in Netbox. +present on Virtual machine objects in NetBox. Use the `config.py` file and set the `sync_vms` variable to `True`. @@ -128,7 +128,7 @@ hostgroups. The default is `cluster_type/cluster/role`. To enable filtering for VM's, check the `nb_vm_filter` variable out. It works the same as with the device filter (see documentation under "Hostgroup layout"). Note that not all filtering capabilities and properties of devices are -applicable to VM's and vice-versa. Check the Netbox API documentation to see +applicable to VM's and vice-versa. Check the NetBox API documentation to see which filtering options are available for each object type. ## Config file @@ -260,15 +260,15 @@ in an error: ``` hostgroup_format = "mycustomfieldname" -Netbox-Zabbix-sync - ERROR - ESXI1 has no reliable hostgroup. This is most likely due to the use of custom fields that are empty. +NetBox-Zabbix-sync - ERROR - ESXI1 has no reliable hostgroup. This is most likely due to the use of custom fields that are empty. ``` ### Device status -By setting a status on a Netbox device you determine how the host is added (or +By setting a status on a NetBox device you determine how the host is added (or updated) in Zabbix. There are, by default, 3 options: -- Delete the host from Zabbix (triggered by Netbox status "Decommissioning" and +- Delete the host from Zabbix (triggered by NetBox status "Decommissioning" and "Inventory") - Create the host in Zabbix but with a disabled status (Trigger by "Offline", "Planned", "Staged" and "Failed") @@ -284,8 +284,8 @@ script: ### Zabbix Inventory This script allows you to enable the inventory on managed Zabbix hosts and sync -NetBox device properties to the specified inventory fields. To map Netbox -information to Netbox inventory fields, set `inventory_sync` to `True`. +NetBox device properties to the specified inventory fields. To map NetBox +information to NetBox inventory fields, set `inventory_sync` to `True`. You can set the inventory mode to "disabled", "manual" or "automatic" with the `inventory_mode` variable. See @@ -310,7 +310,7 @@ fields. ### Template source -You can either use a Netbox device type custom field or Netbox config context +You can either use a NetBox device type custom field or NetBox config context for the Zabbix template information. Using a custom field allows for only one template. You can assign multiple @@ -342,7 +342,7 @@ in the config context in this format: ``` You can also opt for the default device type custom field behaviour but with the -added benefit of overwriting the template should a device in Netbox have a +added benefit of overwriting the template should a device in NetBox have a device specific context defined. In this case the device specific context template(s) will take priority over the device type custom field template. @@ -352,9 +352,9 @@ templates_config_context_overrule = True ## Permissions -### Netbox +### NetBox -Make sure that the Netbox user has proper permissions for device read and modify +Make sure that the NetBox user has proper permissions for device read and modify (modify to set the Zabbix HostID custom field) operations. The user should also have read-only access to the device types. @@ -435,13 +435,13 @@ In the example above the host will use the group on Zabbix 7. On Zabbix 6 and below the host will use the proxy. Zabbix 7 will use the proxy value when ommiting the proxy_group value. -Because of the possible amount of destruction when setting up Netbox but +Because of the possible amount of destruction when setting up NetBox but forgetting the proxy command, the sync works a bit different. By default everything is synced except in a situation where the Zabbix host has a proxy -configured but nothing is configured in Netbox. To force deletion and a full +configured but nothing is configured in NetBox. To force deletion and a full sync, set the `full_proxy_sync` variable in the config file. -### Set interface parameters within Netbox +### Set interface parameters within NetBox When adding a new device, you can set the interface type with custom context. By default, the following configuration is applied when no config context is @@ -453,14 +453,14 @@ provided: - SNMP community: {$SNMP_COMMUNITY} Due to Zabbix limitations of changing interface type with a linked template, -changing the interface type from within Netbox is not supported and the script +changing the interface type from within NetBox is not supported and the script will generate an error. For example when changing a SNMP interface to an Agent interface: ``` -Netbox-Zabbix-sync - WARNING - Device: Interface OUT of sync. -Netbox-Zabbix-sync - ERROR - Device: changing interface type to 1 is not supported. +NetBox-Zabbix-sync - WARNING - Device: Interface OUT of sync. +NetBox-Zabbix-sync - ERROR - Device: changing interface type to 1 is not supported. ``` To configure the interface parameters you'll need to use custom context. Custom @@ -519,7 +519,7 @@ environment. For example, you could: ``` I would recommend using macros for sensitive data such as community strings -since the data in Netbox is plain-text. +since the data in NetBox is plain-text. > **_NOTE:_** Not all SNMP data is required for a working configuration. > [The following parameters are allowed](https://www.zabbix.com/documentation/current/manual/api/reference/hostinterface/object#details_tag "The following parameters are allowed")but diff --git a/config.py.example b/config.py.example index 7228aad..1d83223 100644 --- a/config.py.example +++ b/config.py.example @@ -7,7 +7,7 @@ templates_config_context = False # higher priority then custom field templates templates_config_context_overrule = False -# Set template and device Netbox "custom field" names +# Set template and device NetBox "custom field" names # Template_cf is not used when templates_config_context is enabled template_cf = "zabbix_template" device_cf = "zabbix_hostid" @@ -35,7 +35,7 @@ vm_hostgroup_format = "cluster_type/cluster/role" # With this option disabled proxy's will only be added and modified for Zabbix hosts. full_proxy_sync = False -## Netbox to Zabbix device state convertion +## NetBox to Zabbix device state convertion zabbix_device_removal = ["Decommissioning", "Inventory"] zabbix_device_disable = ["Offline", "Planned", "Staged", "Failed"] @@ -62,7 +62,7 @@ traverse_site_groups = False # nb_device_filter = {"site": ["HQ-AMS", "HQ-FRA"]} #Device must be in either one of these sites # nb_device_filter = {"site": "HQ-AMS", "tag": "zabbix", "role__n": ["PDU", "console-server"]} #Device must be in site HQ-AMS, have the tag zabbix and must not be part of the PDU or console-server role -# Default device filter, only get devices which have a name in Netbox: +# Default device filter, only get devices which have a name in NetBox: nb_device_filter = {"name__n": "null"} # Default filter for VMs nb_vm_filter = {"name__n": "null"} diff --git a/modules/device.py b/modules/device.py index 218e20e..bb6de2a 100644 --- a/modules/device.py +++ b/modules/device.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # pylint: disable=invalid-name, logging-not-lazy, too-many-locals, logging-fstring-interpolation, too-many-lines """ -Device specific handeling for Netbox to Zabbix +Device specific handeling for NetBox to Zabbix """ from os import sys from re import search @@ -29,7 +29,7 @@ class PhysicalDevice(): # pylint: disable=too-many-instance-attributes, too-many-arguments, too-many-positional-arguments """ Represents Network device. - INPUT: (Netbox device class, ZabbixAPI class, journal flag, NB journal class) + INPUT: (NetBox device class, ZabbixAPI class, journal flag, NB journal class) """ def __init__(self, nb, zabbix, nb_journal_class, nb_version, journal=None, logger=None): @@ -92,7 +92,7 @@ class PhysicalDevice(): self.visible_name = self.nb.name self.use_visible_name = True self.logger.info(f"Host {self.visible_name} contains special characters. " - f"Using {self.name} as name for the Netbox object " + f"Using {self.name} as name for the NetBox object " f"and using {self.visible_name} as visible name in Zabbix.") else: pass @@ -164,7 +164,7 @@ class PhysicalDevice(): # Set inventory mode. Default is disabled (see class init function). if inventory_mode == "disabled": if inventory_sync: - self.logger.error(f"Host {self.name}: Unable to map Netbox inventory to Zabbix. " + self.logger.error(f"Host {self.name}: Unable to map NetBox inventory to Zabbix. " "Inventory sync is enabled in config but inventory mode is disabled.") return True if inventory_mode == "manual": @@ -194,7 +194,7 @@ class PhysicalDevice(): self.inventory[zbx_inv_field] = str(value) elif not value: # empty value should just be an empty string for API compatibility - self.logger.debug(f"Host {self.name}: Netbox inventory lookup for " + self.logger.debug(f"Host {self.name}: NetBox inventory lookup for " f"'{nb_inv_field}' returned an empty value") self.inventory[zbx_inv_field] = "" else: @@ -221,7 +221,7 @@ class PhysicalDevice(): self.logger.warning(e) raise SyncInventoryError(e) if not self.nb.virtual_chassis.master: - e = (f"{self.name} is part of a Netbox virtual chassis which does " + e = (f"{self.name} is part of a NetBox virtual chassis which does " "not have a master configured. Skipping for this reason.") self.logger.error(e) raise SyncInventoryError(e) @@ -256,7 +256,7 @@ class PhysicalDevice(): raise SyncInventoryError() # Set variable to empty list self.zbx_templates = [] - # Go through all templates definded in Netbox + # Go through all templates definded in NetBox for nb_template in self.zbx_template_names: template_match = False # Go through all templates found in Zabbix @@ -295,7 +295,7 @@ class PhysicalDevice(): def cleanup(self): """ Removes device from external resources. - Resets custom fields in Netbox. + Resets custom fields in NetBox. """ if self.zabbix_id: try: @@ -303,7 +303,7 @@ class PhysicalDevice(): zbx_host = bool(self.zabbix.host.get(filter={'hostid': self.zabbix_id}, output=[])) e = (f"Host {self.name}: was already deleted from Zabbix." - " Removed link in Netbox.") + " Removed link in NetBox.") if zbx_host: # Delete host should it exists self.zabbix.host.delete(self.zabbix_id) @@ -317,7 +317,7 @@ class PhysicalDevice(): raise SyncExternalError(message) from e def _zeroize_cf(self): - """Sets the hostID custom field in Netbox to zero, + """Sets the hostID custom field in NetBox to zero, effectively destroying the link""" self.nb.custom_fields[device_cf] = None self.nb.save() @@ -336,13 +336,13 @@ class PhysicalDevice(): def setInterfaceDetails(self): """ - Checks interface parameters from Netbox and + Checks interface parameters from NetBox and creates a model for the interface to be used in Zabbix. """ try: # Initiate interface class interface = ZabbixInterface(self.nb.config_context, self.ip) - # Check if Netbox has device context. + # Check if NetBox has device context. # If not fall back to old config. if interface.get_context(): # If device is SNMP type, add aditional information. @@ -377,7 +377,7 @@ class PhysicalDevice(): # Only insert groups in front of list for Zabbix7 proxy_types.insert(0, "proxy_group") for proxy_type in proxy_types: - # Check if the key exists in Netbox CC + # Check if the key exists in NetBox CC if proxy_type in self.nb.config_context["zabbix"]: proxy_name = self.nb.config_context["zabbix"][proxy_type] # go through all proxies @@ -395,9 +395,9 @@ class PhysicalDevice(): return False def createInZabbix(self, groups, templates, proxies, - description="Host added by Netbox sync script."): + description="Host added by NetBox sync script."): """ - Creates Zabbix host object with parameters from Netbox object. + Creates Zabbix host object with parameters from NetBox object. """ # Check if hostname is already present in Zabbix if not self._zabbixHostnameExists(): @@ -445,7 +445,7 @@ class PhysicalDevice(): e = f"Host {self.name}: Couldn't create. Zabbix returned {str(e)}." self.logger.error(e) raise SyncExternalError(e) from None - # Set Netbox custom field to hostID value. + # Set NetBox custom field to hostID value. self.nb.custom_fields[device_cf] = int(self.zabbix_id) self.nb.save() msg = f"Host {self.name}: Created host in Zabbix." @@ -511,7 +511,7 @@ class PhysicalDevice(): def ConsistencyCheck(self, groups, templates, proxies, proxy_power, create_hostgroups): # pylint: disable=too-many-branches, too-many-statements """ - Checks if Zabbix object is still valid with Netbox parameters. + Checks if Zabbix object is still valid with NetBox parameters. """ # If group is found or if the hostgroup is nested if not self.setZabbixGroupID(groups) or len(self.hostgroup.split('/')) > 1: @@ -548,7 +548,7 @@ class PhysicalDevice(): if len(host) == 0: e = (f"Host {self.name}: No Zabbix host found. " f"This is likely the result of a deleted Zabbix host " - f"without zeroing the ID field in Netbox.") + f"without zeroing the ID field in NetBox.") self.logger.error(e) raise SyncInventoryError(e) host = host[0] @@ -615,7 +615,7 @@ class PhysicalDevice(): "monitored_by": self.zbxproxy['monitored_by']} self.updateZabbixHost(**update_data) else: - # No proxy is defined in Netbox + # No proxy is defined in NetBox proxy_set = False # Check if a proxy is defined. Uses the proxy_hostid key for backwards compatibility for key in ("proxy_hostid", "proxyid", "proxy_groupid"): @@ -624,7 +624,7 @@ class PhysicalDevice(): proxy_set = True if proxy_power and proxy_set: # Zabbix <= 6 fix - self.logger.warning(f"Host {self.name}: no proxy is configured in Netbox " + self.logger.warning(f"Host {self.name}: no proxy is configured in NetBox " "but is configured in Zabbix. Removing proxy config in Zabbix") if "proxy_hostid" in host and bool(host["proxy_hostid"]): self.updateZabbixHost(proxy_hostid=0) @@ -638,7 +638,7 @@ class PhysicalDevice(): if proxy_set and not proxy_power: # Display error message self.logger.error(f"Host {self.name} is configured " - f"with proxy in Zabbix but not in Netbox. The" + f"with proxy in Zabbix but not in NetBox. The" " -p flag was ommited: no " "changes have been made.") if not proxy_set: @@ -663,7 +663,7 @@ class PhysicalDevice(): updates = {} # Go through each key / item and check if it matches Zabbix for key, item in self.setInterfaceDetails()[0].items(): - # Check if Netbox value is found in Zabbix + # Check if NetBox value is found in Zabbix if key in host["interfaces"][0]: # If SNMP is used, go through nested dict # to compare SNMP parameters @@ -724,8 +724,8 @@ class PhysicalDevice(): def create_journal_entry(self, severity, message): """ - Send a new Journal entry to Netbox. Usefull for viewing actions - in Netbox without having to look in Zabbix or the script log output + Send a new Journal entry to NetBox. Usefull for viewing actions + in NetBox without having to look in Zabbix or the script log output """ if self.journal: # Check if the severity is valid @@ -739,7 +739,7 @@ class PhysicalDevice(): } try: self.nb_journals.create(journal) - self.logger.debug(f"Host {self.name}: Created journal entry in Netbox") + self.logger.debug(f"Host {self.name}: Created journal entry in NetBox") return True except JournalError(e) as e: self.logger.warning("Unable to create journal entry for " @@ -749,14 +749,14 @@ class PhysicalDevice(): def zbx_template_comparer(self, tmpls_from_zabbix): """ - Compares the Netbox and Zabbix templates with each other. + Compares the NetBox and Zabbix templates with each other. Should there be a mismatch then the function will return false INPUT: list of NB and ZBX templates OUTPUT: Boolean True/False """ succesfull_templates = [] - # Go through each Netbox template + # Go through each NetBox template for nb_tmpl in self.zbx_templates: # Go through each Zabbix template for pos, zbx_tmpl in enumerate(tmpls_from_zabbix): @@ -770,7 +770,7 @@ class PhysicalDevice(): f"{nb_tmpl['name']} is present in Zabbix.") break if len(succesfull_templates) == len(self.zbx_templates) and len(tmpls_from_zabbix) == 0: - # All of the Netbox templates have been confirmed as successfull + # All of the NetBox templates have been confirmed as successfull # and the ZBX template list is empty. This means that # all of the templates match. return True diff --git a/modules/hostgroups.py b/modules/hostgroups.py index a2c7954..9e54dd2 100644 --- a/modules/hostgroups.py +++ b/modules/hostgroups.py @@ -34,7 +34,7 @@ class Hostgroup(): format_options = {} # Set variables for both type of devices if self.type in ("vm", "dev"): - # Role fix for Netbox <=3 + # Role fix for NetBox <=3 role = None if self.nb_version.startswith(("2", "3")) and self.type == "dev": role = self.nb.device_role.name if self.nb.device_role else None @@ -129,7 +129,7 @@ class Hostgroup(): def custom_field_lookup(self, hg_category): """ - Checks if a valid custom field is present in Netbox. + Checks if a valid custom field is present in NetBox. INPUT: Custom field name OUTPUT: dictionary with 'result' and 'cf' keys. """ diff --git a/modules/interface.py b/modules/interface.py index 384c35e..e4413c6 100644 --- a/modules/interface.py +++ b/modules/interface.py @@ -29,7 +29,7 @@ class ZabbixInterface(): return True def get_context(self): - """ check if Netbox custom context has been defined. """ + """ check if NetBox custom context has been defined. """ if "zabbix" in self.context: zabbix = self.context["zabbix"] if "interface_type" in zabbix: @@ -46,7 +46,7 @@ class ZabbixInterface(): """ Check if interface is type SNMP """ # pylint: disable=too-many-branches if self.interface["type"] == 2: - # Checks if SNMP settings are defined in Netbox + # Checks if SNMP settings are defined in NetBox if "snmp" in self.context["zabbix"]: snmp = self.context["zabbix"]["snmp"] self.interface["details"] = {} @@ -56,7 +56,7 @@ class ZabbixInterface(): else: # Fallback to bulk enabled if not specified self.interface["details"]["bulk"] = "1" - # SNMP Version config is required in Netbox config context + # SNMP Version config is required in NetBox config context if snmp.get("version"): self.interface["details"]["version"] = str(snmp.pop("version")) else: @@ -72,7 +72,7 @@ class ZabbixInterface(): community = "{$SNMP_COMMUNITY}" self.interface["details"]["community"] = str(community) # If version 3 has been used, get all - # SNMPv3 Netbox related configs + # SNMPv3 NetBox related configs elif self.interface["details"]["version"] == '3': items = ["securityname", "securitylevel", "authpassphrase", "privpassphrase", "authprotocol", "privprotocol", diff --git a/modules/virtual_machine.py b/modules/virtual_machine.py index 788a903..3e9e410 100644 --- a/modules/virtual_machine.py +++ b/modules/virtual_machine.py @@ -50,7 +50,7 @@ class VirtualMachine(PhysicalDevice): try: # Initiate interface class interface = ZabbixInterface(self.nb.config_context, self.ip) - # Check if Netbox has device context. + # Check if NetBox has device context. # If not fall back to old config. if interface.get_context(): # If device is SNMP type, add aditional information. diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index 785a3bf..935b55e 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # pylint: disable=invalid-name, logging-not-lazy, too-many-locals, logging-fstring-interpolation -"""Netbox to Zabbix sync script.""" +"""NetBox to Zabbix sync script.""" import logging import argparse import ssl @@ -45,7 +45,7 @@ lgfile = logging.FileHandler(path.join(path.dirname( lgfile.setFormatter(log_format) lgfile.setLevel(logging.DEBUG) -logger = logging.getLogger("Netbox-Zabbix-sync") +logger = logging.getLogger("NetBox-Zabbix-sync") logger.addHandler(lgout) logger.addHandler(lgfile) logger.setLevel(logging.WARNING) @@ -80,7 +80,7 @@ def main(arguments): zabbix_host = environ.get("ZABBIX_HOST") netbox_host = environ.get("NETBOX_HOST") netbox_token = environ.get("NETBOX_TOKEN") - # Set Netbox API + # Set NetBox API netbox = api(netbox_host, token=netbox_token, threading=True) # Check if the provided Hostgroup layout is valid hg_objects = hostgroup_format.split("/") @@ -91,11 +91,11 @@ def main(arguments): device_cfs = list(netbox.extras.custom_fields.filter( type="text", content_type_id=23)) except RequestsConnectionError: - logger.error(f"Unable to connect to Netbox with URL {netbox_host}." - " Please check the URL and status of Netbox.") + logger.error(f"Unable to connect to NetBox with URL {netbox_host}." + " Please check the URL and status of NetBox.") sys.exit(1) except NBRequestError as e: - logger.error(f"Netbox error: {e}") + logger.error(f"NetBox error: {e}") sys.exit(1) for cf in device_cfs: allowed_objects.append(cf.name) @@ -129,7 +129,7 @@ def main(arguments): proxy_name = "host" else: proxy_name = "name" - # Get all Zabbix and Netbox data + # Get all Zabbix and NetBox data netbox_devices = list(netbox.dcim.devices.filter(**nb_device_filter)) netbox_vms = [] if sync_vms: @@ -153,10 +153,10 @@ def main(arguments): # Prepare list of all proxy and proxy_groups zabbix_proxy_list = proxy_prepper(zabbix_proxies, zabbix_proxygroups) - # Get Netbox API version + # Get NetBox API version nb_version = netbox.version - # Go through all Netbox devices + # Go through all NetBox devices for nb_vm in netbox_vms: try: vm = VirtualMachine(nb_vm, zabbix, netbox_journals, nb_version, @@ -175,11 +175,11 @@ def main(arguments): if vm.status in zabbix_device_removal: if vm.zabbix_id: # Delete device from Zabbix - # and remove hostID from Netbox. + # and remove hostID from NetBox. vm.cleanup() logger.info(f"VM {vm.name}: cleanup complete") continue - # Device has been added to Netbox + # Device has been added to NetBox # but is not in Activate state logger.info(f"VM {vm.name}: skipping since this VM is " f"not in the active state.") @@ -243,11 +243,11 @@ def main(arguments): if device.status in zabbix_device_removal: if device.zabbix_id: # Delete device from Zabbix - # and remove hostID from Netbox. + # and remove hostID from NetBox. device.cleanup() logger.info(f"Device {device.name}: cleanup complete") continue - # Device has been added to Netbox + # Device has been added to NetBox # but is not in Activate state logger.info(f"Device {device.name}: skipping since this device is " f"not in the active state.") @@ -278,7 +278,7 @@ def main(arguments): if __name__ == "__main__": parser = argparse.ArgumentParser( - description='A script to sync Zabbix with Netbox device data.' + description='A script to sync Zabbix with NetBox device data.' ) parser.add_argument("-v", "--verbose", help="Turn on debugging.", action="store_true")