mirror of
https://github.com/TheNetworkGuy/netbox-zabbix-sync.git
synced 2025-12-19 03:42:23 -06:00
Merge branch 'develop' into additional-hostgroup-support
This commit is contained in:
@@ -2,17 +2,17 @@
|
||||
"""
|
||||
Device specific handeling for NetBox to Zabbix
|
||||
"""
|
||||
|
||||
from copy import deepcopy
|
||||
from logging import getLogger
|
||||
from os import sys
|
||||
from re import search
|
||||
from operator import itemgetter
|
||||
|
||||
from zabbix_utils import APIRequestError
|
||||
from pynetbox import RequestError as NetboxRequestError
|
||||
|
||||
from modules.exceptions import (
|
||||
InterfaceConfigError,
|
||||
JournalError,
|
||||
SyncExternalError,
|
||||
SyncInventoryError,
|
||||
TemplateError,
|
||||
@@ -20,33 +20,11 @@ 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
|
||||
from modules.tools import field_mapper, remove_duplicates, sanatize_log_output
|
||||
from modules.usermacros import ZabbixUsermacros
|
||||
from modules.config import load_config
|
||||
|
||||
try:
|
||||
from config import (
|
||||
device_cf,
|
||||
device_inventory_map,
|
||||
device_tag_map,
|
||||
device_usermacro_map,
|
||||
inventory_mode,
|
||||
inventory_sync,
|
||||
tag_lower,
|
||||
tag_name,
|
||||
tag_sync,
|
||||
tag_value,
|
||||
template_cf,
|
||||
traverse_regions,
|
||||
traverse_site_groups,
|
||||
usermacro_sync,
|
||||
)
|
||||
except ModuleNotFoundError:
|
||||
print(
|
||||
"Configuration file config.py not found in main directory."
|
||||
"Please create the file or rename the config.py.example file to config.py."
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
config = load_config()
|
||||
|
||||
class PhysicalDevice:
|
||||
# pylint: disable=too-many-instance-attributes, too-many-arguments, too-many-positional-arguments
|
||||
@@ -78,7 +56,7 @@ class PhysicalDevice:
|
||||
self.nb_journals = nb_journal_class
|
||||
self.inventory_mode = -1
|
||||
self.inventory = {}
|
||||
self.usermacros = {}
|
||||
self.usermacros = []
|
||||
self.tags = {}
|
||||
self.logger = logger if logger else getLogger(__name__)
|
||||
self._setBasics()
|
||||
@@ -91,15 +69,15 @@ class PhysicalDevice:
|
||||
|
||||
def _inventory_map(self):
|
||||
"""Use device inventory maps"""
|
||||
return device_inventory_map
|
||||
return config["device_inventory_map"]
|
||||
|
||||
def _usermacro_map(self):
|
||||
"""Use device inventory maps"""
|
||||
return device_usermacro_map
|
||||
return config["device_usermacro_map"]
|
||||
|
||||
def _tag_map(self):
|
||||
"""Use device host tag maps"""
|
||||
return device_tag_map
|
||||
return config["device_tag_map"]
|
||||
|
||||
def _setBasics(self):
|
||||
"""
|
||||
@@ -115,10 +93,10 @@ class PhysicalDevice:
|
||||
raise SyncInventoryError(e)
|
||||
|
||||
# Check if device has custom field for ZBX ID
|
||||
if device_cf in self.nb.custom_fields:
|
||||
self.zabbix_id = self.nb.custom_fields[device_cf]
|
||||
if config["device_cf"] in self.nb.custom_fields:
|
||||
self.zabbix_id = self.nb.custom_fields[config["device_cf"]]
|
||||
else:
|
||||
e = f"Host {self.name}: Custom field {device_cf} not present"
|
||||
e = f'Host {self.name}: Custom field {config["device_cf"]} not present'
|
||||
self.logger.warning(e)
|
||||
raise SyncInventoryError(e)
|
||||
|
||||
@@ -147,8 +125,8 @@ class PhysicalDevice:
|
||||
self.nb,
|
||||
self.nb_api_version,
|
||||
logger=self.logger,
|
||||
nested_sitegroup_flag=traverse_site_groups,
|
||||
nested_region_flag=traverse_regions,
|
||||
nested_sitegroup_flag=config['traverse_site_groups'],
|
||||
nested_region_flag=config['traverse_regions'],
|
||||
nb_groups=nb_site_groups,
|
||||
nb_regions=nb_regions,
|
||||
)
|
||||
@@ -187,18 +165,20 @@ class PhysicalDevice:
|
||||
# Get Zabbix templates from the device type
|
||||
device_type_cfs = self.nb.device_type.custom_fields
|
||||
# Check if the ZBX Template CF is present
|
||||
if template_cf in device_type_cfs:
|
||||
if config["template_cf"] in device_type_cfs:
|
||||
# Set value to template
|
||||
return [device_type_cfs[template_cf]]
|
||||
return [device_type_cfs[config["template_cf"]]]
|
||||
# Custom field not found, return error
|
||||
e = (
|
||||
f"Custom field {template_cf} not "
|
||||
f"Custom field {config['template_cf']} not "
|
||||
f"found for {self.nb.device_type.manufacturer.name}"
|
||||
f" - {self.nb.device_type.display}."
|
||||
)
|
||||
self.logger.warning(e)
|
||||
raise TemplateError(e)
|
||||
|
||||
|
||||
|
||||
def get_templates_context(self):
|
||||
"""Get Zabbix templates from the device context"""
|
||||
if "zabbix" not in self.config_context:
|
||||
@@ -221,25 +201,24 @@ class PhysicalDevice:
|
||||
def set_inventory(self, nbdevice):
|
||||
"""Set host inventory"""
|
||||
# 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. "
|
||||
"Inventory sync is enabled in config but inventory mode is disabled."
|
||||
)
|
||||
if config["inventory_mode"] == "disabled":
|
||||
if config["inventory_sync"]:
|
||||
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":
|
||||
if config["inventory_mode"] == "manual":
|
||||
self.inventory_mode = 0
|
||||
elif inventory_mode == "automatic":
|
||||
elif config["inventory_mode"] == "automatic":
|
||||
self.inventory_mode = 1
|
||||
else:
|
||||
self.logger.error(
|
||||
f"Host {self.name}: Specified value for inventory mode in"
|
||||
f" config is not valid. Got value {inventory_mode}"
|
||||
f" config is not valid. Got value {config['inventory_mode']}"
|
||||
)
|
||||
return False
|
||||
self.inventory = {}
|
||||
if 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.inventory = field_mapper(
|
||||
self.name, self._inventory_map(), nbdevice, self.logger
|
||||
@@ -377,7 +356,7 @@ class PhysicalDevice:
|
||||
def _zeroize_cf(self):
|
||||
"""Sets the hostID custom field in NetBox to zero,
|
||||
effectively destroying the link"""
|
||||
self.nb.custom_fields[device_cf] = None
|
||||
self.nb.custom_fields[config["device_cf"]] = None
|
||||
self.nb.save()
|
||||
|
||||
def _zabbixHostnameExists(self):
|
||||
@@ -421,12 +400,13 @@ class PhysicalDevice:
|
||||
macros = ZabbixUsermacros(
|
||||
self.nb,
|
||||
self._usermacro_map(),
|
||||
usermacro_sync,
|
||||
config['usermacro_sync'],
|
||||
logger=self.logger,
|
||||
host=self.name,
|
||||
)
|
||||
if macros.sync is False:
|
||||
self.usermacros = []
|
||||
return True
|
||||
|
||||
self.usermacros = macros.generate()
|
||||
return True
|
||||
@@ -438,10 +418,10 @@ class PhysicalDevice:
|
||||
tags = ZabbixTags(
|
||||
self.nb,
|
||||
self._tag_map(),
|
||||
tag_sync,
|
||||
tag_lower,
|
||||
tag_name=tag_name,
|
||||
tag_value=tag_value,
|
||||
config['tag_sync'],
|
||||
config['tag_lower'],
|
||||
tag_name=config['tag_name'],
|
||||
tag_value=config['tag_value'],
|
||||
logger=self.logger,
|
||||
host=self.name,
|
||||
)
|
||||
@@ -459,7 +439,7 @@ class PhysicalDevice:
|
||||
input: List of all proxies and proxy groups in standardized format
|
||||
"""
|
||||
# check if the key Zabbix is defined in the config context
|
||||
if not "zabbix" in self.nb.config_context:
|
||||
if "zabbix" not in self.nb.config_context:
|
||||
return False
|
||||
if (
|
||||
"proxy" in self.nb.config_context["zabbix"]
|
||||
@@ -557,7 +537,7 @@ class PhysicalDevice:
|
||||
self.logger.error(msg)
|
||||
raise SyncExternalError(msg) from e
|
||||
# Set NetBox custom field to hostID value.
|
||||
self.nb.custom_fields[device_cf] = int(self.zabbix_id)
|
||||
self.nb.custom_fields[config["device_cf"]] = int(self.zabbix_id)
|
||||
self.nb.save()
|
||||
msg = f"Host {self.name}: Created host in Zabbix."
|
||||
self.logger.info(msg)
|
||||
@@ -622,7 +602,7 @@ class PhysicalDevice:
|
||||
)
|
||||
self.logger.error(e)
|
||||
raise SyncExternalError(e) from None
|
||||
self.logger.info(f"Updated host {self.name} with data {kwargs}.")
|
||||
self.logger.info(f"Host {self.name}: updated with data {sanatize_log_output(kwargs)}.")
|
||||
self.create_journal_entry("info", "Updated host in Zabbix with latest NB data.")
|
||||
|
||||
def ConsistencyCheck(
|
||||
@@ -738,10 +718,8 @@ class PhysicalDevice:
|
||||
# Check if a proxy has been defined
|
||||
if self.zbxproxy:
|
||||
# Check if proxy or proxy group is defined
|
||||
if (
|
||||
self.zbxproxy["idtype"] in host
|
||||
and host[self.zbxproxy["idtype"]] == self.zbxproxy["id"]
|
||||
):
|
||||
if (self.zbxproxy["idtype"] in host and
|
||||
host[self.zbxproxy["idtype"]] == self.zbxproxy["id"]):
|
||||
self.logger.debug(f"Host {self.name}: proxy in-sync.")
|
||||
# Backwards compatibility for Zabbix <= 6
|
||||
elif "proxy_hostid" in host and host["proxy_hostid"] == self.zbxproxy["id"]:
|
||||
@@ -799,7 +777,7 @@ class PhysicalDevice:
|
||||
else:
|
||||
self.logger.warning(f"Host {self.name}: inventory_mode OUT of sync.")
|
||||
self.updateZabbixHost(inventory_mode=str(self.inventory_mode))
|
||||
if inventory_sync and self.inventory_mode in [0, 1]:
|
||||
if config["inventory_sync"] and self.inventory_mode in [0, 1]:
|
||||
# Check host inventory mapping
|
||||
if host["inventory"] == self.inventory:
|
||||
self.logger.debug(f"Host {self.name}: inventory in-sync.")
|
||||
@@ -808,24 +786,33 @@ class PhysicalDevice:
|
||||
self.updateZabbixHost(inventory=self.inventory)
|
||||
|
||||
# Check host usermacros
|
||||
if usermacro_sync:
|
||||
macros_filtered = []
|
||||
# Do not re-sync secret usermacros unless sync is set to 'full'
|
||||
if str(usermacro_sync).lower() != "full":
|
||||
for m in deepcopy(self.usermacros):
|
||||
if m["type"] == str(1):
|
||||
# Remove the value as the api doesn't return it
|
||||
# this will allow us to only update usermacros that don't exist
|
||||
m.pop("value")
|
||||
macros_filtered.append(m)
|
||||
if host["macros"] == self.usermacros or host["macros"] == macros_filtered:
|
||||
if config['usermacro_sync']:
|
||||
# Make a full copy synce we dont want to lose the original value
|
||||
# of secret type macros from Netbox
|
||||
netbox_macros = deepcopy(self.usermacros)
|
||||
# Set the sync bit
|
||||
full_sync_bit = bool(str(config['usermacro_sync']).lower() == "full")
|
||||
for macro in netbox_macros:
|
||||
# If the Macro is a secret and full sync is NOT activated
|
||||
if macro["type"] == str(1) and not full_sync_bit:
|
||||
# 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
|
||||
macro.pop("value")
|
||||
# Sort all lists
|
||||
def filter_with_macros(macro):
|
||||
return macro["macro"]
|
||||
host["macros"].sort(key=filter_with_macros)
|
||||
netbox_macros.sort(key=filter_with_macros)
|
||||
# Check if both lists are the same
|
||||
if host["macros"] == netbox_macros:
|
||||
self.logger.debug(f"Host {self.name}: usermacros in-sync.")
|
||||
else:
|
||||
self.logger.warning(f"Host {self.name}: usermacros OUT of sync.")
|
||||
# Update Zabbix with NetBox usermacros
|
||||
self.updateZabbixHost(macros=self.usermacros)
|
||||
|
||||
# Check host usermacros
|
||||
if tag_sync:
|
||||
# Check host tags
|
||||
if config['tag_sync']:
|
||||
if remove_duplicates(host["tags"], sortkey="tag") == self.tags:
|
||||
self.logger.debug(f"Host {self.name}: tags in-sync.")
|
||||
else:
|
||||
@@ -881,7 +868,8 @@ class PhysicalDevice:
|
||||
try:
|
||||
# API call to Zabbix
|
||||
self.zabbix.hostinterface.update(updates)
|
||||
e = f"Host {self.name}: solved interface conflict."
|
||||
e = (f"Host {self.name}: updated interface "
|
||||
f"with data {sanatize_log_output(updates)}.")
|
||||
self.logger.info(e)
|
||||
self.create_journal_entry("info", e)
|
||||
except APIRequestError as e:
|
||||
@@ -923,7 +911,7 @@ class PhysicalDevice:
|
||||
self.nb_journals.create(journal)
|
||||
self.logger.debug(f"Host {self.name}: Created journal entry in NetBox")
|
||||
return True
|
||||
except JournalError(e) as e:
|
||||
except NetboxRequestError as e:
|
||||
self.logger.warning(
|
||||
"Unable to create journal entry for "
|
||||
f"{self.name}: NB returned {e}"
|
||||
|
||||
Reference in New Issue
Block a user