Added Tenant flag, fixed #1. Fixed duplicate 'Hostgroup not found' log message.

This commit is contained in:
Twan Kamans 2021-04-16 21:29:07 +02:00
parent 1f38e165c6
commit b18a0c0897
2 changed files with 523 additions and 467 deletions

View File

@ -1,35 +1,63 @@
A script to sync the Netbox inventory to Zabbix. A script to sync the Netbox device inventory to Zabbix.
Requires pyzabbix and pynetbox ## Requires pyzabbix and pynetbox.
Use the following variables for the environment: ### Script settings
#### Enviroment variables
* ZABBIX_HOST="https://zabbix.local"
* ZABBIX_USER="username" * ZABBIX_HOST="https://zabbix.local"
* ZABBIX_PASS="Password" * ZABBIX_USER="username"
* NETBOX_HOST="https://netbox.local" * ZABBIX_PASS="Password"
* NETBOX_TOKEN="secrettoken" * NETBOX_HOST="https://netbox.local"
* NETBOX_TOKEN="secrettoken"
Logs are generated under sync.log, set the script for debugging / info options etc.
#### Flags
Important: you need to set the hostgroup in Zabbix before a sync can occur. This is in the following format: | Flag | Option | Description |
| ------------ | ------------ | ------------ |
{Site name}/{Manufacturer name}/{Device role name} | -c | cluster | For clustered devices: only add the primary node of a cluster and use the cluster name as hostname. |
| -H | hostgroup | Create non-existing hostgroups in Zabbix. Usefull for a first run to add all required hostgroups. |
Make sure that the Zabbix user has proper permissions to create hosts | -t | tenant | Add the tenant name to the hostgroup format (Tenant/Site/Manufacturer/Role) |
| -v | Verbose | Log with debugging on. |
Use the following custom fields in Netbox to map the Zabbix URL:
* Type: Integer #### Logging
* Name: zabbix_hostid Logs are generated under sync.log, set the script for debugging / info options etc.
* Required: False
* Default: null #### Hostgroups: manual mode
* Object: dcim > device
In case of omitting the -H flag, manual hostgroup creation is required for devices in a new category.
And this field for the Zabbix template
This is in the format:
* Type: Text {Site name}/{Manufacturer name}/{Device role name}
* Name: zabbix_template And with tenants (-t flag):
* Required: False {Tenant name}/{Site name}/{Manufacturer name}/{Device role name}
* Default: null
* Object: dcim > device_type Make sure that the Zabbix user has proper permissions to create hosts.
The hostgroups are in a nested format. This means that proper permissions only need to be applied to the site name hostgroup and cascaded to any child hostgroups.
### Netbox settings
#### Custom fields
Use the following custom fields in Netbox to map the Zabbix URL:
* Type: Integer
* Name: zabbix_hostid
* Required: False
* Default: null
* Object: dcim > device
And this field for the Zabbix template
* Type: Text
* Name: zabbix_template
* Required: False
* Default: null
* Object: dcim > device_type
#### Permissions
Make sure that the user has proper permissions for device read and modify (modify to set the Zabbix HostID custom field) operations.
#### Custom links
To make the user experience easier you could add a custom link that redirects users to the Zabbix latest data.
* Name: zabbix_latestData
* Text: {% if obj.cf["zabbix_hostid"] %}Show host in Zabbix{% endif %}
* URL: {ZABBIX_URL} /zabbix.php?action=latest.view&filter_hostids[]={{ obj.cf["zabbix_hostid"] }}&filter_application=&filter_select=&filter_set=1

View File

