Added tag support

This commit is contained in:
Raymond Kuiper 2025-02-19 15:56:01 +01:00
parent fd70045c6d
commit d65fa5b699
6 changed files with 208 additions and 8 deletions

View File

@ -121,3 +121,33 @@ vm_usermacro_map = {"memory": "{$TOTAL_MEMORY}",
"url": "{$NB_URL}", "url": "{$NB_URL}",
"id": "{$NB_ID}"} "id": "{$NB_ID}"}
# To sync host tags to Zabbix, set to True.
tag_sync = False
# Setting tag_lower to True will lower capital letters ain tag names and values
# This is more inline with the Zabbix way of working with tags.
#
# You can however set this to False to ensure capital letters are synced to Zabbix tags.
tag_lower = True
# We can sync NetBox device/VM tags to Zabbix, but as NetBox tags don't follow the key/value
# pattern, we need to specify a tag name to register the NetBox tags in Zabbix.
#
#
#
# If tag_name is set to False, we won't sync NetBox device/VM tags to Zabbix.
tag_name = 'NetBox'
# We can choose to use 'name', 'slug' or 'display' NetBox tag properties as a value in Zabbix.
# 'name'is used by default.
tag_value = "name"
# device tag_map to map NetBox fields to host tags.
device_tag_map = {"site/name": "site",
"rack/name": "rack",
"platform/name": "target"}
# Virtual machine tag_map to map NetBox fields to host tags.
vm_tag_map = {"site/name": "site",
"cluster/name": "cluster",
"platform/name": "target"}

View File

