Updated script to support config context as source for Zabbix templates. Updated config and readme with instructions

This commit is contained in:
TheNetworkGuy 2023-05-10 21:03:56 +02:00
parent 9b58f523c8
commit 6389146342
3 changed files with 133 additions and 40 deletions

View File

@ -33,7 +33,7 @@ NETBOX_HOST="https://netbox.local"
NETBOX_TOKEN="secrettoken" NETBOX_TOKEN="secrettoken"
``` ```
### Netbox custom fields ### Netbox custom fields
Use the following custom fields in Netbox: Use the following custom fields in Netbox (if you are using config context for the template information then the zabbix_template field is not required):
``` ```
* Type: Integer * Type: Integer
* Name: zabbix_hostid * Name: zabbix_hostid
@ -52,6 +52,31 @@ You can make the hostID field hidden or read-only to prevent human intervention.
This is optional and there is a use case for leaving it read-write in the UI to manually change the ID. For example to re-run a sync. This is optional and there is a use case for leaving it read-write in the UI to manually change the ID. For example to re-run a sync.
### Template source
You can either use a Netbox devicce type custom field or Netbox config context for the Zabbix tempplate information.
Using a custom field allows for only one template. You can assign multiple templates to one host using the config context source.
Should you make use of an advanced templating structure with lots of nesting then i would recommend sticking to the custom field.
You can change the behaviour in the config file. By default this setting is false but you can set it to true to use config context:
```
templates_config_context = True
```
After that make sure that for each host there is at least one template defined in the config context in this format:
```
{
"zabbix": {
"templates": [
"TemplateA",
"TemplateB",
"TemplateC",
"Template123"
]
}
}
```
## Permissions ## Permissions
### Netbox ### Netbox

View File

@ -1,4 +1,10 @@
# Template logic.
# Set to true to enable the template source information
# coming from config context instead of a custom field.
templates_config_context = False
# Set template and device Netbox "custom field" names # Set template and device Netbox "custom field" names
# Template_cf is not used when templates_config_context is enabled
template_cf = "zabbix_template" template_cf = "zabbix_template"
device_cf = "zabbix_hostid" device_cf = "zabbix_hostid"

View File

