mirror of
https://github.com/TheNetworkGuy/netbox-zabbix-sync.git
synced 2025-07-14 01:41:25 -06:00
Updated script to support config context as source for Zabbix templates. Updated config and readme with instructions
This commit is contained in:
parent
9b58f523c8
commit
6389146342
27
README.md
27
README.md
@ -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
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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,9 +251,39 @@ 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):
|
||||||
"""
|
"""
|
||||||
@ -309,29 +329,41 @@ 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:
|
||||||
f"for host {self.name}.")
|
template_match = False
|
||||||
logger.debug(e)
|
# Go through all templates found in Zabbix
|
||||||
return True
|
for zbx_template in templates:
|
||||||
else:
|
# If the template names match
|
||||||
e = (f"Unable to find template {self.template_name} "
|
if(zbx_template['name'] == nb_template):
|
||||||
f"for host {self.name} in Zabbix.")
|
# Set match variable to true, add template details
|
||||||
logger.warning(e)
|
# to class variable and return debug log
|
||||||
raise SyncInventoryError(e)
|
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}.")
|
||||||
|
logger.debug(e)
|
||||||
|
# Return error should the template not be found in Zabbix
|
||||||
|
if(not template_match):
|
||||||
|
e = (f"Unable to find template {nb_template} "
|
||||||
|
f"for host {self.name} in Zabbix. Skipping host...")
|
||||||
|
logger.warning(e)
|
||||||
|
raise SyncInventoryError(e)
|
||||||
|
|
||||||
def getZabbixGroup(self, groups):
|
def getZabbixGroup(self, groups):
|
||||||
"""
|
"""
|
||||||
@ -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',
|
||||||
@ -522,14 +553,14 @@ class NetworkDevice():
|
|||||||
logger.warning(f"Device {self.name}: hostname OUT of sync. "
|
logger.warning(f"Device {self.name}: hostname OUT of sync. "
|
||||||
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):
|
||||||
@ -652,6 +683,37 @@ class NetworkDevice():
|
|||||||
except pynetbox.RequestError as e:
|
except pynetbox.RequestError as e:
|
||||||
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():
|
||||||
|
Loading…
Reference in New Issue
Block a user