@ -12,8 +12,9 @@ from modules.exceptions import (SyncInventoryError, TemplateError, SyncExternalE
InterfaceConfigError, JournalError) InterfaceConfigError, JournalError)
from modules.interface import ZabbixInterface from modules.interface import ZabbixInterface
from modules.usermacros import ZabbixUsermacros from modules.usermacros import ZabbixUsermacros
from modules.tags import ZabbixTags
from modules.hostgroups import Hostgroup from modules.hostgroups import Hostgroup
from modules.tools import field_mapper from modules.tools import field_mapper, remove_duplicates
try: try:
from config import ( from config import (
@ -24,7 +25,12 @@ try:
inventory_mode, inventory_mode,
device_inventory_map, device_inventory_map,
usermacro_sync, usermacro_sync,
device_usermacro_map device_usermacro_map,
tag_sync,
tag_lower,
tag_name,
tag_value,
device_tag_map
) )
except ModuleNotFoundError: except ModuleNotFoundError:
print("Configuration file config.py not found in main directory." print("Configuration file config.py not found in main directory."
@ -60,6 +66,7 @@ class PhysicalDevice():
self.inventory_mode = -1 self.inventory_mode = -1
self.inventory = {} self.inventory = {}
self.usermacros = {} self.usermacros = {}
self.tags = {}
self.logger = logger if logger else getLogger(__name__) self.logger = logger if logger else getLogger(__name__)
self._setBasics() self._setBasics()
@ -77,6 +84,10 @@ class PhysicalDevice():
""" Use device inventory maps """ """ Use device inventory maps """
return device_usermacro_map return device_usermacro_map
def _tag_map(self):
""" Use device host tag maps """
return device_tag_map
def _setBasics(self): def _setBasics(self):
""" """
Sets basic information like IP address. Sets basic information like IP address.
@ -362,6 +373,21 @@ class PhysicalDevice():
self.usermacros = macros.generate() self.usermacros = macros.generate()
return True return True
def set_tags(self):
"""
Generates Host Tags
"""
tags = ZabbixTags(self.nb, self._tag_map(),
tag_sync, tag_lower, tag_name=tag_name,
tag_value=tag_value, logger=self.logger,
host=self.name)
if tags.sync is False:
self.tags = []
self.tags = tags.generate()
return True
def setProxy(self, proxy_list): def setProxy(self, proxy_list):
""" """
Sets proxy or proxy group if this Sets proxy or proxy group if this
@ -432,7 +458,8 @@ class PhysicalDevice():
"description": description, "description": description,
"inventory_mode": self.inventory_mode, "inventory_mode": self.inventory_mode,
"inventory": self.inventory, "inventory": self.inventory,
"macros": self.usermacros "macros": self.usermacros,
"tags": self.tags
} }
# If a Zabbix proxy or Zabbix Proxy group has been defined # If a Zabbix proxy or Zabbix Proxy group has been defined
if self.zbxproxy: if self.zbxproxy:
@ -548,7 +575,8 @@ class PhysicalDevice():
selectHostGroups=["groupid"], selectHostGroups=["groupid"],
selectParentTemplates=["templateid"], selectParentTemplates=["templateid"],
selectInventory=list(self._inventory_map().values()), selectInventory=list(self._inventory_map().values()),
selectMacros=["macro","value","type","description"] selectMacros=["macro","value","type","description"],
selectTags=["tag","value"]
) )
if len(host) > 1: if len(host) > 1:
e = (f"Got {len(host)} results for Zabbix hosts " e = (f"Got {len(host)} results for Zabbix hosts "
@ -598,9 +626,8 @@ class PhysicalDevice():
if group["groupid"] == self.group_id: if group["groupid"] == self.group_id:
self.logger.debug(f"Host {self.name}: hostgroup in-sync.") self.logger.debug(f"Host {self.name}: hostgroup in-sync.")
break break
else: self.logger.warning(f"Host {self.name}: hostgroup OUT of sync.")
self.logger.warning(f"Host {self.name}: hostgroup OUT of sync.") self.updateZabbixHost(groups={'groupid': self.group_id})
self.updateZabbixHost(groups={'groupid': self.group_id})
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(f"Host {self.name}: status in-sync.")
@ -688,6 +715,14 @@ class PhysicalDevice():
self.logger.warning(f"Host {self.name}: usermacros OUT of sync.") self.logger.warning(f"Host {self.name}: usermacros OUT of sync.")
self.updateZabbixHost(macros=self.usermacros) self.updateZabbixHost(macros=self.usermacros)
# Check host usermacros
if tag_sync:
if remove_duplicates(host['tags'],sortkey='tag') == self.tags:
self.logger.debug(f"Host {self.name}: tags in-sync.")
else:
self.logger.warning(f"Host {self.name}: tags OUT of sync.")
self.updateZabbixHost(tags=self.tags)
# If only 1 interface has been found # If only 1 interface has been found
# pylint: disable=too-many-nested-blocks # pylint: disable=too-many-nested-blocks
if len(host['interfaces']) == 1: if len(host['interfaces']) == 1:

117
modules/tags.py Normal file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env python3
# pylint: disable=too-many-instance-attributes, too-many-arguments, too-many-positional-arguments, logging-fstring-interpolation
"""
All of the Zabbix Usermacro related configuration
"""
from logging import getLogger
from modules.tools import field_mapper, remove_duplicates
class ZabbixTags():
"""Class that represents a Zabbix interface."""
def __init__(self, nb, tag_map, tag_sync, tag_lower=True,
tag_name=None, tag_value=None, logger=None, host=None):
self.nb = nb
self.name = host if host else nb.name
self.tag_map = tag_map
self.logger = logger if logger else getLogger(__name__)
self.tags = {}
self.lower = tag_lower
self.tag_name = tag_name
self.tag_value = tag_value
self.tag_sync = tag_sync
self.sync = False
self._set_config()
def __repr__(self):
return self.name
def __str__(self):
return self.__repr__()
def _set_config(self):
"""
Setup class
"""
if self.tag_sync:
self.sync = True
return True
def validate_tag(self, tag_name):
"""
Validates tag name
"""
if tag_name and isinstance(tag_name, str) and len(tag_name)<=256:
return True
return False
def validate_value(self, tag_value):
"""
Validates tag value
"""
if tag_value and isinstance(tag_value, str) and len(tag_value)<=256:
return True
return False
def render_tag(self, tag_name, tag_value):
"""
Renders a tag
"""
tag={}
if self.validate_tag(tag_name):
if self.lower:
tag['tag'] = tag_name.lower()
else:
tag['tag'] = tag_name
else:
self.logger.error(f'Tag {tag_name} is not a valid tag name, skipping.')
return False
if self.validate_value(tag_value):
if self.lower:
tag['value'] = tag_value.lower()
else:
tag['value'] = tag_value
else:
self.logger.error(f'Tag {tag_name} has an invalid value: \'{tag_value}\', skipping.')
return False
return tag
def generate(self):
"""
Generate full set of Usermacros
"""
# pylint: disable=too-many-branches
tags=[]
# Parse the field mapper for tags
if self.tag_map:
self.logger.debug(f"Host {self.nb.name}: Starting tag mapper")
field_tags = field_mapper(self.nb.name, self.tag_map, self.nb, self.logger)
for tag, value in field_tags.items():
t = self.render_tag(tag, value)
if t:
tags.append(t)
# Parse NetBox config context for tags
if ("zabbix" in self.nb.config_context and "tags" in self.nb.config_context['zabbix']
and isinstance(self.nb.config_context['zabbix']['tags'], list)):
for tag in self.nb.config_context['zabbix']['tags']:
if isinstance(tag, dict):
for tagname, value in tag.items():
t = self.render_tag(tagname, value)
if t:
tags.append(t)
# Pull in NetBox device tags if tag_name is set
if self.tag_name and isinstance(self.tag_name, str):
for tag in self.nb.tags:
if self.tag_value.lower() in ['display', 'name', 'slug']:
value = tag[self.tag_value]
else:
value = tag['name']
t = self.render_tag(self.tag_name, value)
if t:
tags.append(t)
return remove_duplicates(tags, sortkey='tag')

View File

@ -76,3 +76,14 @@ def field_mapper(host, mapper, nbdevice, logger):
logger.debug(f"Host {host}: Field mapping complete. " logger.debug(f"Host {host}: Field mapping complete. "
f"Mapped {len(list(filter(None, data.values())))} field(s)") f"Mapped {len(list(filter(None, data.values())))} field(s)")
return data return data
def remove_duplicates(input_list, sortkey=None):
"""
Removes duplicate entries from a list and sorts the list
"""
output_list = []
if isinstance(input_list, list):
output_list = [dict(t) for t in {tuple(d.items()) for d in input_list}]
if sortkey and isinstance(sortkey, str):
output_list.sort(key=lambda x: x[sortkey])
return output_list

View File

@ -11,6 +11,7 @@ try:
from config import ( from config import (
vm_inventory_map, vm_inventory_map,
vm_usermacro_map, vm_usermacro_map,
vm_tag_map,
traverse_site_groups, traverse_site_groups,
traverse_regions traverse_regions
) )
@ -31,9 +32,13 @@ class VirtualMachine(PhysicalDevice):
return vm_inventory_map return vm_inventory_map
def _usermacro_map(self): def _usermacro_map(self):
""" use VM inventory maps """ """ use VM usermacro maps """
return vm_usermacro_map return vm_usermacro_map
def _tag_map(self):
""" use VM tag maps """
return vm_tag_map
def set_hostgroup(self, hg_format, nb_site_groups, nb_regions): def set_hostgroup(self, hg_format, nb_site_groups, nb_regions):
"""Set the hostgroup for this device""" """Set the hostgroup for this device"""
# Create new Hostgroup instance # Create new Hostgroup instance

View File

@ -173,6 +173,7 @@ def main(arguments):
continue continue
vm.set_inventory(nb_vm) vm.set_inventory(nb_vm)
vm.set_usermacros() vm.set_usermacros()
vm.set_tags()
# Checks if device is in cleanup state # Checks if device is in cleanup state
if vm.status in zabbix_device_removal: if vm.status in zabbix_device_removal:
if vm.zabbix_id: if vm.zabbix_id:
@ -227,6 +228,7 @@ def main(arguments):
continue continue
device.set_inventory(nb_device) device.set_inventory(nb_device)
device.set_usermacros() device.set_usermacros()
device.set_tags()
# Checks if device is part of cluster. # Checks if device is part of cluster.
# Requires clustering variable # Requires clustering variable
if device.isCluster() and clustering: if device.isCluster() and clustering: