45 Commits
2.0 ... 3.0

Author SHA1 Message Date
Twan K.
acab7dd6d2 Merge pull request #80 from TheNetworkGuy/virtual_machines
Virtual machines
2024-11-18 13:38:35 +01:00
TheNetworkGuy
2177234d7f Fixed some documentation 2024-11-18 13:27:24 +01:00
TheNetworkGuy
3f4d173ac0 Markdown fixed on custom field in README 2024-11-18 12:59:50 +01:00
TheNetworkGuy
0996059c5f Added better documentation for VMs nd fixed typo 2024-11-18 12:58:57 +01:00
TheNetworkGuy
0155c29fcc Fixed Pylint too-many-public-methods error 2024-11-15 14:08:04 +01:00
TheNetworkGuy
5d4ff9c5ed Fixed #79 2024-11-15 14:03:42 +01:00
TheNetworkGuy
204937b784 Fixed variable name typo 2024-11-13 20:32:47 +01:00
TheNetworkGuy
e0827ac428 Fixed long line linter error 2024-11-13 20:00:50 +01:00
TheNetworkGuy
09a6906a63 Added VM filtering 2024-11-13 19:56:09 +01:00
TheNetworkGuy
30545ec0f3 Added hostname filtering based on Cyrillic characters 2024-11-13 19:39:24 +01:00
TheNetworkGuy
56c19d97de Added basic error message when Netbox details are invalid. Fixed logging message for when template context keys are not present. 2024-11-06 15:57:11 +01:00
TheNetworkGuy
ffc2aa1947 Fixed location hostgroup bug 2024-10-31 20:03:09 +01:00
TheNetworkGuy
9417908994 Swapped hostgroup and template calculation since template lookup is a read-only operation and hostgroup lookup could be a read-write operation. 2024-10-31 15:51:33 +01:00
TheNetworkGuy
06f97b132a Changed some logging messages and removed import logging statement from troubleshooting 2024-10-30 21:25:58 +01:00
TheNetworkGuy
20096a215b Added HG and template checks for devices and VM's. Temp disabled inventory mapping for VMs 2024-10-30 21:10:22 +01:00
TheNetworkGuy
f1da1cfb50 Fixed logging formatting for submodules 2024-10-30 20:57:20 +01:00
TheNetworkGuy
5093823287 Added some logging and fixed role assignment for VM's 2024-10-30 20:52:28 +01:00
TheNetworkGuy
c1504987f1 Fixed bug for Tenant hostgroup generation 2024-10-30 18:21:42 +01:00
TheNetworkGuy
d598a9739a Fixed whitespace 2024-10-30 13:51:30 +01:00
TheNetworkGuy
7bf72de0f9 Fixed bug where a single host exception would stop the sync. 2024-10-30 13:50:20 +01:00
TheNetworkGuy
66f24e6891 Fixed several bugs, set default interface for VM to agent, fixed several linter errors. 2024-10-30 12:23:15 +01:00
TheNetworkGuy
bff34a8e38 Renamed VM module for python naming convention. Fixed 2 VM hostgroup bugs. Fixed some whitespace in the VM module. 2024-10-30 10:06:29 +01:00
TheNetworkGuy
886ef2a172 Replaced old device role field with new one 2024-10-25 18:56:58 +02:00
TheNetworkGuy
9c07d7dbc4 Updated Readme with VM info 2024-10-25 18:54:17 +02:00
TheNetworkGuy
9f29d2b27b Added basic VM support 2024-10-25 18:46:20 +02:00
TheNetworkGuy
e827953d8d Fixed extra space in config.py.example 2024-10-25 16:50:56 +02:00
TheNetworkGuy
053028b283 Splitted hostgroup generation logic into its seperate module. Changed hostgroup "dev_role" to "role" for VM role prepration. Started work on basic VM class. 2024-10-25 16:02:08 +02:00
TheNetworkGuy
2e867d1129 Added .venv to gitignore for developing. 2024-10-25 15:53:33 +02:00
TheNetworkGuy
a0ea21d731 Fixed bug in which some instances a nested hostgroup was not created 2024-07-25 16:23:59 +02:00
TheNetworkGuy
70a5c3e384 Implemented #73 2024-07-25 15:47:09 +02:00
TheNetworkGuy
91796395ef Fixed readme documentation with inventory settings 2024-07-24 15:02:31 +02:00
TheNetworkGuy
610a73c061 Merge branch 'main' of https://github.com/TheNetworkGuy/netbox-zabbix-sync 2024-07-24 14:31:33 +02:00
TheNetworkGuy
4de022496e Fixed invalid mapper log message even when not using the inventory mapping function. 2024-07-24 14:31:29 +02:00
Twan K.
0603d8c244 Merge pull request #71 from q1x/allow-proxy-null-value
Allow proxy null value
2024-07-24 14:26:21 +02:00
TheNetworkGuy
2b92f8da9b Fixed #72 2024-07-24 14:25:22 +02:00
TheNetworkGuy
d1ec1114ac Fixes #70 2024-07-24 13:14:12 +02:00
Raymond Kuiper
acad07eed4 Merge pull request #2 from q1x/main
Allow for NetBox Config Context to have a null value for proxy.
2024-07-24 11:07:29 +02:00
Raymond Kuiper
da4fec6bf1 Removed trailing whitespace 2024-07-24 11:06:50 +02:00
Raymond Kuiper
07049ea6d8 Adjustment from linting suggestion 2024-07-24 11:05:35 +02:00
Raymond Kuiper
2094799a51 Allow for NetBox Config Context to have a null value for proxy. 2024-07-24 10:58:22 +02:00
Twan K.
c0c52f973e Merge pull request #69 from doteater/patch-1
Update README.md
2024-07-24 09:59:41 +02:00
doteater
39b63aa420 Update README.md
reference to "latest" container label updated to "main". Also case issue with repo name.
2024-07-10 14:26:00 -07:00
Twan K
017b5623f5 Merge pull request #68 from q1x/main
corrected minor typo
2024-07-04 14:42:41 +02:00
Raymond Kuiper
9be09bca10 corrected minor typo 2024-07-04 13:26:58 +02:00
TheNetworkGuy
23997f9423 Properly styled code as suggested in #61. Also implemented #63 2024-06-12 19:02:44 +02:00
9 changed files with 599 additions and 215 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
*.log
.venv
config.py
# Byte-compiled / optimized / DLL files
__pycache__/

View File

