From c76e36ad3849df5c4085acf612e23b3bd2dd1d79 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Thu, 19 Dec 2024 16:26:18 +0100 Subject: [PATCH 1/9] Split inventory from the device module and started working on vm inventory support --- Pipfile | 13 +++ Pipfile.lock | 188 +++++++++++++++++++++++++++++++++++++ modules/device.py | 99 +++++++++---------- modules/inventory.py | 81 ++++++++++++++++ modules/tools.py | 1 + modules/virtual_machine.py | 7 ++ netbox_zabbix_sync.py | 2 + 7 files changed, 344 insertions(+), 47 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 modules/inventory.py diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..bd0a2ba --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +pynetbox = "*" +zabbix-utils = "*" + +[dev-packages] + +[requires] +python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..4be3d95 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,188 @@ +{ + "_meta": { + "hash": { + "sha256": "6c35ac0ebf3610e4591484dfd9246af60fc4679b2d0d39193818d62961b2703c" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.11" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.8.30" + }, + "charset-normalizer": { + "hashes": [ + "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621", + "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", + "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", + "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", + "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", + "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", + "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", + "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", + "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", + "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", + "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", + "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", + "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab", + "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", + "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", + "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", + "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", + "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", + "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62", + "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", + "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", + "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", + "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", + "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", + "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455", + "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858", + "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", + "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", + "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", + "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", + "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", + "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea", + "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", + "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", + "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", + "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", + "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", + "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", + "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", + "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee", + "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", + "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", + "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51", + "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", + "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8", + "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", + "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613", + "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", + "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", + "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", + "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", + "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", + "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", + "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", + "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", + "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", + "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417", + "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", + "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", + "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", + "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", + "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", + "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149", + "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41", + "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574", + "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", + "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f", + "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", + "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654", + "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", + "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19", + "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", + "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578", + "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", + "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", + "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51", + "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", + "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", + "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", + "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", + "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade", + "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", + "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", + "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6", + "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", + "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", + "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6", + "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2", + "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12", + "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf", + "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", + "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7", + "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", + "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", + "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", + "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", + "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", + "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4", + "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", + "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", + "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", + "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748", + "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", + "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", + "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.4.0" + }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, + "packaging": { + "hashes": [ + "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" + ], + "markers": "python_version >= '3.8'", + "version": "==24.2" + }, + "pynetbox": { + "hashes": [ + "sha256:3f82b5964ca77a608aef6cc2fc48a3961f7667fbbdbb60646655373e3dae00c3", + "sha256:f42ce4df6ce97765df91bb4cc0c0e315683d15135265270d78f595114dd20e2b" + ], + "index": "pypi", + "version": "==7.4.1" + }, + "requests": { + "hashes": [ + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + ], + "markers": "python_version >= '3.8'", + "version": "==2.32.3" + }, + "urllib3": { + "hashes": [ + "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", + "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.3" + }, + "zabbix-utils": { + "hashes": [ + "sha256:1eb918096dcf1980a975ff72e4449b5d72c605f79842595dedd0f4ceba3b1225", + "sha256:3c4a98a24c101d89fd938ebe0ad6c9aaa391ac901f2afb75ae682eea88fb77af" + ], + "index": "pypi", + "version": "==2.0.2" + } + }, + "develop": {} +} diff --git a/modules/device.py b/modules/device.py index 07554d0..97206ce 100644 --- a/modules/device.py +++ b/modules/device.py @@ -11,14 +11,15 @@ 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 ( template_cf, device_cf, traverse_site_groups, traverse_regions, inventory_sync, - inventory_mode, - inventory_map + device_inventory_map ) except ModuleNotFoundError: print("Configuration file config.py not found in main directory." @@ -162,51 +163,55 @@ class PhysicalDevice(): return self.config_context["zabbix"]["templates"] 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 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. @@ -541,7 +546,7 @@ class PhysicalDevice(): 'interfaceid'], selectGroups=["groupid"], selectParentTemplates=["templateid"], - selectInventory=list(inventory_map.values())) + selectInventory=list(device_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 new file mode 100644 index 0000000..7c7cd78 --- /dev/null +++ b/modules/inventory.py @@ -0,0 +1,81 @@ +#!/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/tools.py b/modules/tools.py index f722524..5e09265 100644 --- a/modules/tools.py +++ b/modules/tools.py @@ -42,3 +42,4 @@ def proxy_prepper(proxy_list, proxy_group_list): group["monitored_by"] = 2 output.append(group) return output + diff --git a/modules/virtual_machine.py b/modules/virtual_machine.py index 331a463..27069e6 100644 --- a/modules/virtual_machine.py +++ b/modules/virtual_machine.py @@ -6,9 +6,12 @@ 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, + vm_inventory_map, traverse_site_groups, traverse_regions ) @@ -35,6 +38,10 @@ 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_vm_template(self): """ Set Template for VMs. Overwrites default class to skip a lookup of custom fields.""" diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index 935b55e..12c6960 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -5,6 +5,7 @@ import logging import argparse import ssl +from pprint import pprint from os import environ, path, sys from pynetbox import api from pynetbox.core.query import RequestError as NBRequestError @@ -171,6 +172,7 @@ def main(arguments): # Check if a valid hostgroup has been found for this VM. if not vm.hostgroup: continue + vm.set_inventory(nb_vm) # Checks if device is in cleanup state if vm.status in zabbix_device_removal: if vm.zabbix_id: From 8272e34c129373050317abb3096e3d33f7a38e38 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 12 Feb 2025 11:20:45 +0100 Subject: [PATCH 2/9] removed pipenv artefacts --- Pipfile | 13 ---- Pipfile.lock | 188 --------------------------------------------------- 2 files changed, 201 deletions(-) delete mode 100644 Pipfile delete mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile deleted file mode 100644 index bd0a2ba..0000000 --- a/Pipfile +++ /dev/null @@ -1,13 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -pynetbox = "*" -zabbix-utils = "*" - -[dev-packages] - -[requires] -python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 4be3d95..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,188 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "6c35ac0ebf3610e4591484dfd9246af60fc4679b2d0d39193818d62961b2703c" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.11" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "certifi": { - "hashes": [ - "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", - "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" - ], - "markers": "python_version >= '3.6'", - "version": "==2024.8.30" - }, - "charset-normalizer": { - "hashes": [ - "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621", - "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", - "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", - "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", - "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", - "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", - "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", - "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", - "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", - "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", - "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", - "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", - "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab", - "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", - "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", - "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", - "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", - "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", - "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62", - "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", - "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", - "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", - "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", - "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", - "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455", - "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858", - "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", - "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", - "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", - "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", - "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", - "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea", - "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", - "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", - "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", - "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", - "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", - "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", - "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", - "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee", - "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", - "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", - "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51", - "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", - "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8", - "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", - "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613", - "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", - "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", - "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", - "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", - "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", - "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", - "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", - "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", - "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", - "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417", - "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", - "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", - "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", - "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", - "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", - "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149", - "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41", - "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574", - "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", - "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f", - "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", - "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654", - "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", - "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19", - "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", - "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578", - "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", - "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", - "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51", - "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", - "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", - "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", - "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", - "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade", - "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", - "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", - "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6", - "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", - "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", - "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6", - "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2", - "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12", - "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf", - "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", - "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7", - "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", - "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", - "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", - "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", - "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", - "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4", - "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", - "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", - "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", - "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748", - "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", - "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", - "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.4.0" - }, - "idna": { - "hashes": [ - "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", - "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" - ], - "markers": "python_version >= '3.6'", - "version": "==3.10" - }, - "packaging": { - "hashes": [ - "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", - "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" - ], - "markers": "python_version >= '3.8'", - "version": "==24.2" - }, - "pynetbox": { - "hashes": [ - "sha256:3f82b5964ca77a608aef6cc2fc48a3961f7667fbbdbb60646655373e3dae00c3", - "sha256:f42ce4df6ce97765df91bb4cc0c0e315683d15135265270d78f595114dd20e2b" - ], - "index": "pypi", - "version": "==7.4.1" - }, - "requests": { - "hashes": [ - "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", - "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" - ], - "markers": "python_version >= '3.8'", - "version": "==2.32.3" - }, - "urllib3": { - "hashes": [ - "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", - "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" - ], - "markers": "python_version >= '3.8'", - "version": "==2.2.3" - }, - "zabbix-utils": { - "hashes": [ - "sha256:1eb918096dcf1980a975ff72e4449b5d72c605f79842595dedd0f4ceba3b1225", - "sha256:3c4a98a24c101d89fd938ebe0ad6c9aaa391ac901f2afb75ae682eea88fb77af" - ], - "index": "pypi", - "version": "==2.0.2" - } - }, - "develop": {} -} From 4c91c660a8bb55b4b9a52b548ad7fc0a2770f599 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 12 Feb 2025 11:22:27 +0100 Subject: [PATCH 3/9] removed newline --- modules/tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/tools.py b/modules/tools.py index 5e09265..f722524 100644 --- a/modules/tools.py +++ b/modules/tools.py @@ -42,4 +42,3 @@ def proxy_prepper(proxy_list, proxy_group_list): group["monitored_by"] = 2 output.append(group) return output - From ba2f77a640d8396c7ebc0535baa186c130d318c7 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 12 Feb 2025 11:25:27 +0100 Subject: [PATCH 4/9] Added Pipfile ignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c3069c9..2a3448b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.log .venv config.py +Pipfile +Pipfile.lock # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] From c7d3dab27ca762fb2e59b92157ad3d54097b5e93 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 12 Feb 2025 12:30:28 +0100 Subject: [PATCH 5/9] 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 From 1157ed9e640c826a689ad3bfe13f03cf921ea8cf Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 12 Feb 2025 12:32:42 +0100 Subject: [PATCH 6/9] cleanup --- modules/device.py | 4 ---- modules/virtual_machine.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/modules/device.py b/modules/device.py index ae1488c..349db0a 100644 --- a/modules/device.py +++ b/modules/device.py @@ -166,10 +166,6 @@ 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). diff --git a/modules/virtual_machine.py b/modules/virtual_machine.py index 353a245..b8fa1a1 100644 --- a/modules/virtual_machine.py +++ b/modules/virtual_machine.py @@ -42,10 +42,6 @@ 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_vm_template(self): """ Set Template for VMs. Overwrites default class to skip a lookup of custom fields.""" From 5f78a2c7890b49cb720ffb4ff1de1bffc32a2168 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 12 Feb 2025 12:35:21 +0100 Subject: [PATCH 7/9] removed unsupported field from vm_inventory_map --- config.py.example | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config.py.example b/config.py.example index 7f8861e..dcc307f 100644 --- a/config.py.example +++ b/config.py.example @@ -98,8 +98,7 @@ device_inventory_map = { "asset_tag": "asset_tag", "oob_ip/address": "oob_ip" } # We also support inventory mapping on Virtual Machines. -vm_inventory_map = { "asset_tag": "asset_tag", - "status/label": "deployment_status", +vm_inventory_map = { "status/label": "deployment_status", "location/name": "location", "latitude": "location_lat", "longitude": "location_lon", From b8bb3fb3f091c4da0d9b33507fff5594b1f5abc1 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 12 Feb 2025 12:36:27 +0100 Subject: [PATCH 8/9] removed unsupported fields from vm_inventory_map --- config.py.example | 3 --- 1 file changed, 3 deletions(-) diff --git a/config.py.example b/config.py.example index dcc307f..0a653d6 100644 --- a/config.py.example +++ b/config.py.example @@ -99,8 +99,5 @@ device_inventory_map = { "asset_tag": "asset_tag", # We also support inventory mapping on Virtual Machines. vm_inventory_map = { "status/label": "deployment_status", - "location/name": "location", - "latitude": "location_lat", - "longitude": "location_lon", "comments": "notes", "name": "name" } From c67180138eb60ee7a8fefd433bd836d3c2ff7032 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 12 Feb 2025 12:39:36 +0100 Subject: [PATCH 9/9] cleanup --- netbox_zabbix_sync.py | 1 - 1 file changed, 1 deletion(-) diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index 12c6960..3eaea3f 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -5,7 +5,6 @@ import logging import argparse import ssl -from pprint import pprint from os import environ, path, sys from pynetbox import api from pynetbox.core.query import RequestError as NBRequestError