From 6d4e250b236efe0a70594f6fd604d0a29cbf2d4a Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Fri, 14 Feb 2025 08:28:10 +0100 Subject: [PATCH] :sparkles: Working usermacros based on config context --- modules/device.py | 40 ++++++++++++++++++++++----------- modules/usermacros.py | 51 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/modules/device.py b/modules/device.py index dc12a28..1a9e452 100644 --- a/modules/device.py +++ b/modules/device.py @@ -5,6 +5,7 @@ Device specific handeling for NetBox to Zabbix """ from os import sys from re import search +from copy import deepcopy from logging import getLogger from zabbix_utils import APIRequestError from modules.exceptions import (SyncInventoryError, TemplateError, SyncExternalError, @@ -22,6 +23,7 @@ try: inventory_sync, inventory_mode, device_inventory_map, + usermacro_sync, device_usermacro_map ) except ModuleNotFoundError: @@ -372,18 +374,13 @@ class PhysicalDevice(): raise SyncInventoryError(message) from e def setUsermacros(self): - try: - # Initiate interface class - macros = ZabbixUsermacros(self.nb.config_context, self._usermacro_map()) - if macros.sync == False: - return {} - else: - return [{'macro': '{$USERMACRO}', 'value': '123', 'type': 0, 'description': 'just a test'}] - except UsermacroError as e: - message = f"{self.name}: {e}" - self.logger.warning(message) - raise UsermacroError(message) from e - + # Initiate Usermacros class + macros = ZabbixUsermacros(self.nb.config_context, self._usermacro_map()) + if macros.sync == False: + return [] + else: + return macros.generate() + def setProxy(self, proxy_list): """ Sets proxy or proxy group if this @@ -574,7 +571,6 @@ class PhysicalDevice(): selectInventory=list(self._inventory_map().values()), selectMacros=["macro","value","type","description"] ) - pprint(host) if len(host) > 1: e = (f"Got {len(host)} results for Zabbix hosts " f"with ID {self.zabbix_id} - hostname {self.name}.") @@ -696,6 +692,24 @@ class PhysicalDevice(): self.logger.warning(f"Host {self.name}: inventory OUT of sync.") self.updateZabbixHost(inventory=self.inventory) + # Check host usermacros + if usermacro_sync: + macros_filtered = [] + self.usermacros = self.setUsermacros() + # Do not re-sync secret usermacros unless sync is set to 'full' + if not 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: + self.logger.debug(f"Host {self.name}: usermacros in-sync.") + else: + self.logger.warning(f"Host {self.name}: usermacros OUT of sync.") + self.updateZabbixHost(macros=self.usermacros) + # If only 1 interface has been found # pylint: disable=too-many-nested-blocks if len(host['interfaces']) == 1: diff --git a/modules/usermacros.py b/modules/usermacros.py index 9f53760..19d85a6 100644 --- a/modules/usermacros.py +++ b/modules/usermacros.py @@ -2,11 +2,11 @@ """ All of the Zabbix Usermacro related configuration """ +from re import match from logging import getLogger from zabbix_utils import APIRequestError from modules.exceptions import UsermacroError - from pprint import pprint try: @@ -37,11 +37,54 @@ class ZabbixUsermacros(): return self.__repr__() def _setConfig(self): - if str(usermacro_sync) == "full": + if str(usermacro_sync).lower() == "full": self.sync = True self.force_sync = True elif usermacro_sync: self.sync = True return True - - + + def validate_macro(self, macro_name): + pattern = '\{\$[A-Z0-9\._]*(\:.*)?\}' + return match(pattern, macro_name) + + def render_macro(self, macro_name, macro_properties): + macro={} + macrotypes={'text': 0, 'secret': 1, 'vault': 2} + if self.validate_macro(macro_name): + macro['macro'] = str(macro_name) + if isinstance(macro_properties, dict): + if not 'value' in macro_properties: + self.logger.error(f'Usermacro {macro_name} has no value, skipping.') + return False + else: + macro['value'] = macro_properties['value'] + + if 'type' in macro_properties and macro_properties['type'].lower() in macrotypes: + macro['type'] = str(macrotypes[macro_properties['type']]) + else: + macro['type'] = str(0) + + if 'description' in macro_properties and isinstance(macro_properties['description'], str): + macro['description'] = macro_properties['description'] + else: + macro['description'] = "" + + elif isinstance(macro_properties, str): + macro['value'] = macro_properties + macro['type'] = str(0) + macro['description'] = "" + else: + self.logger.error(f'Usermacro {macro_name} is not a valid usermacro name, skipping.') + return False + return macro + + def generate(self): + macros=[] + if "zabbix" in self.context and "usermacros" in self.context['zabbix']: + for macro, properties in self.context['zabbix']['usermacros'].items(): + m = self.render_macro(macro, properties) + pprint(m) + if m: + macros.append(m) + return macros