Skip to content

Commit bf69006

Browse files
authored
Inventory: Add dns_name option for the Primary IP's dns_name host_var (#394)
1 parent 3df9506 commit bf69006

18 files changed

+127
-29
lines changed

plugins/inventory/nb_inventory.py

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@
171171
- The host var values will be from the virtual chassis master.
172172
type: boolean
173173
default: False
174+
dns_name:
175+
description:
176+
- Force IP Addresses to be fetched so that the dns_name for the primary_ip of each device or VM is set as a host_var.
177+
- Setting interfaces will also fetch IP addresses and the dns_name host_var will be set.
178+
type: boolean
179+
default: False
174180
compose:
175181
description: List of custom ansible host vars to create from the device object fetched from NetBox
176182
default: {}
@@ -420,6 +426,11 @@ def group_extractors(self):
420426
{"interfaces": self.extract_interfaces,}
421427
)
422428

429+
if self.interfaces or self.dns_name:
430+
extractors.update(
431+
{"dns_name": self.extract_dns_name,}
432+
)
433+
423434
return extractors
424435

425436
def _pluralize_group_by(self, group_by):
@@ -638,18 +649,20 @@ def extract_interfaces(self, host):
638649

639650
interfaces = list(interfaces_lookup[host["id"]].values())
640651

641-
before_netbox_v29 = bool(self.ipaddresses_lookup)
652+
before_netbox_v29 = bool(self.ipaddresses_intf_lookup)
642653
# Attach IP Addresses to their interface
643654
for interface in interfaces:
644655
if before_netbox_v29:
645656
interface["ip_addresses"] = list(
646-
self.ipaddresses_lookup[interface["id"]].values()
657+
self.ipaddresses_intf_lookup[interface["id"]].values()
647658
)
648659
else:
649660
interface["ip_addresses"] = list(
650-
self.vm_ipaddresses_lookup[interface["id"]].values()
661+
self.vm_ipaddresses_intf_lookup[interface["id"]].values()
651662
if host["is_virtual"]
652-
else self.device_ipaddresses_lookup[interface["id"]].values()
663+
else self.device_ipaddresses_intf_lookup[
664+
interface["id"]
665+
].values()
653666
)
654667

655668
return interfaces
@@ -707,6 +720,28 @@ def extract_cluster_type(self, host):
707720
def extract_is_virtual(self, host):
708721
return host.get("is_virtual")
709722

723+
def extract_dns_name(self, host):
724+
# No primary IP assigned
725+
if not host.get("primary_ip"):
726+
return None
727+
728+
before_netbox_v29 = bool(self.ipaddresses_lookup)
729+
if before_netbox_v29:
730+
ip_address = self.ipaddresses_lookup.get(host["primary_ip"]["id"])
731+
else:
732+
if host["is_virtual"]:
733+
ip_address = self.vm_ipaddresses_lookup.get(host["primary_ip"]["id"])
734+
else:
735+
ip_address = self.device_ipaddresses_lookup.get(
736+
host["primary_ip"]["id"]
737+
)
738+
739+
# Don"t assign a host_var for empty dns_name
740+
if ip_address.get("dns_name") == "":
741+
return None
742+
743+
return ip_address.get("dns_name")
744+
710745
def refresh_platforms_lookup(self):
711746
url = self.api_endpoint + "/api/dcim/platforms/?limit=0"
712747
platforms = self.get_resource_list(api_url=url)
@@ -953,9 +988,13 @@ def refresh_ipaddresses(self):
953988

954989
# Construct a dictionary of lists, to allow looking up ip addresses by interface id
955990
# Note that interface ids share the same namespace for both devices and vms so this is a single dictionary
991+
self.ipaddresses_intf_lookup = defaultdict(dict)
992+
# Construct a dictionary of the IP addresses themselves
956993
self.ipaddresses_lookup = defaultdict(dict)
957994
# NetBox v2.9 and onwards
995+
self.vm_ipaddresses_intf_lookup = defaultdict(dict)
958996
self.vm_ipaddresses_lookup = defaultdict(dict)
997+
self.device_ipaddresses_intf_lookup = defaultdict(dict)
959998
self.device_ipaddresses_lookup = defaultdict(dict)
960999

9611000
for ipaddress in ipaddresses:
@@ -967,9 +1006,13 @@ def refresh_ipaddresses(self):
9671006
ipaddress_copy = ipaddress.copy()
9681007

