Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
[![Build Status](https://travis-ci.com/netbox-community/ansible_modules.svg?branch=devel)](https://travis-ci.com/netbox-community/ansible_modules)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
![Release](https://img.shields.io/github/v/release/netbox-community/ansible_modules)

# Netbox modules for Ansible using Ansible Collections

## Requirements

- Netbox 2.5+
- **pynetbox 4.1.0+**
- **pynetbox 4.2.5+**
- Python 3.6+
- Ansible 2.9+

Expand Down
168 changes: 39 additions & 129 deletions plugins/module_utils/netbox_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

# Import necessary packages
import traceback
from itertools import chain
from ansible.module_utils.compat import ipaddress
from ansible.module_utils._text import to_text

Expand Down Expand Up @@ -180,111 +181,6 @@
"vrfs": "vrf",
}

FACE_ID = dict(front=0, rear=1)

DEVICE_STATUS = dict(offline=0, active=1, planned=2, staged=3, failed=4, inventory=5)

IP_ADDRESS_STATUS = dict(active=1, reserved=2, deprecated=3, dhcp=5)

IP_ADDRESS_ROLE = dict(
loopback=10, secondary=20, anycast=30, vip=40, vrrp=41, hsrp=42, glbp=43, carp=44
)

PREFIX_STATUS = dict(container=0, active=1, reserved=2, deprecated=3)

SITE_STATUS = dict(active=1, planned=2, retired=4)

RACK_STATUS = dict(active=3, planned=2, reserved=0, available=1, deprecated=4)

RACK_UNIT = dict(millimeters=1000, inches=2000)

SUBDEVICE_ROLES = dict(parent=True, child=False)

VLAN_STATUS = dict(active=1, reserved=2, deprecated=3)

SERVICE_PROTOCOL = dict(tcp=6, udp=17)

RACK_TYPE = {
"2-post frame": 100,
"4-post frame": 200,
"4-post cabinet": 300,
"wall-mounted frame": 1000,
"wall-mounted cabinet": 1100,
}

INTF_FORM_FACTOR = {
"virtual": 0,
"link aggregation group (lag)": 200,
"100base-tx (10/100me)": 800,
"1000base-t (1ge)": 1000,
"10gbase-t (10ge)": 1150,
"10gbase-cx4 (10ge)": 1170,
"gbic (1ge)": 1050,
"sfp (1ge)": 1100,
"2.5gbase-t (2.5ge)": 1120,
"5gbase-t (5ge)": 1130,
"sfp+ (10ge)": 1200,
"xfp (10ge)": 1300,
"xenpak (10ge)": 1310,
"x2 (10ge)": 1320,
"sfp28 (25ge)": 1350,
"qsfp+ (40ge)": 1400,
"qsfp28 (50ge)": 1420,
"cfp (100ge)": 1500,
"cfp2 (100ge)": 1510,
"cfp2 (200ge)": 1650,
"cfp4 (100ge)": 1520,
"cisco cpak (100ge)": 1550,
"qsfp28 (100ge)": 1600,
"qsfp56 (200ge)": 1700,
"qsfp-dd (400ge)": 1750,
"ieee 802.11a": 2600,
"ieee 802.11b/g": 2610,
"ieee 802.11n": 2620,
"ieee 802.11ac": 2630,
"ieee 802.11ad": 2640,
"gsm": 2810,
"cdma": 2820,
"lte": 2830,
"oc-3/stm-1": 6100,
"oc-12/stm-4": 6200,
"oc-48/stm-16": 6300,
"oc-192/stm-64": 6400,
"oc-768/stm-256": 6500,
"oc-1920/stm-640": 6600,
"oc-3840/stm-1234": 6700,
"sfp (1gfc)": 3010,
"sfp (2gfc)": 3020,
"sfp (4gfc)": 3040,
"sfp+ (8gfc)": 3080,
"sfp+ (16gfc)": 3160,
"sfp28 (32gfc)": 3320,
"qsfp28 (128gfc)": 3400,
"t1 (1.544 mbps)": 4000,
"e1 (2.048 mbps)": 4010,
"t3 (45 mbps)": 4040,
"e3 (34 mbps)": 4050,
"cisco stackwise": 5000,
"cisco stackwise plus": 5050,
"cisco flexstack": 5100,
"cisco flexstack plus": 5150,
"juniper vcp": 5200,
"extreme summitstack": 5300,
"extreme summitstack-128": 5310,
"extreme summitstack-256": 5320,
"extreme summitstack-512": 5330,
"other": 32767,
}

INTF_MODE = {"access": 100, "tagged": 200, "tagged all": 300}

VIRTUAL_MACHINE_STATUS = dict(offline=0, active=1, staged=3)

CIRCUIT_STATUS = dict(
deprovisioning=0, active=1, planned=2, provisioning=3, offline=4, decommissioned=5,
)

# This is used when attempting to search for existing endpoints
ALLOWED_QUERY_PARAMS = {
"aggregate": set(["prefix", "rir"]),
"circuit": set(["cid"]),
Expand Down Expand Up @@ -345,19 +241,18 @@
]
)

# This is used when converting static choices to an ID value acceptable to Netbox API
REQUIRED_ID_FIND = {
"circuits": [{"status": CIRCUIT_STATUS}],
"devices": [{"status": DEVICE_STATUS, "face": FACE_ID}],
"device_types": [{"subdevice_role": SUBDEVICE_ROLES}],
"interfaces": [{"form_factor": INTF_FORM_FACTOR, "mode": INTF_MODE}],
"ip_addresses": [{"status": IP_ADDRESS_STATUS, "role": IP_ADDRESS_ROLE}],
"prefixes": [{"status": PREFIX_STATUS}],
"racks": [{"status": RACK_STATUS, "outer_unit": RACK_UNIT, "type": RACK_TYPE}],
"services": [{"protocol": SERVICE_PROTOCOL}],
"sites": [{"status": SITE_STATUS}],
"virtual_machines": [{"status": VIRTUAL_MACHINE_STATUS, "face": FACE_ID}],
"vlans": [{"status": VLAN_STATUS}],
"circuits": set(["status"]),
"devices": set(["status", "face"]),
"device_types": set(["subdevice_role"]),
"interfaces": set(["form_factor", "mode"]),
"ip_addresses": set(["status", "role"]),
"prefixes": set(["status"]),
"racks": set(["status", "outer_unit", "type"]),
"services": set(["protocol"]),
"sites": set(["status"]),
"virtual_machines": set(["status", "face"]),
"vlans": set(["status"]),
}

# This is used to map non-clashing keys to Netbox API compliant keys to prevent bad logic in code for similar keys but different modules
Expand Down Expand Up @@ -417,6 +312,7 @@ def __init__(self, module, endpoint, nb_client=None):
self.state = self.module.params["state"]
self.check_mode = self.module.check_mode
self.endpoint = endpoint
self.version = None

if not HAS_PYNETBOX:
self.module.fail_json(
Expand Down Expand Up @@ -444,7 +340,7 @@ def _connect_netbox_api(self, url, token, ssl_verify):
try:
nb = pynetbox.api(url, token=token, ssl_verify=ssl_verify)
try:
self.version = nb.version
self.version = float(nb.version)
except AttributeError:
self.module.fail_json(msg="Must have pynetbox >=4.1.0")
except Exception:
Expand Down Expand Up @@ -487,6 +383,9 @@ def _convert_identical_keys(self, data):
Returns data
:params data (dict): Data dictionary after _find_ids method ran
"""
if self.version and self.version >= 2.7:
if data.get("form_factor"):
data["type"] = data.pop("form_factor")
for key in data:
if key in CONVERT_KEYS:
new_key = CONVERT_KEYS[key]
Expand Down Expand Up @@ -580,6 +479,23 @@ def _build_query_params(self, parent, module_data, child=None):

return query_dict

def _fetch_choice_value(self, search, endpoint):
app = self._find_app(endpoint)
nb_app = getattr(self.nb, app)
nb_endpoint = getattr(nb_app, endpoint)
endpoint_choices = nb_endpoint.choices()

choices = [x for x in chain.from_iterable(endpoint_choices.values())]

for item in choices:
if item["display_name"].lower() == search.lower():
return item["value"]
elif item["value"] == search.lower():
return item["value"]
self._handle_errors(
msg="%s was not found as a valid choice for %s" % (search, endpoint)
)

def _change_choices_id(self, endpoint, data):
"""Used to change data that is static and under _choices for the application.
ex. DEVICE_STATUS
Expand All @@ -590,17 +506,11 @@ def _change_choices_id(self, endpoint, data):
if REQUIRED_ID_FIND.get(endpoint):
required_choices = REQUIRED_ID_FIND[endpoint]
for choice in required_choices:
for key, value in choice.items():
if data.get(key):
if isinstance(data[key], int):
break
try:
data[key] = value[data[key].lower()]
except KeyError:
self._handle_errors(
msg="%s may not be a valid choice. If it is valid, please submit bug report."
% (key)
)
if data.get(choice):
if isinstance(data[choice], int):
continue
choice_value = self._fetch_choice_value(data[choice], endpoint)
data[choice] = choice_value

return data

Expand Down
27 changes: 11 additions & 16 deletions tests/integration/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@
device: test100
name: GigabitEthernet3
enabled: false
form_factor: 1000Base-t (1GE)
form_factor: 1000Base-T (1GE)
lag:
name: port-channel1
mtu: 1600
Expand Down Expand Up @@ -325,7 +325,7 @@
device: test100
name: GigabitEthernet21
enabled: false
form_factor: 1000Base-t (1GE)
form_factor: 1000Base-T (1GE)
untagged_vlan:
name: Wireless
site: Test Site
Expand Down Expand Up @@ -382,7 +382,7 @@
device: test100
name: GigabitEthernet4
enabled: false
form_factor: 1000Base-t (1GE)
form_factor: 1000Base-T (1GE)
lag: "port-channel1"
mtu: 1600
mgmt_only: false
Expand Down Expand Up @@ -4171,11 +4171,11 @@
- test_five['msg'] == "circuit_termination test_circuit_a deleted"
-

##
##
### NETBOX_SERVICE
##
##
##
##
### NETBOX_SERVICE
##
##
- name: "1 - Device with required information needs to add new service"
netbox_device:
netbox_url: "http://localhost:32768"
Expand Down Expand Up @@ -4250,7 +4250,6 @@
- test_service_update['diff']['after']['protocol'] == 17
- test_service_update['msg'] == "services node-exporter updated"


- name: "NETBOX_SERVICE: Test service deletion"
netbox_service:
netbox_url: "http://localhost:32768"
Expand All @@ -4271,7 +4270,6 @@
- test_service_delete['diff']['before']['state'] == "present"
- test_service_delete['msg'] == "services node-exporter deleted"


##
##
### NETBOX_LOOKUP
Expand Down Expand Up @@ -4317,7 +4315,7 @@
device_role: "Core Switch"
site: "Test Site"
status: "Staged"
tags:
tags:
- "nolookup"
state: present

Expand All @@ -4331,15 +4329,12 @@
device_role: "Core Switch"
site: "Test Site"
status: "Staged"
tags:
tags:
- "lookup"
state: present

- name: "NETBOX_LOOKUP 7: Device query returns exactly the L2 device"
assert:
that: "{{ query_result|json_query('[?value.display_name==`L2`]')|count }} == 1"
vars:
query_result: "{{ query('netbox_community.ansible_modules.netbox', 'devices', api_filter='role=core-switch tag=lookup', api_endpoint='http://localhost:32768', token='0123456789abcdef0123456789abcdef01234567') }}"



query_result: "{{ query('netbox_community.ansible_modules.netbox', 'devices', api_filter='role=core-switch tag=lookup', api_endpoint='http://localhost:32768', token='0123456789abcdef0123456789abcdef01234567') }}"
28 changes: 28 additions & 0 deletions tests/unit/module_utils/fixtures/choices/circuits.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"status": [
{
"value": "planned",
"display_name": "Planned"
},
{
"value": "provisioning",
"display_name": "Provisioning"
},
{
"value": "active",
"display_name": "Active"
},
{
"value": "offline",
"display_name": "Offline"
},
{
"value": "deprovisioning",
"display_name": "Deprovisioning"
},
{
"value": "decommissioned",
"display_name": "Decommissioned"
}
]
}
12 changes: 12 additions & 0 deletions tests/unit/module_utils/fixtures/choices/device_types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"subdevice_role": [
{
"value": "parent",
"display_name": "Parent"
},
{
"value": "child",
"display_name": "Child"
}
]
}
Loading