Skip to content

Commit 3f28986

Browse files
committed
Implemented #48
1 parent e2ddb06 commit 3f28986

File tree

3 files changed

+141
-124
lines changed

3 files changed

+141
-124
lines changed

README.md

+89-82
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@ A script to create, update and delete Zabbix hosts using Netbox device objects.
55

66

77
## Installation
8-
### Packages
9-
Make sure that you have a python environment with the following packages installed.
10-
```
11-
pynetbox
12-
pyzabbix
13-
```
148

159
### Cloning the repository
1610
```
1711
git clone https://github.com/TheNetworkGuy/netbox-zabbix-sync.git
1812
```
1913

14+
### Packages
15+
Make sure that you have a python environment with the following packages installed. You can also use the requirements.txt file for installation with pip.
16+
```
17+
pynetbox
18+
pyzabbix
19+
```
20+
2021
### Config file
2122
First time user? Copy the config.py.example file to config.py. This file is used for modifying filters and setting variables such as custom field names.
2223
```
@@ -52,6 +53,83 @@ You can make the hostID field hidden or read-only to prevent human intervention.
5253

5354
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.
5455

56+
## Config file
57+
58+
### Hostgroup
59+
Setting the create_hostgroups variable to False requires manual hostgroup creation for devices in a new category.
60+
61+
The format can be set with the hostgroup_format variable.
62+
63+
Make sure that the Zabbix user has proper permissions to create hosts.
64+
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.
65+
66+
#### layout
67+
The default hostgroup layout is "site/manufacturer/device_role".
68+
69+
**Variables**
70+
71+
You can change this behaviour with the hostgroup_format variable. The following values can be used:
72+
| name | description |
73+
| ------------ | ------------ |
74+
|dev_location|The device location name|
75+
|dev_role|The device role name|
76+
|manufacturer|Manufacturer name|
77+
|region|The region name of the device|
78+
|site|Site name|
79+
|site_group|Site group name|
80+
|tenant|Tenant name|
81+
|tenant_group|Tenant group name|
82+
83+
84+
You can specify the value like so, sperated by a "/":
85+
```
86+
hostgroup_format = "tenant/site/dev_location/dev_role"
87+
```
88+
**custom fields**
89+
90+
You can also use the value of custom fields under the device object.
91+
92+
This allows more freedom and even allows a full static mapping instead of a dynamic rendered hostgroup name.
93+
```
94+
hostgroup_format = "site/mycustomfieldname"
95+
```
96+
**Empty variables or hostgroups**
97+
98+
Should the content of a variable be empty, then the hostgroup position is skipped.
99+
100+
For example, consider the following scenario with 2 devices, both the same device type and site. One of them is linked to a tenant, the other one does not have a relationship with a tenant.
101+
- Device_role: PDU
102+
- Site: HQ-AMS
103+
```
104+
hostgroup_format = "site/tenant/device_role"
105+
```
106+
When running the script like above, the following hostgroup (HG) will be generated for both hosts:
107+
- Device A with no relationship with a tenant: HQ-AMS/PDU
108+
- Device B with a relationship to tenant "Fork Industries": HQ-AMS/Fork Industries/PDU
109+
110+
The same logic applies to custom fields being used in the HG format:
111+
```
112+
hostgroup_format = "site/mycustomfieldname"
113+
```
114+
For device A with the value "ABC123" in the custom field "mycustomfieldname" -> HQ-AMS/ABC123
115+
For a device which does not have a value in the custom field "mycustomfieldname" -> HQ-AMS
116+
117+
Should there be a scenario where a custom field does not have a value under a device, and the HG format only uses this single variable, then this will result in an error:
118+
```
119+
hostgroup_format = "mycustomfieldname"
120+
121+
Netbox-Zabbix-sync - ERROR - ESXI1 has no reliable hostgroup. This is most likely due to the use of custom fields that are empty.
122+
```
123+
### Device status
124+
By setting a status on a Netbox device you determine how the host is added (or updated) in Zabbix. There are, by default, 3 options:
125+
* Delete the host from Zabbix (triggered by Netbox status "Decommissioning" and "Inventory")
126+
* Create the host in Zabbix but with a disabled status (Trigger by "Offline", "Planned", "Staged" and "Failed")
127+
* Create the host in Zabbix with an enabled status (For now only enabled with the "Active" status)
128+
129+
You can modify this behaviour by changing the following list variables in the script:
130+
- zabbix_device_removal
131+
- zabbix_device_disable
132+
55133
### Template source
56134
You can either use a Netbox device type custom field or Netbox config context for the Zabbix template information.
57135

@@ -106,83 +184,9 @@ python3 netbox_zabbix_sync.py
106184
### Flags
107185
| Flag | Option | Description |
108186
| ------------ | ------------ | ------------ |
109-
| -c | cluster | For clustered devices: only add the primary node of a cluster and use the cluster name as hostname. |
110-
| -H | hostgroup | Create non-existing hostgroups in Zabbix. Usefull for a first run to add all required hostgroups. |
111-
| -l | layout | Set the hostgroup layout. Default is site/manufacturer/dev_role. Posible options (seperated with '/'): site, manufacturer, dev_role, tenant |
112187
| -v | verbose | Log with debugging on. |
113-
| -j | journal | Create journal entries in Netbox when a host gets added, modified or deleted in Zabbix |
114-
| -p | proxy-power | Force a full proxy sync (includes deleting the proxy in Zabbix if not present in config context in Netbox) |
115-
116-
#### Hostgroup
117-
In case of omitting the -H flag, manual hostgroup creation is required for devices in a new category.
118-
119-
The format can be set with the -l flag. If not provided the default format will be:
120-
{Site name}/{Manufacturer name}/{Device role name}
121-
122-
Make sure that the Zabbix user has proper permissions to create hosts.
123-
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.
124-
125-
#### layout
126-
The default hostgroup layout is "site/manufacturer/device_role".
127-
128-
**Variables**
129-
130-
You can change this behaviour with the --layout flag. The following variables can be used:
131-
| name | description |
132-
| ------------ | ------------ |
133-
|tenant|Tenant name|
134-
|site|Site name|
135-
|manufacturer|Manufacturer name|
136-
|device_role|The device role name|
137-
138-
You can specify the variables like so, sperated by a "/":
139-
```
140-
python3 netbox_zabbix_sync.py -l tenant/site/device_role
141-
```
142-
**custom fields**
143-
144-
You can also use the value of custom fields under the device object.
145-
146-
This allows more freedom and even allows a ful static mapping instead of a dynamic rendered hostgroup name.
147-
```
148-
python3 netbox_zabbix_sync.py -l site/mycustomfieldname
149-
```
150-
**Empty variables or hostgroups**
151188

152-
Should the content of a variable be empty, then the hostgroup position is skipped.
153-
154-
For example, consider the following scenario with 2 devices, both the same device type and site. One of them is linked to a tenant, the other one does not have a relationship with a tenant.
155-
- Device_role: PDU
156-
- Site: HQ-AMS
157-
```
158-
python3 netbox_zabbix_sync.py -l site/tenant/device_role
159-
```
160-
When running the script like above, the following hostgroup (HG) will be generated for both hosts:
161-
- Device A with no relationship with a tenant: HQ-AMS/PDU
162-
- Device B with a relationship to tenant "Fork Industries": HQ-AMS/Fork Industries/PDU
163-
164-
The same logic applies to custom fields being used in the HG format:
165-
```
166-
python3 netbox_zabbix_sync.py -l site/mycustomfieldname
167-
```
168-
For device A with the value "ABC123" in the custom field "mycustomfieldname" -> HQ-AMS/ABC123
169-
For a device which does not have a value in the custom field "mycustomfieldname" -> HQ-AMS
170-
171-
Should there be a scenario where a custom field does not have a value under a device, and the HG format only uses this signle variable, then this will result in an error:
172-
```
173-
python3 netbox_zabbix_sync.py -l mycustomfieldname
174-
175-
Netbox-Zabbix-sync - ERROR - ESXI1 has no reliable hostgroup. This is most likely due to the use of custom fields that are empty.
176-
```
177-
### Device status
178-
By setting a status on a Netbox device you determine how the host is added (or updated) in Zabbix. There are, by default, 3 options:
179-
* Delete the host from Zabbix (triggered by Netbox status "Decommissioning" and "Inventory")
180-
* Create the host in Zabbix but with a disabled status (Trigger by "Offline", "Planned", "Staged" and "Failed")
181-
* Create the host in Zabbix with an enabled status (For now only enabled with the "Active" status)
182-
183-
You can modify this behaviour by changing the following list variables in the script:
184-
- zabbix_device_removal
185-
- zabbix_device_disable
189+
## Config context
186190

187191
### Zabbix proxy
188192
You can set the proxy for a device using the 'proxy' key in config context.
@@ -193,7 +197,7 @@ You can set the proxy for a device using the 'proxy' key in config context.
193197
}
194198
}
195199
```
196-
Because of the posible amount of destruction when setting up Netbox but forgetting the proxy command, the sync works a bit different. By default everything is synced except in a situation where the Zabbix host has a proxy configured but nothing is configured in Netbox. To force deletion and a full sync, use the -p flag.
200+
Because of the posible amount of destruction when setting up Netbox but forgetting the proxy command, the sync works a bit different. By default everything is synced except in a situation where the Zabbix host has a proxy configured but nothing is configured in Netbox. To force deletion and a full sync, set the full_proxy_sync variable in the config file.
197201

