🔊 Removed f-strings usage from logs

This commit is contained in:
Wouter de Bruijn 2025-06-25 13:56:41 +02:00
parent e0ec3c0632
commit 57c7f83e6a
No known key found for this signature in database
GPG Key ID: AC71F96733B92BFA
6 changed files with 246 additions and 176 deletions

View File

@ -26,6 +26,7 @@ from modules.config import load_config
config = load_config() config = load_config()
class PhysicalDevice: class PhysicalDevice:
# pylint: disable=too-many-instance-attributes, too-many-arguments, too-many-positional-arguments # pylint: disable=too-many-instance-attributes, too-many-arguments, too-many-positional-arguments
""" """
@ -97,7 +98,7 @@ class PhysicalDevice:
if config["device_cf"] in self.nb.custom_fields: if config["device_cf"] in self.nb.custom_fields:
self.zabbix_id = self.nb.custom_fields[config["device_cf"]] self.zabbix_id = self.nb.custom_fields[config["device_cf"]]
else: else:
e = f'Host {self.name}: Custom field {config["device_cf"]} not present' e = f"Host {self.name}: Custom field {config['device_cf']} not present"
self.logger.error(e) self.logger.error(e)
raise SyncInventoryError(e) raise SyncInventoryError(e)
@ -111,9 +112,10 @@ class PhysicalDevice:
self.visible_name = self.nb.name self.visible_name = self.nb.name
self.use_visible_name = True self.use_visible_name = True
self.logger.info( self.logger.info(
f"Host {self.visible_name} contains special characters. " "Host %s contains special characters. Using %s as name for the NetBox object and using %s as visible name in Zabbix.",
f"Using {self.name} as name for the NetBox object " self.visible_name,
f"and using {self.visible_name} as visible name in Zabbix." self.name,
self.visible_name,
) )
else: else:
pass pass
@ -126,8 +128,8 @@ class PhysicalDevice:
self.nb, self.nb,
self.nb_api_version, self.nb_api_version,
logger=self.logger, logger=self.logger,
nested_sitegroup_flag=config['traverse_site_groups'], nested_sitegroup_flag=config["traverse_site_groups"],
nested_region_flag=config['traverse_regions'], nested_region_flag=config["traverse_regions"],
nb_groups=nb_site_groups, nb_groups=nb_site_groups,
nb_regions=nb_regions, nb_regions=nb_regions,
) )
@ -139,12 +141,12 @@ class PhysicalDevice:
# Remove duplicates and None values # Remove duplicates and None values
self.hostgroups = list(filter(None, list(set(self.hostgroups)))) self.hostgroups = list(filter(None, list(set(self.hostgroups))))
if self.hostgroups: if self.hostgroups:
self.logger.debug(f"Host {self.name}: Should be member " self.logger.debug(
f"of groups: {self.hostgroups}") "Host %s: Should be member of groups: %s", self.name, self.hostgroups
)
return True return True
return False return False
def set_template(self, prefer_config_context, overrule_custom): def set_template(self, prefer_config_context, overrule_custom):
"""Set Template""" """Set Template"""
self.zbx_template_names = None self.zbx_template_names = None
@ -210,9 +212,10 @@ class PhysicalDevice:
# Set inventory mode. Default is disabled (see class init function). # Set inventory mode. Default is disabled (see class init function).
if config["inventory_mode"] == "disabled": if config["inventory_mode"] == "disabled":
if config["inventory_sync"]: if config["inventory_sync"]:
self.logger.error(f"Host {self.name}: Unable to map NetBox inventory to Zabbix. " self.logger.error(
"Inventory sync is enabled in " "Host %s: Unable to map NetBox inventory to Zabbix. Inventory sync is enabled in config but inventory mode is disabled",
"config but inventory mode is disabled.") self.name,
)
return True return True
if config["inventory_mode"] == "manual": if config["inventory_mode"] == "manual":
self.inventory_mode = 0 self.inventory_mode = 0
@ -220,17 +223,20 @@ class PhysicalDevice:
self.inventory_mode = 1 self.inventory_mode = 1
else: else:
self.logger.error( self.logger.error(
f"Host {self.name}: Specified value for inventory mode in" "Host %s: Specified value for inventory mode in config is not valid. Got value %s",
f" config is not valid. Got value {config['inventory_mode']}" self.name,
config["inventory_mode"],
) )
return False return False
self.inventory = {} self.inventory = {}
if config["inventory_sync"] and self.inventory_mode in [0, 1]: if config["inventory_sync"] and self.inventory_mode in [0, 1]:
self.logger.debug(f"Host {self.name}: Starting inventory mapper.") self.logger.debug("Host %s: Starting inventory mapper.", self.name)
self.inventory = field_mapper( self.inventory = field_mapper(
self.name, self._inventory_map(), nbdevice, self.logger self.name, self._inventory_map(), nbdevice, self.logger
) )
self.logger.debug(f"Host {self.name}: Resolved inventory: {self.inventory}") self.logger.debug(
"Host %s: Resolved inventory: %s", self.name, self.inventory
)
return True return True
def isCluster(self): def isCluster(self):
@ -268,13 +274,14 @@ class PhysicalDevice:
masterid = self.getClusterMaster() masterid = self.getClusterMaster()
if masterid == self.id: if masterid == self.id:
self.logger.info( self.logger.info(
f"Host {self.name} is primary cluster member. " "Host %s is primary cluster member. Modifying hostname from %s to %s.",
f"Modifying hostname from {self.name} to " self.name,
+ f"{self.nb.virtual_chassis.name}." self.name,
self.nb.virtual_chassis.name,
) )
self.name = self.nb.virtual_chassis.name self.name = self.nb.virtual_chassis.name
return True return True
self.logger.info(f"Host {self.name} is non-primary cluster member.") self.logger.info("Host %s is non-primary cluster member.", self.name)
return False return False
def zbxTemplatePrepper(self, templates): def zbxTemplatePrepper(self, templates):
@ -306,8 +313,10 @@ class PhysicalDevice:
"name": zbx_template["name"], "name": zbx_template["name"],
} }
) )
e = (f"Host {self.name}: Found template '{zbx_template['name']}' " e = (
f"(ID:{zbx_template['templateid']})") f"Host {self.name}: Found template '{zbx_template['name']}' "
f"(ID:{zbx_template['templateid']})"
)
self.logger.debug(e) self.logger.debug(e)
# Return error should the template not be found in Zabbix # Return error should the template not be found in Zabbix
if not template_match: if not template_match:
@ -331,7 +340,7 @@ class PhysicalDevice:
self.group_ids.append({"groupid": group["groupid"]}) self.group_ids.append({"groupid": group["groupid"]})
e = ( e = (
f"Host {self.name}: Matched group " f"Host {self.name}: Matched group "
f"\"{group['name']}\" (ID:{group['groupid']})" f'"{group["name"]}" (ID:{group["groupid"]})'
) )
self.logger.debug(e) self.logger.debug(e)
if len(self.group_ids) == len(self.hostgroups): if len(self.group_ids) == len(self.hostgroups):
@ -412,7 +421,7 @@ class PhysicalDevice:
macros = ZabbixUsermacros( macros = ZabbixUsermacros(
self.nb, self.nb,
self._usermacro_map(), self._usermacro_map(),
config['usermacro_sync'], config["usermacro_sync"],
logger=self.logger, logger=self.logger,
host=self.name, host=self.name,
) )
@ -430,14 +439,14 @@ class PhysicalDevice:
tags = ZabbixTags( tags = ZabbixTags(
self.nb, self.nb,
self._tag_map(), self._tag_map(),
tag_sync=config['tag_sync'], tag_sync=config["tag_sync"],
tag_lower=config['tag_lower'], tag_lower=config["tag_lower"],
tag_name=config['tag_name'], tag_name=config["tag_name"],
tag_value=config['tag_value'], tag_value=config["tag_value"],
logger=self.logger, logger=self.logger,
host=self.name, host=self.name,
) )
if config['tag_sync'] is False: if config["tag_sync"] is False:
self.tags = [] self.tags = []
return False return False
self.tags = tags.generate() self.tags = tags.generate()
@ -477,12 +486,12 @@ class PhysicalDevice:
# If the proxy name matches # If the proxy name matches
if proxy["name"] == proxy_name: if proxy["name"] == proxy_name:
self.logger.debug( self.logger.debug(
f"Host {self.name}: using {proxy['type']}" f" '{proxy_name}'" "Host %s: using {proxy['type']} '%s'", self.name, proxy_name
) )
self.zbxproxy = proxy self.zbxproxy = proxy
return True return True
self.logger.warning( self.logger.warning(
f"Host {self.name}: unable to find proxy {proxy_name}" "Host %s: unable to find proxy %s", self.name, proxy_name
) )
return False return False
@ -554,7 +563,7 @@ class PhysicalDevice:
self.create_journal_entry("success", msg) self.create_journal_entry("success", msg)
else: else:
self.logger.error( self.logger.error(
f"Host {self.name}: Unable to add to Zabbix. Host already present." "Host %s: Unable to add to Zabbix. Host already present.", self.name
) )
def createZabbixHostgroup(self, hostgroups): def createZabbixHostgroup(self, hostgroups):
@ -612,7 +621,9 @@ class PhysicalDevice:
) )
self.logger.error(e) self.logger.error(e)
raise SyncExternalError(e) from None raise SyncExternalError(e) from None
self.logger.info(f"Host {self.name}: updated with data {sanatize_log_output(kwargs)}.") self.logger.info(
"Host %s: updated with data %s.", self.name, sanatize_log_output(kwargs)
)
self.create_journal_entry("info", "Updated host in Zabbix with latest NB data.") self.create_journal_entry("info", "Updated host in Zabbix with latest NB data.")
def ConsistencyCheck( def ConsistencyCheck(
@ -672,28 +683,30 @@ class PhysicalDevice:
raise SyncInventoryError(e) raise SyncInventoryError(e)
host = host[0] host = host[0]
if host["host"] == self.name: if host["host"] == self.name:
self.logger.debug(f"Host {self.name}: Hostname in-sync.") self.logger.debug("Host %s: Hostname in-sync.", self.name)
else: else:
self.logger.info( self.logger.info(
f"Host {self.name}: Hostname OUT of sync. " "Host %s: Hostname OUT of sync. Received value: %s",
f"Received value: {host['host']}" self.name,
host["host"],
) )
self.updateZabbixHost(host=self.name) self.updateZabbixHost(host=self.name)
# Execute check depending on wether the name is special or not # Execute check depending on wether the name is special or not
if self.use_visible_name: if self.use_visible_name:
if host["name"] == self.visible_name: if host["name"] == self.visible_name:
self.logger.debug(f"Host {self.name}: Visible name in-sync.") self.logger.debug("Host %s: Visible name in-sync.", self.name)
else: else:
self.logger.info( self.logger.info(
f"Host {self.name}: Visible name OUT of sync." "Host %s: Visible name OUT of sync. Received value: %s",
f" Received value: {host['name']}" self.name,
host["name"],
) )
self.updateZabbixHost(name=self.visible_name) self.updateZabbixHost(name=self.visible_name)
# Check if the templates are in-sync # Check if the templates are in-sync
if not self.zbx_template_comparer(host["parentTemplates"]): if not self.zbx_template_comparer(host["parentTemplates"]):
self.logger.info(f"Host {self.name}: Template(s) OUT of sync.") self.logger.info("Host %s: Template(s) OUT of sync.", self.name)
# Prepare Templates for API parsing # Prepare Templates for API parsing
templateids = [] templateids = []
for template in self.zbx_templates: for template in self.zbx_templates:
@ -703,38 +716,41 @@ class PhysicalDevice:
templates_clear=host["parentTemplates"], templates=templateids templates_clear=host["parentTemplates"], templates=templateids
) )
else: else:
self.logger.debug(f"Host {self.name}: Template(s) in-sync.") self.logger.debug("Host %s: Template(s) in-sync.", self.name)
# Check if Zabbix version is 6 or higher. Issue #93 # Check if Zabbix version is 6 or higher. Issue #93
group_dictname = "hostgroups" group_dictname = "hostgroups"
if str(self.zabbix.version).startswith(("6", "5")): if str(self.zabbix.version).startswith(("6", "5")):
group_dictname = "groups" group_dictname = "groups"
# Check if hostgroups match # Check if hostgroups match
if (sorted(host[group_dictname], key=itemgetter('groupid')) == if sorted(host[group_dictname], key=itemgetter("groupid")) == sorted(
sorted(self.group_ids, key=itemgetter('groupid'))): self.group_ids, key=itemgetter("groupid")
self.logger.debug(f"Host {self.name}: Hostgroups in-sync.") ):
self.logger.debug("Host %s: Hostgroups in-sync.", self.name)
else: else:
self.logger.info(f"Host {self.name}: Hostgroups OUT of sync.") self.logger.info("Host %s: Hostgroups OUT of sync.", self.name)
self.updateZabbixHost(groups=self.group_ids) self.updateZabbixHost(groups=self.group_ids)
if int(host["status"]) == self.zabbix_state: if int(host["status"]) == self.zabbix_state:
self.logger.debug(f"Host {self.name}: Status in-sync.") self.logger.debug("Host %s: Status in-sync.", self.name)
else: else:
self.logger.info(f"Host {self.name}: Status OUT of sync.") self.logger.info("Host %s: Status OUT of sync.", self.name)
self.updateZabbixHost(status=str(self.zabbix_state)) self.updateZabbixHost(status=str(self.zabbix_state))
# Check if a proxy has been defined # Check if a proxy has been defined
if self.zbxproxy: if self.zbxproxy:
# Check if proxy or proxy group is defined # Check if proxy or proxy group is defined
if (self.zbxproxy["idtype"] in host and if (
host[self.zbxproxy["idtype"]] == self.zbxproxy["id"]): self.zbxproxy["idtype"] in host
self.logger.debug(f"Host {self.name}: Proxy in-sync.") and host[self.zbxproxy["idtype"]] == self.zbxproxy["id"]
):
self.logger.debug("Host %s: Proxy in-sync.", self.name)
# Backwards compatibility for Zabbix <= 6 # Backwards compatibility for Zabbix <= 6
elif "proxy_hostid" in host and host["proxy_hostid"] == self.zbxproxy["id"]: elif "proxy_hostid" in host and host["proxy_hostid"] == self.zbxproxy["id"]:
self.logger.debug(f"Host {self.name}: Proxy in-sync.") self.logger.debug("Host %s: Proxy in-sync.", self.name)
# Proxy does not match, update Zabbix # Proxy does not match, update Zabbix
else: else:
self.logger.info(f"Host {self.name}: Proxy OUT of sync.") self.logger.info("Host %s: Proxy OUT of sync.", self.name)
# Zabbix <= 6 patch # Zabbix <= 6 patch
if not str(self.zabbix.version).startswith("7"): if not str(self.zabbix.version).startswith("7"):
self.updateZabbixHost(proxy_hostid=self.zbxproxy["id"]) self.updateZabbixHost(proxy_hostid=self.zbxproxy["id"])
@ -757,8 +773,8 @@ class PhysicalDevice:
if proxy_power and proxy_set: if proxy_power and proxy_set:
# Zabbix <= 6 fix # Zabbix <= 6 fix
self.logger.warning( self.logger.warning(
f"Host {self.name}: No proxy is configured in NetBox " "Host %s: No proxy is configured in NetBox but is configured in Zabbix. Removing proxy config in Zabbix",
"but is configured in Zabbix. Removing proxy config in Zabbix" self.name,
) )
if "proxy_hostid" in host and bool(host["proxy_hostid"]): if "proxy_hostid" in host and bool(host["proxy_hostid"]):
self.updateZabbixHost(proxy_hostid=0) self.updateZabbixHost(proxy_hostid=0)
@ -772,59 +788,59 @@ class PhysicalDevice:
if proxy_set and not proxy_power: if proxy_set and not proxy_power:
# Display error message # Display error message
self.logger.warning( self.logger.warning(
f"Host {self.name}: Is configured " "Host %s: Is configured with proxy in Zabbix but not in NetBox. The -p flag was ommited: no changes have been made.",
f"with proxy in Zabbix but not in NetBox. The" self.name,
" -p flag was ommited: no "
"changes have been made."
) )
if not proxy_set: if not proxy_set:
self.logger.debug(f"Host {self.name}: Proxy in-sync.") self.logger.debug("Host %s: Proxy in-sync.", self.name)
# Check host inventory mode # Check host inventory mode
if str(host["inventory_mode"]) == str(self.inventory_mode): if str(host["inventory_mode"]) == str(self.inventory_mode):
self.logger.debug(f"Host {self.name}: inventory_mode in-sync.") self.logger.debug("Host %s: inventory_mode in-sync.", self.name)
else: else:
self.logger.info(f"Host {self.name}: inventory_mode OUT of sync.") self.logger.info("Host %s: inventory_mode OUT of sync.", self.name)
self.updateZabbixHost(inventory_mode=str(self.inventory_mode)) self.updateZabbixHost(inventory_mode=str(self.inventory_mode))
if config["inventory_sync"] and self.inventory_mode in [0, 1]: if config["inventory_sync"] and self.inventory_mode in [0, 1]:
# Check host inventory mapping # Check host inventory mapping
if host["inventory"] == self.inventory: if host["inventory"] == self.inventory:
self.logger.debug(f"Host {self.name}: Inventory in-sync.") self.logger.debug("Host %s: Inventory in-sync.", self.name)
else: else:
self.logger.info(f"Host {self.name}: Inventory OUT of sync.") self.logger.info("Host %s: Inventory OUT of sync.", self.name)
self.updateZabbixHost(inventory=self.inventory) self.updateZabbixHost(inventory=self.inventory)
# Check host usermacros # Check host usermacros
if config['usermacro_sync']: if config["usermacro_sync"]:
# Make a full copy synce we dont want to lose the original value # Make a full copy synce we dont want to lose the original value
# of secret type macros from Netbox # of secret type macros from Netbox
netbox_macros = deepcopy(self.usermacros) netbox_macros = deepcopy(self.usermacros)
# Set the sync bit # Set the sync bit
full_sync_bit = bool(str(config['usermacro_sync']).lower() == "full") full_sync_bit = bool(str(config["usermacro_sync"]).lower() == "full")
for macro in netbox_macros: for macro in netbox_macros:
# If the Macro is a secret and full sync is NOT activated # If the Macro is a secret and full sync is NOT activated
if macro["type"] == str(1) and not full_sync_bit: if macro["type"] == str(1) and not full_sync_bit:
# Remove the value as the Zabbix api does not return the value key # Remove the value as the Zabbix api does not return the value key
# This is required when you want to do a diff between both lists # This is required when you want to do a diff between both lists
macro.pop("value") macro.pop("value")
# Sort all lists # Sort all lists
def filter_with_macros(macro): def filter_with_macros(macro):
return macro["macro"] return macro["macro"]
host["macros"].sort(key=filter_with_macros) host["macros"].sort(key=filter_with_macros)
netbox_macros.sort(key=filter_with_macros) netbox_macros.sort(key=filter_with_macros)
# Check if both lists are the same # Check if both lists are the same
if host["macros"] == netbox_macros: if host["macros"] == netbox_macros:
self.logger.debug(f"Host {self.name}: Usermacros in-sync.") self.logger.debug("Host %s: Usermacros in-sync.", self.name)
else: else:
self.logger.info(f"Host {self.name}: Usermacros OUT of sync.") self.logger.info("Host %s: Usermacros OUT of sync.", self.name)
# Update Zabbix with NetBox usermacros # Update Zabbix with NetBox usermacros
self.updateZabbixHost(macros=self.usermacros) self.updateZabbixHost(macros=self.usermacros)
# Check host tags # Check host tags
if config['tag_sync']: if config["tag_sync"]:
if remove_duplicates(host["tags"], sortkey="tag") == self.tags: if remove_duplicates(host["tags"], sortkey="tag") == self.tags:
self.logger.debug(f"Host {self.name}: Tags in-sync.") self.logger.debug("Host %s: Tags in-sync.", self.name)
else: else:
self.logger.info(f"Host {self.name}: Tags OUT of sync.") self.logger.info("Host %s: Tags OUT of sync.", self.name)
self.updateZabbixHost(tags=self.tags) self.updateZabbixHost(tags=self.tags)
# If only 1 interface has been found # If only 1 interface has been found
@ -862,7 +878,7 @@ class PhysicalDevice:
updates[key] = item updates[key] = item
if updates: if updates:
# If interface updates have been found: push to Zabbix # If interface updates have been found: push to Zabbix
self.logger.info(f"Host {self.name}: Interface OUT of sync.") self.logger.info("Host %s: Interface OUT of sync.", self.name)
if "type" in updates: if "type" in updates:
# Changing interface type not supported. Raise exception. # Changing interface type not supported. Raise exception.
e = ( e = (
@ -876,26 +892,27 @@ class PhysicalDevice:
try: try:
# API call to Zabbix # API call to Zabbix
self.zabbix.hostinterface.update(updates) self.zabbix.hostinterface.update(updates)
e = (f"Host {self.name}: Updated interface " err_msg = (
f"with data {sanatize_log_output(updates)}.") f"Host {self.name}: Updated interface "
self.logger.info(e) f"with data {sanatize_log_output(updates)}."
self.create_journal_entry("info", e) )
self.logger.info(err_msg)
self.create_journal_entry("info", err_msg)
except APIRequestError as e: except APIRequestError as e:
msg = f"Zabbix returned the following error: {str(e)}." msg = f"Zabbix returned the following error: {str(e)}."
self.logger.error(msg) self.logger.error(msg)
raise SyncExternalError(msg) from e raise SyncExternalError(msg) from e
else: else:
# If no updates are found, Zabbix interface is in-sync # If no updates are found, Zabbix interface is in-sync
e = f"Host {self.name}: Interface in-sync." self.logger.debug("Host %s: Interface in-sync.", self.name)
self.logger.debug(e)
else: else:
e = ( err_msg = (
f"Host {self.name}: Has unsupported interface configuration." f"Host {self.name}: Has unsupported interface configuration."
f" Host has total of {len(host['interfaces'])} interfaces. " f" Host has total of {len(host['interfaces'])} interfaces. "
"Manual intervention required." "Manual intervention required."
) )
self.logger.error(e) self.logger.error(err_msg)
raise SyncInventoryError(e) raise SyncInventoryError(err_msg)
def create_journal_entry(self, severity, message): def create_journal_entry(self, severity, message):
""" """
@ -906,7 +923,7 @@ class PhysicalDevice:
# Check if the severity is valid # Check if the severity is valid
if severity not in ["info", "success", "warning", "danger"]: if severity not in ["info", "success", "warning", "danger"]:
self.logger.warning( self.logger.warning(
f"Value {severity} not valid for NB journal entries." "Value %s not valid for NB journal entries.", severity
) )
return False return False
journal = { journal = {
@ -917,12 +934,11 @@ class PhysicalDevice:
} }
try: try:
self.nb_journals.create(journal) self.nb_journals.create(journal)
self.logger.debug(f"Host {self.name}: Created journal entry in NetBox") self.logger.debug("Host %s: Created journal entry in NetBox", self.name)
return True return True
except NetboxRequestError as e: except NetboxRequestError as e:
self.logger.warning( self.logger.warning(
"Unable to create journal entry for " "Unable to create journal entry for %s: NB returned {e}", self.name
f"{self.name}: NB returned {e}"
) )
return False return False
return False return False
@ -947,8 +963,9 @@ class PhysicalDevice:
tmpls_from_zabbix.pop(pos) tmpls_from_zabbix.pop(pos)
succesfull_templates.append(nb_tmpl) succesfull_templates.append(nb_tmpl)
self.logger.debug( self.logger.debug(
f"Host {self.name}: Template " "Host %s: Template '%s' is present in Zabbix.",
f"'{nb_tmpl['name']}' is present in Zabbix." self.name,
nb_tmpl["name"],
) )
break break
if ( if (

View File

@ -94,8 +94,11 @@ class Hostgroup:
format_options["cluster"] = self.nb.cluster.name format_options["cluster"] = self.nb.cluster.name
format_options["cluster_type"] = self.nb.cluster.type.name format_options["cluster_type"] = self.nb.cluster.type.name
self.format_options = format_options self.format_options = format_options
self.logger.debug(f"Host {self.name}: Resolved properties for use " self.logger.debug(
f"in hostgroups: {self.format_options}") "Host %s: Resolved properties for use in hostgroups: %s",
self.name,
self.format_options,
)
def set_nesting( def set_nesting(
self, nested_sitegroup_flag, nested_region_flag, nb_groups, nb_regions self, nested_sitegroup_flag, nested_region_flag, nb_groups, nb_regions
@ -134,7 +137,9 @@ class Hostgroup:
if hostgroup_value: if hostgroup_value:
hg_output.append(hostgroup_value) hg_output.append(hostgroup_value)
else: else:
self.logger.info(f"Host {self.name}: Used field '{hg_item}' has no value.") self.logger.info(
"Host %s: Used field '%s' has no value.", self.name, hg_item
)
# Check if the hostgroup is populated with at least one item. # Check if the hostgroup is populated with at least one item.
if bool(hg_output): if bool(hg_output):
return "/".join(hg_output) return "/".join(hg_output)

View File

@ -3,6 +3,7 @@
""" """
All of the Zabbix Usermacro related configuration All of the Zabbix Usermacro related configuration
""" """
from logging import getLogger from logging import getLogger
from modules.tools import field_mapper, remove_duplicates from modules.tools import field_mapper, remove_duplicates
@ -76,7 +77,7 @@ class ZabbixTags:
else: else:
tag["tag"] = tag_name tag["tag"] = tag_name
else: else:
self.logger.warning(f"Tag '{tag_name}' is not a valid tag name, skipping.") self.logger.warning("Tag '%s' is not a valid tag name, skipping.", tag_name)
return False return False
if self.validate_value(tag_value): if self.validate_value(tag_value):
@ -86,7 +87,7 @@ class ZabbixTags:
tag["value"] = tag_value tag["value"] = tag_value
else: else:
self.logger.info( self.logger.info(
f"Tag '{tag_name}' has an invalid value: '{tag_value}', skipping." "Tag '%s' has an invalid value: '%s', skipping.", tag_name, tag_value
) )
return False return False
return tag return tag
@ -99,7 +100,7 @@ class ZabbixTags:
tags = [] tags = []
# Parse the field mapper for tags # Parse the field mapper for tags
if self.tag_map: if self.tag_map:
self.logger.debug(f"Host {self.nb.name}: Starting tag mapper.") self.logger.debug("Host %s: Starting tag mapper.", self.nb.name)
field_tags = field_mapper(self.nb.name, self.tag_map, self.nb, self.logger) field_tags = field_mapper(self.nb.name, self.tag_map, self.nb, self.logger)
for tag, value in field_tags.items(): for tag, value in field_tags.items():
t = self.render_tag(tag, value) t = self.render_tag(tag, value)
@ -131,5 +132,5 @@ class ZabbixTags:
tags.append(t) tags.append(t)
tags = remove_duplicates(tags, sortkey="tag") tags = remove_duplicates(tags, sortkey="tag")
self.logger.debug(f"Host {self.name}: Resolved tags: {tags}") self.logger.debug("Host %s: Resolved tags: %s", self.name, tags)
return tags return tags

View File

@ -1,6 +1,8 @@
"""A collection of tools used by several classes""" """A collection of tools used by several classes"""
from modules.exceptions import HostgroupError from modules.exceptions import HostgroupError
def convert_recordset(recordset): def convert_recordset(recordset):
"""Converts netbox RedcordSet to list of dicts.""" """Converts netbox RedcordSet to list of dicts."""
recordlist = [] recordlist = []
@ -72,19 +74,22 @@ def field_mapper(host, mapper, nbdevice, logger):
elif not value: elif not value:
# empty value should just be an empty string for API compatibility # empty value should just be an empty string for API compatibility
logger.info( logger.info(
f"Host {host}: NetBox lookup for " "Host %s: NetBox lookup for '%s' returned an empty value.",
f"'{nb_field}' returned an empty value." host,
nb_field,
) )
data[zbx_field] = "" data[zbx_field] = ""
else: else:
# Value is not a string or numeral, probably not what the user expected. # Value is not a string or numeral, probably not what the user expected.
logger.info( logger.info(
f"Host {host}: Lookup for '{nb_field}'" "Host %s: Lookup for '%s' returned an unexpected type: it will be skipped.",
" returned an unexpected type: it will be skipped." host,
nb_field,
) )
logger.debug( logger.debug(
f"Host {host}: Field mapping complete. " "Host %s: Field mapping complete. Mapped %s field(s).",
f"Mapped {len(list(filter(None, data.values())))} field(s)." host,
len(list(filter(None, data.values()))),
) )
return data return data
@ -101,7 +106,9 @@ def remove_duplicates(input_list, sortkey=None):
return output_list return output_list
def verify_hg_format(hg_format, device_cfs=None, vm_cfs=None, hg_type="dev", logger=None): def verify_hg_format(
hg_format, device_cfs=None, vm_cfs=None, hg_type="dev", logger=None
):
""" """
Verifies hostgroup field format Verifies hostgroup field format
""" """
@ -109,7 +116,9 @@ def verify_hg_format(hg_format, device_cfs=None, vm_cfs=None, hg_type="dev", log
device_cfs = [] device_cfs = []
if not vm_cfs: if not vm_cfs:
vm_cfs = [] vm_cfs = []
allowed_objects = {"dev": ["location", allowed_objects = {
"dev": [
"location",
"rack", "rack",
"role", "role",
"manufacturer", "manufacturer",
@ -119,8 +128,10 @@ def verify_hg_format(hg_format, device_cfs=None, vm_cfs=None, hg_type="dev", log
"tenant", "tenant",
"tenant_group", "tenant_group",
"platform", "platform",
"cluster"] "cluster",
,"vm": ["cluster_type", ],
"vm": [
"cluster_type",
"role", "role",
"manufacturer", "manufacturer",
"region", "region",
@ -130,23 +141,26 @@ def verify_hg_format(hg_format, device_cfs=None, vm_cfs=None, hg_type="dev", log
"tenant_group", "tenant_group",
"cluster", "cluster",
"device", "device",
"platform"] "platform",
,"cfs": {"dev": [], "vm": []} ],
"cfs": {"dev": [], "vm": []},
} }
for cf in device_cfs: for cf in device_cfs:
allowed_objects['cfs']['dev'].append(cf.name) allowed_objects["cfs"]["dev"].append(cf.name)
for cf in vm_cfs: for cf in vm_cfs:
allowed_objects['cfs']['vm'].append(cf.name) allowed_objects["cfs"]["vm"].append(cf.name)
hg_objects = [] hg_objects = []
if isinstance(hg_format,list): if isinstance(hg_format, list):
for f in hg_format: for f in hg_format:
hg_objects = hg_objects + f.split("/") hg_objects = hg_objects + f.split("/")
else: else:
hg_objects = hg_format.split("/") hg_objects = hg_format.split("/")
hg_objects = sorted(set(hg_objects)) hg_objects = sorted(set(hg_objects))
for hg_object in hg_objects: for hg_object in hg_objects:
if (hg_object not in allowed_objects[hg_type] and if (
hg_object not in allowed_objects['cfs'][hg_type]): hg_object not in allowed_objects[hg_type]
and hg_object not in allowed_objects["cfs"][hg_type]
):
e = ( e = (
f"Hostgroup item {hg_object} is not valid. Make sure you" f"Hostgroup item {hg_object} is not valid. Make sure you"
" use valid items and separate them with '/'." " use valid items and separate them with '/'."
@ -168,8 +182,7 @@ def sanatize_log_output(data):
if "macros" in data: if "macros" in data:
for macro in sanitized_data["macros"]: for macro in sanitized_data["macros"]:
# Check if macro is secret type # Check if macro is secret type
if not (macro["type"] == str(1) or if not (macro["type"] == str(1) or macro["type"] == 1):
macro["type"] == 1):
continue continue
macro["value"] = "********" macro["value"] = "********"
# Check for interface data # Check for interface data

View File

@ -3,6 +3,7 @@
""" """
All of the Zabbix Usermacro related configuration All of the Zabbix Usermacro related configuration
""" """
from logging import getLogger from logging import getLogger
from re import match from re import match
@ -57,8 +58,11 @@ class ZabbixUsermacros:
macro["macro"] = str(macro_name) macro["macro"] = str(macro_name)
if isinstance(macro_properties, dict): if isinstance(macro_properties, dict):
if not "value" in macro_properties: if not "value" in macro_properties:
self.logger.info(f"Host {self.name}: Usermacro {macro_name} has " self.logger.info(
"no value in Netbox, skipping.") "Host %s: Usermacro %s has no value in Netbox, skipping.",
self.name,
macro_name,
)
return False return False
macro["value"] = macro_properties["value"] macro["value"] = macro_properties["value"]
@ -83,12 +87,17 @@ class ZabbixUsermacros:
macro["description"] = "" macro["description"] = ""
else: else:
self.logger.info(f"Host {self.name}: Usermacro {macro_name} " self.logger.info(
"has no value, skipping.") "Host %s: Usermacro %s has no value, skipping.",
self.name,
macro_name,
)
return False return False
else: else:
self.logger.warning( self.logger.warning(
f"Host {self.name}: Usermacro {macro_name} is not a valid usermacro name, skipping." "Host %s: Usermacro %s is not a valid usermacro name, skipping.",
self.name,
macro_name,
) )
return False return False
return macro return macro
@ -98,10 +107,10 @@ class ZabbixUsermacros:
Generate full set of Usermacros Generate full set of Usermacros
""" """
macros = [] macros = []
data={} data = {}
# Parse the field mapper for usermacros # Parse the field mapper for usermacros
if self.usermacro_map: if self.usermacro_map:
self.logger.debug(f"Host {self.nb.name}: Starting usermacro mapper.") self.logger.debug("Host %s: Starting usermacro mapper.", self.nb.name)
field_macros = field_mapper( field_macros = field_mapper(
self.nb.name, self.usermacro_map, self.nb, self.logger self.nb.name, self.usermacro_map, self.nb, self.logger
) )
@ -120,6 +129,8 @@ class ZabbixUsermacros:
m = self.render_macro(macro, properties) m = self.render_macro(macro, properties)
if m: if m:
macros.append(m) macros.append(m)
data={'macros': macros} data = {"macros": macros}
self.logger.debug(f"Host {self.name}: Resolved macros: {sanatize_log_output(data)}") self.logger.debug(
"Host %s: Resolved macros: %s", self.name, sanatize_log_output(data)
)
return macros return macros

View File

@ -2,6 +2,7 @@
# pylint: disable=invalid-name, logging-not-lazy, too-many-locals, logging-fstring-interpolation # pylint: disable=invalid-name, logging-not-lazy, too-many-locals, logging-fstring-interpolation
"""NetBox to Zabbix sync script.""" """NetBox to Zabbix sync script."""
import argparse import argparse
import logging import logging
import ssl import ssl
@ -67,15 +68,15 @@ def main(arguments):
try: try:
# Get NetBox version # Get NetBox version
nb_version = netbox.version nb_version = netbox.version
logger.debug(f"NetBox version is {nb_version}.") logger.debug("NetBox version is %s.", nb_version)
except RequestsConnectionError: except RequestsConnectionError:
logger.error( logger.error(
f"Unable to connect to NetBox with URL {netbox_host}." "Unable to connect to NetBox with URL %s. Please check the URL and status of NetBox.",
" Please check the URL and status of NetBox." netbox_host,
) )
sys.exit(1) sys.exit(1)
except NBRequestError as e: except NBRequestError as e:
logger.error(f"NetBox error: {e}") logger.error("NetBox error: %s", e)
sys.exit(1) sys.exit(1)
# Check if the provided Hostgroup layout is valid # Check if the provided Hostgroup layout is valid
device_cfs = [] device_cfs = []
@ -83,14 +84,18 @@ def main(arguments):
device_cfs = list( device_cfs = list(
netbox.extras.custom_fields.filter(type="text", content_types="dcim.device") netbox.extras.custom_fields.filter(type="text", content_types="dcim.device")
) )
verify_hg_format(config["hostgroup_format"], verify_hg_format(
device_cfs=device_cfs, hg_type="dev", logger=logger) config["hostgroup_format"], device_cfs=device_cfs, hg_type="dev", logger=logger
)
if config["sync_vms"]: if config["sync_vms"]:
vm_cfs = list( vm_cfs = list(
netbox.extras.custom_fields.filter(type="text", netbox.extras.custom_fields.filter(
content_types="virtualization.virtualmachine") type="text", content_types="virtualization.virtualmachine"
)
)
verify_hg_format(
config["vm_hostgroup_format"], vm_cfs=vm_cfs, hg_type="vm", logger=logger
) )
verify_hg_format(config["vm_hostgroup_format"], vm_cfs=vm_cfs, hg_type="vm", logger=logger)
# Set Zabbix API # Set Zabbix API
try: try:
ssl_ctx = ssl.create_default_context() ssl_ctx = ssl.create_default_context()
@ -120,7 +125,8 @@ def main(arguments):
netbox_vms = [] netbox_vms = []
if config["sync_vms"]: if config["sync_vms"]:
netbox_vms = list( netbox_vms = list(
netbox.virtualization.virtual_machines.filter(**config["nb_vm_filter"])) netbox.virtualization.virtual_machines.filter(**config["nb_vm_filter"])
)
netbox_site_groups = convert_recordset((netbox.dcim.site_groups.all())) netbox_site_groups = convert_recordset((netbox.dcim.site_groups.all()))
netbox_regions = convert_recordset(netbox.dcim.regions.all()) netbox_regions = convert_recordset(netbox.dcim.regions.all())
netbox_journals = netbox.extras.journal_entries netbox_journals = netbox.extras.journal_entries
@ -141,15 +147,22 @@ def main(arguments):
# Go through all NetBox devices # Go through all NetBox devices
for nb_vm in netbox_vms: for nb_vm in netbox_vms:
try: try:
vm = VirtualMachine(nb_vm, zabbix, netbox_journals, nb_version, vm = VirtualMachine(
config["create_journal"], logger) nb_vm,
logger.debug(f"Host {vm.name}: Started operations on VM.") zabbix,
netbox_journals,
nb_version,
config["create_journal"],
logger,
)
logger.debug("Host %s: Started operations on VM.", vm.name)
vm.set_vm_template() vm.set_vm_template()
# Check if a valid template has been found for this VM. # Check if a valid template has been found for this VM.
if not vm.zbx_template_names: if not vm.zbx_template_names:
continue continue
vm.set_hostgroup(config["vm_hostgroup_format"], vm.set_hostgroup(
netbox_site_groups, netbox_regions) config["vm_hostgroup_format"], netbox_site_groups, netbox_regions
)
# Check if a valid hostgroup has been found for this VM. # Check if a valid hostgroup has been found for this VM.
if not vm.hostgroups: if not vm.hostgroups:
continue continue
@ -162,13 +175,12 @@ def main(arguments):
# Delete device from Zabbix # Delete device from Zabbix
# and remove hostID from NetBox. # and remove hostID from NetBox.
vm.cleanup() vm.cleanup()
logger.info(f"VM {vm.name}: cleanup complete") logger.info("VM %s: cleanup complete", vm.name)
continue continue
# Device has been added to NetBox # Device has been added to NetBox
# but is not in Activate state # but is not in Activate state
logger.info( logger.info(
f"VM {vm.name}: Skipping since this VM is " "VM %s: Skipping since this VM is not in the active state.", vm.name
f"not in the active state."
) )
continue continue
# Check if the VM is in the disabled state # Check if the VM is in the disabled state
@ -200,20 +212,31 @@ def main(arguments):
for nb_device in netbox_devices: for nb_device in netbox_devices:
try: try:
# Set device instance set data such as hostgroup and template information. # Set device instance set data such as hostgroup and template information.
device = PhysicalDevice(nb_device, zabbix, netbox_journals, nb_version, device = PhysicalDevice(
config["create_journal"], logger) nb_device,
logger.debug(f"Host {device.name}: Started operations on device.") zabbix,
device.set_template(config["templates_config_context"], netbox_journals,
config["templates_config_context_overrule"]) nb_version,
config["create_journal"],
logger,
)
logger.debug("Host %s: Started operations on device.", device.name)
device.set_template(
config["templates_config_context"],
config["templates_config_context_overrule"],
)
# Check if a valid template has been found for this VM. # Check if a valid template has been found for this VM.
if not device.zbx_template_names: if not device.zbx_template_names:
continue continue
device.set_hostgroup( device.set_hostgroup(
config["hostgroup_format"], netbox_site_groups, netbox_regions) config["hostgroup_format"], netbox_site_groups, netbox_regions
)
# Check if a valid hostgroup has been found for this VM. # Check if a valid hostgroup has been found for this VM.
if not device.hostgroups: if not device.hostgroups:
logger.warning(f"Host {device.name}: Host has no valid " logger.warning(
f"hostgroups, Skipping this host...") "Host %s: Host has no valid hostgroups, Skipping this host...",
device.name,
)
continue continue
device.set_inventory(nb_device) device.set_inventory(nb_device)
device.set_usermacros() device.set_usermacros()
@ -223,16 +246,16 @@ def main(arguments):
if device.isCluster() and config["clustering"]: if device.isCluster() and config["clustering"]:
# Check if device is primary or secondary # Check if device is primary or secondary
if device.promoteMasterDevice(): if device.promoteMasterDevice():
e = f"Device {device.name}: is " f"part of cluster and primary." logger.info(
logger.info(e) "Device %s: is part of cluster and primary.", device.name
)
else: else:
# Device is secondary in cluster. # Device is secondary in cluster.
# Don't continue with this device. # Don't continue with this device.
e = ( logger.info(
f"Device {device.name}: Is part of cluster " "Device %s: Is part of cluster but not primary. Skipping this host...",
f"but not primary. Skipping this host..." device.name,
) )
logger.info(e)
continue continue
# Checks if device is in cleanup state # Checks if device is in cleanup state
if device.status in config["zabbix_device_removal"]: if device.status in config["zabbix_device_removal"]:
@ -240,13 +263,13 @@ def main(arguments):
# Delete device from Zabbix # Delete device from Zabbix
# and remove hostID from NetBox. # and remove hostID from NetBox.
device.cleanup() device.cleanup()
logger.info(f"Device {device.name}: cleanup complete") logger.info("Device %s: cleanup complete", device.name)
continue continue
# Device has been added to NetBox # Device has been added to NetBox
# but is not in Activate state # but is not in Activate state
logger.info( logger.info(
f"Device {device.name}: Skipping since this device is " "Device %s: Skipping since this device is not in the active state.",
f"not in the active state." device.name,
) )
continue continue
# Check if the device is in the disabled state # Check if the device is in the disabled state