diff --git a/meta/runtime.yml b/meta/runtime.yml index a512a116d..5724e7798 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -47,6 +47,7 @@ action_groups: - netbox_ipam_role - netbox_journal_entry - netbox_l2vpn + - netbox_l2vpn_termination - netbox_location - netbox_manufacturer - netbox_module_type diff --git a/plugins/module_utils/netbox_ipam.py b/plugins/module_utils/netbox_ipam.py index e376acd0f..64bbc314a 100644 --- a/plugins/module_utils/netbox_ipam.py +++ b/plugins/module_utils/netbox_ipam.py @@ -198,6 +198,12 @@ def run(self): data.get("interface_type"), data.get("interface_id"), ) + elif self.endpoint == "l2vpn_terminations": + name = "l2vpn %s <> %s %s" % ( + data.get("l2vpn"), + data.get("assigned_object_type"), + data.get("assigned_object_id"), + ) else: name = data.get("name") diff --git a/plugins/module_utils/netbox_utils.py b/plugins/module_utils/netbox_utils.py index ce1b6462c..497a7c544 100644 --- a/plugins/module_utils/netbox_utils.py +++ b/plugins/module_utils/netbox_utils.py @@ -140,6 +140,7 @@ inventory_item_role="name", import_targets="name", l2vpn="name", + l2vpn_termination="id", location="slug", manufacturer="slug", module_type="model", @@ -329,6 +330,7 @@ "inventory_item_roles": "inventory_item_role", "ip_addresses": "ip_address", "l2vpns": "l2vpn", + "l2vpn_terminations": "l2vpn_termination", "locations": "location", "manufacturers": "manufacturer", "module_types": "module_type", @@ -440,6 +442,9 @@ ["address", "vrf", "device", "interface", "assigned_object", "virtual_machine"] ), "l2vpn": set(["name"]), + "l2vpn_termination": set( + ["l2vpn", "assigned_object_type", "interface_id", "vlan_id", "vminterface_id"] + ), "lag": set(["name"]), "location": set(["name", "slug", "site"]), "module_type": set(["model"]), @@ -1004,7 +1009,19 @@ def _build_query_params( "name": module_data.get("power_port_template"), } query_dict.update(power_port_template) - + elif parent == "l2vpn_termination": + query_param_mapping = { + "dcim.interface": "interface_id", + "ipam.vlan": "vlan_id", + "virtualization.vminterface": "vminterface_id", + } + query_key = query_param_mapping[module_data.get("assigned_object_type")] + query_dict.update( + { + "l2vpn_id": query_dict.pop("l2vpn"), + query_key: module_data.get("assigned_object_id"), + } + ) elif "_template" in parent: if query_dict.get("device_type"): query_dict["devicetype_id"] = query_dict.pop("device_type") diff --git a/plugins/modules/netbox_l2vpn_termination.py b/plugins/modules/netbox_l2vpn_termination.py new file mode 100644 index 000000000..d065abff5 --- /dev/null +++ b/plugins/modules/netbox_l2vpn_termination.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2023, Andrii Konts (@andrii-konts) +# 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: netbox_l2vpn_termination +short_description: Create, update or delete L2VPNs terminations within NetBox +description: + - Creates, updates or removes L2VPNs terminations from NetBox +notes: + - Tags should be defined as a YAML list + - This should be ran with connection C(local) and hosts C(localhost) +author: + - Andrii Konts (@andrii-konts) +requirements: + - pynetbox +seealso: + - name: FHRP Group Model reference + description: NetBox Documentation for FHRP Group Model. + link: https://docs.netbox.dev/en/stable/models/ipam/l2vpntermination/ +version_added: '3.13.0' +extends_documentation_fragment: + - netbox.netbox.common +options: + data: + type: dict + description: + - Defines the L2VPN termination configuration + suboptions: + l2vpn: + description: + - L2vpn object id + required: true + type: int + assigned_object_type: + description: + - Assigned object type + required: true + choices: + - dcim.interface + - ipam.vlan + - virtualization.vminterface + type: str + assigned_object_id: + description: + - Assigned object id + required: true + type: int + tags: + description: + - Any tags that the L2VPN termination may need to be associated with + required: false + type: list + elements: raw + custom_fields: + description: + - Must exist in NetBox + required: false + type: dict + required: true +""" + +EXAMPLES = r""" +- hosts: localhost + connection: local + module_defaults: + group/netbox.netbox.netbox: + netbox_url: "http://netbox.local" + netbox_token: "thisIsMyToken" + tasks: + - name: Create L2VPN termination within NetBox with only required information + netbox.netbox.netbox_l2vpn_termination: + data: + l2vpn: 1 + assigned_object_type: dcim.interface + assigned_object_id: 32 + state: present + + - name: Delete L2VPN termination within netbox + netbox.netbox.netbox_l2vpn_termination: + data: + l2vpn: 1 + assigned_object_type: dcim.interface + assigned_object_id: 32 + state: absent + +""" + +RETURN = r""" +l2vpn_termination: + description: Serialized object as created or already existent within NetBox + 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.netbox.netbox.plugins.module_utils.netbox_utils import ( + NetboxAnsibleModule, + NETBOX_ARG_SPEC, +) + +from ansible_collections.netbox.netbox.plugins.module_utils.netbox_ipam import ( + NetboxIpamModule, + NB_L2VPN_TERMINATIONS, +) + + +from copy import deepcopy + + +def main(): + """ + Main entry point for module execution + """ + argument_spec = deepcopy(NETBOX_ARG_SPEC) + argument_spec.update( + dict( + data=dict( + type="dict", + required=True, + options=dict( + l2vpn=dict(required=True, type="int"), + assigned_object_type=dict( + required=True, + choices=[ + "dcim.interface", + "ipam.vlan", + "virtualization.vminterface", + ], + ), + assigned_object_id=dict(required=True, type="int"), + tags=dict(required=False, type="list", elements="raw"), + custom_fields=dict(required=False, type="dict"), + ), + ), + ) + ) + + module = NetboxAnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + netbox_l2vpn_termination = NetboxIpamModule(module, NB_L2VPN_TERMINATIONS) + netbox_l2vpn_termination.run() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/netbox-deploy.py b/tests/integration/netbox-deploy.py index 6c9a19e61..fb5b7451d 100755 --- a/tests/integration/netbox-deploy.py +++ b/tests/integration/netbox-deploy.py @@ -546,6 +546,23 @@ def make_netbox_calls(endpoint, payload): ] created_route_targets = make_netbox_calls(nb.ipam.route_targets, route_targets) +## Create L2VPNs +l2vpns = [ + { + "identifier": 111111, + "name": "Test L2VPN 1", + "slug": "Test_L2VPN_1", + "type": "vxlan", + }, + { + "identifier": 222222, + "name": "Test L2VPN 2", + "slug": "Test_L2VPN_2", + "type": "vxlan", + }, +] +created_l2vpns = make_netbox_calls(nb.ipam.l2vpns, l2vpns) + if ERRORS: sys.exit( "Errors have occurred when creating objects, and should have been printed out. Check previous output." diff --git a/tests/integration/targets/v3.3/tasks/main.yml b/tests/integration/targets/v3.3/tasks/main.yml index f637a55fd..d35e6d3c5 100644 --- a/tests/integration/targets/v3.3/tasks/main.yml +++ b/tests/integration/targets/v3.3/tasks/main.yml @@ -247,6 +247,15 @@ tags: - netbox_l2vpn +- name: "NETBOX_L2VPN_TERMINATION TESTS" + include_tasks: + file: "netbox_l2vpn_termination.yml" + apply: + tags: + - netbox_l2vpn_termination + tags: + - netbox_l2vpn_termination + - name: "NETBOX_INVENTORY_ITEM_ROLE TESTS" include_tasks: file: "netbox_inventory_item_role.yml" diff --git a/tests/integration/targets/v3.3/tasks/netbox_l2vpn_termination.yml b/tests/integration/targets/v3.3/tasks/netbox_l2vpn_termination.yml new file mode 100644 index 000000000..a4560f407 --- /dev/null +++ b/tests/integration/targets/v3.3/tasks/netbox_l2vpn_termination.yml @@ -0,0 +1,94 @@ +--- +## +## +### NETBOX_L2VPN_TERMINATION +## +## +- name: "L2VPN_TERMINATION 1: Necessary info creation" + netbox.netbox.netbox_l2vpn_termination: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + l2vpn: 1 + assigned_object_type: dcim.interface + assigned_object_id: 1 + state: present + register: test_one + +- name: "L2VPN_TERMINATION 1: ASSERT - Necessary info creation" + ansible.builtin.assert: + that: + - test_one is changed + - test_one['diff']['before']['state'] == "absent" + - test_one['diff']['after']['state'] == "present" + - test_one['l2vpn_termination']['l2vpn'] == 1 + - test_one['l2vpn_termination']['assigned_object_type'] == "dcim.interface" + - test_one['l2vpn_termination']['assigned_object_id'] == 1 + - test_one['msg'] == "l2vpn_termination l2vpn 1 <> dcim.interface 1 created" + +- name: "L2VPN_TERMINATION 2: Create duplicate" + netbox.netbox.netbox_l2vpn_termination: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + l2vpn: 1 + assigned_object_type: dcim.interface + assigned_object_id: 1 + state: present + register: test_two + +- name: "L2VPN_TERMINATION 2: ASSERT - Create duplicate" + ansible.builtin.assert: + that: + - not test_two['changed'] + - test_two['l2vpn_termination']['l2vpn'] == 1 + - test_two['l2vpn_termination']['assigned_object_type'] == "dcim.interface" + - test_two['l2vpn_termination']['assigned_object_id'] == 1 + - test_two['msg'] == "l2vpn_termination l2vpn 1 <> dcim.interface 1 already exists" + +- name: "L2VPN_TERMINATION 3: Update" + netbox.netbox.netbox_l2vpn_termination: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + l2vpn: 1 + assigned_object_type: dcim.interface + assigned_object_id: 1 + tags: + - "Schnozzberry" + state: present + register: test_three + +- name: "L2VPN_TERMINATION 3: ASSERT - Updated" + ansible.builtin.assert: + that: + - test_three is changed + - test_three['diff']['after']['tags'][0] == 4 + - test_three['l2vpn_termination']['l2vpn'] == 1 + - test_three['l2vpn_termination']['assigned_object_type'] == "dcim.interface" + - test_three['l2vpn_termination']['assigned_object_id'] == 1 + - test_three['l2vpn_termination']['tags'][0] == 4 + - test_three['msg'] == "l2vpn_termination l2vpn 1 <> dcim.interface 1 updated" + +- name: "L2VPN_TERMINATION 4: Delete" + netbox.netbox.netbox_l2vpn_termination: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + l2vpn: 1 + assigned_object_type: dcim.interface + assigned_object_id: 1 + state: absent + register: test_four + +- name: "L2VPN_TERMINATION 4: ASSERT - Delete" + ansible.builtin.assert: + that: + - test_four is changed + - test_four['diff']['before']['state'] == "present" + - test_four['diff']['after']['state'] == "absent" + - test_four['l2vpn_termination']['l2vpn'] == 1 + - test_four['l2vpn_termination']['assigned_object_type'] == "dcim.interface" + - test_four['l2vpn_termination']['assigned_object_id'] == 1 + - test_four['l2vpn_termination']['tags'][0] == 4 + - test_four['msg'] == "l2vpn_termination l2vpn 1 <> dcim.interface 1 deleted" diff --git a/tests/integration/targets/v3.4/tasks/main.yml b/tests/integration/targets/v3.4/tasks/main.yml index 5eadbb2f9..888d79096 100644 --- a/tests/integration/targets/v3.4/tasks/main.yml +++ b/tests/integration/targets/v3.4/tasks/main.yml @@ -247,6 +247,15 @@ tags: - netbox_l2vpn +- name: "NETBOX_L2VPN_TERMINATION TESTS" + include_tasks: + file: "netbox_l2vpn_termination.yml" + apply: + tags: + - netbox_l2vpn_termination + tags: + - netbox_l2vpn_termination + - name: "NETBOX_INVENTORY_ITEM_ROLE TESTS" include_tasks: file: "netbox_inventory_item_role.yml" diff --git a/tests/integration/targets/v3.4/tasks/netbox_l2vpn_termination.yml b/tests/integration/targets/v3.4/tasks/netbox_l2vpn_termination.yml new file mode 100644 index 000000000..a4560f407 --- /dev/null +++ b/tests/integration/targets/v3.4/tasks/netbox_l2vpn_termination.yml @@ -0,0 +1,94 @@ +--- +## +## +### NETBOX_L2VPN_TERMINATION +## +## +- name: "L2VPN_TERMINATION 1: Necessary info creation" + netbox.netbox.netbox_l2vpn_termination: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + l2vpn: 1 + assigned_object_type: dcim.interface + assigned_object_id: 1 + state: present + register: test_one + +- name: "L2VPN_TERMINATION 1: ASSERT - Necessary info creation" + ansible.builtin.assert: + that: + - test_one is changed + - test_one['diff']['before']['state'] == "absent" + - test_one['diff']['after']['state'] == "present" + - test_one['l2vpn_termination']['l2vpn'] == 1 + - test_one['l2vpn_termination']['assigned_object_type'] == "dcim.interface" + - test_one['l2vpn_termination']['assigned_object_id'] == 1 + - test_one['msg'] == "l2vpn_termination l2vpn 1 <> dcim.interface 1 created" + +- name: "L2VPN_TERMINATION 2: Create duplicate" + netbox.netbox.netbox_l2vpn_termination: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + l2vpn: 1 + assigned_object_type: dcim.interface + assigned_object_id: 1 + state: present + register: test_two + +- name: "L2VPN_TERMINATION 2: ASSERT - Create duplicate" + ansible.builtin.assert: + that: + - not test_two['changed'] + - test_two['l2vpn_termination']['l2vpn'] == 1 + - test_two['l2vpn_termination']['assigned_object_type'] == "dcim.interface" + - test_two['l2vpn_termination']['assigned_object_id'] == 1 + - test_two['msg'] == "l2vpn_termination l2vpn 1 <> dcim.interface 1 already exists" + +- name: "L2VPN_TERMINATION 3: Update" + netbox.netbox.netbox_l2vpn_termination: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + l2vpn: 1 + assigned_object_type: dcim.interface + assigned_object_id: 1 + tags: + - "Schnozzberry" + state: present + register: test_three + +- name: "L2VPN_TERMINATION 3: ASSERT - Updated" + ansible.builtin.assert: + that: + - test_three is changed + - test_three['diff']['after']['tags'][0] == 4 + - test_three['l2vpn_termination']['l2vpn'] == 1 + - test_three['l2vpn_termination']['assigned_object_type'] == "dcim.interface" + - test_three['l2vpn_termination']['assigned_object_id'] == 1 + - test_three['l2vpn_termination']['tags'][0] == 4 + - test_three['msg'] == "l2vpn_termination l2vpn 1 <> dcim.interface 1 updated" + +- name: "L2VPN_TERMINATION 4: Delete" + netbox.netbox.netbox_l2vpn_termination: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + l2vpn: 1 + assigned_object_type: dcim.interface + assigned_object_id: 1 + state: absent + register: test_four + +- name: "L2VPN_TERMINATION 4: ASSERT - Delete" + ansible.builtin.assert: + that: + - test_four is changed + - test_four['diff']['before']['state'] == "present" + - test_four['diff']['after']['state'] == "absent" + - test_four['l2vpn_termination']['l2vpn'] == 1 + - test_four['l2vpn_termination']['assigned_object_type'] == "dcim.interface" + - test_four['l2vpn_termination']['assigned_object_id'] == 1 + - test_four['l2vpn_termination']['tags'][0] == 4 + - test_four['msg'] == "l2vpn_termination l2vpn 1 <> dcim.interface 1 deleted"