198202
### Set interface parameters within Netbox
199203
When adding a new device, you can set the interface type with custom context. By default, the following configuration is applied when no config context is provided:
@@ -256,4 +260,7 @@ To configure the interface parameters you'll need to use custom context. Custom
256260
}
257261
}
258262
```
263+
264+
I would recommend using macros for sensitive data such as community strings since the data in Netbox is plain-text.
265+
259266
Note: Not all SNMP data is required for a working configuration. [The following parameters are allowed ](https://www.zabbix.com/documentation/current/manual/api/reference/hostinterface/object#details_tag "The following parameters are allowed ")but are not all required, depending on your environment.

config.py.example

+19
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,29 @@ templates_config_context_overrule = False
1111
template_cf = "zabbix_template"
1212
device_cf = "zabbix_hostid"
1313

14+
# Enable clustering of devices with virtual chassis setup
15+
clustering = False
16+
17+
# Enable hostgroup generation. Requires permissions in Zabbix
18+
create_hostgroups = True
19+
20+
# Create journal entries
21+
create_journal = False
22+
23+
# Set to true to enable removal of proxy's under hosts. Use with caution and make sure that you specified
24+
# all the required proxy's in the device config context before enabeling this option.
25+
# With this option disabled proxy's will only be added and modified for Zabbix hosts.
26+
full_proxy_sync = False
27+
1428
# Netbox to Zabbix device state convertion
1529
zabbix_device_removal = ["Decommissioning", "Inventory"]
1630
zabbix_device_disable = ["Offline", "Planned", "Staged", "Failed"]
1731

32+
# Hostgroup mapping
33+
# Available choices: dev_location, dev_role, manufacturer, region, site, site_group, tenant, tenant_group
34+
# You can also use CF (custom field) names under the device. The CF content will be used for the hostgroup generation.
35+
hostgroup_format = "site/manufacturer/dev_role"
36+
1837
# Custom filter for device filtering. Variable must be present but can be left empty with no filtering.
1938
# A couple of examples are as follows:
2039

netbox_zabbix_sync.py

+33-42
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,18 @@
77
from pynetbox import api
88
from pyzabbix import ZabbixAPI, ZabbixAPIException
99
try:
10-
from config import *
10+
from config import (
11+
templates_config_context,
12+
templates_config_context_overrule,
13+
clustering, create_hostgroups,
14+
create_journal, full_proxy_sync,
15+
template_cf, device_cf,
16+
zabbix_device_removal,
17+
zabbix_device_disable,
18+
hostgroup_format,
19+
nb_device_filter
20+
)
21+
1122
except ModuleNotFoundError:
1223
print(f"Configuration file config.py not found in main directory."
1324
"Please create the file or rename the config.py.example file to config.py.")
@@ -52,20 +63,19 @@ def main(arguments):
5263
# Set Netbox API
5364
netbox = api(netbox_host, token=netbox_token, threading=True)
5465
# Check if the provided Hostgroup layout is valid
55-
if(arguments.layout):
56-
hg_objects = arguments.layout.split("/")
57-
allowed_objects = ["dev_location", "dev_role", "manufacturer", "region",
58-
"site", "site_group", "tenant", "tenant_group"]
59-
# Create API call to get all custom fields which are on the device objects
60-
device_cfs = netbox.extras.custom_fields.filter(type="text", content_type_id=23)
61-
for cf in device_cfs:
62-
allowed_objects.append(cf.name)
63-
for object in hg_objects:
64-
if(object not in allowed_objects):
65-
e = (f"Hostgroup item {object} is not valid. Make sure you"
66-
" use valid items and seperate them with '/'.")
67-
logger.error(e)
68-
raise HostgroupError(e)
66+
hg_objects = hostgroup_format.split("/")
67+
allowed_objects = ["dev_location", "dev_role", "manufacturer", "region",
68+
"site", "site_group", "tenant", "tenant_group"]
69+
# Create API call to get all custom fields which are on the device objects
70+
device_cfs = netbox.extras.custom_fields.filter(type="text", content_type_id=23)
71+
for cf in device_cfs:
72+
allowed_objects.append(cf.name)
73+
for object in hg_objects:
74+
if(object not in allowed_objects):
75+
e = (f"Hostgroup item {object} is not valid. Make sure you"
76+
" use valid items and seperate them with '/'.")
77+
logger.error(e)
78+
raise HostgroupError(e)
6979
# Set Zabbix API
7080
try:
7181
zabbix = ZabbixAPI(zabbix_host)
@@ -83,12 +93,12 @@ def main(arguments):
8393
for nb_device in netbox_devices:
8494
try:
8595
device = NetworkDevice(nb_device, zabbix, netbox_journals,
86-
arguments.journal)
87-
device.set_hostgroup(arguments.layout)
96+
create_journal)
97+
device.set_hostgroup(hostgroup_format)
8898
device.set_template(templates_config_context, templates_config_context_overrule)
8999
# Checks if device is part of cluster.
90-
# Requires the cluster argument.
91-
if(device.isCluster() and arguments.cluster):
100+
# Requires clustering variable
101+
if(device.isCluster() and clustering):
92102
# Check if device is master or slave
93103
if(device.promoteMasterDevice()):
94104
e = (f"Device {device.name} is "
@@ -117,9 +127,9 @@ def main(arguments):
117127
continue
118128
elif(device.status in zabbix_device_disable):
119129
device.zabbix_state = 1
120-
# Add hostgroup is flag is true
130+
# Add hostgroup is variable is True
121131
# and Hostgroup is not present in Zabbix
122-
if(arguments.hostgroups):
132+
if(create_hostgroups):
123133
for group in zabbix_groups:
124134
# If hostgroup is already present in Zabbix
125135
if(group["name"] == device.hostgroup):
@@ -131,7 +141,7 @@ def main(arguments):
131141
# Device is already present in Zabbix
132142
if(device.zabbix_id):
133143
device.ConsistencyCheck(zabbix_groups, zabbix_templates,
134-
zabbix_proxys, arguments.proxy_power)
144+
zabbix_proxys, full_proxy_sync)
135145
# Add device to Zabbix
136146
else:
137147
device.createInZabbix(zabbix_groups, zabbix_templates,
@@ -612,7 +622,7 @@ def ConsistencyCheck(self, groups, templates, proxys, proxy_power):
612622
else:
613623
if(not host["proxy_hostid"] == "0"):
614624
if(proxy_power):
615-
# If the -p flag has been issued,
625+
# Variable full_proxy_sync has been enabled
616626
# delete the proxy link in Zabbix
617627
self.updateZabbixHost(proxy_hostid=self.zbxproxy)
618628
else:
@@ -820,29 +830,10 @@ def set_default(self):
820830

821831

822832
if(__name__ == "__main__"):
823-
# Arguments parsing
824833
parser = argparse.ArgumentParser(
825834
description='A script to sync Zabbix with Netbox device data.'
826835
)
827836
parser.add_argument("-v", "--verbose", help="Turn on debugging.",
828837
action="store_true")
829-
parser.add_argument("-c", "--cluster", action="store_true",
830-
help=("Only add the primary node of a cluster "
831-
"to Zabbix. Usefull when a shared virtual IP is "
832-
"used for the control plane."))
833-
parser.add_argument("-H", "--hostgroups",
834-
help="Create Zabbix hostgroups if not present",
835-
action="store_true")
836-
parser.add_argument("-l", "--layout", type=str,
837-
help="Defines the hostgroup layout",
838-
default='site/manufacturer/dev_role')
839-
parser.add_argument("-p", "--proxy_power", action="store_true",
840-
help=("USE WITH CAUTION. If there is a proxy "
841-
"configured in Zabbix but not in Netbox, sync "
842-
"the device and remove the host - proxy "
843-
"link in Zabbix."))
844-
parser.add_argument("-j", "--journal", action="store_true",
845-
help="Create journal entries in Netbox at write actions")
846838
args = parser.parse_args()
847-
848839
main(args)

0 commit comments

Comments
 (0)