Skip to content

Commit 203e9d0

Browse files
committed
* python 3
* generate v6 PTR * generate $hostname.$domain for Primary IP (while preserving current $interface-$hostname entries) * enumerate all VLAN's & prefixes in ungrouped dict c/o c3noc team for 35c3 event
1 parent 37ea321 commit 203e9d0

File tree

1 file changed

+104
-60
lines changed

1 file changed

+104
-60
lines changed

netbox.py

Lines changed: 104 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@
66
try:
77
import pynetbox
88
from netaddr import *
9-
except ImportError, e:
9+
except ImportError as e:
1010
sys.exit("Please install required python modules: pynetbox netaddr")
1111

12-
class NetboxInventory:
12+
class NetboxInventory(dict):
1313
def __init__(self):
14+
super(NetboxInventory, self).__init__()
1415
self.config = self.load_config()
1516

16-
if self.config['debug']:
17+
if self.config.get('debug', False):
1718
logging.basicConfig(filename='netbox_inventory.log', level=logging.DEBUG)
1819

1920
self.netbox = self.connect_netbox(self.config['url'], self.config['token'])
20-
self.result = {
21+
self.update({
2122
"all": {
2223
"hosts": []
2324
},
@@ -30,7 +31,7 @@ def __init__(self):
3031
"vlans": {}
3132
}
3233
}
33-
}
34+
})
3435

3536
def load_config(self):
3637
with open(os.path.join(os.path.dirname(__file__), "netbox.yml"), 'r') as stream:
@@ -50,12 +51,16 @@ def connect_netbox(self, url, token):
5051
def lookup_ip_for_interfaces(self, interfaces, device):
5152
ips = self.netbox.ipam.ip_addresses.filter(device=device)
5253
for ip in ips:
54+
if (str(ip.address) == str(device.primary_ip4) or (str(ip.address) == str(device.primary_ip6))):
55+
ip.isPrimary = True
56+
else:
57+
ip.isPrimary = False
5358
if ip.family == 4:
5459
interfaces[ip.interface.name]['v4_address'].append(str(ip.address))
55-
self.create_dns_records_v4(ip.interface.name, device, str(ip.address))
60+
self.create_dns_records_v4(ip.interface.name, device, ip)
5661
elif ip.family == 6:
5762
interfaces[ip.interface.name]['v6_address'].append(str(ip.address))
58-
self.create_dns_records_v6(ip.interface.name, device, str(ip.address))
63+
self.create_dns_records_v6(ip.interface.name, device, ip)
5964

6065
return interfaces
6166

@@ -71,32 +76,64 @@ def create_dns_record(self, domain, record_name, record_type, value):
7176
}
7277

7378
def create_dns_records_v4(self, interface, device, ip):
79+
try:
80+
ipIsPrimary = ip.isPrimary
81+
except:
82+
ipIsPrimary = False
83+
ip = str(ip.address)
7484
ip = str(IPNetwork(ip).ip)
7585
pre = ip.split(".")
7686
record_name = pre.pop()
7787
domain = ".".join(pre[::-1] + ['in-addr', 'arpa'])
7888
intf = self.sanitize_dns_name(interface)
7989

80-
self.result['ungrouped']['vars']['dns_entries'].append(
81-
self.create_dns_record(domain, record_name, "PTR", ".".join([intf, device.name, self.config['dns_name']]))
82-
)
83-
self.result['ungrouped']['vars']['dns_entries'].append(
84-
self.create_dns_record(self.config['dns_name'], ".".join([intf, device.name, self.config['dns_name']]), "A", ip)
85-
)
90+
if (ipIsPrimary == True):
91+
self['ungrouped']['vars']['dns_entries'].append(
92+
self.create_dns_record(domain, IPAddress(ip).reverse_dns, "PTR", ".".join([device.name, self.config['dns_name']]))
93+
)
94+
self['ungrouped']['vars']['dns_entries'].append(
95+
self.create_dns_record(self.config['dns_name'], ".".join([device.name, self.config['dns_name']]), "A", ip)
96+
)
97+
self['ungrouped']['vars']['dns_entries'].append(
98+
self.create_dns_record(self.config['dns_name'], ".".join([intf, device.name, self.config['dns_name']]), "A", ip)
99+
)
100+
else:
101+
self['ungrouped']['vars']['dns_entries'].append(
102+
self.create_dns_record(domain, IPAddress(ip).reverse_dns, "PTR", ".".join([intf, device.name, self.config['dns_name']]))
103+
)
104+
self['ungrouped']['vars']['dns_entries'].append(
105+
self.create_dns_record(self.config['dns_name'], ".".join([intf, device.name, self.config['dns_name']]), "A", ip)
106+
)
86107

