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}",
"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)
from modules.interface import ZabbixInterface
from modules.usermacros import ZabbixUsermacros
from modules.tags import ZabbixTags
from modules.hostgroups import Hostgroup
from modules.tools import field_mapper
from modules.tools import field_mapper, remove_duplicates
try:
from config import (
@ -24,7 +25,12 @@ try:
inventory_mode,
device_inventory_map,
usermacro_sync,
device_usermacro_map
device_usermacro_map,
tag_sync,
tag_lower,
tag_name,
tag_value,
device_tag_map
)
except ModuleNotFoundError:
print("Configuration file config.py not found in main directory."
@ -60,6 +66,7 @@ class PhysicalDevice():
self.inventory_mode = -1
self.inventory = {}
self.usermacros = {}
self.tags = {}
self.logger = logger if logger else getLogger(__name__)
self._setBasics()
@ -77,6 +84,10 @@ class PhysicalDevice():
""" Use device inventory maps """
return device_usermacro_map
def _tag_map(self):
""" Use device host tag maps """
return device_tag_map
def _setBasics(self):
"""
Sets basic information like IP address.
@ -362,6 +373,21 @@ class PhysicalDevice():
self.usermacros = macros.generate()
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):
"""
Sets proxy or proxy group if this
@ -432,7 +458,8 @@ class PhysicalDevice():
"description": description,
"inventory_mode": self.inventory_mode,
"inventory": self.inventory,
"macros": self.usermacros
"macros": self.usermacros,
"tags": self.tags
}
# If a Zabbix proxy or Zabbix Proxy group has been defined
if self.zbxproxy:
@ -548,7 +575,8 @@ class PhysicalDevice():
selectHostGroups=["groupid"],
selectParentTemplates=["templateid"],
selectInventory=list(self._inventory_map().values()),
selectMacros=["macro","value","type","description"]
selectMacros=["macro","value","type","description"],
selectTags=["tag","value"]
)
if len(host) > 1:
e = (f"Got {len(host)} results for Zabbix hosts "
@ -598,9 +626,8 @@ class PhysicalDevice():
if group["groupid"] == self.group_id:
self.logger.debug(f"Host {self.name}: hostgroup in-sync.")
break
else:
self.logger.warning(f"Host {self.name}: hostgroup OUT of sync.")
self.updateZabbixHost(groups={'groupid': self.group_id})
self.logger.warning(f"Host {self.name}: hostgroup OUT of sync.")
self.updateZabbixHost(groups={'groupid': self.group_id})
if int(host["status"]) == self.zabbix_state:
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.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
# pylint: disable=too-many-nested-blocks
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. "
f"Mapped {len(list(filter(None, data.values())))} field(s)")
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 (
vm_inventory_map,
vm_usermacro_map,
vm_tag_map,
traverse_site_groups,
traverse_regions
)
@ -31,9 +32,13 @@ class VirtualMachine(PhysicalDevice):
return vm_inventory_map
def _usermacro_map(self):
""" use VM inventory maps """
""" use VM usermacro maps """
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):
"""Set the hostgroup for this device"""
# Create new Hostgroup instance

View File

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