From 5810cbe621def08a6c01ace4dc50671403e9a040 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Thu, 11 Sep 2025 17:20:05 +0200 Subject: [PATCH 01/13] First working version of proxy by custom fields --- README.md | 39 ++++++++++++++++++++++++++++++++++----- modules/config.py | 2 ++ modules/device.py | 20 ++++++++++++++++++-- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 477f41c..9616983 100644 --- a/README.md +++ b/README.md @@ -414,9 +414,9 @@ Tags can be synced from the following sources: Syncing tags will override any tags that were set manually on the host, making NetBox the single source-of-truth for managing tags. -To enable syncing, turn on tag_sync in the config file. +To enable syncing, turn on `tag_sync` in the config file. By default, this script will modify tag names and tag values to lowercase. -You can change this behaviour by setting tag_lower to False. +You can change this behaviour by setting `tag_lower` to `False`. ```python tag_sync = True @@ -429,7 +429,8 @@ As NetBox doesn't follow the tag/value pattern for tags, we will need a tag name set to register the netbox tags. By default the tag name is "NetBox", but you can change this to whatever you want. -The value for the tag can be set to 'name', 'display', or 'slug', which refers to the property of the NetBox tag object that will be used as the value in Zabbix. +The value for the tag can be set to 'name', 'display', or 'slug', which refers to the +property of the NetBox tag object that will be used as the value in Zabbix. ```python tag_name = 'NetBox' @@ -512,7 +513,7 @@ Through this method, it is possible to define the following types of usermacros: 2. Secret 3. Vault -The default macro type is text if no `type` and `value` have been set. +The default macro type is text, if no `type` and `value` have been set. It is also possible to create usermacros with [context](https://www.zabbix.com/documentation/7.0/en/manual/config/macros/user_macros_context). @@ -632,7 +633,8 @@ python3 netbox_zabbix_sync.py ### Zabbix proxy -You can set the proxy for a device using the 'proxy' key in config context. +#### Config Context +You can set the proxy for a device using the `proxy` key in config context. ```json { @@ -673,6 +675,33 @@ 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 omitting the proxy_group value. +#### Custom Field + +Alternatively, you can use a custom field for assigning a device or VM to +a Zabbix proxy or proxy group. The custom fields can be assigned to both +Devices and VMs. + +You can also assign these custom fields to a site to allow all devices/VMs +in that site to be confured with the same proxy or proxy group. +In order for this to work, `extended_site_properties` needs to be enabled in +the configuation as well. + +To use the custom fields for proxy configuration, configure one or both +of the following settings in the configuration file with the actual names of your +custom fields: + +```python +proxy_cf = "zabbix_proxy" +proxy_group_cf = "zabbix_proxy_group" +``` + +As with config context proxy configuration, proxy group will take precedence over +proxy when configured. +Proxy settings configured on the device or VM will take precedence over any site configuration. + +If the custom fields have no value but the proxy or proxy group is configured in config context, +that setting will be used. + ### Set interface parameters within NetBox When adding a new device, you can set the interface type with custom context. By diff --git a/modules/config.py b/modules/config.py index 43bd775..b1afcfb 100644 --- a/modules/config.py +++ b/modules/config.py @@ -16,6 +16,8 @@ DEFAULT_CONFIG = { "templates_config_context_overrule": False, "template_cf": "zabbix_template", "device_cf": "zabbix_hostid", + "proxy_cf": False, + "proxy_group_cf" : False, "clustering": False, "create_hostgroups": True, "create_journal": False, diff --git a/modules/device.py b/modules/device.py index cf0e2a2..ad99e9d 100644 --- a/modules/device.py +++ b/modules/device.py @@ -458,9 +458,14 @@ class PhysicalDevice: """ Sets proxy or proxy group if this value has been defined in config context + or custom fields. input: List of all proxies and proxy groups in standardized format """ + proxy_name = None + + #!!! FIX this check to still work if only CF is configurd. + # check if the key Zabbix is defined in the config context if "zabbix" not in self.nb.config_context: return False @@ -477,10 +482,21 @@ 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 - if proxy_type in self.nb.config_context["zabbix"]: + # Check if we should use custom fields for proxy config + field_config = "proxy_cf" if proxy_type=="proxy" else "proxy_group_cf" + if config[field_config]: + if config[field_config] in self.nb.custom_fields: + if self.nb.custom_fields[config[field_config]]: + proxy_name = self.nb.custom_fields[config[field_config]] + elif config[field_config] in self.nb.site.custom_fields: + if self.nb.site.custom_fields[config[field_config]]: + proxy_name = self.nb.site.custom_fields[config[field_config]] + + # Otherwise check if the proxy is configured in NetBox CC + if not proxy_name and proxy_type in self.nb.config_context["zabbix"]: proxy_name = self.nb.config_context["zabbix"][proxy_type] # go through all proxies + if proxy_name: for proxy in proxy_list: # If the proxy does not match the type, ignore and continue if not proxy["type"] == proxy_type: From 17ba97be458c94e38c0fa46a9f9c74a1d1a32d3d Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Thu, 11 Sep 2025 17:26:05 +0200 Subject: [PATCH 02/13] Minor update on README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9616983..e4b9f38 100644 --- a/README.md +++ b/README.md @@ -682,7 +682,7 @@ a Zabbix proxy or proxy group. The custom fields can be assigned to both Devices and VMs. You can also assign these custom fields to a site to allow all devices/VMs -in that site to be confured with the same proxy or proxy group. +in that site to be configured with the same proxy or proxy group. In order for this to work, `extended_site_properties` needs to be enabled in the configuation as well. @@ -696,8 +696,9 @@ proxy_group_cf = "zabbix_proxy_group" ``` As with config context proxy configuration, proxy group will take precedence over -proxy when configured. -Proxy settings configured on the device or VM will take precedence over any site configuration. +standalone proxy when configured. +Proxy settings configured on the device or VM will in their turn take precedence +over any site configuration. If the custom fields have no value but the proxy or proxy group is configured in config context, that setting will be used. From 7d9bb9f637563575efa003ce76aafb0287673c14 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Fri, 12 Sep 2025 10:21:42 +0200 Subject: [PATCH 03/13] Refactoring --- modules/device.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/modules/device.py b/modules/device.py index ad99e9d..a636523 100644 --- a/modules/device.py +++ b/modules/device.py @@ -462,25 +462,16 @@ class PhysicalDevice: input: List of all proxies and proxy groups in standardized format """ - proxy_name = None - - #!!! FIX this check to still work if only CF is configurd. - - # check if the key Zabbix is defined in the config context - if "zabbix" not in self.nb.config_context: - return False - if ( - "proxy" in self.nb.config_context["zabbix"] - and not self.nb.config_context["zabbix"]["proxy"] - ): - return False # Proxy group takes priority over a proxy due # to it being HA and therefore being more reliable # Includes proxy group fix since Zabbix <= 6 should ignore this proxy_types = ["proxy"] + proxy_name = None if str(self.zabbix.version).startswith("7"): # Only insert groups in front of list for Zabbix7 proxy_types.insert(0, "proxy_group") + + # loop through supported proxy-types for proxy_type in proxy_types: # Check if we should use custom fields for proxy config field_config = "proxy_cf" if proxy_type=="proxy" else "proxy_group_cf" @@ -493,7 +484,8 @@ class PhysicalDevice: proxy_name = self.nb.site.custom_fields[config[field_config]] # Otherwise check if the proxy is configured in NetBox CC - if not proxy_name and proxy_type in self.nb.config_context["zabbix"]: + if (not proxy_name and "zabbix" in self.nb.config_context and + proxy_type in self.nb.config_context["zabbix"]): proxy_name = self.nb.config_context["zabbix"][proxy_type] # go through all proxies if proxy_name: From 123b243f5692ffc2cf7f93e7c755b34e151064ce Mon Sep 17 00:00:00 2001 From: Wouter de Bruijn Date: Fri, 12 Sep 2025 10:48:29 +0200 Subject: [PATCH 04/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improved=20Zabbix=20?= =?UTF-8?q?version=20check=20for=20proxy=20group=20insertion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/device.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/device.py b/modules/device.py index a636523..f1b63c7 100644 --- a/modules/device.py +++ b/modules/device.py @@ -7,6 +7,7 @@ from copy import deepcopy from logging import getLogger from re import search from operator import itemgetter +from typing import Any from zabbix_utils import APIRequestError from pynetbox import RequestError as NetboxRequestError @@ -454,7 +455,7 @@ class PhysicalDevice: self.tags = tags.generate() return True - def setProxy(self, proxy_list): + def _setProxy(self, proxy_list: list[dict[str, Any]]) -> bool: """ Sets proxy or proxy group if this value has been defined in config context @@ -467,7 +468,8 @@ class PhysicalDevice: # Includes proxy group fix since Zabbix <= 6 should ignore this proxy_types = ["proxy"] proxy_name = None - if str(self.zabbix.version).startswith("7"): + + if self.zabbix.version >= 7.0: # Only insert groups in front of list for Zabbix7 proxy_types.insert(0, "proxy_group") @@ -488,6 +490,7 @@ class PhysicalDevice: proxy_type in self.nb.config_context["zabbix"]): proxy_name = self.nb.config_context["zabbix"][proxy_type] # go through all proxies + if proxy_name: for proxy in proxy_list: # If the proxy does not match the type, ignore and continue @@ -500,6 +503,7 @@ class PhysicalDevice: ) self.zbxproxy = proxy return True + self.logger.warning( "Host %s: unable to find proxy %s", self.name, proxy_name ) @@ -532,7 +536,7 @@ class PhysicalDevice: # Set interface, group and template configuration interfaces = self.setInterfaceDetails() # Set Zabbix proxy if defined - self.setProxy(proxies) + self._setProxy(proxies) # Set basic data for host creation create_data = { "host": self.name, @@ -664,7 +668,7 @@ class PhysicalDevice: # Prepare templates and proxy config self.zbxTemplatePrepper(templates) - self.setProxy(proxies) + self._setProxy(proxies) # Get host object from Zabbix host = self.zabbix.host.get( filter={"hostid": self.zabbix_id}, From 422d343c1f52ac5d32f1fee6b4abc1ab714cfea2 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Fri, 12 Sep 2025 14:11:38 +0200 Subject: [PATCH 05/13] * Added support for object and select custom fields in host groups and proxy config. * Corrected error when `full_proxy_sync` was not set and a host no longer uses a proxy. --- modules/device.py | 12 ++++++------ modules/hostgroups.py | 4 ++-- modules/tools.py | 15 ++++++++++++++- netbox_zabbix_sync.py | 4 ++-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/modules/device.py b/modules/device.py index f1b63c7..fa62ec0 100644 --- a/modules/device.py +++ b/modules/device.py @@ -21,7 +21,7 @@ from modules.exceptions import ( from modules.hostgroups import Hostgroup from modules.interface import ZabbixInterface from modules.tags import ZabbixTags -from modules.tools import field_mapper, remove_duplicates, sanatize_log_output +from modules.tools import field_mapper, cf_to_string, remove_duplicates, sanatize_log_output from modules.usermacros import ZabbixUsermacros from modules.config import load_config @@ -480,17 +480,17 @@ class PhysicalDevice: if config[field_config]: if config[field_config] in self.nb.custom_fields: if self.nb.custom_fields[config[field_config]]: - proxy_name = self.nb.custom_fields[config[field_config]] + proxy_name = cf_to_string(self.nb.custom_fields[config[field_config]]) elif config[field_config] in self.nb.site.custom_fields: if self.nb.site.custom_fields[config[field_config]]: - proxy_name = self.nb.site.custom_fields[config[field_config]] + proxy_name = cf_to_string(self.nb.site.custom_fields[config[field_config]]) # Otherwise check if the proxy is configured in NetBox CC if (not proxy_name and "zabbix" in self.nb.config_context and proxy_type in self.nb.config_context["zabbix"]): proxy_name = self.nb.config_context["zabbix"][proxy_type] - # go through all proxies - + + # If a proxy name was found, loop through all proxies to find a match if proxy_name: for proxy in proxy_list: # If the proxy does not match the type, ignore and continue @@ -804,7 +804,7 @@ class PhysicalDevice: # Display error message self.logger.warning( "Host %s: Is configured with proxy in Zabbix but not in NetBox." - "The -p flag was ommited: no changes have been made.", + "full_proxy_sync is not set: no changes have been made.", self.name, ) if not proxy_set: diff --git a/modules/hostgroups.py b/modules/hostgroups.py index 5916c0a..785e172 100644 --- a/modules/hostgroups.py +++ b/modules/hostgroups.py @@ -3,7 +3,7 @@ from logging import getLogger from modules.exceptions import HostgroupError -from modules.tools import build_path +from modules.tools import build_path, cf_to_string class Hostgroup: @@ -134,7 +134,7 @@ class Hostgroup: raise HostgroupError(msg) # CF data is populated if cf_data["cf"]: - hg_output.append(cf_data["cf"]) + hg_output.append(cf_to_string(cf_data["cf"])) continue # Check if there is a value associated to the variable. # For instance, if a device has no location, do not use it with hostgroup calculation diff --git a/modules/tools.py b/modules/tools.py index dacab20..efa6922 100644 --- a/modules/tools.py +++ b/modules/tools.py @@ -50,6 +50,19 @@ def proxy_prepper(proxy_list, proxy_group_list): return output +def cf_to_string(cf, key="name", logger=None): + """ + Converts a dict custom fields to string + """ + if isinstance(cf, dict): + if key: + return cf[key] + else: + logger.error("Conversion of custom field failed, '%s' not found in cf dict.", key) + return None + return cf + + def field_mapper(host, mapper, nbdevice, logger): """ Maps NetBox field data to Zabbix properties. @@ -204,4 +217,4 @@ def sanatize_log_output(data): continue # A macro is not used, so we sanitize the value. sanitized_data["details"][key] = "********" - return sanitized_data + return sanitized_data \ No newline at end of file diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index 3a03f66..08e507f 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -82,7 +82,7 @@ def main(arguments): device_cfs = [] vm_cfs = [] device_cfs = list( - netbox.extras.custom_fields.filter(type="text", content_types="dcim.device") + netbox.extras.custom_fields.filter(type=["text","object","select"], content_types="dcim.device") ) verify_hg_format( config["hostgroup_format"], device_cfs=device_cfs, hg_type="dev", logger=logger @@ -90,7 +90,7 @@ def main(arguments): if config["sync_vms"]: vm_cfs = list( netbox.extras.custom_fields.filter( - type="text", content_types="virtualization.virtualmachine" + type=["text","object","select"], content_types="virtualization.virtualmachine" ) ) verify_hg_format( From bc12064b6a60a9b322677acdcf3480fbedddaf7c Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Fri, 12 Sep 2025 14:27:06 +0200 Subject: [PATCH 06/13] corrected linting error --- netbox_zabbix_sync.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index 08e507f..c2e0865 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -82,7 +82,8 @@ def main(arguments): device_cfs = [] vm_cfs = [] device_cfs = list( - netbox.extras.custom_fields.filter(type=["text","object","select"], content_types="dcim.device") + netbox.extras.custom_fields.filter(type=["text","object","select"], + content_types="dcim.device") ) verify_hg_format( config["hostgroup_format"], device_cfs=device_cfs, hg_type="dev", logger=logger @@ -90,7 +91,8 @@ def main(arguments): if config["sync_vms"]: vm_cfs = list( netbox.extras.custom_fields.filter( - type=["text","object","select"], content_types="virtualization.virtualmachine" + type=["text","object","select"], + content_types="virtualization.virtualmachine" ) ) verify_hg_format( From c27505b9272cac8402a266e3dc6b0a5baf2c8532 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Fri, 12 Sep 2025 14:39:11 +0200 Subject: [PATCH 07/13] corrected linting errors and a minor bug in cf_to_string --- modules/device.py | 14 +++++++------- modules/tools.py | 9 ++++----- netbox_zabbix_sync.py | 4 ++-- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/modules/device.py b/modules/device.py index fa62ec0..3a68afc 100644 --- a/modules/device.py +++ b/modules/device.py @@ -468,25 +468,25 @@ class PhysicalDevice: # Includes proxy group fix since Zabbix <= 6 should ignore this proxy_types = ["proxy"] proxy_name = None - + if self.zabbix.version >= 7.0: # Only insert groups in front of list for Zabbix7 proxy_types.insert(0, "proxy_group") - # loop through supported proxy-types + # loop through supported proxy-types for proxy_type in proxy_types: # Check if we should use custom fields for proxy config - field_config = "proxy_cf" if proxy_type=="proxy" else "proxy_group_cf" - if config[field_config]: + field_config = "proxy_cf" if proxy_type=="proxy" else "proxy_group_cf" + if config[field_config]: if config[field_config] in self.nb.custom_fields: if self.nb.custom_fields[config[field_config]]: proxy_name = cf_to_string(self.nb.custom_fields[config[field_config]]) elif config[field_config] in self.nb.site.custom_fields: if self.nb.site.custom_fields[config[field_config]]: - proxy_name = cf_to_string(self.nb.site.custom_fields[config[field_config]]) + proxy_name = cf_to_string(self.nb.site.custom_fields[config[field_config]]) # Otherwise check if the proxy is configured in NetBox CC - if (not proxy_name and "zabbix" in self.nb.config_context and + if (not proxy_name and "zabbix" in self.nb.config_context and proxy_type in self.nb.config_context["zabbix"]): proxy_name = self.nb.config_context["zabbix"][proxy_type] @@ -503,7 +503,7 @@ class PhysicalDevice: ) self.zbxproxy = proxy return True - + self.logger.warning( "Host %s: unable to find proxy %s", self.name, proxy_name ) diff --git a/modules/tools.py b/modules/tools.py index efa6922..023cef8 100644 --- a/modules/tools.py +++ b/modules/tools.py @@ -55,11 +55,10 @@ def cf_to_string(cf, key="name", logger=None): Converts a dict custom fields to string """ if isinstance(cf, dict): - if key: + if key in cf: return cf[key] - else: - logger.error("Conversion of custom field failed, '%s' not found in cf dict.", key) - return None + logger.error("Conversion of custom field failed, '%s' not found in cf dict.", key) + return None return cf @@ -217,4 +216,4 @@ def sanatize_log_output(data): continue # A macro is not used, so we sanitize the value. sanitized_data["details"][key] = "********" - return sanitized_data \ No newline at end of file + return sanitized_data diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index c2e0865..4ee2ba0 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -82,7 +82,7 @@ def main(arguments): device_cfs = [] vm_cfs = [] device_cfs = list( - netbox.extras.custom_fields.filter(type=["text","object","select"], + netbox.extras.custom_fields.filter(type=["text","object","select"], content_types="dcim.device") ) verify_hg_format( @@ -91,7 +91,7 @@ def main(arguments): if config["sync_vms"]: vm_cfs = list( netbox.extras.custom_fields.filter( - type=["text","object","select"], + type=["text","object","select"], content_types="virtualization.virtualmachine" ) ) From 37774cfec36a6f34b6791a3df5dbdfb4deba3637 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Fri, 12 Sep 2025 14:40:53 +0200 Subject: [PATCH 08/13] More linting fixes --- modules/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/device.py b/modules/device.py index 3a68afc..13e74aa 100644 --- a/modules/device.py +++ b/modules/device.py @@ -468,7 +468,7 @@ class PhysicalDevice: # Includes proxy group fix since Zabbix <= 6 should ignore this proxy_types = ["proxy"] proxy_name = None - + if self.zabbix.version >= 7.0: # Only insert groups in front of list for Zabbix7 proxy_types.insert(0, "proxy_group") From 337184159bc074f1d4562a08b83d1f68a3488b20 Mon Sep 17 00:00:00 2001 From: Wouter de Bruijn Date: Fri, 12 Sep 2025 16:44:04 +0200 Subject: [PATCH 09/13] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20key/value=20check?= =?UTF-8?q?=20for=20proxy=20assignment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/device.py | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/modules/device.py b/modules/device.py index 13e74aa..f0efbf3 100644 --- a/modules/device.py +++ b/modules/device.py @@ -5,13 +5,14 @@ Device specific handeling for NetBox to Zabbix from copy import deepcopy from logging import getLogger + from operator import itemgetter from re import search -from operator import itemgetter from typing import Any -from zabbix_utils import APIRequestError from pynetbox import RequestError as NetboxRequestError +from zabbix_utils import APIRequestError +from modules.config import load_config from modules.exceptions import ( InterfaceConfigError, SyncExternalError, @@ -21,9 +22,13 @@ from modules.exceptions import ( from modules.hostgroups import Hostgroup from modules.interface import ZabbixInterface from modules.tags import ZabbixTags -from modules.tools import field_mapper, cf_to_string, remove_duplicates, sanatize_log_output +from modules.tools import ( + cf_to_string, + field_mapper, + remove_duplicates, + sanatize_log_output, +) from modules.usermacros import ZabbixUsermacros -from modules.config import load_config config = load_config() @@ -476,18 +481,29 @@ class PhysicalDevice: # loop through supported proxy-types for proxy_type in proxy_types: # Check if we should use custom fields for proxy config - field_config = "proxy_cf" if proxy_type=="proxy" else "proxy_group_cf" + field_config = "proxy_cf" if proxy_type == "proxy" else "proxy_group_cf" if config[field_config]: - if config[field_config] in self.nb.custom_fields: - if self.nb.custom_fields[config[field_config]]: - proxy_name = cf_to_string(self.nb.custom_fields[config[field_config]]) - elif config[field_config] in self.nb.site.custom_fields: - if self.nb.site.custom_fields[config[field_config]]: - proxy_name = cf_to_string(self.nb.site.custom_fields[config[field_config]]) + if ( + config[field_config] in self.nb.custom_fields + and self.nb.custom_fields[config[field_config]] + ): + proxy_name = cf_to_string( + self.nb.custom_fields[config[field_config]] + ) + elif ( + config[field_config] in self.nb.site.custom_fields + and self.nb.site.custom_fields[config[field_config]] + ): + proxy_name = cf_to_string( + self.nb.site.custom_fields[config[field_config]] + ) # Otherwise check if the proxy is configured in NetBox CC - if (not proxy_name and "zabbix" in self.nb.config_context and - proxy_type in self.nb.config_context["zabbix"]): + if ( + not proxy_name + and "zabbix" in self.nb.config_context + and proxy_type in self.nb.config_context["zabbix"] + ): proxy_name = self.nb.config_context["zabbix"][proxy_type] # If a proxy name was found, loop through all proxies to find a match From bf512ada0b6335e3dae1dc231b5f45fef21570c9 Mon Sep 17 00:00:00 2001 From: Wouter de Bruijn Date: Fri, 12 Sep 2025 16:45:03 +0200 Subject: [PATCH 10/13] =?UTF-8?q?=F0=9F=92=84=20Codebase=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/tools.py | 6 ++++-- netbox_zabbix_sync.py | 16 ++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/modules/tools.py b/modules/tools.py index 023cef8..db6a1a6 100644 --- a/modules/tools.py +++ b/modules/tools.py @@ -57,7 +57,9 @@ def cf_to_string(cf, key="name", logger=None): if isinstance(cf, dict): if key in cf: return cf[key] - logger.error("Conversion of custom field failed, '%s' not found in cf dict.", key) + logger.error( + "Conversion of custom field failed, '%s' not found in cf dict.", key + ) return None return cf @@ -172,7 +174,7 @@ def verify_hg_format( if ( hg_object not in allowed_objects[hg_type] and hg_object not in allowed_objects["cfs"][hg_type] - and not hg_object.startswith(('"',"'")) + and not hg_object.startswith(('"', "'")) ): e = ( f"Hostgroup item {hg_object} is not valid. Make sure you" diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index 4ee2ba0..8791e47 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -12,6 +12,7 @@ from pynetbox import api from pynetbox.core.query import RequestError as NBRequestError from requests.exceptions import ConnectionError as RequestsConnectionError from zabbix_utils import APIRequestError, ProcessingError, ZabbixAPI + from modules.config import load_config from modules.device import PhysicalDevice from modules.exceptions import EnvironmentVarError, SyncError @@ -82,8 +83,9 @@ def main(arguments): device_cfs = [] vm_cfs = [] device_cfs = list( - netbox.extras.custom_fields.filter(type=["text","object","select"], - content_types="dcim.device") + netbox.extras.custom_fields.filter( + type=["text", "object", "select"], content_types="dcim.device" + ) ) verify_hg_format( config["hostgroup_format"], device_cfs=device_cfs, hg_type="dev", logger=logger @@ -91,8 +93,8 @@ def main(arguments): if config["sync_vms"]: vm_cfs = list( netbox.extras.custom_fields.filter( - type=["text","object","select"], - content_types="virtualization.virtualmachine" + type=["text", "object", "select"], + content_types="virtualization.virtualmachine", ) ) verify_hg_format( @@ -170,7 +172,7 @@ def main(arguments): continue if config["extended_site_properties"] and nb_vm.site: logger.debug("VM %s: extending site information.", vm.name) - vm.site=(convert_recordset(netbox.dcim.sites.filter(id=nb_vm.site.id))) + vm.site = convert_recordset(netbox.dcim.sites.filter(id=nb_vm.site.id)) vm.set_inventory(nb_vm) vm.set_usermacros() vm.set_tags() @@ -245,7 +247,9 @@ def main(arguments): continue if config["extended_site_properties"] and nb_device.site: logger.debug("Device %s: extending site information.", device.name) - device.site=(convert_recordset(netbox.dcim.sites.filter(id=nb_device.site.id))) + device.site = convert_recordset( + netbox.dcim.sites.filter(id=nb_device.site.id) + ) device.set_inventory(nb_device) device.set_usermacros() device.set_tags() From 047fb33332cb361f109fcdb6ab8c9659dabd2923 Mon Sep 17 00:00:00 2001 From: Wouter de Bruijn Date: Fri, 12 Sep 2025 16:47:57 +0200 Subject: [PATCH 11/13] =?UTF-8?q?=F0=9F=9A=91=20Fixed=20random=20space=20o?= =?UTF-8?q?n=20line=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/device.py b/modules/device.py index f0efbf3..cc32390 100644 --- a/modules/device.py +++ b/modules/device.py @@ -5,7 +5,7 @@ Device specific handeling for NetBox to Zabbix from copy import deepcopy from logging import getLogger - from operator import itemgetter +from operator import itemgetter from re import search from typing import Any From efb42916fd7546a036fec73fa0852443e09d108d Mon Sep 17 00:00:00 2001 From: Wouter de Bruijn Date: Wed, 15 Oct 2025 17:26:54 +0200 Subject: [PATCH 12/13] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Minor=20typo=20clean?= =?UTF-8?q?up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- config.py.example | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e4b9f38..4c762ed 100644 --- a/README.md +++ b/README.md @@ -416,7 +416,7 @@ making NetBox the single source-of-truth for managing tags. To enable syncing, turn on `tag_sync` in the config file. By default, this script will modify tag names and tag values to lowercase. -You can change this behaviour by setting `tag_lower` to `False`. +You can change this behavior by setting `tag_lower` to `False`. ```python tag_sync = True @@ -684,7 +684,7 @@ Devices and VMs. You can also assign these custom fields to a site to allow all devices/VMs in that site to be configured with the same proxy or proxy group. In order for this to work, `extended_site_properties` needs to be enabled in -the configuation as well. +the configuration as well. To use the custom fields for proxy configuration, configure one or both of the following settings in the configuration file with the actual names of your diff --git a/config.py.example b/config.py.example index cf0cf6b..acc1629 100644 --- a/config.py.example +++ b/config.py.example @@ -53,9 +53,9 @@ hostgroup_format = "site/manufacturer/role" traverse_regions = False traverse_site_groups = False -## Extended site properteis +## Extended site properties # By default, NetBox will only return basic site info for any device or VM. -# By setting `extended_site_properties` to True, the script will query NetBox for additiopnal site info. +# By setting `extended_site_properties` to True, the script will query NetBox for additional site info. # Be aware that this will increase the number of API queries to NetBox. extended_site_properties = False From 8197f41788c30bd303e1c21db03e0803dcc642ad Mon Sep 17 00:00:00 2001 From: Wouter de Bruijn Date: Wed, 15 Oct 2025 17:27:07 +0200 Subject: [PATCH 13/13] =?UTF-8?q?=F0=9F=8E=A8=20Minor=20formatting=20clean?= =?UTF-8?q?up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/config.py | 19 ++++++++++--------- modules/hostgroups.py | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/modules/config.py b/modules/config.py index b1afcfb..3d69d6c 100644 --- a/modules/config.py +++ b/modules/config.py @@ -1,6 +1,7 @@ """ Module for parsing configuration from the top level config.py file """ + from pathlib import Path from importlib import util from os import environ, path @@ -17,7 +18,7 @@ DEFAULT_CONFIG = { "template_cf": "zabbix_template", "device_cf": "zabbix_hostid", "proxy_cf": False, - "proxy_group_cf" : False, + "proxy_group_cf": False, "clustering": False, "create_hostgroups": True, "create_journal": False, @@ -47,40 +48,40 @@ DEFAULT_CONFIG = { "serial": "serialno_a", "device_type/model": "type", "device_type/manufacturer/name": "vendor", - "oob_ip/address": "oob_ip" + "oob_ip/address": "oob_ip", }, "vm_inventory_map": { "status/label": "deployment_status", "comments": "notes", - "name": "name" + "name": "name", }, "usermacro_sync": False, "device_usermacro_map": { "serial": "{$HW_SERIAL}", "role/name": "{$DEV_ROLE}", "url": "{$NB_URL}", - "id": "{$NB_ID}" + "id": "{$NB_ID}", }, "vm_usermacro_map": { "memory": "{$TOTAL_MEMORY}", "role/name": "{$DEV_ROLE}", "url": "{$NB_URL}", - "id": "{$NB_ID}" + "id": "{$NB_ID}", }, "tag_sync": False, "tag_lower": True, - "tag_name": 'NetBox', + "tag_name": "NetBox", "tag_value": "name", "device_tag_map": { "site/name": "site", "rack/name": "rack", - "platform/name": "target" + "platform/name": "target", }, "vm_tag_map": { "site/name": "site", "cluster/name": "cluster", - "platform/name": "target" - } + "platform/name": "target", + }, } diff --git a/modules/hostgroups.py b/modules/hostgroups.py index 785e172..49890e6 100644 --- a/modules/hostgroups.py +++ b/modules/hostgroups.py @@ -118,8 +118,8 @@ class Hostgroup: # Check if requested data is available as option for this host if hg_item not in self.format_options: if hg_item.startswith(("'", '"')) and hg_item.endswith(("'", '"')): - hg_item = hg_item.strip("\'") - hg_item = hg_item.strip('\"') + hg_item = hg_item.strip("'") + hg_item = hg_item.strip('"') hg_output.append(hg_item) else: # Check if a custom field exists with this name