87108
def create_dns_records_v6(self, interface, device, ip):
109+
try:
110+
ipIsPrimary = ip.isPrimary
111+
except:
112+
ipIsPrimary = False
113+
ip = str(ip.address)
88114
ip = str(IPNetwork(ip).ip)
89115
pre = ip.split(".")
90116
record_name = pre.pop()
91-
domain = ".".join(pre[::-1] + ['in-addr', 'arpa'])
117+
domain = ".".join(pre[::-1] + ['ip6', 'arpa'])
92118
intf = self.sanitize_dns_name(interface)
93119

94-
self.result['ungrouped']['vars']['dns_entries'].append(
95-
self.create_dns_record(self.config['dns_name'], ".".join([intf, device.name, self.config['dns_name']]), "AAAA", ip)
96-
)
97-
self.result['ungrouped']['vars']['dns_entries'].append(
98-
self.create_dns_record(self.config['ipv6_ptr_domain'], IPAddress(ip).reverse_dns, "PTR", ".".join([intf, device.name, self.config['dns_name']]))
99-
)
120+
if (ipIsPrimary == True):
121+
self['ungrouped']['vars']['dns_entries'].append(
122+
self.create_dns_record(domain, IPAddress(ip).reverse_dns, "PTR", ".".join([device.name, self.config['dns_name']]))
123+
)
124+
self['ungrouped']['vars']['dns_entries'].append(
125+
self.create_dns_record(self.config['dns_name'], ".".join([device.name, self.config['dns_name']]), "AAAA", ip)
126+
)
127+
self['ungrouped']['vars']['dns_entries'].append(
128+
self.create_dns_record(self.config['dns_name'], ".".join([intf, device.name, self.config['dns_name']]), "AAAA", ip)
129+
)
130+
else:
131+
self['ungrouped']['vars']['dns_entries'].append(
132+
self.create_dns_record(domain, IPAddress(ip).reverse_dns, "PTR", ".".join([intf, device.name, self.config['dns_name']]))
133+
)
134+
self['ungrouped']['vars']['dns_entries'].append(
135+
self.create_dns_record(self.config['dns_name'], ".".join([intf, device.name, self.config['dns_name']]), "AAAA", ip)
136+
)
100137

101138
def lookup_circuits(self, circuit_id, interface_id, mtu_size, interface_mode, vlans = {}):
102139
circuit = self.netbox.circuits.circuits.get(circuit_id)
@@ -129,11 +166,10 @@ def create_slug(self, text):
129166
return slug if len(slug) <= 32 else slug[0:32]
130167

131168
def save_vlan_global(self, vlan_id, vlan_name):
132-
if vlan_id not in self.result['ungrouped']['vars']['vlans']:
133-
self.result['ungrouped']['vars']['vlans'][vlan_id] = {
134-
"name": vlan_name,
135-
"slug": self.create_slug(vlan_name)
136-
}
169+
self['ungrouped']['vars']['vlans'].setdefault(vlan_id, {
170+
"name": vlan_name,
171+
"slug": self.create_slug(vlan_name)
172+
})
137173

138174
def return_vlans(self, interface):
139175
r = []
@@ -165,84 +201,91 @@ def get_interfaces(self, device):
165201
def return_interface(self, description, int_type, id, mtu='', interface_mode='Untagged', vlans=[]):
166202
intf = { 'description': description, 'type': int_type, 'id': id, 'interface_mode': interface_mode, 'vlans': vlans, 'v4_address': [], 'v6_address': [] }
167203

168-
if mtu != None:
204+
if mtu is not None:
169205
intf['mtu'] = mtu
170206

171207
return intf
172208

173209
def create_group_entry(self, group, hostname):
174-
if group not in self.result:
175-
self.result[group] = {}
176-
self.result[group]["hosts"] = []
177-
178-
self.result[group]["hosts"].append(hostname)
210+
gdict = self.setdefault(group, {})
211+
hosts = gdict.setdefault('hosts', [])
212+
hosts.append(hostname)
179213

180214
def fetch_netbox_devices(self):
181215
for host in self.netbox.dcim.devices.filter(status=1):
182-
if host.name == None:
216+
if host.name is None:
183217
continue
184218

185219
h = host.name
186-
self.result["all"]["hosts"].append(h)
220+
self["all"]["hosts"].append(h)
187221

188222
# Create hostgroups by device_role, device_type and site
189223
self.create_group_entry(host.device_role.slug, h)
190224
self.create_group_entry(host.device_type.slug, h)
191225
self.create_group_entry(host.site.slug, h)
192226

193-
if not host in self.result["_meta"]["hostvars"]:
194-
self.result["_meta"]["hostvars"][h] = { "interfaces": {}}
227+
hvar = self["_meta"]["hostvars"].setdefault(h, { "interfaces": {}})
195228

