From c7d3dab27ca762fb2e59b92157ad3d54097b5e93 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 12 Feb 2025 12:30:28 +0100 Subject: [PATCH] reverted module split, switched to class inheretance instead. Updated config example. --- config.py.example | 39 ++++++++------ modules/device.py | 106 +++++++++++++++++++------------------ modules/inventory.py | 81 ---------------------------- modules/virtual_machine.py | 12 +++-- 4 files changed, 87 insertions(+), 151 deletions(-) delete mode 100644 modules/inventory.py diff --git a/config.py.example b/config.py.example index 1d83223..7f8861e 100644 --- a/config.py.example +++ b/config.py.example @@ -80,19 +80,28 @@ inventory_sync = False # For nested properties, you can use the '/' seperator. # For example, the following map will assign the custom field 'mycustomfield' to the 'alias' Zabbix inventory field: # -# inventory_map = { "custom_fields/mycustomfield/name": "alias"} +# device_inventory_map = { "custom_fields/mycustomfield/name": "alias"} # -# The following map should provide some nice defaults: -inventory_map = { "asset_tag": "asset_tag", - "virtual_chassis/name": "chassis", - "status/label": "deployment_status", - "location/name": "location", - "latitude": "location_lat", - "longitude": "location_lon", - "comments": "notes", - "name": "name", - "rack/name": "site_rack", - "serial": "serialno_a", - "device_type/model": "type", - "device_type/manufacturer/name": "vendor", - "oob_ip/address": "oob_ip" } +# The following maps should provide some nice defaults: +device_inventory_map = { "asset_tag": "asset_tag", + "virtual_chassis/name": "chassis", + "status/label": "deployment_status", + "location/name": "location", + "latitude": "location_lat", + "longitude": "location_lon", + "comments": "notes", + "name": "name", + "rack/name": "site_rack", + "serial": "serialno_a", + "device_type/model": "type", + "device_type/manufacturer/name": "vendor", + "oob_ip/address": "oob_ip" } + +# We also support inventory mapping on Virtual Machines. +vm_inventory_map = { "asset_tag": "asset_tag", + "status/label": "deployment_status", + "location/name": "location", + "latitude": "location_lat", + "longitude": "location_lon", + "comments": "notes", + "name": "name" } diff --git a/modules/device.py b/modules/device.py index 97206ce..ae1488c 100644 --- a/modules/device.py +++ b/modules/device.py @@ -11,7 +11,6 @@ from modules.exceptions import (SyncInventoryError, TemplateError, SyncExternalE InterfaceConfigError, JournalError) from modules.interface import ZabbixInterface from modules.hostgroups import Hostgroup -from modules.inventory import Inventory try: from config import ( @@ -19,6 +18,7 @@ try: traverse_site_groups, traverse_regions, inventory_sync, + inventory_mode, device_inventory_map ) except ModuleNotFoundError: @@ -63,6 +63,10 @@ class PhysicalDevice(): def __str__(self): return self.__repr__() + def _inventory_map(self): + """ Use device inventory maps """ + return device_inventory_map + def _setBasics(self): """ Sets basic information like IP address. @@ -162,56 +166,56 @@ class PhysicalDevice(): return [self.config_context["zabbix"]["templates"]] return self.config_context["zabbix"]["templates"] - def set_inventory(self, nbdevice): - """ Set inventory """ - Inventory.set_inventory(self, nbdevice) - # 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.") -# return True -# if inventory_mode == "manual": -# self.inventory_mode = 0 -# elif 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}") -# return False -# self.inventory = {} -# if inventory_sync and self.inventory_mode in [0,1]: -# self.logger.debug(f"Host {self.name}: Starting inventory mapper") -# # Let's build an inventory dict for each property in the inventory_map -# for nb_inv_field, zbx_inv_field in inventory_map.items(): -# field_list = nb_inv_field.split("/") # convert str to list based on delimiter -# # start at the base of the dict... -# value = nbdevice -# # ... and step through the dict till we find the needed value -# for item in field_list: -# value = value[item] if value else None -# # Check if the result is usable and expected -# # We want to apply any int or float 0 values, -# # even if python thinks those are empty. -# if ((value and isinstance(value, int | float | str )) or -# (isinstance(value, int | float) and int(value) ==0)): -# 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 " -# f"'{nb_inv_field}' returned an empty value") -# self.inventory[zbx_inv_field] = "" -# else: -# # Value is not a string or numeral, probably not what the user expected. -# self.logger.error(f"Host {self.name}: Inventory lookup for '{nb_inv_field}'" -# " returned an unexpected type: it will be skipped.") -# self.logger.debug(f"Host {self.name}: Inventory mapping complete. " -# f"Mapped {len(list(filter(None, self.inventory.values())))} field(s)") -# return True -# +# """ Set inventory """ +# Inventory.set_inventory(self, nbdevice) + + 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.") + return True + if inventory_mode == "manual": + self.inventory_mode = 0 + elif 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}") + return False + self.inventory = {} + if inventory_sync and self.inventory_mode in [0,1]: + self.logger.debug(f"Host {self.name}: Starting inventory mapper") + # Let's build an inventory dict for each property in the inventory_map + for nb_inv_field, zbx_inv_field in self._inventory_map().items(): + field_list = nb_inv_field.split("/") # convert str to list based on delimiter + # start at the base of the dict... + value = nbdevice + # ... and step through the dict till we find the needed value + for item in field_list: + value = value[item] if value else None + # Check if the result is usable and expected + # We want to apply any int or float 0 values, + # even if python thinks those are empty. + if ((value and isinstance(value, int | float | str )) or + (isinstance(value, int | float) and int(value) ==0)): + 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 " + f"'{nb_inv_field}' returned an empty value") + self.inventory[zbx_inv_field] = "" + else: + # Value is not a string or numeral, probably not what the user expected. + self.logger.error(f"Host {self.name}: Inventory lookup for '{nb_inv_field}'" + " returned an unexpected type: it will be skipped.") + self.logger.debug(f"Host {self.name}: Inventory mapping complete. " + f"Mapped {len(list(filter(None, self.inventory.values())))} field(s)") + return True + def isCluster(self): """ Checks if device is part of cluster. @@ -546,7 +550,7 @@ class PhysicalDevice(): 'interfaceid'], selectGroups=["groupid"], selectParentTemplates=["templateid"], - selectInventory=list(device_inventory_map.values())) + selectInventory=list(self._inventory_map().values())) if len(host) > 1: e = (f"Got {len(host)} results for Zabbix hosts " f"with ID {self.zabbix_id} - hostname {self.name}.") diff --git a/modules/inventory.py b/modules/inventory.py deleted file mode 100644 index 7c7cd78..0000000 --- a/modules/inventory.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/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 -""" -from pprint import pprint -from logging import getLogger -from zabbix_utils import APIRequestError -from modules.exceptions import (SyncInventoryError, TemplateError, SyncExternalError, - InterfaceConfigError, JournalError) -try: - from config import ( - inventory_sync, - inventory_mode, - device_inventory_map, - vm_inventory_map - ) -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) - -class Inventory(): - # 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) - """ - -# def __init__(self, nb, logger=None): -# self.nb = nb - - def set_inventory(self, nbobject): - if hasattr(nbobject, 'device_type'): - inventory_map = device_inventory_map - else: - inventory_map = vm_inventory_map - """ 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.") - return True - if inventory_mode == "manual": - self.inventory_mode = 0 - elif 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}") - return False - self.inventory = {} - if inventory_sync and self.inventory_mode in [0,1]: - self.logger.debug(f"Host {self.name}: Starting inventory mapper") - # Let's build an inventory dict for each property in the inventory_map - for nb_inv_field, zbx_inv_field in inventory_map.items(): - field_list = nb_inv_field.split("/") # convert str to list based on delimiter - # start at the base of the dict... - value = nbobject - # ... and step through the dict till we find the needed value - for item in field_list: - value = value[item] if value else None - # Check if the result is usable and expected - # We want to apply any int or float 0 values, - # even if python thinks those are empty. - if ((value and isinstance(value, int | float | str )) or - (isinstance(value, int | float) and int(value) ==0)): - 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 " - f"'{nb_inv_field}' returned an empty value") - self.inventory[zbx_inv_field] = "" - else: - # Value is not a string or numeral, probably not what the user expected. - self.logger.error(f"Host {self.name}: Inventory lookup for '{nb_inv_field}'" - " returned an unexpected type: it will be skipped.") - self.logger.debug(f"Host {self.name}: Inventory mapping complete. " - f"Mapped {len(list(filter(None, self.inventory.values())))} field(s)") -# return True diff --git a/modules/virtual_machine.py b/modules/virtual_machine.py index 27069e6..353a245 100644 --- a/modules/virtual_machine.py +++ b/modules/virtual_machine.py @@ -6,11 +6,11 @@ from os import sys from modules.device import PhysicalDevice from modules.hostgroups import Hostgroup from modules.interface import ZabbixInterface -from modules.inventory import Inventory from modules.exceptions import TemplateError, InterfaceConfigError, SyncInventoryError try: from config import ( inventory_sync, + inventory_mode, vm_inventory_map, traverse_site_groups, traverse_regions @@ -27,6 +27,10 @@ class VirtualMachine(PhysicalDevice): self.hostgroup = None self.zbx_template_names = None + def _inventory_map(self): + """ use VM inventory maps """ + return vm_inventory_map + def set_hostgroup(self, hg_format, nb_site_groups, nb_regions): """Set the hostgroup for this device""" # Create new Hostgroup instance @@ -38,9 +42,9 @@ class VirtualMachine(PhysicalDevice): # Generate hostgroup based on hostgroup format self.hostgroup = hg.generate(hg_format) - def set_inventory(self, nbvm): - """ Set inventory """ - Inventory.set_inventory(self, nbvm) +# def set_inventory(self, nbvm): +# """ Set inventory """ +# Inventory.set_inventory(self, nbvm) def set_vm_template(self): """ Set Template for VMs. Overwrites default class