diff --git a/plugins/lookup/lookup.py b/plugins/lookup/lookup.py index 175b1dd5..eccfa545 100644 --- a/plugins/lookup/lookup.py +++ b/plugins/lookup/lookup.py @@ -233,6 +233,7 @@ def get_endpoint(nautobot, term): "virtualization-interfaces": {"endpoint": nautobot.virtualization.interfaces}, "vlan-groups": {"endpoint": nautobot.ipam.vlan_groups}, "vlans": {"endpoint": nautobot.ipam.vlans}, + "vlan-location-assignments": {"endpoint": nautobot.ipam.vlan_location_assignments}, "vrfs": {"endpoint": nautobot.ipam.vrfs}, } diff --git a/plugins/module_utils/ipam.py b/plugins/module_utils/ipam.py index 605bbd58..e0530cc5 100644 --- a/plugins/module_utils/ipam.py +++ b/plugins/module_utils/ipam.py @@ -24,6 +24,7 @@ NB_ROUTE_TARGETS = "route_targets" NB_VLANS = "vlans" NB_VLAN_GROUPS = "vlan_groups" +NB_VLAN_LOCATIONS = "vlan_location_assignments" NB_VRFS = "vrfs" NB_SERVICES = "services" @@ -144,6 +145,8 @@ def run(self): name = data.get("address") elif self.endpoint in ["prefixes"]: name = data.get("prefix") + elif self.endpoint == "vlan_location_assignments": + name = data.get("display") else: name = data.get("name") diff --git a/plugins/module_utils/utils.py b/plugins/module_utils/utils.py index 3443ba33..bf20947a 100644 --- a/plugins/module_utils/utils.py +++ b/plugins/module_utils/utils.py @@ -83,6 +83,7 @@ "services", "vlans", "vlan_groups", + "vlan_location_assignments", "vrfs", ], plugins=[], @@ -266,6 +267,7 @@ "virtual_machines": "virtual_machine", "vlans": "vlan", "vlan_groups": "vlan_group", + "vlan_location_assignments": "vlan_location_assignments", "vrfs": "vrf", } @@ -348,6 +350,7 @@ "virtual_machine": set(["name", "cluster"]), "vlan": set(["name", "location", "tenant", "vid", "vlan_group"]), "vlan_group": set(["name", "location"]), + "vlan_location_assignments": set(["vlan", "location"]), "vm_interface": set(["name", "virtual_machine"]), "vrf": set(["name", "namespace", "rd"]), } diff --git a/plugins/modules/vlan_location.py b/plugins/modules/vlan_location.py new file mode 100644 index 00000000..29be9231 --- /dev/null +++ b/plugins/modules/vlan_location.py @@ -0,0 +1,102 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2024, Network to Code (@networktocode) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: vlan_location +short_description: Create, update or delete Location assignments to VLANs within Nautobot +description: + - Create, update or delete Location assignments to VLANs within Nautobot +notes: + - This module requires Nautobot v2.2+ + - This should be ran with connection C(local) and hosts C(localhost) +author: + - Joe Wesch (@joewesch) +version_added: "5.3.0" +extends_documentation_fragment: + - networktocode.nautobot.fragments.base +options: + vlan: + description: + - The VLAN to associate with the location + required: true + type: raw + location: + description: + - The location the VLAN will be associated to + required: true + type: raw +""" + +EXAMPLES = r""" +- name: "Test Nautobot modules" + connection: local + hosts: localhost + gather_facts: False + + tasks: + - name: Assign Location to VLAN + networktocode.nautobot.vlan_location: + url: http://nautobot.local + token: thisIsMyToken + vlan: Test VLAN + location: + name: My Child Location + parent: My Parent Location + state: present + + - name: Unassign Location from VLAN + networktocode.nautobot.vlan_location: + url: http://nautobot.local + token: thisIsMyToken + vlan: Test VLAN + location: My Location + state: absent +""" + +RETURN = r""" +vlan_location_assignments: + description: Serialized object as created or already existent within Nautobot + returned: success (when I(state=present)) + type: dict +msg: + description: Message indicating failure or info about what has been achieved + returned: always + type: str +""" + +from ansible_collections.networktocode.nautobot.plugins.module_utils.utils import NAUTOBOT_ARG_SPEC +from ansible_collections.networktocode.nautobot.plugins.module_utils.ipam import ( + NautobotIpamModule, + NB_VLAN_LOCATIONS, +) +from ansible.module_utils.basic import AnsibleModule +from copy import deepcopy + + +def main(): + """ + Main entry point for module execution + """ + argument_spec = deepcopy(NAUTOBOT_ARG_SPEC) + argument_spec.update( + dict( + vlan=dict(required=True, type="raw"), + location=dict(required=True, type="raw"), + ) + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + vlan_location = NautobotIpamModule(module, NB_VLAN_LOCATIONS) + vlan_location.run() + + +if __name__ == "__main__": # pragma: no cover + main() diff --git a/tests/integration/nautobot-populate.py b/tests/integration/nautobot-populate.py index 04640ce2..c3b2778c 100755 --- a/tests/integration/nautobot-populate.py +++ b/tests/integration/nautobot-populate.py @@ -187,6 +187,7 @@ def make_nautobot_calls(endpoint, payload): "vlan_group": test_vlan_group.id, "status": {"name": "Active"}, }, + {"name": "Test VLAN 600", "vid": 600, "status": {"name": "Active"}}, ] created_vlans = make_nautobot_calls(nb.ipam.vlans, vlans) diff --git a/tests/integration/targets/latest/tasks/main.yml b/tests/integration/targets/latest/tasks/main.yml index 80784d29..d497f62d 100644 --- a/tests/integration/targets/latest/tasks/main.yml +++ b/tests/integration/targets/latest/tasks/main.yml @@ -520,3 +520,12 @@ - namespace tags: - namespace + +- name: "PYNAUTOBOT_VLAN_LOCATION TESTS" + include_tasks: + file: "vlan_location.yml" + apply: + tags: + - vlan_location + tags: + - vlan_location diff --git a/tests/integration/targets/latest/tasks/vlan_location.yml b/tests/integration/targets/latest/tasks/vlan_location.yml new file mode 100644 index 00000000..74dfe6a1 --- /dev/null +++ b/tests/integration/targets/latest/tasks/vlan_location.yml @@ -0,0 +1,90 @@ +--- +## +## +### PYNAUTOBOT_VLAN_LOCATION +## +## +- block: + - set_fact: + test_vlan: "{{ lookup('networktocode.nautobot.lookup', 'vlans', api_endpoint=nautobot_url, token=nautobot_token, api_filter='name=\"Test VLAN 600\"') }}" + test_location: "{{ lookup('networktocode.nautobot.lookup', 'locations', api_endpoint=nautobot_url, token=nautobot_token, api_filter='name=\"Child Test Location\" parent=\"Parent Test Location\"') }}" + + - name: "VLAN Location 1: Create VLAN Location assignment" + networktocode.nautobot.vlan_location: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + vlan: Test VLAN 600 + location: + name: Child Test Location + parent: Parent Test Location + state: present + register: test_one + + - name: "VLAN Location 1: ASSERT - Create VLAN Location assignment" + assert: + that: + - test_one is changed + - test_one['diff']['before']['state'] == "absent" + - test_one['diff']['after']['state'] == "present" + - test_one['vlan_location_assignments']['vlan'] == test_vlan['key'] + - test_one['vlan_location_assignments']['location'] == test_location['key'] + - test_one['msg'] == "vlan_location_assignments Test VLAN 600 (600): Child Test Location created" + + - name: "VLAN Location 2: Create idempotent" + networktocode.nautobot.vlan_location: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + vlan: Test VLAN 600 + location: + name: Child Test Location + parent: Parent Test Location + state: present + register: test_two + + - name: "VLAN Location 2: ASSERT - Create idempotent" + assert: + that: + - not test_two['changed'] + - test_two['vlan_location_assignments']['vlan'] == test_vlan['key'] + - test_two['vlan_location_assignments']['location'] == test_location['key'] + - test_two['msg'] == "vlan_location_assignments Test VLAN 600 (600): Child Test Location already exists" + + - name: "VLAN Location 3: Delete VLAN Location assignment" + networktocode.nautobot.vlan_location: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + vlan: Test VLAN 600 + location: + name: Child Test Location + parent: Parent Test Location + state: absent + register: test_three + + - name: "VLAN Location 3: ASSERT - Delete VLAN Location assignment" + assert: + that: + - test_three is changed + - test_three['diff']['before']['state'] == "present" + - test_three['diff']['after']['state'] == "absent" + - "'deleted' in test_three['msg']" + + - name: "VLAN Location 4: Delete idempotent" + networktocode.nautobot.vlan_location: + url: "{{ nautobot_url }}" + token: "{{ nautobot_token }}" + vlan: Test VLAN 600 + location: + name: Child Test Location + parent: Parent Test Location + state: absent + register: test_four + + - name: "VLAN Location 4: ASSERT - Delete idempotent" + assert: + that: + - not test_four['changed'] + - "'already absent' in test_four['msg']" + + when: + # VLAN to Location assignments are only in Nautobot v2.2+ + - "nautobot_version is version('2.2', '>=')"