9691008
if ipaddress["assigned_object_type"] == "virtualization.vminterface":
970-
self.vm_ipaddresses_lookup[interface_id][ip_id] = ipaddress_copy
1009+
self.vm_ipaddresses_lookup[ip_id] = ipaddress_copy
1010+
self.vm_ipaddresses_intf_lookup[interface_id][
1011+
ip_id
1012+
] = ipaddress_copy
9711013
else:
972-
self.device_ipaddresses_lookup[interface_id][
1014+
self.device_ipaddresses_lookup[ip_id] = ipaddress_copy
1015+
self.device_ipaddresses_intf_lookup[interface_id][
9731016
ip_id
9741017
] = ipaddress_copy # Remove "assigned_object_X" attributes, as that's redundant when ipaddress is added to an interface
9751018

@@ -986,7 +1029,8 @@ def refresh_ipaddresses(self):
9861029
# We need to copy the ipaddress entry to preserve the original in case caching is used.
9871030
ipaddress_copy = ipaddress.copy()
9881031

989-
self.ipaddresses_lookup[interface_id][ip_id] = ipaddress_copy
1032+
self.ipaddresses_intf_lookup[interface_id][ip_id] = ipaddress_copy
1033+
self.ipaddresses_lookup[ip_id] = ipaddress_copy
9901034
# Remove "interface" attribute, as that's redundant when ipaddress is added to an interface
9911035
del ipaddress_copy["interface"]
9921036

@@ -1017,7 +1061,8 @@ def lookup_processes(self):
10171061
def lookup_processes_secondary(self):
10181062
lookups = []
10191063

1020-
if self.interfaces:
1064+
# IP addresses are needed for either interfaces or dns_name options
1065+
if self.interfaces or self.dns_name:
10211066
lookups.append(self.refresh_ipaddresses)
10221067

10231068
return lookups
@@ -1458,5 +1503,6 @@ def parse(self, inventory, loader, path, cache=True):
14581503
self.device_query_filters = self.get_option("device_query_filters")
14591504
self.vm_query_filters = self.get_option("vm_query_filters")
14601505
self.virtual_chassis_name = self.get_option("virtual_chassis_name")
1506+
self.dns_name = self.get_option("dns_name")
14611507

14621508
self.main()

tests/integration/netbox-deploy.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import sys
99
import pynetbox
10+
from packaging import version
1011

1112
# NOTE: If anything depends on specific versions of NetBox, can check INTEGRATION_TESTS in env
1213
# os.environ["INTEGRATION_TESTS"]
@@ -16,7 +17,7 @@
1617
nb_host = os.getenv("NETBOX_HOST", "http://localhost:32768")
1718
nb_token = os.getenv("NETBOX_TOKEN", "0123456789abcdef0123456789abcdef01234567")
1819
nb = pynetbox.api(nb_host, nb_token)
19-
version = float(nb.version)
20+
nb_version = version.parse(nb.version)
2021

2122
ERRORS = False
2223

@@ -39,7 +40,7 @@ def make_netbox_calls(endpoint, payload):
3940

4041

4142
# Create tags used in future tests
42-
if version >= 2.9:
43+
if nb_version >= version.parse("2.9"):
4344
create_tags = nb.extras.tags.create(
4445
[
4546
{"name": "First", "slug": "first"},
@@ -187,7 +188,7 @@ def make_netbox_calls(endpoint, payload):
187188
},
188189
{"model": "1841", "slug": "1841", "manufacturer": cisco_manu.id,},
189190
]
190-
if version > 2.8:
191+
if nb_version > version.parse("2.8"):
191192
temp_dt = []
192193
for dt_type in device_types:
193194
if dt_type.get("subdevice_role") is not None and not dt_type["subdevice_role"]:
@@ -322,10 +323,14 @@ def make_netbox_calls(endpoint, payload):
322323
{"address": "172.16.180.1/24", "interface": test100_gi1.id},
323324
{"address": "2001::1:1/64", "interface": test100_gi2.id},
324325
{"address": "172.16.180.11/24", "interface": created_nexus_interfaces[0].id},
325-
{"address": "172.16.180.12/24", "interface": created_nexus_interfaces[1].id},
326+
{
327+
"address": "172.16.180.12/24",
328+
"interface": created_nexus_interfaces[1].id,
329+
"dns_name": "nexus.example.com",
330+
},
326331
{"address": "172.16.180.254/24"},
327332
]
328-
if version > 2.8:
333+
if nb_version > version.parse("2.8"):
329334
temp_ips = []
330335
for ip in ip_addresses:
331336
if ip.get("interface"):
@@ -335,6 +340,8 @@ def make_netbox_calls(endpoint, payload):
335340

336341
created_ip_addresses = make_netbox_calls(nb.ipam.ip_addresses, ip_addresses)
337342

343+
# Assign Primary IP
344+
nexus.update({"primary_ip4": 4})
338345

339346
## Create RIRs
340347
rirs = [{"name": "Example RIR", "slug": "example-rir"}]
@@ -425,6 +432,12 @@ def make_netbox_calls(endpoint, payload):
425432
"protocol": "tcp",
426433
},
427434
]
435+
# 2.10+ requires the port attribute to be 'ports' and a list instead of an integer
436+
for service in services:
437+
if nb_version >= version.parse("2.10"):
438+
service["ports"] = [service["port"]]
439+
del service["port"]
440+
428441
created_services = make_netbox_calls(nb.ipam.services, services)
429442

430443