@ -84,6 +84,7 @@ def main(arguments):
device = NetworkDevice(nb_device, zabbix, netbox_journals, device = NetworkDevice(nb_device, zabbix, netbox_journals,
arguments.journal) arguments.journal)
device.set_hostgroup(arguments.layout) device.set_hostgroup(arguments.layout)
device.set_template(templates_config_context)
# Checks if device is part of cluster. # Checks if device is part of cluster.
# Requires the cluster argument. # Requires the cluster argument.
if(device.isCluster() and arguments.cluster): if(device.isCluster() and arguments.cluster):
@ -213,17 +214,6 @@ class NetworkDevice():
logger.warning(e) logger.warning(e)
raise SyncInventoryError(e) raise SyncInventoryError(e)
# Gather device Zabbix template
device_type_cf = self.nb.device_type.custom_fields
if(template_cf in device_type_cf):
self.template_name = device_type_cf[template_cf]
else:
e = (f"Custom field {template_cf} not "
f"found for {self.nb.device_type.manufacturer.name}"
f" - {self.nb.device_type.display}.")
logger.warning(e)
raise SyncInventoryError(e)
def set_hostgroup(self, format): def set_hostgroup(self, format):
"""Set the hostgroup for this device""" """Set the hostgroup for this device"""
# Get all variables from the NB data # Get all variables from the NB data
@ -261,10 +251,40 @@ class NetworkDevice():
# Add the item to the hostgroup format # Add the item to the hostgroup format
self.hostgroup += hostgroup_vars[item] self.hostgroup += hostgroup_vars[item]
if(not self.hostgroup): if(not self.hostgroup):
e = f"{self.name} has no reliable hostgroup. This is most likely due to the use of custom fields that are empty." e = (f"{self.name} has no reliable hostgroup. This is"
"most likely due to the use of custom fields that are empty.")
logger.error(e) logger.error(e)
raise SyncInventoryError(e) raise SyncInventoryError(e)
def set_template(self, templates_config_context):
if templates_config_context:
# Template lookup using config context
if("zabbix" not in self.config_context):
e = ("Key 'zabbix' not found in config "
f"context for template host {self.name}")
logger.warning(e)
raise SyncInventoryError(e)
if("templates" not in self.config_context["zabbix"]):
e = ("Key 'zabbix' not found in config "
f"context for template host {self.name}")
logger.warning(e)
raise SyncInventoryError(e)
self.zbx_template_names = self.config_context["zabbix"]["templates"]
else:
# Get device type custom fields
device_type_cfs = self.nb.device_type.custom_fields
# Check if the ZBX Template CF is present
if(template_cf in device_type_cfs):
# Set value to template
self.zbx_template_names = [device_type_cfs[template_cf]]
else:
# Custom field not found, return error
e = (f"Custom field {template_cf} not "
f"found for {self.nb.device_type.manufacturer.name}"
f" - {self.nb.device_type.display}.")
logger.warning(e)
raise SyncInventoryError(e)
def isCluster(self): def isCluster(self):
""" """
Checks if device is part of cluster. Checks if device is part of cluster.
@ -309,27 +329,39 @@ class NetworkDevice():
logger.debug(f"Device {self.name} is non-primary cluster member.") logger.debug(f"Device {self.name} is non-primary cluster member.")
return False return False
def getZabbixTemplate(self, templates): def zbxTemplatePrepper(self, templates):
""" """
Returns Zabbix template IDs Returns Zabbix template IDs
INPUT: list of templates INPUT: list of templates from Zabbix
OUTPUT: True OUTPUT: True
""" """
if(not self.template_name): # Check if there are templates defined
if(not self.zbx_template_names):
e = (f"Device template '{self.nb.device_type.display}' " e = (f"Device template '{self.nb.device_type.display}' "
"has no Zabbix template defined.") "has no Zabbix templates defined.")
logger.info(e) logger.info(e)
raise SyncInventoryError() raise SyncInventoryError()
for template in templates: # Set variable to empty list
if(template['name'] == self.template_name): self.zbx_templates = []
self.template_id = template['templateid'] # Go through all templates definded in Netbox
e = (f"Found template ID {str(template['templateid'])} " for nb_template in self.zbx_template_names:
template_match = False
# Go through all templates found in Zabbix
for zbx_template in templates:
# If the template names match
if(zbx_template['name'] == nb_template):
# Set match variable to true, add template details
# to class variable and return debug log
template_match = True
self.zbx_templates.append({"templateid": zbx_template['templateid'],
"name": zbx_template['name']})
e = (f"Found template {zbx_template['name']}"
f" for host {self.name}.") f" for host {self.name}.")
logger.debug(e) logger.debug(e)
return True # Return error should the template not be found in Zabbix
else: if(not template_match):
e = (f"Unable to find template {self.template_name} " e = (f"Unable to find template {nb_template} "
f"for host {self.name} in Zabbix.") f"for host {self.name} in Zabbix. Skipping host...")
logger.warning(e) logger.warning(e)
raise SyncInventoryError(e) raise SyncInventoryError(e)
@ -429,11 +461,10 @@ class NetworkDevice():
# Get group and template ID's for host # Get group and template ID's for host
if(not self.getZabbixGroup(groups)): if(not self.getZabbixGroup(groups)):
raise SyncInventoryError() raise SyncInventoryError()
self.getZabbixTemplate(templates) self.zbxTemplatePrepper(templates)
# Set interface, group and template configuration # Set interface, group and template configuration
interfaces = self.setInterfaceDetails() interfaces = self.setInterfaceDetails()
groups = [{"groupid": self.group_id}] groups = [{"groupid": self.group_id}]
templates = [{"templateid": self.template_id}]
# Set Zabbix proxy if defined # Set Zabbix proxy if defined
self.setProxy(proxys) self.setProxy(proxys)
# Add host to Zabbix # Add host to Zabbix
@ -442,7 +473,7 @@ class NetworkDevice():
status=self.zabbix_state, status=self.zabbix_state,
interfaces=interfaces, interfaces=interfaces,
groups=groups, groups=groups,
templates=templates, templates=self.zbx_templates,
proxy_hostid=self.zbxproxy, proxy_hostid=self.zbxproxy,
description=description) description=description)
self.zabbix_id = host["hostids"][0] self.zabbix_id = host["hostids"][0]
@ -494,7 +525,7 @@ class NetworkDevice():
Checks if Zabbix object is still valid with Netbox parameters. Checks if Zabbix object is still valid with Netbox parameters.
""" """
self.getZabbixGroup(groups) self.getZabbixGroup(groups)
self.getZabbixTemplate(templates) self.zbxTemplatePrepper(templates)
self.setProxy(proxys) self.setProxy(proxys)
host = self.zabbix.host.get(filter={'hostid': self.zabbix_id}, host = self.zabbix.host.get(filter={'hostid': self.zabbix_id},
selectInterfaces=['type', 'ip', selectInterfaces=['type', 'ip',
@ -523,13 +554,13 @@ class NetworkDevice():
f"Received value: {host['host']}") f"Received value: {host['host']}")
self.updateZabbixHost(host=self.name) self.updateZabbixHost(host=self.name)
for template in host["parentTemplates"]: # Check if the templates are in-sync
if(template["templateid"] == self.template_id): if(not self.zbx_template_comparer(host["parentTemplates"])):
logger.debug(f"Device {self.name}: template in-sync.") logger.warning(f"Device {self.name}: template(s) OUT of sync.")
break # Update Zabbix with NB templates and clear any old / lost templates
self.updateZabbixHost(templates_clear=host["parentTemplates"], templates=self.zbx_templates)
else: else:
logger.warning(f"Device {self.name}: template OUT of sync.") logger.debug(f"Device {self.name}: template(s) in-sync.")
self.updateZabbixHost(templates=self.template_id)
for group in host["groups"]: for group in host["groups"]:
if(group["groupid"] == self.group_id): if(group["groupid"] == self.group_id):
@ -653,6 +684,37 @@ class NetworkDevice():
logger.warning("Unable to create journal entry for " logger.warning("Unable to create journal entry for "
f"{self.name}: NB returned {e}") f"{self.name}: NB returned {e}")
def zbx_template_comparer(self, tmpls_from_zabbix):
"""
Compares the Netbox and Zabbix templates with each other.
Should there be a mismatch then the function will return false
INPUT: list of NB and ZBX templates
OUTPUT: Boolean True/False
"""
succesfull_templates = []
# Go through each Netbox template
for nb_tmpl in self.zbx_templates:
# Go through each Zabbix template
for pos, zbx_tmpl in enumerate(tmpls_from_zabbix):
# Check if template IDs match
if(nb_tmpl["templateid"] == zbx_tmpl["templateid"]):
# Templates match. Remove this template from the Zabbix templates
# and add this NB template to the list of successfull templates
tmpls_from_zabbix.pop(pos)
succesfull_templates.append(nb_tmpl)
logger.debug(f"Device {self.name}: template {nb_tmpl['name']} is present in Zabbix.")
break
if(len(succesfull_templates) == len(self.zbx_templates) and
len(tmpls_from_zabbix) == 0):
# All of the Netbox templates have been confirmed as successfull
# and the ZBX template list is empty. This means that
# all of the templates match.
return True
return False
class ZabbixInterface(): class ZabbixInterface():
"""Class that represents a Zabbix interface.""" """Class that represents a Zabbix interface."""