mirror of
https://github.com/TheNetworkGuy/netbox-zabbix-sync.git
synced 2025-07-13 07:24:47 -06:00
Added tag support
This commit is contained in:
parent
fd70045c6d
commit
d65fa5b699
@ -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"}
|
||||||
|
@ -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
117
modules/tags.py
Normal 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')
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user