tests/integration/targets/inventory-latest/files/test-inventory-legacy.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"tags": []
3434
},
3535
"Test Nexus One": {
36+
"ansible_host": "172.16.180.12",
3637
"custom_fields": {},
3738
"device_roles": [
3839
"core-switch"
@@ -47,6 +48,7 @@
4748
"manufacturers": [
4849
"cisco"
4950
],
51+
"primary_ip4": "172.16.180.12",
5052
"regions": [
5153
"test-region",
5254
"parent-region"

tests/integration/targets/inventory-latest/files/test-inventory-options-flatten.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444
"tags": []
4545
},
4646
"Test Nexus One": {
47+
"ansible_host": "172.16.180.12",
4748
"device_type": "nexus-parent",
49+
"dns_name": "nexus.example.com",
4850
"interfaces": [
4951
{
5052
"cable": null,
@@ -117,7 +119,7 @@
117119
"address": "172.16.180.12/24",
118120
"custom_fields": {},
119121
"description": "",
120-
"dns_name": "",
122+
"dns_name": "nexus.example.com",
121123
"family": {
122124
"label": "IPv4",
123125
"value": 4
@@ -153,6 +155,7 @@
153155
],
154156
"is_virtual": false,
155157
"manufacturer": "cisco",
158+
"primary_ip4": "172.16.180.12",
156159
"regions": [
157160
"test-region",
158161
"parent-region"

tests/integration/targets/inventory-latest/files/test-inventory-options.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,14 @@
8181
"tags": []
8282
},
8383
"VC1": {
84+
"ansible_host": "172.16.180.12",
8485
"custom_fields": {},
8586
"device_type": "nexus-parent",
8687
"display": "Test Nexus One",
88+
"dns_name": "nexus.example.com",
8789
"is_virtual": false,
8890
"manufacturer": "cisco",
91+
"primary_ip4": "172.16.180.12",
8992
"regions": [
9093
"test-region",
9194
"parent-region"

tests/integration/targets/inventory-latest/files/test-inventory-options.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interfaces: False
1616
services: False
1717
group_names_raw: True
1818
virtual_chassis_name: True
19+
dns_name: True
1920

2021
group_by:
2122
- site

tests/integration/targets/inventory-latest/files/test-inventory-plurals-flatten.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"tags": []
5656
},
5757
"Test Nexus One": {
58+
"ansible_host": "172.16.180.12",
5859
"device_roles": [
5960
"core-switch"
6061
],
@@ -68,6 +69,7 @@
6869
"manufacturers": [
6970
"cisco"
7071
],
72+
"primary_ip4": "172.16.180.12",
7173
"regions": [
7274
"test-region",
7375
"parent-region"

tests/integration/targets/inventory-latest/files/test-inventory-plurals.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"tags": []
3838
},
3939
"Test Nexus One": {
40+
"ansible_host": "172.16.180.12",
4041
"config_context": [
4142
{}
4243
],
@@ -47,6 +48,7 @@
4748
"device_types": [
4849
"nexus-parent"
4950
],
51+
"dns_name": "nexus.example.com",
5052
"interfaces": [
5153
{
5254
"cable": null,
@@ -119,7 +121,7 @@
119121
"address": "172.16.180.12/24",
120122
"custom_fields": {},
121123
"description": "",
122-
"dns_name": "",
124+
"dns_name": "nexus.example.com",
123125
"family": {
124126
"label": "IPv4",
125127
"value": 4
@@ -160,6 +162,7 @@
160162
"manufacturers": [
161163
"cisco"
162164
],
165+
"primary_ip4": "172.16.180.12",
163166
"regions": [
164167
"test-region",
165168
"parent-region"

tests/integration/targets/inventory-latest/files/test-inventory.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
"tags": []
2323
},
2424
"Test Nexus One": {
25+
"ansible_host": "172.16.180.12",
2526
"config_context": {},
2627
"custom_fields": {},
2728
"device_type": "nexus-parent",
29+
"dns_name": "nexus.example.com",
2830
"interfaces": [
2931
{
3032
"cable": null,
@@ -97,7 +99,7 @@
9799
"address": "172.16.180.12/24",
98100
"custom_fields": {},
99101
"description": "",
100-
"dns_name": "",
102+
"dns_name": "nexus.example.com",
101103
"family": {
102104
"label": "IPv4",
103105
"value": 4
@@ -133,6 +135,7 @@
133135
],
134136
"is_virtual": false,
135137
"manufacturer": "cisco",
138+
"primary_ip4": "172.16.180.12",
136139
"regions": [
137140
"test-region",
138141
"parent-region"
@@ -843,14 +846,14 @@
843846
"test100"
844847
]
845848
},
846-
"service_ssh": {
849+
"service_http": {
847850
"hosts": [
848-
"Test VM With Spaces",
849851
"test100"
850852
]
851853
},
852-
"service_http": {
854+
"service_ssh": {
853855
"hosts": [
856+
"Test VM With Spaces",
854857
"test100"
855858
]
856859
},

tests/integration/targets/inventory-v2.8/files/test-inventory-legacy.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"tags": []
3535
},
3636
"Test Nexus One": {
37+
"ansible_host": "172.16.180.12",
3738
"custom_fields": {},
3839
"device_roles": [
3940
"core-switch"
@@ -48,6 +49,7 @@
4849
"manufacturers": [
4950
"cisco"
5051
],
52+
"primary_ip4": "172.16.180.12",
5153
"regions": [
5254
"test-region",
5355
"parent-region"

0 commit comments

Comments
 (0)