@ -1,432 +1,460 @@
#!/usr/bin/python3 #!/usr/bin/python3
"""Netbox to Zabbix sync script.""" """Netbox to Zabbix sync script."""
from os import environ from os import environ
import logging import logging
import argparse import argparse
from pynetbox import api from pynetbox import api
from pyzabbix import ZabbixAPI, ZabbixAPIException from pyzabbix import ZabbixAPI, ZabbixAPIException
# Set logging # Set logging
log_format = logging.Formatter('%(asctime)s - %(name)s - ' log_format = logging.Formatter('%(asctime)s - %(name)s - '
'%(levelname)s - %(message)s') '%(levelname)s - %(message)s')
lgout = logging.StreamHandler() lgout = logging.StreamHandler()
lgout.setFormatter(log_format) lgout.setFormatter(log_format)
lgout.setLevel(logging.DEBUG) lgout.setLevel(logging.DEBUG)
lgfile = logging.FileHandler("sync.log") lgfile = logging.FileHandler("sync.log")
lgfile.setFormatter(log_format) lgfile.setFormatter(log_format)
lgfile.setLevel(logging.DEBUG) lgfile.setLevel(logging.DEBUG)
logger = logging.getLogger("Netbox-Zabbix-sync") logger = logging.getLogger("Netbox-Zabbix-sync")
logger.addHandler(lgout) logger.addHandler(lgout)
logger.addHandler(lgfile) logger.addHandler(lgfile)
logger.setLevel(logging.WARNING) logger.setLevel(logging.WARNING)
template_cf = "zabbix_template" template_cf = "zabbix_template"
device_cf = "zabbix_hostid" device_cf = "zabbix_hostid"
def main(arguments): def main(arguments):
"""Module that runs the sync process.""" """Module that runs the sync process."""
# set environment variables # set environment variables
if(arguments.verbose): if(arguments.verbose):
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
env_vars = ["ZABBIX_HOST", "ZABBIX_USER", "ZABBIX_PASS", env_vars = ["ZABBIX_HOST", "ZABBIX_USER", "ZABBIX_PASS",
"NETBOX_HOST", "NETBOX_TOKEN"] "NETBOX_HOST", "NETBOX_TOKEN"]
for var in env_vars: for var in env_vars:
if var not in environ: if var not in environ:
e = f"Environment variable {var} has not been defined." e = f"Environment variable {var} has not been defined."
logger.error(e) logger.error(e)
raise EnvironmentVarError(e) raise EnvironmentVarError(e)
# Get all virtual environment variables # Get all virtual environment variables
zabbix_host = environ.get("ZABBIX_HOST") zabbix_host = environ.get("ZABBIX_HOST")
zabbix_user = environ.get("ZABBIX_USER") zabbix_user = environ.get("ZABBIX_USER")
zabbix_pass = environ.get("ZABBIX_PASS") zabbix_pass = environ.get("ZABBIX_PASS")
netbox_host = environ.get("NETBOX_HOST") netbox_host = environ.get("NETBOX_HOST")
netbox_token = environ.get("NETBOX_TOKEN") netbox_token = environ.get("NETBOX_TOKEN")
# Set Zabbix API # Set Zabbix API
try: try:
zabbix = ZabbixAPI(zabbix_host) zabbix = ZabbixAPI(zabbix_host)
zabbix.login(zabbix_user, zabbix_pass) zabbix.login(zabbix_user, zabbix_pass)
except ZabbixAPIException as e: except ZabbixAPIException as e:
e = f"Zabbix returned the following error: {str(e)}." e = f"Zabbix returned the following error: {str(e)}."
logger.error(e) logger.error(e)
# Set Netbox API # Set Netbox API
netbox = api(netbox_host, netbox_token, threading=True) netbox = api(netbox_host, netbox_token, threading=True)
# Get all Zabbix and Netbox data # Get all Zabbix and Netbox data
netbox_devices = netbox.dcim.devices.all() netbox_devices = netbox.dcim.devices.all()
zabbix_groups = zabbix.hostgroup.get(output=['name']) zabbix_groups = zabbix.hostgroup.get(output=['name'])
zabbix_templates = zabbix.template.get(output=['name']) zabbix_templates = zabbix.template.get(output=['name'])
# Go through all Netbox devices # Go through all Netbox devices
for nb_device in netbox_devices: for nb_device in netbox_devices:
try: try:
device = NetworkDevice(nb_device, zabbix) device = NetworkDevice(nb_device, zabbix)
# 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):
# Check if device is master or slave # Check if device is master or slave
if(device.promoteMasterDevice()): if(device.promoteMasterDevice()):
e = (f"Device {device.name} is " e = (f"Device {device.name} is "
f"part of cluster and primary.") f"part of cluster and primary.")
logger.info(e) logger.info(e)
else: else:
# Device is secondary in cluster. # Device is secondary in cluster.
# Don't continue with this device. # Don't continue with this device.
e = (f"Device {device.name} is part of cluster " e = (f"Device {device.name} is part of cluster "
f"but not primary. Skipping this host...") f"but not primary. Skipping this host...")
logger.info(e) logger.info(e)
continue continue
# Checks if device is in cleanup state # With -t flag: add Tenant name to hostgroup name.
if(device.status != "Active"): if(arguments.tenant):
# Delete device from Zabbix if(device.tenant):
# and remove hostID from Netbox. device.hg_format.insert(1, device.tenant.name)
device.cleanup() device.setHostgroup()
continue logger.debug(f"Added Tenant {device.tenant.name} to "
if(device.zabbix_id): f"hostgroup format of {device.name}.")
# Device is already present in Zabbix else:
device.ConsistencyCheck(zabbix_groups, zabbix_templates) logger.debug(f"{device.name} is not linked to a tenant. "
else: f"Using HG format '{device.hostgroup}'.")
# Add hostgroup is flag is true # Checks if device is in cleanup state
# and Hostgroup is not present in Zabbix if(device.status != "Active"):
if(not device.getZabbixGroup(zabbix_groups) if(device.zabbix_id):
and arguments.hostgroups): # Delete device from Zabbix
# Create new hostgroup # and remove hostID from Netbox.
device.createZabbixHostgroup() device.cleanup()
zabbix_groups = zabbix.hostgroup.get(output=['name']) logger.info(f"Cleaned up host {device.name}.")
# Add device to Zabbix
device.createInZabbix(zabbix_groups, zabbix_templates) else:
except SyncError: # Device has been added to Netbox
# logger.error(e) # but is not in Activate state
pass logger.info(f"Skipping host {device.name} since its "
f"not in the active state.")
continue
class SyncError(Exception): if(device.zabbix_id):
pass # Device is already present in Zabbix
device.ConsistencyCheck(zabbix_groups, zabbix_templates)
else:
class SyncExternalError(SyncError): # Add hostgroup is flag is true
pass # and Hostgroup is not present in Zabbix
if(not device.hostgroup and arguments.hostgroups):
# Create new hostgroup
class SyncInventoryError(SyncError): device.createZabbixHostgroup()
pass zabbix_groups = zabbix.hostgroup.get(output=['name'])
# Add device to Zabbix
device.createInZabbix(zabbix_groups, zabbix_templates)
class SyncDuplicateError(SyncError): except SyncError:
pass pass
class EnvironmentVarError(SyncError): class SyncError(Exception):
pass pass
class NetworkDevice(): class SyncExternalError(SyncError):
""" pass
Represents Network device.
INPUT: (Netbox device class, ZabbixAPI class)
""" class SyncInventoryError(SyncError):
def __init__(self, nb, zabbix): pass
self.nb = nb
self.id = nb.id
self.name = nb.name class SyncDuplicateError(SyncError):
self.status = nb.status.label pass
self.zabbix = zabbix
self.hg_format = (f"{self.nb.site.name}/"
f"{self.nb.device_type.manufacturer.name}/" class EnvironmentVarError(SyncError):
f"{self.nb.device_role.name}") pass
self._setBasics()
def _setBasics(self): class NetworkDevice():
""" """
Sets basic information like IP address. Represents Network device.
""" INPUT: (Netbox device class, ZabbixAPI class)
# Return error if device does not have primary IP. """
if(self.nb.primary_ip): def __init__(self, nb, zabbix):
self.cidr = self.nb.primary_ip.address self.nb = nb
self.ip = self.cidr.split("/")[0] self.id = nb.id
else: self.name = nb.name
e = f"Device {self.name}: no primary IP." self.status = nb.status.label
logger.warning(e) self.zabbix = zabbix
raise SyncInventoryError(e) self.tenant = nb.tenant
self.hostgroup = None
# Check if device_type has custom field self.hg_format = [self.nb.site.name,
device_type_cf = self.nb.device_type.custom_fields self.nb.device_type.manufacturer.name,
if(template_cf in device_type_cf): self.nb.device_role.name]
self.template_name = device_type_cf[template_cf] self._setBasics()
else: self.setHostgroup()
e = (f"Custom field {template_cf} not "
f"found for {self.nb.device_type.name}.") def _setBasics(self):
logger.warning(e) """
raise SyncInventoryError(e) Sets basic information like IP address.
"""
# Check if device has custom field # Return error if device does not have primary IP.
if(device_cf in self.nb.custom_fields): if(self.nb.primary_ip):
self.zabbix_id = self.nb.custom_fields[device_cf] self.cidr = self.nb.primary_ip.address
else: self.ip = self.cidr.split("/")[0]
e = f"Custom field {template_cf} not found for {self.name}." else:
logger.warning(e) e = f"Device {self.name}: no primary IP."
raise SyncInventoryError(e) logger.warning(e)
raise SyncInventoryError(e)
def isCluster(self):
""" # Check if device_type has custom field
Checks if device is part of cluster. device_type_cf = self.nb.device_type.custom_fields
""" if(template_cf in device_type_cf):
if(self.nb.virtual_chassis): self.template_name = device_type_cf[template_cf]
return True else:
else: e = (f"Custom field {template_cf} not "
return False f"found for {self.nb.device_type.name}.")
logger.warning(e)
def getClusterMaster(self): raise SyncInventoryError(e)
"""
Returns chassis master ID. # Check if device has custom field
""" if(device_cf in self.nb.custom_fields):
if(not self.isCluster()): self.zabbix_id = self.nb.custom_fields[device_cf]
e = (f"Unable to proces {self.name} for cluster calculation: " else:
f"not part of a cluster.") e = f"Custom field {template_cf} not found for {self.name}."
logger.warning(e) logger.warning(e)
raise SyncInventoryError(e) raise SyncInventoryError(e)
else:
return self.nb.virtual_chassis.master.id def setHostgroup(self):
"""Sets hostgroup to a string with hg_format parameters."""
def promoteMasterDevice(self): self.hostgroup = "/".join(self.hg_format)
"""
If device is Primary in cluster, def isCluster(self):
promote device name to the cluster name. """
Returns True if succesfull, returns False if device is secondary. Checks if device is part of cluster.
""" """
masterid = self.getClusterMaster() if(self.nb.virtual_chassis):
if(masterid == self.id): return True
logger.debug(f"Device {self.name} is primary cluster member. " else:
f"Modifying hostname from {self.name} to " + return False
f"{self.nb.virtual_chassis.name}.")
self.name = self.nb.virtual_chassis.name def getClusterMaster(self):
return True """
else: Returns chassis master ID.
logger.debug(f"Device {self.name} is non-primary cluster member.") """
return False if(not self.isCluster()):
e = (f"Unable to proces {self.name} for cluster calculation: "
def getZabbixTemplate(self, templates): f"not part of a cluster.")
""" logger.warning(e)
Returns Zabbix template ID raise SyncInventoryError(e)
INPUT: list of templates else:
OUTPUT: True return self.nb.virtual_chassis.master.id
"""
if(not self.template_name): def promoteMasterDevice(self):
e = (f"Device template '{self.nb.device_type.display_name}' " """
"has no template defined.") If device is Primary in cluster,
logger.info(e) promote device name to the cluster name.
raise SyncInventoryError() Returns True if succesfull, returns False if device is secondary.
for template in templates: """
if(template['name'] == self.template_name): masterid = self.getClusterMaster()
self.template_id = template['templateid'] if(masterid == self.id):
e = (f"Found template ID {str(template['templateid'])} " logger.debug(f"Device {self.name} is primary cluster member. "
f"for host {self.name}.") f"Modifying hostname from {self.name} to " +
logger.debug(e) f"{self.nb.virtual_chassis.name}.")
return True self.name = self.nb.virtual_chassis.name
else: return True
e = (f"Unable to find template {self.template_name} " else:
f"for host {self.name} in Zabbix.") logger.debug(f"Device {self.name} is non-primary cluster member.")
logger.warning(e) return False
raise SyncInventoryError(e)
def getZabbixTemplate(self, templates):
def getZabbixGroup(self, groups): """
""" Returns Zabbix template ID
Returns Zabbix group ID INPUT: list of templates
INPUT: list of hostgroups OUTPUT: True
OUTPUT: True / False """
""" if(not self.template_name):
# Go through all groups e = (f"Device template '{self.nb.device_type.display_name}' "
for group in groups: "has no Zabbix template defined.")
if(group['name'] == self.hg_format): logger.info(e)
self.group_id = group['groupid'] raise SyncInventoryError()
e = (f"Found group ID {str(group['groupid'])} " for template in templates:
f"for host {self.name}.") if(template['name'] == self.template_name):
logger.debug(e) self.template_id = template['templateid']
return True e = (f"Found template ID {str(template['templateid'])} "
else: f"for host {self.name}.")
e = (f"Unable to find group '{self.hg_format}' " logger.debug(e)
f"for host {self.name} in Zabbix.") return True
logger.warning(e) else:
e = (f"Unable to find template {self.template_name} "
def cleanup(self): f"for host {self.name} in Zabbix.")
""" logger.warning(e)
Removes device from external resources. raise SyncInventoryError(e)
Resets custom fields in Netbox.
""" def getZabbixGroup(self, groups):
if(self.zabbix_id): """
try: Returns Zabbix group ID
self.zabbix.host.delete(self.zabbix_id) INPUT: list of hostgroups
self.nb.custom_fields[device_cf] = None OUTPUT: True / False
self.nb.save() """
e = f"Deleted host {self.name} from Zabbix." # Go through all groups
logger.info(e) for group in groups:
except ZabbixAPIException as e: if(group['name'] == self.hostgroup):
e = f"Zabbix returned the following error: {str(e)}." self.group_id = group['groupid']
logger.error(e) e = (f"Found group {group['name']} for host {self.name}.")
raise SyncExternalError(e) #e = (f"Found group ID {str(group['groupid'])} "
# f"for host {self.name}.")
def _zabbixHostnameExists(self): logger.debug(e)
""" return True
Checks if hostname exists in Zabbix. else:
""" e = (f"Unable to find group '{self.hostgroup}' "
host = self.zabbix.host.get(filter={'name': self.name}, output=[]) f"for host {self.name} in Zabbix.")
if(host): logger.warning(e)
return True raise SyncInventoryError(e)
else:
return False def cleanup(self):
"""
def createInZabbix(self, groups, templates, Removes device from external resources.
description="Host added by Netbox sync script."): Resets custom fields in Netbox.
""" """
Creates Zabbix host object with parameters from Netbox object. if(self.zabbix_id):
""" try:
# Check if hostname is already present in Zabbix self.zabbix.host.delete(self.zabbix_id)
if(not self._zabbixHostnameExists()): self.nb.custom_fields[device_cf] = None
# Get group and template ID's for host self.nb.save()
if(not self.getZabbixGroup(groups)): e = f"Deleted host {self.name} from Zabbix."
raise SyncInventoryError() logger.info(e)
self.getZabbixTemplate(templates) except ZabbixAPIException as e:
# Set interface, group and template configuration e = f"Zabbix returned the following error: {str(e)}."
interfaces = [{"type": 2, "main": 1, "useip": 1, logger.error(e)
"ip": self.ip, "dns": "", "port": 161, raise SyncExternalError(e)
"details": {"version": 2, "bulk": 0,
"community": "{$SNMP_COMMUNITY}"}}] def _zabbixHostnameExists(self):
groups = [{"groupid": self.group_id}] """
templates = [{"templateid": self.template_id}] Checks if hostname exists in Zabbix.
# Add host to Zabbix """
try: host = self.zabbix.host.get(filter={'name': self.name}, output=[])
host = self.zabbix.host.create(host=self.name, status=0, if(host):
interfaces=interfaces, return True
groups=groups, else:
templates=templates, return False
description=description)
self.zabbix_id = host["hostids"][0] def createInZabbix(self, groups, templates,
except ZabbixAPIException as e: description="Host added by Netbox sync script."):
e = f"Couldn't add {self.name}, Zabbix returned {str(e)}." """
logger.error(e) Creates Zabbix host object with parameters from Netbox object.
raise SyncExternalError(e) """
# Set Netbox custom field to hostID value. # Check if hostname is already present in Zabbix
self.nb.custom_fields[device_cf] = self.zabbix_id if(not self._zabbixHostnameExists()):
self.nb.save() # Get group and template ID's for host
logger.info(f"Created host {self.name} in Zabbix.") if(not self.getZabbixGroup(groups)):
else: raise SyncInventoryError()
e = f"Unable to add {self.name} to Zabbix: host already present." self.getZabbixTemplate(templates)
logger.warning(e) # Set interface, group and template configuration
interfaces = [{"type": 2, "main": 1, "useip": 1,
def createZabbixHostgroup(self): "ip": self.ip, "dns": "", "port": 161,
""" "details": {"version": 2, "bulk": 0,
Creates Zabbix host group based on hostgroup format. "community": "{$SNMP_COMMUNITY}"}}]
""" groups = [{"groupid": self.group_id}]
try: templates = [{"templateid": self.template_id}]
self.zabbix.hostgroup.create(name=self.hg_format) # Add host to Zabbix
e = f"Added hostgroup '{self.hg_format}'." try:
logger.info(e) host = self.zabbix.host.create(host=self.name, status=0,
except ZabbixAPIException as e: interfaces=interfaces,
e = f"Couldn't add hostgroup, Zabbix returned {str(e)}." groups=groups,
logger.error(e) templates=templates,
raise SyncExternalError(e) description=description)
self.zabbix_id = host["hostids"][0]
def updateZabbixHost(self, **kwargs): except ZabbixAPIException as e:
""" e = f"Couldn't add {self.name}, Zabbix returned {str(e)}."
Updates Zabbix host with given parameters. logger.error(e)
INPUT: Key word arguments for Zabbix host object. raise SyncExternalError(e)
""" # Set Netbox custom field to hostID value.
try: self.nb.custom_fields[device_cf] = self.zabbix_id
self.zabbix.host.update(hostid=self.zabbix_id, **kwargs) self.nb.save()
except ZabbixAPIException as e: logger.info(f"Created host {self.name} in Zabbix.")
e = f"Zabbix returned the following error: {str(e)}." else:
logger.error(e) e = f"Unable to add {self.name} to Zabbix: host already present."
raise SyncExternalError(e) logger.warning(e)
logger.info(f"Updated host {self.name} with data {kwargs}.")
def createZabbixHostgroup(self):
def ConsistencyCheck(self, groups, templates): """
""" Creates Zabbix host group based on hostgroup format.
Checks if Zabbix object is still valid with Netbox parameters. """
""" try:
self.getZabbixGroup(groups) self.zabbix.hostgroup.create(name=self.hostgroup)
self.getZabbixTemplate(templates) e = f"Added hostgroup '{self.hostgroup}'."
host = self.zabbix.host.get(filter={'hostid': self.zabbix_id}, logger.info(e)
selectInterfaces=["interfaceid", "ip"], except ZabbixAPIException as e:
selectGroups=["id"], e = f"Couldn't add hostgroup, Zabbix returned {str(e)}."
selectParentTemplates=["id"]) logger.error(e)
if(len(host) > 1): raise SyncExternalError(e)
e = (f"Got {len(host)} results for Zabbix hosts "
f"with ID {self.zabbix_id} - hostname {self.name}.") def updateZabbixHost(self, **kwargs):
logger.error(e) """
raise SyncInventoryError(e) Updates Zabbix host with given parameters.
elif(len(host) == 0): INPUT: Key word arguments for Zabbix host object.
e = (f"No Zabbix host found for {self.name}. " """
f"This is likely the result of a deleted Zabbix host " try:
f"without zeroing the ID field in Netbox.") self.zabbix.host.update(hostid=self.zabbix_id, **kwargs)
logger.error(e) except ZabbixAPIException as e:
raise SyncInventoryError(e) e = f"Zabbix returned the following error: {str(e)}."
else: logger.error(e)
host = host[0] raise SyncExternalError(e)
logger.info(f"Updated host {self.name} with data {kwargs}.")
if(host["host"] == self.name):
logger.debug(f"Device {self.name}: hostname in-sync.") def ConsistencyCheck(self, groups, templates):
else: """
logger.warning(f"Device {self.name}: hostname OUT of sync. " Checks if Zabbix object is still valid with Netbox parameters.
f"Received value: {host['host']}") """
self.updateZabbixHost(host=self.name) self.getZabbixGroup(groups)
self.getZabbixTemplate(templates)
for template in host["parentTemplates"]: host = self.zabbix.host.get(filter={'hostid': self.zabbix_id},
if(template["templateid"] == self.template_id): selectInterfaces=["interfaceid", "ip"],
logger.debug(f"Device {self.name}: template in-sync.") selectGroups=["id"],
break selectParentTemplates=["id"])
else: if(len(host) > 1):
logger.warning(f"Device {self.name}: template OUT of sync.") e = (f"Got {len(host)} results for Zabbix hosts "
self.updateZabbixHost(templates=self.template_id) f"with ID {self.zabbix_id} - hostname {self.name}.")
logger.error(e)
for group in host["groups"]: raise SyncInventoryError(e)
if(group["groupid"] == self.group_id): elif(len(host) == 0):
logger.debug(f"Device {self.name}: hostgroup in-sync.") e = (f"No Zabbix host found for {self.name}. "
break f"This is likely the result of a deleted Zabbix host "
else: f"without zeroing the ID field in Netbox.")
logger.warning(f"Device {self.name}: hostgroup OUT of sync.") logger.error(e)
self.updateZabbixHost(groups={'groupid': self.group_id}) raise SyncInventoryError(e)
else:
for interface in host["interfaces"]: host = host[0]
if(interface["ip"] == self.ip):
logger.debug(f"Device {self.name}: IP address in-sync.") if(host["host"] == self.name):
break logger.debug(f"Device {self.name}: hostname in-sync.")
else: else:
if(len(host['interfaces']) == 1): logger.warning(f"Device {self.name}: hostname OUT of sync. "
logger.warning(f"Device {self.name}: IP address OUT of sync.") f"Received value: {host['host']}")
int_id = host["interfaces"][0]['interfaceid'] self.updateZabbixHost(host=self.name)
try:
self.zabbix.hostinterface.update(interfaceid=int_id, for template in host["parentTemplates"]:
ip=self.ip) if(template["templateid"] == self.template_id):
e = f"Updated host {self.name} with IP {self.ip}." logger.debug(f"Device {self.name}: template in-sync.")
logger.warning(e) break
except ZabbixAPIException as e: else:
e = f"Zabbix returned the following error: {str(e)}." logger.warning(f"Device {self.name}: template OUT of sync.")
logger.error(e) self.updateZabbixHost(templates=self.template_id)
raise SyncExternalError(e)
else: for group in host["groups"]:
e = (f"Device {self.name} has conflicting IP. Host has total " if(group["groupid"] == self.group_id):
f"of {len(host['interfaces'])} interfaces. Manual " logger.debug(f"Device {self.name}: hostgroup in-sync.")
"interfention required.") break
logger.error(e) else:
SyncInventoryError(e) logger.warning(f"Device {self.name}: hostgroup OUT of sync.")
self.updateZabbixHost(groups={'groupid': self.group_id})
if(__name__ == "__main__"): for interface in host["interfaces"]:
# Arguments parsing if(interface["ip"] == self.ip):
parser = argparse.ArgumentParser( logger.debug(f"Device {self.name}: IP address in-sync.")
description='A script to sync Zabbix with Netbox device data.' break
) else:
parser.add_argument("-v", "--verbose", help="Turn on debugging.", if(len(host['interfaces']) == 1):
action="store_true") logger.warning(f"Device {self.name}: IP address OUT of sync.")
parser.add_argument("-c", "--cluster", action="store_true", int_id = host["interfaces"][0]['interfaceid']
help=("Only add the primary node of a cluster " try:
"to Zabbix. Usefull when a shared virtual IP is " self.zabbix.hostinterface.update(interfaceid=int_id,
"used for the control plane.")) ip=self.ip)
parser.add_argument("-H", "--hostgroups", e = f"Updated host {self.name} with IP {self.ip}."
help="Create Zabbix hostgroups if not present", logger.warning(e)
action="store_true") except ZabbixAPIException as e:
args = parser.parse_args() e = f"Zabbix returned the following error: {str(e)}."
logger.error(e)
main(args) raise SyncExternalError(e)
else:
e = (f"Device {self.name} has conflicting IP. Host has total "
f"of {len(host['interfaces'])} interfaces. Manual "
"interfention required.")
logger.error(e)
SyncInventoryError(e)
if(__name__ == "__main__"):
# Arguments parsing
parser = argparse.ArgumentParser(
description='A script to sync Zabbix with Netbox device data.'
)
parser.add_argument("-v", "--verbose", help="Turn on debugging.",
action="store_true")
parser.add_argument("-c", "--cluster", action="store_true",
help=("Only add the primary node of a cluster "
"to Zabbix. Usefull when a shared virtual IP is "
"used for the control plane."))
parser.add_argument("-H", "--hostgroups",
help="Create Zabbix hostgroups if not present",
action="store_true")
parser.add_argument("-t", "--tenant", action="store_true",
help=("Add Tenant name to the Zabbix "
"hostgroup name scheme."))
args = parser.parse_args()
main(args)