diff --git a/README.md b/README.md index 4c87a17e7..37fbc6a4c 100644 --- a/README.md +++ b/README.md @@ -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+ diff --git a/plugins/module_utils/netbox_utils.py b/plugins/module_utils/netbox_utils.py index 21691467a..618f6d738 100644 --- a/plugins/module_utils/netbox_utils.py +++ b/plugins/module_utils/netbox_utils.py @@ -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 @@ -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"]), @@ -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 @@ -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( @@ -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: @@ -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] @@ -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 @@ -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 diff --git a/tests/integration/integration-tests.yml b/tests/integration/integration-tests.yml index ad4d9b08e..650c50bd1 100644 --- a/tests/integration/integration-tests.yml +++ b/tests/integration/integration-tests.yml @@ -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 @@ -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 @@ -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 @@ -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" @@ -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" @@ -4271,7 +4270,6 @@ - test_service_delete['diff']['before']['state'] == "present" - test_service_delete['msg'] == "services node-exporter deleted" - ## ## ### NETBOX_LOOKUP @@ -4317,7 +4315,7 @@ device_role: "Core Switch" site: "Test Site" status: "Staged" - tags: + tags: - "nolookup" state: present @@ -4331,7 +4329,7 @@ device_role: "Core Switch" site: "Test Site" status: "Staged" - tags: + tags: - "lookup" state: present @@ -4339,7 +4337,4 @@ 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') }}" diff --git a/tests/unit/module_utils/fixtures/choices/circuits.json b/tests/unit/module_utils/fixtures/choices/circuits.json new file mode 100644 index 000000000..e7f112b72 --- /dev/null +++ b/tests/unit/module_utils/fixtures/choices/circuits.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/tests/unit/module_utils/fixtures/choices/device_types.json b/tests/unit/module_utils/fixtures/choices/device_types.json new file mode 100644 index 000000000..e82964eef --- /dev/null +++ b/tests/unit/module_utils/fixtures/choices/device_types.json @@ -0,0 +1,12 @@ +{ + "subdevice_role": [ + { + "value": "parent", + "display_name": "Parent" + }, + { + "value": "child", + "display_name": "Child" + } + ] +} \ No newline at end of file diff --git a/tests/unit/module_utils/fixtures/choices/devices.json b/tests/unit/module_utils/fixtures/choices/devices.json new file mode 100644 index 000000000..5bf1f2f39 --- /dev/null +++ b/tests/unit/module_utils/fixtures/choices/devices.json @@ -0,0 +1,42 @@ +{ + "face": [ + { + "value": "front", + "display_name": "Front" + }, + { + "value": "rear", + "display_name": "Rear" + } + ], + "status": [ + { + "value": "offline", + "display_name": "Offline" + }, + { + "value": "active", + "display_name": "Active" + }, + { + "value": "planned", + "display_name": "Planned" + }, + { + "value": "staged", + "display_name": "Staged" + }, + { + "value": "failed", + "display_name": "Failed" + }, + { + "value": "inventory", + "display_name": "Inventory" + }, + { + "value": "decommissioning", + "display_name": "Decommissioning" + } + ] +} \ No newline at end of file diff --git a/tests/unit/module_utils/fixtures/choices/interfaces.json b/tests/unit/module_utils/fixtures/choices/interfaces.json new file mode 100644 index 000000000..2bda690ff --- /dev/null +++ b/tests/unit/module_utils/fixtures/choices/interfaces.json @@ -0,0 +1,306 @@ +{ + "type": [ + { + "value": "virtual", + "display_name": "Virtual" + }, + { + "value": "lag", + "display_name": "Link Aggregation Group (LAG)" + }, + { + "value": "100base-tx", + "display_name": "100BASE-TX (10/100ME)" + }, + { + "value": "1000base-t", + "display_name": "1000BASE-T (1GE)" + }, + { + "value": "2.5gbase-t", + "display_name": "2.5GBASE-T (2.5GE)" + }, + { + "value": "5gbase-t", + "display_name": "5GBASE-T (5GE)" + }, + { + "value": "10gbase-t", + "display_name": "10GBASE-T (10GE)" + }, + { + "value": "10gbase-cx4", + "display_name": "10GBASE-CX4 (10GE)" + }, + { + "value": "1000base-x-gbic", + "display_name": "GBIC (1GE)" + }, + { + "value": "1000base-x-sfp", + "display_name": "SFP (1GE)" + }, + { + "value": "10gbase-x-sfpp", + "display_name": "SFP+ (10GE)" + }, + { + "value": "10gbase-x-xfp", + "display_name": "XFP (10GE)" + }, + { + "value": "10gbase-x-xenpak", + "display_name": "XENPAK (10GE)" + }, + { + "value": "10gbase-x-x2", + "display_name": "X2 (10GE)" + }, + { + "value": "25gbase-x-sfp28", + "display_name": "SFP28 (25GE)" + }, + { + "value": "40gbase-x-qsfpp", + "display_name": "QSFP+ (40GE)" + }, + { + "value": "50gbase-x-sfp28", + "display_name": "QSFP28 (50GE)" + }, + { + "value": "100gbase-x-cfp", + "display_name": "CFP (100GE)" + }, + { + "value": "100gbase-x-cfp2", + "display_name": "CFP2 (100GE)" + }, + { + "value": "200gbase-x-cfp2", + "display_name": "CFP2 (200GE)" + }, + { + "value": "100gbase-x-cfp4", + "display_name": "CFP4 (100GE)" + }, + { + "value": "100gbase-x-cpak", + "display_name": "Cisco CPAK (100GE)" + }, + { + "value": "100gbase-x-qsfp28", + "display_name": "QSFP28 (100GE)" + }, + { + "value": "200gbase-x-qsfp56", + "display_name": "QSFP56 (200GE)" + }, + { + "value": "400gbase-x-qsfpdd", + "display_name": "QSFP-DD (400GE)" + }, + { + "value": "400gbase-x-osfp", + "display_name": "OSFP (400GE)" + }, + { + "value": "ieee802.11a", + "display_name": "IEEE 802.11a" + }, + { + "value": "ieee802.11g", + "display_name": "IEEE 802.11b/g" + }, + { + "value": "ieee802.11n", + "display_name": "IEEE 802.11n" + }, + { + "value": "ieee802.11ac", + "display_name": "IEEE 802.11ac" + }, + { + "value": "ieee802.11ad", + "display_name": "IEEE 802.11ad" + }, + { + "value": "ieee802.11ax", + "display_name": "IEEE 802.11ax" + }, + { + "value": "gsm", + "display_name": "GSM" + }, + { + "value": "cdma", + "display_name": "CDMA" + }, + { + "value": "lte", + "display_name": "LTE" + }, + { + "value": "sonet-oc3", + "display_name": "OC-3/STM-1" + }, + { + "value": "sonet-oc12", + "display_name": "OC-12/STM-4" + }, + { + "value": "sonet-oc48", + "display_name": "OC-48/STM-16" + }, + { + "value": "sonet-oc192", + "display_name": "OC-192/STM-64" + }, + { + "value": "sonet-oc768", + "display_name": "OC-768/STM-256" + }, + { + "value": "sonet-oc1920", + "display_name": "OC-1920/STM-640" + }, + { + "value": "sonet-oc3840", + "display_name": "OC-3840/STM-1234" + }, + { + "value": "1gfc-sfp", + "display_name": "SFP (1GFC)" + }, + { + "value": "2gfc-sfp", + "display_name": "SFP (2GFC)" + }, + { + "value": "4gfc-sfp", + "display_name": "SFP (4GFC)" + }, + { + "value": "8gfc-sfpp", + "display_name": "SFP+ (8GFC)" + }, + { + "value": "16gfc-sfpp", + "display_name": "SFP+ (16GFC)" + }, + { + "value": "32gfc-sfp28", + "display_name": "SFP28 (32GFC)" + }, + { + "value": "128gfc-sfp28", + "display_name": "QSFP28 (128GFC)" + }, + { + "value": "inifiband-sdr", + "display_name": "SDR (2 Gbps)" + }, + { + "value": "inifiband-ddr", + "display_name": "DDR (4 Gbps)" + }, + { + "value": "inifiband-qdr", + "display_name": "QDR (8 Gbps)" + }, + { + "value": "inifiband-fdr10", + "display_name": "FDR10 (10 Gbps)" + }, + { + "value": "inifiband-fdr", + "display_name": "FDR (13.5 Gbps)" + }, + { + "value": "inifiband-edr", + "display_name": "EDR (25 Gbps)" + }, + { + "value": "inifiband-hdr", + "display_name": "HDR (50 Gbps)" + }, + { + "value": "inifiband-ndr", + "display_name": "NDR (100 Gbps)" + }, + { + "value": "inifiband-xdr", + "display_name": "XDR (250 Gbps)" + }, + { + "value": "t1", + "display_name": "T1 (1.544 Mbps)" + }, + { + "value": "e1", + "display_name": "E1 (2.048 Mbps)" + }, + { + "value": "t3", + "display_name": "T3 (45 Mbps)" + }, + { + "value": "e3", + "display_name": "E3 (34 Mbps)" + }, + { + "value": "cisco-stackwise", + "display_name": "Cisco StackWise" + }, + { + "value": "cisco-stackwise-plus", + "display_name": "Cisco StackWise Plus" + }, + { + "value": "cisco-flexstack", + "display_name": "Cisco FlexStack" + }, + { + "value": "cisco-flexstack-plus", + "display_name": "Cisco FlexStack Plus" + }, + { + "value": "juniper-vcp", + "display_name": "Juniper VCP" + }, + { + "value": "extreme-summitstack", + "display_name": "Extreme SummitStack" + }, + { + "value": "extreme-summitstack-128", + "display_name": "Extreme SummitStack-128" + }, + { + "value": "extreme-summitstack-256", + "display_name": "Extreme SummitStack-256" + }, + { + "value": "extreme-summitstack-512", + "display_name": "Extreme SummitStack-512" + }, + { + "value": "other", + "display_name": "Other" + } + ], + "mode": [ + { + "value": "access", + "display_name": "Access" + }, + { + "value": "tagged", + "display_name": "Tagged" + }, + { + "value": "tagged-all", + "display_name": "Tagged (All)" + } + ] +} \ No newline at end of file diff --git a/tests/unit/module_utils/fixtures/choices/prefixes.json b/tests/unit/module_utils/fixtures/choices/prefixes.json new file mode 100644 index 000000000..135ce2bba --- /dev/null +++ b/tests/unit/module_utils/fixtures/choices/prefixes.json @@ -0,0 +1,20 @@ +{ + "status": [ + { + "value": "container", + "display_name": "Container" + }, + { + "value": "active", + "display_name": "Active" + }, + { + "value": "reserved", + "display_name": "Reserved" + }, + { + "value": "deprecated", + "display_name": "Deprecated" + } + ] +} \ No newline at end of file diff --git a/tests/unit/module_utils/fixtures/choices/racks.json b/tests/unit/module_utils/fixtures/choices/racks.json new file mode 100644 index 000000000..3068db3da --- /dev/null +++ b/tests/unit/module_utils/fixtures/choices/racks.json @@ -0,0 +1,66 @@ +{ + "status": [ + { + "value": "reserved", + "display_name": "Reserved" + }, + { + "value": "available", + "display_name": "Available" + }, + { + "value": "planned", + "display_name": "Planned" + }, + { + "value": "active", + "display_name": "Active" + }, + { + "value": "deprecated", + "display_name": "Deprecated" + } + ], + "type": [ + { + "value": "2-post-frame", + "display_name": "2-post frame" + }, + { + "value": "4-post-frame", + "display_name": "4-post frame" + }, + { + "value": "4-post-cabinet", + "display_name": "4-post cabinet" + }, + { + "value": "wall-frame", + "display_name": "Wall-mounted frame" + }, + { + "value": "wall-cabinet", + "display_name": "Wall-mounted cabinet" + } + ], + "width": [ + { + "value": 19, + "display_name": "19 inches" + }, + { + "value": 23, + "display_name": "23 inches" + } + ], + "outer_unit": [ + { + "value": "mm", + "display_name": "Millimeters" + }, + { + "value": "in", + "display_name": "Inches" + } + ] +} \ No newline at end of file diff --git a/tests/unit/module_utils/fixtures/choices/services.json b/tests/unit/module_utils/fixtures/choices/services.json new file mode 100644 index 000000000..e14147a55 --- /dev/null +++ b/tests/unit/module_utils/fixtures/choices/services.json @@ -0,0 +1,12 @@ +{ + "protocol": [ + { + "value": "tcp", + "display_name": "TCP" + }, + { + "value": "udp", + "display_name": "UDP" + } + ] +} \ No newline at end of file diff --git a/tests/unit/module_utils/fixtures/choices/sites.json b/tests/unit/module_utils/fixtures/choices/sites.json new file mode 100644 index 000000000..636289373 --- /dev/null +++ b/tests/unit/module_utils/fixtures/choices/sites.json @@ -0,0 +1,16 @@ +{ + "status": [ + { + "value": "active", + "display_name": "Active" + }, + { + "value": "planned", + "display_name": "Planned" + }, + { + "value": "retired", + "display_name": "Retired" + } + ] +} \ No newline at end of file diff --git a/tests/unit/module_utils/fixtures/choices/virtual_machines.json b/tests/unit/module_utils/fixtures/choices/virtual_machines.json new file mode 100644 index 000000000..d4e781e8c --- /dev/null +++ b/tests/unit/module_utils/fixtures/choices/virtual_machines.json @@ -0,0 +1,16 @@ +{ + "status": [ + { + "value": "active", + "display_name": "Active" + }, + { + "value": "offline", + "display_name": "Offline" + }, + { + "value": "staged", + "display_name": "Staged" + } + ] +} \ No newline at end of file diff --git a/tests/unit/module_utils/fixtures/choices/vlans.json b/tests/unit/module_utils/fixtures/choices/vlans.json new file mode 100644 index 000000000..1f0e6765f --- /dev/null +++ b/tests/unit/module_utils/fixtures/choices/vlans.json @@ -0,0 +1,16 @@ +{ + "status": [ + { + "value": "active", + "display_name": "Active" + }, + { + "value": "reserved", + "display_name": "Reserved" + }, + { + "value": "deprecated", + "display_name": "Deprecated" + } + ] +} \ No newline at end of file diff --git a/tests/unit/module_utils/test_netbox_base_class.py b/tests/unit/module_utils/test_netbox_base_class.py index e0e8c15fc..45181767e 100644 --- a/tests/unit/module_utils/test_netbox_base_class.py +++ b/tests/unit/module_utils/test_netbox_base_class.py @@ -3,10 +3,6 @@ # Copyright: (c) 2019, Mikhail Yohman (@FragmentedPacket) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -# import unittest -# from units.compat import unittest -# from units.compat.mock import patch, MagicMock, Mock - import pytest from unittest.mock import patch, MagicMock, Mock from ansible.module_utils.basic import AnsibleModule @@ -48,6 +44,13 @@ def fixture_arg_spec(): } +@pytest.fixture +def choices_data(choice): + with open(f"{choice}.json", "r") as f: + choice_data = f.read() + return choice_data + + @pytest.fixture def normalized_data(): return { @@ -367,178 +370,53 @@ def test_update_netbox_object_with_changes_check_mode_true( @pytest.mark.parametrize( "endpoint, data, expected", [ - ( - "circuits", - { - "status": "Active", - "status": "Offline", - "status": "Planned", - "status": "Provisioning", - "status": "Deprovisioning", - "status": "Decommissioned", - "status": 1, - }, - { - "status": 1, - "status": 4, - "status": 2, - "status": 3, - "status": 0, - "status": 5, - "status": 1, - }, - ), + ("circuits", {"status": "Active"}, {"status": "temp"},), + ("circuits", {"status": 1}, {"status": 1},), ( "devices", - { - "status": "Active", - "status": "Offline", - "status": "Planned", - "status": "Staged", - "status": "Failed", - "status": "Inventory", - "status": 5, - "face": "Front", - "face": "Rear", - "face": 1, - }, - { - "status": 1, - "status": 0, - "status": 2, - "status": 3, - "status": 4, - "status": 5, - "face": 0, - "face": 1, - "face": 1, - }, - ), - ( - "device_types", - {"subdevice_role": "Parent", "subdevice_role": "Child"}, - {"subdevice_role": True, "subdevice_role": False}, + {"status": "Active", "face": "Front"}, + {"status": "temp", "face": "temp"}, ), + ("devices", {"status": 5, "face": 1}, {"status": 5, "face": 1},), + ("device_types", {"subdevice_role": "Parent"}, {"subdevice_role": "temp"},), + ("device_types", {"subdevice_role": "Child"}, {"subdevice_role": "temp"},), ( "interfaces", - { - "form_factor": "1000base-t (1ge)", - "mode": "Access", - "mode": "Tagged", - "mode": "Tagged all", - "mode": 100, - }, - {"form_factor": 1000, "mode": 100, "mode": 200, "mode": 300, "mode": 100}, + {"form_factor": "1000base-t (1ge)", "mode": "Access"}, + {"form_factor": "temp", "mode": "temp"}, ), + ("interfaces", {"mode": 100}, {"mode": 100},), ( "ip_addresses", - { - "status": "Active", - "status": "Reserved", - "status": "Deprecated", - "status": "DHCP", - "status": 1, - "role": "Loopback", - "role": "Secondary", - "role": "Anycast", - "role": "VIP", - "role": "VRRP", - "role": "HSRP", - "role": "GLBP", - "role": "CARP", - "role": 30, - }, - { - "status": 1, - "status": 2, - "status": 3, - "status": 5, - "status": 1, - "role": 10, - "role": 20, - "role": 30, - "role": 40, - "role": 41, - "role": 42, - "role": 43, - "role": 44, - "role": 30, - }, - ), - ( - "prefixes", - { - "status": "Active", - "status": "Container", - "status": "Reserved", - "status": "Deprecated", - "status": 2, - }, - {"status": 1, "status": 0, "status": 2, "status": 3, "status": 2}, + {"status": "Active", "role": "Loopback"}, + {"status": "temp", "role": "temp"}, ), + ("ip_addresses", {"status": 1, "role": 30}, {"status": 1, "role": 30},), + ("prefixes", {"status": "Active"}, {"status": "temp"},), + ("prefixes", {"status": 2}, {"status": 2},), ( "racks", - { - "status": "Active", - "status": "Planned", - "status": "Reserved", - "status": "Available", - "status": "Deprecated", - "outer_unit": "Inches", - "outer_unit": "Millimeters", - "type": "2-post Frame", - "type": "4-post Frame", - "type": "4-post Cabinet", - "type": "Wall-mounted Frame", - "type": "Wall-mounted Cabinet", - "type": 1100, - }, - { - "status": 3, - "status": 2, - "status": 0, - "status": 1, - "status": 4, - "outer_unit": 2000, - "outer_unit": 1000, - "type": 100, - "type": 200, - "type": 300, - "type": 1000, - "type": 1100, - "type": 1100, - }, - ), - ( - "sites", - {"status": "Active", "status": "Planned", "status": "Retired", "status": 2}, - {"status": 1, "status": 2, "status": 4, "status": 2}, + {"status": "Active", "outer_unit": "Inches", "type": "2-post Frame",}, + {"status": "temp", "outer_unit": "temp", "type": "temp",}, ), + ("racks", {"status": 0, "type": 1100,}, {"status": 0, "type": 1100,},), + ("sites", {"status": "Active"}, {"status": "temp"}), + ("sites", {"status": 2}, {"status": 2}), ( "virtual_machines", - { - "status": "Offline", - "status": "Active", - "status": "Staged", - "face": "Front", - "face": "Rear", - "face": 0, - }, - {"status": 0, "status": 1, "status": 3, "face": 0, "face": 1, "face": 0}, - ), - ( - "vlans", - { - "status": "Active", - "status": "Reserved", - "status": "Deprecated", - "status": 2, - }, - {"status": 1, "status": 2, "status": 3, "status": 2}, + {"status": "Offline", "face": "Front"}, + {"status": "temp", "face": "temp"}, ), + ("virtual_machines", {"status": 1, "face": 0}, {"status": 1, "face": 0},), + ("vlans", {"status": "Active"}, {"status": "temp"}), + ("vlans", {"status": 2}, {"status": 2}), ], ) -def test_change_choices_id(mock_netbox_module, endpoint, data, expected): +def test_change_choices_id(mocker, mock_netbox_module, endpoint, data, expected): + fetch_choice_value = mocker.patch( + "%s%s" % (MOCKER_PATCH_PATH, "._fetch_choice_value") + ) + fetch_choice_value.return_value = "temp" new_data = mock_netbox_module._change_choices_id(endpoint, data) assert new_data == expected