196-
if host.virtual_chassis != None:
197-
self.result["_meta"]["hostvars"][h]['virtual_chassis'] = []
229+
if host.virtual_chassis is not None:
230+
hvar['virtual_chassis'] = []
198231
vc_members = self.netbox.dcim.devices.filter(virtual_chassis_id=host.virtual_chassis.id)
199232
for vcm in vc_members:
200233
if vcm.vc_position == 0 or vcm.vc_position == 1:
201234
role = 'routing-engine'
202235
else:
203236
role = 'line-card'
204237

205-
self.result["_meta"]["hostvars"][h]['virtual_chassis'].append({ 'serial': vcm.serial, 'role': role, 'device_name': vcm.name, 'vc_position': vcm.vc_position })
206-
207-
if host.custom_fields != None:
208-
for k,v in host.custom_fields.items():
209-
self.result["_meta"]["hostvars"][h][k] = v
238+
hvar['virtual_chassis'].append({ 'serial': vcm.serial, 'role': role, 'device_name': vcm.name, 'vc_position': vcm.vc_position })
210239

211-
self.result["_meta"]["hostvars"][h]['site'] = host.site.slug
212-
self.result["_meta"]["hostvars"][h]['serial'] = host.serial
213-
self.result["_meta"]["hostvars"][h]['siteid'] = host.site.id
214-
self.result["_meta"]["hostvars"][h]['model'] = host.device_type.slug
215-
self.result["_meta"]["hostvars"][h]['manufacturer'] = host.device_type.manufacturer.slug
240+
hvar.update(host.custom_fields or {})
241+
hvar.update({
242+
'site': host.site.slug,
243+
'serial': host.serial,
244+
'siteid': host.site.id,
245+
'model': host.device_type.slug,
246+
'manufacturer': host.device_type.manufacturer.slug,
247+
})
216248

217249
# Lookup site details
218250
site = self.netbox.dcim.sites.filter(slug=host.site.slug)
219251
if len(site) > 0:
220-
self.result["_meta"]["hostvars"][h]['latitude'] = site[0].latitude
221-
self.result["_meta"]["hostvars"][h]['longitude'] = site[0].longitude
252+
hvar.update({
253+
'latitude': site[0].latitude,
254+
'longitude': site[0].longitude,
255+
})
222256

223-
self.result["_meta"]["hostvars"][h]["interfaces"] = self.get_interfaces(host)
257+
hvar["interfaces"] = self.get_interfaces(host)
224258
if host.primary_ip != None:
225-
self.result["_meta"]["hostvars"][h]['ansible_ssh_host'] = str(host.primary_ip.address.ip)
259+
hvar['ansible_ssh_host'] = str(host.primary_ip.address.ip)
226260

227-
return self.result
261+
return self
228262

229263
def fetch_virtual_machines(self):
230264
for host in self.netbox.virtualization.virtual_machines.filter(status=1):
231-
self.result["all"]["hosts"].append(host.name)
265+
self["all"]["hosts"].append(host.name)
232266

233267
# Create hostgroups by cluster, role
234268
if host.cluster is not None:
235269
self.create_group_entry(host.cluster.name, host.name)
236270
if host.role is not None:
237271
self.create_group_entry(host.role.slug, host.name)
238272

239-
if not host in self.result["_meta"]["hostvars"]:
240-
self.result["_meta"]["hostvars"][host.name] = {}
241-
273+
hvar = self["_meta"]["hostvars"].setdefault(host.name, {})
242274
if host.primary_ip != None:
243-
self.result["_meta"]["hostvars"][host.name]['ansible_ssh_host'] = str(IPNetwork(host.primary_ip.address).ip)
275+
hvar['ansible_ssh_host'] = str(IPNetwork(host.primary_ip.address).ip)
276+
277+
return self
278+
279+
def fetch_prefixes(self):
280+
prefixes = self['ungrouped']['vars'].setdefault('prefixes', {})
281+
282+
for prefix in self.netbox.ipam.prefixes.filter(status=1):
283+
prefixes[str(prefix)] = {
284+
'vlan': prefix.vlan.vid,
285+
}
286+
prefixes[str(prefix)].update(prefix.custom_fields)
244287

245-
return self.result
288+
return self
246289

247290
class OFNetboxInventory(NetboxInventory):
248291
def __init__(self):
@@ -308,9 +351,10 @@ def modification_date(filename):
308351
def cache_inventory(cache_file):
309352
n = NetboxInventory()
310353
n.fetch_virtual_machines()
311-
hosts = n.fetch_netbox_devices()
354+
n.fetch_netbox_devices()
355+
n.fetch_prefixes()
312356

313-
cache = json.dumps(hosts, sort_keys=True, indent=3)
357+
cache = json.dumps(n, sort_keys=True, indent=3)
314358
with open(cache_file, 'w') as f:
315359
f.write(cache)
316360

0 commit comments

Comments
 (0)