@@ -19,7 +19,7 @@ docker run -d -t -i -e ZABBIX_HOST='https://zabbix.local' \
-e ZABBIX_TOKEN='othersecrettoken' \
-e NETBOX_HOST='https://netbox.local' \
-e NETBOX_TOKEN='secrettoken' \
--name netbox-zabbix-sync ghcr.io/TheNetworkGuy/netbox-zabbix-sync:latest
--name netbox-zabbix-sync ghcr.io/thenetworkguy/netbox-zabbix-sync:main
```
This should run a one-time sync, you can check the sync with `docker logs netbox-zabbix-sync`.
@@ -87,37 +87,59 @@ You can make the `zabbix_hostid` field hidden or read-only to prevent human inte
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.
## Virtual Machine (VM) Syncing
In order to use VM syncing, make sure that the `zabbix_id` custom field is also present on Virtual machine objects in Netbox.
Use the `config.py` file and set the `sync_vms` variable to `True`.
You can set the `vm_hostgroup_format` variable to a customizable value for VM hostgroups. The default is `cluster_type/cluster/role`.
To enable filtering for VM's, check the `nb_vm_filter` variable out. It works the same as with the device filter (see documentation under "Hostgroup layout"). Note that not all filtering capabilities and properties of devices are applicable to VM's and vice-versa. Check the Netbox API documentation to see which filtering options are available for each object type.
## Config file
### Hostgroup
Setting the `create_hostgroups` variable to `False` requires manual hostgroup creation for devices in a new category.
Setting the `create_hostgroups` variable to `False` requires manual hostgroup creation for devices in a new category. I would recommend setting this variable to `True` since leaving it on `False` results in a lot of manual work.
The format can be set with the `hostgroup_format` variable.
The format can be set with the `hostgroup_format` variable for devices and `vm_hostgroup_format` for devices.
Any nested parent hostgroups will also be created automatically. For instance the region `Berlin` with parent region `Germany` will create the hostgroup `Germany/Berlin`.
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.
#### Layout
The default hostgroup layout is "site/manufacturer/device_role".
**Variables**
You can change this behaviour with the hostgroup_format variable. The following values can be used:
**Both devices and virtual machines**
| name | description |
| ------------ | ------------ |
|dev_location|The device location name|
|dev_role|The device role name|
|manufacturer|Manufacturer name|
|region|The region name of the device|
|role|Role name of a device or VM|
|region|The region name|
|site|Site name|
|site_group|Site group name|
|tenant|Tenant name|
|tenant_group|Tenant group name|
|platform|Software platform of a device or VM|
|custom fields|See the section "Layout -> Custom Fields" to use custom fields as hostgroup variable|
**Only for devices**
| name | description |
| ------------ | ------------ |
|location|The device location name|
|manufacturer|Device manufacturer name|
**Only for VMs**
| name | description |
| ------------ | ------------ |
|cluster|VM cluster name|
|cluster_type|VM cluster type|
You can specify the value like so, sperated by a "/":
You can specify the value sperated by a "/" like so:
```
hostgroup_format = "tenant/site/dev_location/dev_role"
hostgroup_format = "tenant/site/dev_location/role"
```
**Group traversal**
@@ -127,11 +149,18 @@ However, by setting `traverse_region` to `True` in `config.py` the script will r
**Custom fields**
You can also use the value of custom fields under the device object.
You can use the value of custom fields for hostgroup generation. This allows more freedom and even allows a full static mapping instead of a dynamic rendered hostgroup name.
This allows more freedom and even allows a full static mapping instead of a dynamic rendered hostgroup name.
For instance a custom field with the name `mycustomfieldname` and type string has the following values for 2 devices:
```
hostgroup_format = "site/mycustomfieldname"
Device A has the value Train for custom field mycustomfieldname.
Device B has the value Bus for custom field mycustomfieldname.
Both devices are located in the site Paris.
```
With the hostgroup format `site/mycustomfieldname` the following hostgroups will be generated:
```
Device A: Paris/Train
Device B: Paris/Bus
```
**Empty variables or hostgroups**
@@ -172,16 +201,17 @@ You can modify this behaviour by changing the following list variables in the sc
### Zabbix Inventory
This script allows you to enable the inventory on managed Zabbix hosts and sync NetBox device properties to the specified inventory fields.
To enable, set `inventory_sync` to `True`.
Set `inventory_automatic` to `False` to use manual inventory, or `True` for automatic.
See [Zabix Manual](https://www.zabbix.com/documentation/current/en/manual/config/hosts/inventory#building-inventory) for more information about the modes.
To map Netbox information to Netbox inventory fields, set `inventory_sync` to `True`.
You can set the inventory mode to "disabled", "manual" or "automatic" with the `inventory_mode` variable.
See [Zabbix Manual](https://www.zabbix.com/documentation/current/en/manual/config/hosts/inventory#building-inventory) for more information about the modes.
Use the `inventory_map` variable to map which NetBox properties are used in which Zabbix Inventory fields.
For nested properties, you can use the '/' seperator.
For example, the following map will assign the custom field 'mycustomfield' to the 'alias' Zabbix inventory field:
```
inventory_sync = True
inventory_automatic = True
inventory_mode = "manual"
inventory_map = { "custom_fields/mycustomfield/name": "alias"}
```
See `config.py.example` for an extensive example map.

View File

@@ -3,7 +3,7 @@
# coming from config context instead of a custom field.
templates_config_context = False
# Set to true to give config context templates a
# Set to true to give config context templates a
# higher priority then custom field templates
templates_config_context_overrule = False
@@ -21,6 +21,14 @@ create_hostgroups = True
## Create journal entries
create_journal = False
## Virtual machine sync
# Set sync_vms to True in order to use this new feature
# Use the hostgroup vm_hostgroup_format mapper for specific
# hostgroup atributes of VM's such as cluster_type and cluster
sync_vms = False
# Check the README documentation for values to use in the VM hostgroup format.
vm_hostgroup_format = "cluster_type/cluster/role"
## Proxy Sync
# Set to true to enable removal of proxy's under hosts. Use with caution and make sure that you specified
# all the required proxy's in the device config context before enabeling this option.
@@ -32,7 +40,7 @@ zabbix_device_removal = ["Decommissioning", "Inventory"]
zabbix_device_disable = ["Offline", "Planned", "Staged", "Failed"]
## Hostgroup mapping
# Available choices: dev_location, dev_role, manufacturer, region, site, site_group, tenant, tenant_group
# See the README documentation for available options
# You can also use CF (custom field) names under the device. The CF content will be used for the hostgroup generation.
#
# When using region in the group name, the default behaviour is to use name of the directly assigned region.
@@ -40,8 +48,8 @@ zabbix_device_disable = ["Offline", "Planned", "Staged", "Failed"]
#
# 'Global/Europe/Netherlands/Amsterdam' instead of just 'Amsterdam'.
#
# traverse_site_groups controls the same behaviour for any assigned site_groups.
hostgroup_format = "site/manufacturer/dev_role"
# traverse_site_groups controls the same behaviour for any assigned site_groups.
hostgroup_format = "site/manufacturer/role"
traverse_regions = False
traverse_site_groups = False
@@ -56,15 +64,18 @@ traverse_site_groups = False
# Default device filter, only get devices which have a name in Netbox:
nb_device_filter = {"name__n": "null"}
# Default filter for VMs
nb_vm_filter = {"name__n": "null"}
## Inventory
# See https://www.zabbix.com/documentation/current/en/manual/config/hosts/inventory#building-inventory
# Choice between disabled, manual or automatic.
# Make sure to select at least manual or automatic in use with the inventory_sync function.
inventory_mode = "disabled"
# To allow syncing of NetBox device properties, set inventory_sync to True
inventory_sync = False
# Set inventory_automatic to False to use manual inventory, True for automatic
# See https://www.zabbix.com/documentation/current/en/manual/config/hosts/inventory#building-inventory
inventory_automatic = True
# inventory_map is used to map NetBox properties to Zabbix Inventory fields.
# For nested properties, you can use the '/' seperator.
# For example, the following map will assign the custom field 'mycustomfield' to the 'alias' Zabbix inventory field:

0
modules/__init__.py Normal file
View File

View File

@@ -4,19 +4,20 @@
Device specific handeling for Netbox to Zabbix
"""
from os import sys
from re import search
from logging import getLogger
from zabbix_utils import APIRequestError
from modules.exceptions import (SyncInventoryError, TemplateError, SyncExternalError,
InterfaceConfigError, JournalError)
from modules.interface import ZabbixInterface
from modules.tools import build_path
from modules.hostgroups import Hostgroup
try:
from config import (
template_cf, device_cf,
traverse_site_groups,
traverse_regions,
inventory_sync,
inventory_automatic,
inventory_mode,
inventory_map
)
except ModuleNotFoundError:
@@ -24,8 +25,8 @@ except ModuleNotFoundError:
"Please create the file or rename the config.py.example file to config.py.")
sys.exit(0)
class NetworkDevice():
# pylint: disable=too-many-instance-attributes, too-many-arguments
class PhysicalDevice():
# pylint: disable=too-many-instance-attributes, too-many-arguments, too-many-positional-arguments
"""
Represents Network device.
INPUT: (Netbox device class, ZabbixAPI class, journal flag, NB journal class)
@@ -55,6 +56,12 @@ class NetworkDevice():
self.logger = logger if logger else getLogger(__name__)
self._setBasics()
def __repr__(self):
return self.name
def __str__(self):
return self.__repr__()
def _setBasics(self):
"""
Sets basic information like IP address.
@@ -64,7 +71,7 @@ class NetworkDevice():
self.cidr = self.nb.primary_ip.address
self.ip = self.cidr.split("/")[0]
else:
e = f"Device {self.name}: no primary IP."
e = f"Host {self.name}: no primary IP."
self.logger.info(e)
raise SyncInventoryError(e)
@@ -72,18 +79,19 @@ class NetworkDevice():
if device_cf in self.nb.custom_fields:
self.zabbix_id = self.nb.custom_fields[device_cf]
else:
e = f"Custom field {device_cf} not found for {self.name}."
e = f"Host {self.name}: Custom field {device_cf} not present"
self.logger.warning(e)
raise SyncInventoryError(e)
# Validate hostname format.
odd_character_list = ["ä", "ö", "ü", "Ä", "Ö", "Ü", "ß"]
self.use_visible_name = False
if any(letter in self.name for letter in odd_character_list):
if (any(letter in self.name for letter in odd_character_list) or
bool(search('[\u0400-\u04FF]', self.name))):
self.name = f"NETBOX_ID{self.id}"
self.visible_name = self.nb.name
self.use_visible_name = True
self.logger.info(f"Device {self.visible_name} contains special characters. "
self.logger.info(f"Host {self.visible_name} contains special characters. "
f"Using {self.name} as name for the Netbox object "
f"and using {self.visible_name} as visible name in Zabbix.")
else:
@@ -91,58 +99,12 @@ class NetworkDevice():
def set_hostgroup(self, hg_format, nb_site_groups, nb_regions):
"""Set the hostgroup for this device"""
# Get all variables from the NB data
dev_location = str(self.nb.location) if self.nb.location else None
# Check the Netbox version. Use backwards compatibility for versions 2 and 3.
if self.nb_api_version.startswith(("2", "3")):
dev_role = self.nb.device_role.name
else:
dev_role = self.nb.role.name
manufacturer = self.nb.device_type.manufacturer.name
region = str(self.nb.site.region) if self.nb.site.region else None
site = self.nb.site.name
site_group = str(self.nb.site.group) if self.nb.site.group else None
tenant = str(self.tenant) if self.tenant else None
tenant_group = str(self.tenant.group) if tenant else None
# Set mapper for string -> variable
hostgroup_vars = {"dev_location": dev_location, "dev_role": dev_role,
"manufacturer": manufacturer, "region": region,
"site": site, "site_group": site_group,
"tenant": tenant, "tenant_group": tenant_group}
# Generate list based off string input format
hg_items = hg_format.split("/")
hostgroup = ""
# Go through all hostgroup items
for item in hg_items:
# Check if the variable (such as Tenant) is empty.
if not hostgroup_vars[item]:
continue
# Check if the item is a custom field name
if item not in hostgroup_vars:
cf_value = self.nb.custom_fields[item] if item in self.nb.custom_fields else None
if cf_value:
# If there is a cf match, add the value of this cf to the hostgroup
hostgroup += cf_value + "/"
# Should there not be a match, this means that
# the variable is invalid. Skip regardless.
continue
# Add value of predefined variable to hostgroup format
if item == "site_group" and nb_site_groups and traverse_site_groups:
group_path = build_path(site_group, nb_site_groups)
hostgroup += "/".join(group_path) + "/"
elif item == "region" and nb_regions and traverse_regions:
region_path = build_path(region, nb_regions)
hostgroup += "/".join(region_path) + "/"
else:
hostgroup += hostgroup_vars[item] + "/"
# If the final hostgroup variable is empty
if not hostgroup:
e = (f"{self.name} has no reliable hostgroup. This is"
"most likely due to the use of custom fields that are empty.")
self.logger.error(e)
raise SyncInventoryError(e)
# Remove final inserted "/" and set hostgroup to class var
self.hostgroup = hostgroup.rstrip("/")
# Create new Hostgroup instance
hg = Hostgroup("dev", self.nb, self.nb_api_version)
# Set Hostgroup nesting options
hg.set_nesting(traverse_site_groups, traverse_regions, nb_site_groups, nb_regions)
# Generate hostgroup based on hostgroup format
self.hostgroup = hg.generate(hg_format)
def set_template(self, prefer_config_context, overrule_custom):
""" Set Template """
@@ -185,23 +147,34 @@ class NetworkDevice():
def get_templates_context(self):
""" Get Zabbix templates from the device context """
if "zabbix" not in self.config_context:
e = ("Key 'zabbix' not found in config "
f"context for template host {self.name}")
e = (f"Host {self.name}: Key 'zabbix' not found in config "
"context for template lookup")
raise TemplateError(e)
if "templates" not in self.config_context["zabbix"]:
e = ("Key 'templates' not found in config "
f"context 'zabbix' for template host {self.name}")
e = (f"Host {self.name}: Key 'templates' not found in config "
"context 'zabbix' for template lookup")
raise TemplateError(e)
return self.config_context["zabbix"]["templates"]
def set_inventory(self, nbdevice):
""" Set host inventory """
self.inventory_mode = -1
# Set inventory mode. Default is disabled (see class init function).
if inventory_mode == "disabled":
if inventory_sync:
self.logger.error(f"Host {self.name}: Unable to map Netbox inventory to Zabbix. "
"Inventory sync is enabled in config but inventory mode is disabled.")
return True
if inventory_mode == "manual":
self.inventory_mode = 0
elif inventory_mode == "automatic":
self.inventory_mode = 1
else:
self.logger.error(f"Host {self.name}: Specified value for inventory mode in"
f" config is not valid. Got value {inventory_mode}")
return False
self.inventory = {}
if inventory_sync:
# Set inventory mode to automatic or manual
self.inventory_mode = 1 if inventory_automatic else 0
if inventory_sync and self.inventory_mode in [0,1]:
self.logger.debug(f"Host {self.name}: Starting inventory mapper")
# Let's build an inventory dict for each property in the inventory_map
for nb_inv_field, zbx_inv_field in inventory_map.items():
field_list = nb_inv_field.split("/") # convert str to list based on delimiter
@@ -218,13 +191,15 @@ class NetworkDevice():
self.inventory[zbx_inv_field] = str(value)
elif not value:
# empty value should just be an empty string for API compatibility
self.logger.debug(f"Inventory lookup for '{nb_inv_field}'"
" returned an empty value")
self.logger.debug(f"Host {self.name}: Netbox inventory lookup for "
f"'{nb_inv_field}' returned an empty value")
self.inventory[zbx_inv_field] = ""
else:
# Value is not a string or numeral, probably not what the user expected.
self.logger.error(f"Inventory lookup for '{nb_inv_field}' returned"
" an unexpected type: it will be skipped.")
self.logger.error(f"Host {self.name}: Inventory lookup for '{nb_inv_field}'"
" returned an unexpected type: it will be skipped.")
self.logger.debug(f"Host {self.name}: Inventory mapping complete. "
f"Mapped {len(list(filter(None, self.inventory.values())))} field(s)")
return True
def isCluster(self):
@@ -257,12 +232,12 @@ class NetworkDevice():
"""
masterid = self.getClusterMaster()
if masterid == self.id:
self.logger.debug(f"Device {self.name} is primary cluster member. "
self.logger.debug(f"Host {self.name} is primary cluster member. "
f"Modifying hostname from {self.name} to " +
f"{self.nb.virtual_chassis.name}.")
self.name = self.nb.virtual_chassis.name
return True
self.logger.debug(f"Device {self.name} is non-primary cluster member.")
self.logger.debug(f"Host {self.name} is non-primary cluster member.")
return False
def zbxTemplatePrepper(self, templates):
@@ -273,7 +248,7 @@ class NetworkDevice():
"""
# Check if there are templates defined
if not self.zbx_template_names:
e = f"No templates found for device {self.name}"
e = f"Host {self.name}: No templates found"
self.logger.info(e)
raise SyncInventoryError()
# Set variable to empty list
@@ -290,8 +265,7 @@ class NetworkDevice():
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}.")
e = f"Host {self.name}: found template {zbx_template['name']}"
self.logger.debug(e)
# Return error should the template not be found in Zabbix
if not template_match:
@@ -300,9 +274,9 @@ class NetworkDevice():
self.logger.warning(e)
raise SyncInventoryError(e)
def getZabbixGroup(self, groups):
def setZabbixGroupID(self, groups):
"""
Returns Zabbix group ID
Sets Zabbix group ID as instance variable
INPUT: list of hostgroups
OUTPUT: True / False
"""
@@ -310,7 +284,7 @@ class NetworkDevice():
for group in groups:
if group['name'] == self.hostgroup:
self.group_id = group['groupid']
e = f"Found group {group['name']} for host {self.name}."
e = f"Host {self.name}: matched group {group['name']}"
self.logger.debug(e)
return True
return False
@@ -322,16 +296,28 @@ class NetworkDevice():
"""
if self.zabbix_id:
try:
self.zabbix.host.delete(self.zabbix_id)
self.nb.custom_fields[device_cf] = None
self.nb.save()
e = f"Deleted host {self.name} from Zabbix."
# Check if the Zabbix host exists in Zabbix
zbx_host = bool(self.zabbix.host.get(filter={'hostid': self.zabbix_id},
output=[]))
e = (f"Host {self.name}: was already deleted from Zabbix."
" Removed link in Netbox.")
if zbx_host:
# Delete host should it exists
self.zabbix.host.delete(self.zabbix_id)
e = f"Host {self.name}: Deleted host from Zabbix."
self._zeroize_cf()
self.logger.info(e)
self.create_journal_entry("warning", "Deleted host from Zabbix")
except APIRequestError as e:
e = f"Zabbix returned the following error: {str(e)}."
self.logger.error(e)
raise SyncExternalError(e) from e
message = f"Zabbix returned the following error: {str(e)}."
self.logger.error(message)
raise SyncExternalError(message) from e
def _zeroize_cf(self):
"""Sets the hostID custom field in Netbox to zero,
effectively destroying the link"""
self.nb.custom_fields[device_cf] = None
self.nb.save()
def _zabbixHostnameExists(self):
"""
@@ -360,23 +346,26 @@ class NetworkDevice():
if interface.interface["type"] == 2:
interface.set_snmp()
else:
interface.set_default()
interface.set_default_snmp()
return [interface.interface]
except InterfaceConfigError as e:
e = f"{self.name}: {e}"
self.logger.warning(e)
raise SyncInventoryError(e) from e
message = f"{self.name}: {e}"
self.logger.warning(message)
raise SyncInventoryError(message) from e
def setProxy(self, proxy_list):
"""
Sets proxy or proxy group if this
value has been defined in config context
input: List of all proxies and proxy gorups in standardized format
input: List of all proxies and proxy groups in standardized format
"""
# check if the key Zabbix is defined in the config context
if not "zabbix" in self.nb.config_context:
return False
if ("proxy" in self.nb.config_context["zabbix"] and
not self.nb.config_context["zabbix"]["proxy"]):
return False
# Proxy group takes priority over a proxy due
# to it being HA and therefore being more reliable
# Includes proxy group fix since Zabbix <= 6 should ignore this
@@ -395,11 +384,11 @@ class NetworkDevice():
continue
# If the proxy name matches
if proxy["name"] == proxy_name:
self.logger.debug(f"Host {self.name}: using {proxy['type']}"
f" {proxy_name}")
self.zbxproxy = proxy
return True
else:
self.logger.warning(f"Device {self.name}: unable to find proxy {proxy_name}")
break
self.logger.warning(f"Host {self.name}: unable to find proxy {proxy_name}")
return False
def createInZabbix(self, groups, templates, proxies,
@@ -409,8 +398,8 @@ class NetworkDevice():
"""
# Check if hostname is already present in Zabbix
if not self._zabbixHostnameExists():
# Get group and template ID's for host
if not self.getZabbixGroup(groups):
# Set group and template ID's for host
if not self.setZabbixGroupID(groups):
e = (f"Unable to find group '{self.hostgroup}' "
f"for host {self.name} in Zabbix.")
self.logger.warning(e)
@@ -450,33 +439,56 @@ class NetworkDevice():
host = self.zabbix.host.create(**create_data)
self.zabbix_id = host["hostids"][0]
except APIRequestError as e:
e = f"Couldn't add {self.name}, Zabbix returned {str(e)}."
e = f"Host {self.name}: Couldn't create. Zabbix returned {str(e)}."
self.logger.error(e)
raise SyncExternalError(e) from e
raise SyncExternalError(e) from None
# Set Netbox custom field to hostID value.
self.nb.custom_fields[device_cf] = int(self.zabbix_id)
self.nb.save()
msg = f"Created host {self.name} in Zabbix."
msg = f"Host {self.name}: Created host in Zabbix."
self.logger.info(msg)
self.create_journal_entry("success", msg)
else:
e = f"Unable to add {self.name} to Zabbix: host already present."
e = f"Host {self.name}: Unable to add to Zabbix. Host already present."
self.logger.warning(e)
def createZabbixHostgroup(self):
def createZabbixHostgroup(self, hostgroups):
"""
Creates Zabbix host group based on hostgroup format.
Creates multiple when using a nested format.
"""
try:
groupid = self.zabbix.hostgroup.create(name=self.hostgroup)
e = f"Added hostgroup '{self.hostgroup}'."
self.logger.info(e)
data = {'groupid': groupid["groupids"][0], 'name': self.hostgroup}
return data
except APIRequestError as e:
e = f"Couldn't add hostgroup, Zabbix returned {str(e)}."
self.logger.error(e)
raise SyncExternalError(e) from e
final_data = []
# Check if the hostgroup is in a nested format and check each parent
for pos in range(len(self.hostgroup.split('/'))):
zabbix_hg = self.hostgroup.rsplit('/', pos)[0]
if self.lookupZabbixHostgroup(hostgroups, zabbix_hg):
# Hostgroup already exists
continue
# Create new group
try:
# API call to Zabbix
groupid = self.zabbix.hostgroup.create(name=zabbix_hg)
e = f"Hostgroup '{zabbix_hg}': created in Zabbix."
self.logger.info(e)
# Add group to final data
final_data.append({'groupid': groupid["groupids"][0], 'name': zabbix_hg})
except APIRequestError as e:
msg = f"Hostgroup '{zabbix_hg}': unable to create. Zabbix returned {str(e)}."
self.logger.error(msg)
raise SyncExternalError(msg) from e
return final_data
def lookupZabbixHostgroup(self, group_list, lookup_group):
"""
Function to check if a hostgroup
exists in a list of Zabbix hostgroups
INPUT: Group list and group lookup
OUTPUT: Boolean
"""
for group in group_list:
if group["name"] == lookup_group:
return True
return False
def updateZabbixHost(self, **kwargs):
"""
@@ -486,7 +498,8 @@ class NetworkDevice():
try:
self.zabbix.host.update(hostid=self.zabbix_id, **kwargs)
except APIRequestError as e:
e = f"Zabbix returned the following error: {str(e)}."
e = (f"Host {self.name}: Unable to update. "
f"Zabbix returned the following error: {str(e)}.")
self.logger.error(e)
raise SyncExternalError(e) from None
self.logger.info(f"Updated host {self.name} with data {kwargs}.")
@@ -497,20 +510,26 @@ class NetworkDevice():
"""
Checks if Zabbix object is still valid with Netbox parameters.
"""
# Check if the hostgroup exists.
# If not, create the hostgroup and try finding the group again
if not self.getZabbixGroup(groups):
# If group is found or if the hostgroup is nested
if not self.setZabbixGroupID(groups) or len(self.hostgroup.split('/')) > 1:
if create_hostgroups:
new_group = self.createZabbixHostgroup()
groups.append(new_group)
self.getZabbixGroup(groups)
else:
e = (f"Device {self.name}: different hostgroup is required but "
"unable to create hostgroup without generation permission.")
self.logger.warning(e)
raise SyncInventoryError(e)
# Script is allowed to create a new hostgroup
new_groups = self.createZabbixHostgroup(groups)
for group in new_groups:
# Add all new groups to the list of groups
groups.append(group)
# check if the initial group was not already found (and this is a nested folder check)
if not self.group_id:
# Function returns true / false but also sets GroupID
if not self.setZabbixGroupID(groups) and not create_hostgroups:
e = (f"Host {self.name}: different hostgroup is required but "
"unable to create hostgroup without generation permission.")
self.logger.warning(e)
raise SyncInventoryError(e)
# Prepare templates and proxy config
self.zbxTemplatePrepper(templates)
self.setProxy(proxies)
# Get host object from Zabbix
host = self.zabbix.host.get(filter={'hostid': self.zabbix_id},
selectInterfaces=['type', 'ip',
'port', 'details',
@@ -524,61 +543,65 @@ class NetworkDevice():
self.logger.error(e)
raise SyncInventoryError(e)
if len(host) == 0:
e = (f"No Zabbix host found for {self.name}. "
e = (f"Host {self.name}: No Zabbix host found. "
f"This is likely the result of a deleted Zabbix host "
f"without zeroing the ID field in Netbox.")
self.logger.error(e)
raise SyncInventoryError(e)
host = host[0]
if host["host"] == self.name:
self.logger.debug(f"Device {self.name}: hostname in-sync.")
self.logger.debug(f"Host {self.name}: hostname in-sync.")
else:
self.logger.warning(f"Device {self.name}: hostname OUT of sync. "
self.logger.warning(f"Host {self.name}: hostname OUT of sync. "
f"Received value: {host['host']}")
self.updateZabbixHost(host=self.name)
# Execute check depending on wether the name is special or not
if self.use_visible_name:
if host["name"] == self.visible_name:
self.logger.debug(f"Device {self.name}: visible name in-sync.")
self.logger.debug(f"Host {self.name}: visible name in-sync.")
else:
self.logger.warning(f"Device {self.name}: visible name OUT of sync."
self.logger.warning(f"Host {self.name}: visible name OUT of sync."
f" Received value: {host['name']}")
self.updateZabbixHost(name=self.visible_name)
# Check if the templates are in-sync
if not self.zbx_template_comparer(host["parentTemplates"]):
self.logger.warning(f"Device {self.name}: template(s) OUT of sync.")
self.logger.warning(f"Host {self.name}: template(s) OUT of sync.")
# Prepare Templates for API parsing
templateids = []
for template in self.zbx_templates:
templateids.append({'templateid': template['templateid']})
# Update Zabbix with NB templates and clear any old / lost templates
self.updateZabbixHost(templates_clear=host["parentTemplates"],
templates=self.zbx_templates)
templates=templateids)
else:
self.logger.debug(f"Device {self.name}: template(s) in-sync.")
self.logger.debug(f"Host {self.name}: template(s) in-sync.")
for group in host["groups"]:
if group["groupid"] == self.group_id:
self.logger.debug(f"Device {self.name}: hostgroup in-sync.")
self.logger.debug(f"Host {self.name}: hostgroup in-sync.")
break
else:
self.logger.warning(f"Device {self.name}: hostgroup OUT of sync.")
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"Device {self.name}: status in-sync.")
self.logger.debug(f"Host {self.name}: status in-sync.")
else:
self.logger.warning(f"Device {self.name}: status OUT of sync.")
self.logger.warning(f"Host {self.name}: status OUT of sync.")
self.updateZabbixHost(status=str(self.zabbix_state))
# Check if a proxy has been defined
if self.zbxproxy:
# Check if proxy or proxy group is defined
if (self.zbxproxy["idtype"] in host and
host[self.zbxproxy["idtype"]] == self.zbxproxy["id"]):
self.logger.debug(f"Device {self.name}: proxy in-sync.")
self.logger.debug(f"Host {self.name}: proxy in-sync.")
# Backwards compatibility for Zabbix <= 6
elif "proxy_hostid" in host and host["proxy_hostid"] == self.zbxproxy["id"]:
self.logger.debug(f"Device {self.name}: proxy in-sync.")
self.logger.debug(f"Host {self.name}: proxy in-sync.")
# Proxy does not match, update Zabbix
else:
self.logger.warning(f"Device {self.name}: proxy OUT of sync.")
self.logger.warning(f"Host {self.name}: proxy OUT of sync.")
# Zabbix <= 6 patch
if not str(self.zabbix.version).startswith('7'):
self.updateZabbixHost(proxy_hostid=self.zbxproxy['id'])
@@ -598,7 +621,7 @@ class NetworkDevice():
proxy_set = True
if proxy_power and proxy_set:
# Zabbix <= 6 fix
self.logger.warning(f"Device {self.name}: no proxy is configured in Netbox "
self.logger.warning(f"Host {self.name}: no proxy is configured in Netbox "
"but is configured in Zabbix. Removing proxy config in Zabbix")
if "proxy_hostid" in host and bool(host["proxy_hostid"]):
self.updateZabbixHost(proxy_hostid=0)
@@ -611,26 +634,24 @@ class NetworkDevice():
# Checks if a proxy has been defined in Zabbix and if proxy_power config has been set
if proxy_set and not proxy_power:
# Display error message
self.logger.error(f"Device {self.name} is configured "
self.logger.error(f"Host {self.name} is configured "
f"with proxy in Zabbix but not in Netbox. The"
" -p flag was ommited: no "
"changes have been made.")
if not proxy_set:
self.logger.debug(f"Device {self.name}: proxy in-sync.")
# Check host inventory
if inventory_sync:
# check inventory mode first, as we need it set to parse
# actual inventory values
if str(host['inventory_mode']) == str(self.inventory_mode):
self.logger.debug(f"Device {self.name}: inventory_mode in-sync.")
else:
self.logger.warning(f"Device {self.name}: inventory_mode OUT of sync.")
self.updateZabbixHost(inventory_mode=str(self.inventory_mode))
# Now we can check if inventory is in-sync.
self.logger.debug(f"Host {self.name}: proxy in-sync.")
# Check host inventory mode
if str(host['inventory_mode']) == str(self.inventory_mode):
self.logger.debug(f"Host {self.name}: inventory_mode in-sync.")
else:
self.logger.warning(f"Host {self.name}: inventory_mode OUT of sync.")
self.updateZabbixHost(inventory_mode=str(self.inventory_mode))
if inventory_sync and self.inventory_mode in [0,1]:
# Check host inventory mapping
if host['inventory'] == self.inventory:
self.logger.debug(f"Device {self.name}: inventory in-sync.")
self.logger.debug(f"Host {self.name}: inventory in-sync.")
else:
self.logger.warning(f"Device {self.name}: inventory OUT of sync.")
self.logger.warning(f"Host {self.name}: inventory OUT of sync.")
self.updateZabbixHost(inventory=self.inventory)
# If only 1 interface has been found
@@ -668,10 +689,10 @@ class NetworkDevice():
updates[key] = item
if updates:
# If interface updates have been found: push to Zabbix
self.logger.warning(f"Device {self.name}: Interface OUT of sync.")
self.logger.warning(f"Host {self.name}: Interface OUT of sync.")
if "type" in updates:
# Changing interface type not supported. Raise exception.
e = (f"Device {self.name}: changing interface type to "
e = (f"Host {self.name}: changing interface type to "
f"{str(updates['type'])} is not supported.")
self.logger.error(e)
raise InterfaceConfigError(e)
@@ -680,19 +701,19 @@ class NetworkDevice():
try:
# API call to Zabbix
self.zabbix.hostinterface.update(updates)
e = f"Solved {self.name} interface conflict."
e = f"Host {self.name}: solved interface conflict."
self.logger.info(e)
self.create_journal_entry("info", e)
except APIRequestError as e:
e = f"Zabbix returned the following error: {str(e)}."
self.logger.error(e)
raise SyncExternalError(e) from e
msg = f"Zabbix returned the following error: {str(e)}."
self.logger.error(msg)
raise SyncExternalError(msg) from e
else:
# If no updates are found, Zabbix interface is in-sync
e = f"Device {self.name}: interface in-sync."
e = f"Host {self.name}: interface in-sync."
self.logger.debug(e)
else:
e = (f"Device {self.name} has unsupported interface configuration."
e = (f"Host {self.name} has unsupported interface configuration."
f" Host has total of {len(host['interfaces'])} interfaces. "
"Manual interfention required.")
self.logger.error(e)
@@ -715,7 +736,7 @@ class NetworkDevice():
}
try:
self.nb_journals.create(journal)
self.logger.debug(f"Created journal entry in NB for host {self.name}")
self.logger.debug(f"Host {self.name}: Created journal entry in Netbox")
return True
except JournalError(e) as e:
self.logger.warning("Unable to create journal entry for "
@@ -742,7 +763,7 @@ class NetworkDevice():
# and add this NB template to the list of successfull templates
tmpls_from_zabbix.pop(pos)
succesfull_templates.append(nb_tmpl)
self.logger.debug(f"Device {self.name}: template "
self.logger.debug(f"Host {self.name}: template "
f"{nb_tmpl['name']} is present in Zabbix.")
break
if len(succesfull_templates) == len(self.zbx_templates) and len(tmpls_from_zabbix) == 0:

160
modules/hostgroups.py Normal file
View File

@@ -0,0 +1,160 @@
"""Module for all hostgroup related code"""
from logging import getLogger
from modules.exceptions import HostgroupError
from modules.tools import build_path
class Hostgroup():
"""Hostgroup class for devices and VM's
Takes type (vm or dev) and NB object"""
def __init__(self, obj_type, nb_obj, version, logger=None):
self.logger = logger if logger else getLogger(__name__)
if obj_type not in ("vm", "dev"):
msg = f"Unable to create hostgroup with type {type}"
self.logger.error()
raise HostgroupError(msg)
self.type = str(obj_type)
self.nb = nb_obj
self.name = self.nb.name
self.nb_version = version
# Used for nested data objects
self.nested_objects = {}
self._set_format_options()
def __str__(self):
return f"Hostgroup for {self.type} {self.name}"
def __repr__(self):
return self.__str__()
def _set_format_options(self):
"""
Set all available variables
for hostgroup generation
"""
format_options = {}
# Set variables for both type of devices
if self.type in ("vm", "dev"):
# Role fix for Netbox <=3
role = None
if self.nb_version.startswith(("2", "3")) and self.type == "dev":
role = self.nb.device_role.name if self.nb.device_role else None
else:
role = self.nb.role.name if self.nb.role else None
# Add default formatting options
# Check if a site is configured. A site is optional for VMs
format_options["region"] = None
format_options["site_group"] = None
if self.nb.site:
if self.nb.site.region:
format_options["region"] = self.generate_parents("region",
str(self.nb.site.region))
if self.nb.site.group:
format_options["site_group"] = self.generate_parents("site_group",
str(self.nb.site.group))
format_options["role"] = role
format_options["site"] = self.nb.site.name if self.nb.site else None
format_options["tenant"] = str(self.nb.tenant) if self.nb.tenant else None
format_options["tenant_group"] = str(self.nb.tenant.group) if self.nb.tenant else None
format_options["platform"] = self.nb.platform.name if self.nb.platform else None
# Variables only applicable for devices
if self.type == "dev":
format_options["manufacturer"] = self.nb.device_type.manufacturer.name
format_options["location"] = str(self.nb.location) if self.nb.location else None
# Variables only applicable for VM's
if self.type == "vm":
# Check if a cluster is configured. Could also be configured in a site.
if self.nb.cluster:
format_options["cluster"] = self.nb.cluster.name
format_options["cluster_type"] = self.nb.cluster.type.name
self.format_options = format_options
def set_nesting(self, nested_sitegroup_flag, nested_region_flag,
nb_groups, nb_regions):
"""Set nesting options for this Hostgroup"""
self.nested_objects = {"site_group": {"flag": nested_sitegroup_flag, "data": nb_groups},
"region": {"flag": nested_region_flag, "data": nb_regions}}
def generate(self, hg_format=None):
"""Generate hostgroup based on a provided format"""
# Set format to default in case its not specified
if not hg_format:
hg_format = "site/manufacturer/role" if self.type == "dev" else "cluster/role"
# Split all given names
hg_output = []
hg_items = hg_format.split("/")
for hg_item in hg_items:
# Check if requested data is available as option for this host
if hg_item not in self.format_options:
# Check if a custom field exists with this name
cf_data = self.custom_field_lookup(hg_item)
# CF does not exist
if not cf_data["result"]:
msg = (f"Unable to generate hostgroup for host {self.name}. "
f"Item type {hg_item} not supported.")
self.logger.error(msg)
raise HostgroupError(msg)
# CF data is populated
if cf_data["cf"]:
hg_output.append(cf_data["cf"])
continue
# Check if there is a value associated to the variable.
# For instance, if a device has no location, do not use it with hostgroup calculation
hostgroup_value = self.format_options[hg_item]
if hostgroup_value:
hg_output.append(hostgroup_value)
# Check if the hostgroup is populated with at least one item.
if bool(hg_output):
return "/".join(hg_output)
msg = (f"Unable to generate hostgroup for host {self.name}."
" Not enough valid items. This is most likely"
" due to the use of custom fields that are empty"
" or an invalid hostgroup format.")
self.logger.error(msg)
raise HostgroupError(msg)
def list_formatoptions(self):
"""
Function to easily troubleshoot which values
are generated for a specific device or VM.
"""
print(f"The following options are available for host {self.name}")
for option_type, value in self.format_options.items():
if value is not None:
print(f"{option_type} - {value}")
print("The following options are not available")
for option_type, value in self.format_options.items():
if value is None:
print(f"{option_type}")
def custom_field_lookup(self, hg_category):
"""
Checks if a valid custom field is present in Netbox.
INPUT: Custom field name
OUTPUT: dictionary with 'result' and 'cf' keys.
"""
# Check if the custom field exists
if hg_category not in self.nb.custom_fields:
return {"result": False, "cf": None}
# Checks if the custom field has been populated
if not bool(self.nb.custom_fields[hg_category]):
return {"result": True, "cf": None}
# Custom field exists and is populated
return {"result": True, "cf": self.nb.custom_fields[hg_category]}
def generate_parents(self, nest_type, child_object):
"""
Generates parent objects to implement nested regions / nested site groups
INPUT: nest_type to set which type of nesting is going to be processed
child_object: the name of the child object (for instance the last NB region)
OUTPUT: STRING - Either the single child name or child and parents.
"""
# Check if this type of nesting is supported.
if not nest_type in self.nested_objects:
return child_object
# If the nested flag is True, perform parent calculation
if self.nested_objects[nest_type]["flag"]:
final_nested_object = build_path(child_object, self.nested_objects[nest_type]["data"])
return "/".join(final_nested_object)
# Nesting is not allowed for this object. Return child_object
return child_object

View File

@@ -13,12 +13,30 @@ class ZabbixInterface():
self.skelet = {"main": "1", "useip": "1", "dns": "", "ip": self.ip}
self.interface = self.skelet
def _set_default_port(self):
"""Sets default TCP / UDP port for different interface types"""
interface_mapping = {
1: 10050,
2: 161,
3: 623,
4: 12345
}
# Check if interface type is listed in mapper.
if self.interface['type'] not in interface_mapping:
return False
# Set default port to interface
self.interface["port"] = str(interface_mapping[self.interface['type']])
return True
def get_context(self):
""" check if Netbox custom context has been defined. """
if "zabbix" in self.context:
zabbix = self.context["zabbix"]
if("interface_type" in zabbix and "interface_port" in zabbix):
if "interface_type" in zabbix:
self.interface["type"] = zabbix["interface_type"]
if not "interface_port" in zabbix:
self._set_default_port()
return True
self.interface["port"] = zabbix["interface_port"]
return True
return False
@@ -72,7 +90,7 @@ class ZabbixInterface():
e = "Interface type is not SNMP, unable to set SNMP details"
raise InterfaceConfigError(e)
def set_default(self):
def set_default_snmp(self):
""" Set default config to SNMPv2, port 161 and community macro. """
self.interface = self.skelet
self.interface["type"] = "2"
@@ -80,3 +98,8 @@ class ZabbixInterface():
self.interface["details"] = {"version": "2",
"community": "{$SNMP_COMMUNITY}",
"bulk": "1"}
def set_default_agent(self):
"""Sets interface to Zabbix agent defaults"""
self.interface["type"] = "1"
self.interface["port"] = "10050"

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python3
# pylint: disable=duplicate-code
"""Module that hosts all functions for virtual machine processing"""
from os import sys
from modules.device import PhysicalDevice
from modules.hostgroups import Hostgroup
from modules.interface import ZabbixInterface
from modules.exceptions import TemplateError, InterfaceConfigError, SyncInventoryError
try:
from config import (
traverse_site_groups,
traverse_regions
)
except ModuleNotFoundError:
print("Configuration file config.py not found in main directory."
"Please create the file or rename the config.py.example file to config.py.")
sys.exit(0)
class VirtualMachine(PhysicalDevice):
"""Model for virtual machines"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.hostgroup = None
self.zbx_template_names = None
def set_hostgroup(self, hg_format, nb_site_groups, nb_regions):
"""Set the hostgroup for this device"""
# Create new Hostgroup instance
hg = Hostgroup("vm", self.nb, self.nb_api_version, logger=self.logger)
hg.set_nesting(traverse_site_groups, traverse_regions, nb_site_groups, nb_regions)
# Generate hostgroup based on hostgroup format
self.hostgroup = hg.generate(hg_format)
def set_vm_template(self):
""" Set Template for VMs. Overwrites default class
to skip a lookup of custom fields."""
# Gather templates ONLY from the device specific context
try:
self.zbx_template_names = self.get_templates_context()
except TemplateError as e:
self.logger.warning(e)
return True
def setInterfaceDetails(self): # pylint: disable=invalid-name
"""
Overwrites device function to select an agent interface type by default
Agent type interfaces are more likely to be used with VMs then SNMP
"""
try:
# Initiate interface class
interface = ZabbixInterface(self.nb.config_context, self.ip)
# Check if Netbox has device context.
# If not fall back to old config.
if interface.get_context():
# If device is SNMP type, add aditional information.
if interface.interface["type"] == 2:
interface.set_snmp()
else:
interface.set_default_agent()
return [interface.interface]
except InterfaceConfigError as e:
message = f"{self.name}: {e}"
self.logger.warning(message)
raise SyncInventoryError(message) from e

View File

@@ -6,8 +6,11 @@ import logging
import argparse
from os import environ, path, sys
from pynetbox import api
from pynetbox.core.query import RequestError as NBRequestError
from requests.exceptions import ConnectionError as RequestsConnectionError
from zabbix_utils import ZabbixAPI, APIRequestError, ProcessingError
from modules.device import NetworkDevice
from modules.device import PhysicalDevice
from modules.virtual_machine import VirtualMachine
from modules.tools import convert_recordset, proxy_prepper
from modules.exceptions import EnvironmentVarError, HostgroupError, SyncError
try:
@@ -19,7 +22,10 @@ try:
zabbix_device_removal,
zabbix_device_disable,
hostgroup_format,
nb_device_filter
vm_hostgroup_format,
nb_device_filter,
sync_vms,
nb_vm_filter
)
except ModuleNotFoundError:
print("Configuration file config.py not found in main directory."
@@ -76,10 +82,18 @@ def main(arguments):
netbox = api(netbox_host, token=netbox_token, threading=True)
# Check if the provided Hostgroup layout is valid
hg_objects = hostgroup_format.split("/")
allowed_objects = ["dev_location", "dev_role", "manufacturer", "region",
allowed_objects = ["location", "role", "manufacturer", "region",
"site", "site_group", "tenant", "tenant_group"]
# Create API call to get all custom fields which are on the device objects
device_cfs = netbox.extras.custom_fields.filter(type="text", content_type_id=23)
try:
device_cfs = list(netbox.extras.custom_fields.filter(type="text", content_type_id=23))
except RequestsConnectionError:
logger.error(f"Unable to connect to Netbox with URL {netbox_host}."
" Please check the URL and status of Netbox.")
sys.exit(1)
except NBRequestError as e:
logger.error(f"Netbox error: {e}")
sys.exit(1)
for cf in device_cfs:
allowed_objects.append(cf.name)
for hg_object in hg_objects:
@@ -105,7 +119,10 @@ def main(arguments):
else:
proxy_name = "name"
# Get all Zabbix and Netbox data
netbox_devices = netbox.dcim.devices.filter(**nb_device_filter)
netbox_devices = list(netbox.dcim.devices.filter(**nb_device_filter))
netbox_vms = []
if sync_vms:
netbox_vms = list(netbox.virtualization.virtual_machines.filter(**nb_vm_filter))
netbox_site_groups = convert_recordset((netbox.dcim.site_groups.all()))
netbox_regions = convert_recordset(netbox.dcim.regions.all())
netbox_journals = netbox.extras.journal_entries
@@ -127,26 +144,85 @@ def main(arguments):
nb_version = netbox.version
# Go through all Netbox devices
for nb_vm in netbox_vms:
try:
vm = VirtualMachine(nb_vm, zabbix, netbox_journals, nb_version,
create_journal, logger)
logger.debug(f"Host {vm.name}: started operations on VM.")
vm.set_vm_template()
# Check if a valid template has been found for this VM.
if not vm.zbx_template_names:
continue
vm.set_hostgroup(vm_hostgroup_format,netbox_site_groups,netbox_regions)
# Check if a valid hostgroup has been found for this VM.
if not vm.hostgroup:
continue
# Temporary disable inventory sync for VM's
# vm.set_inventory(nb_vm)
# Checks if device is in cleanup state
if vm.status in zabbix_device_removal:
if vm.zabbix_id:
# Delete device from Zabbix
# and remove hostID from Netbox.
vm.cleanup()
logger.info(f"VM {vm.name}: cleanup complete")
continue
# Device has been added to Netbox
# but is not in Activate state
logger.info(f"VM {vm.name}: skipping since this VM is "
f"not in the active state.")
continue
# Check if the VM is in the disabled state
if vm.status in zabbix_device_disable:
vm.zabbix_state = 1
# Check if VM is already in Zabbix
if vm.zabbix_id:
vm.ConsistencyCheck(zabbix_groups, zabbix_templates,
zabbix_proxy_list, full_proxy_sync,
create_hostgroups)
continue
# Add hostgroup is config is set
if create_hostgroups:
# Create new hostgroup. Potentially multiple groups if nested
hostgroups = vm.createZabbixHostgroup(zabbix_groups)
# go through all newly created hostgroups
for group in hostgroups:
# Add new hostgroups to zabbix group list
zabbix_groups.append(group)
# Add VM to Zabbix
vm.createInZabbix(zabbix_groups, zabbix_templates,
zabbix_proxy_list)
except SyncError:
pass
for nb_device in netbox_devices:
try:
# Set device instance set data such as hostgroup and template information.
device = NetworkDevice(nb_device, zabbix, netbox_journals, nb_version,
create_journal, logger)
device.set_hostgroup(hostgroup_format,netbox_site_groups,netbox_regions)
device = PhysicalDevice(nb_device, zabbix, netbox_journals, nb_version,
create_journal, logger)
logger.debug(f"Host {device.name}: started operations on device.")
device.set_template(templates_config_context, templates_config_context_overrule)
# Check if a valid template has been found for this VM.
if not device.zbx_template_names:
continue
device.set_hostgroup(hostgroup_format,netbox_site_groups,netbox_regions)
# Check if a valid hostgroup has been found for this VM.
if not device.hostgroup:
continue
device.set_inventory(nb_device)
# Checks if device is part of cluster.
# Requires clustering variable
if device.isCluster() and clustering:
# Check if device is primary or secondary
if device.promoteMasterDevice():
e = (f"Device {device.name} is "
e = (f"Device {device.name}: is "
f"part of cluster and primary.")
logger.info(e)
else:
# Device is secondary in cluster.
# 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...")
logger.info(e)
continue
@@ -156,11 +232,11 @@ def main(arguments):
# Delete device from Zabbix
# and remove hostID from Netbox.
device.cleanup()
logger.info(f"Cleaned up host {device.name}.")
logger.info(f"Device {device.name}: cleanup complete")
continue
# Device has been added to Netbox
# but is not in Activate state
logger.info(f"Skipping host {device.name} since its "
logger.info(f"Device {device.name}: skipping since this device is "
f"not in the active state.")
continue
# Check if the device is in the disabled state
@@ -173,16 +249,13 @@ def main(arguments):
create_hostgroups)
continue
# Add hostgroup is config is set
# and Hostgroup is not present in Zabbix
if create_hostgroups:
for group in zabbix_groups:
# If hostgroup is already present in Zabbix
if group["name"] == device.hostgroup:
break
else:
# Create new hostgroup
hostgroup = device.createZabbixHostgroup()
zabbix_groups.append(hostgroup)
# Create new hostgroup. Potentially multiple groups if nested
hostgroups = device.createZabbixHostgroup(zabbix_groups)
# go through all newly created hostgroups
for group in hostgroups:
# Add new hostgroups to zabbix group list
zabbix_groups.append(group)
# Add device to Zabbix
device.createInZabbix(zabbix_groups, zabbix_templates,
zabbix_proxy_list)