From e3e5aa8ac41116f6971f06e1c40d1745315c9b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=B8dvand?= Date: Wed, 19 Oct 2022 21:35:32 +0200 Subject: [PATCH 1/7] Start on module --- plugins/module_utils/netbox_ipam.py | 4 + plugins/module_utils/netbox_utils.py | 6 + plugins/modules/netbox_l2vpn.py | 170 +++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 plugins/modules/netbox_l2vpn.py diff --git a/plugins/module_utils/netbox_ipam.py b/plugins/module_utils/netbox_ipam.py index a5970a8cf..71d3997e1 100644 --- a/plugins/module_utils/netbox_ipam.py +++ b/plugins/module_utils/netbox_ipam.py @@ -28,6 +28,8 @@ NB_VLAN_GROUPS = "vlan_groups" NB_VRFS = "vrfs" NB_SERVICES = "services" +NB_L2VPNS = "l2vpns" +NB_L2VPN_TERMINATIONS = "l2vpn_terminations" class NetboxIpamModule(NetboxModule): @@ -147,6 +149,8 @@ def run(self): - aggregates - ipam_roles - ip_addresses + - l2vpns + - l2vpn_terminations - prefixes - rirs - route_targets diff --git a/plugins/module_utils/netbox_utils.py b/plugins/module_utils/netbox_utils.py index 9b2e16d82..b32132326 100644 --- a/plugins/module_utils/netbox_utils.py +++ b/plugins/module_utils/netbox_utils.py @@ -85,6 +85,8 @@ ipam=[ "aggregates", "ip_addresses", + "l2vpns", + "l2vpn_terminations", "prefixes", "rirs", "roles", @@ -126,6 +128,7 @@ group="slug", installed_device="name", import_targets="name", + l2vpn="name", location="slug", manufacturer="slug", nat_inside="address", @@ -306,6 +309,7 @@ "interface_templates": "interface_template", "inventory_items": "inventory_item", "ip_addresses": "ip_address", + "l2vpns": "l2vpn", "locations": "location", "manufacturers": "manufacturer", "platforms": "platform", @@ -408,6 +412,7 @@ "ipaddresses": set( ["address", "vrf", "device", "interface", "assigned_object", "virtual_machine"] ), + "l2vpn": set(["name"]), "lag": set(["name"]), "location": set(["slug"]), "manufacturer": set(["slug"]), @@ -556,6 +561,7 @@ "device_roles", "device_types", "ipam_roles", + "l2vpns", "locations", "rack_groups", "rack_roles", diff --git a/plugins/modules/netbox_l2vpn.py b/plugins/modules/netbox_l2vpn.py new file mode 100644 index 000000000..a4abf8f9a --- /dev/null +++ b/plugins/modules/netbox_l2vpn.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: netbox_l2vpn +short_description: Create, update or delete L2VPNs within NetBox +description: + - Creates, updates or removes L2VPNs from NetBox +notes: + - Tags should be defined as a YAML list + - This should be ran with connection C(local) and hosts C(localhost) +author: + - Martin Rødvand (@rodvand) +requirements: + - pynetbox +version_added: '3.9.0' +extends_documentation_fragment: + - netbox.netbox.common +options: + data: + type: dict + description: + - Defines the L2VPN configuration + suboptions: + name: + description: + - The name of the L2VPN + required: true + type: str + type: + description: + - The type of L2VPN + required: true + type: raw + identifier: + description: + - The identifier of the L2VPN + required: false + type: str + description: + description: + - The description of the vlan + required: false + type: str + tenant: + description: + - The tenant that the vlan will be assigned to + required: false + type: raw + tags: + description: + - Any tags that the vlan 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""" +- name: "Test NetBox modules" + connection: local + hosts: localhost + gather_facts: False + + tasks: + - name: Create vlan within NetBox with only required information + netbox_vlan: + netbox_url: http://netbox.local + netbox_token: thisIsMyToken + data: + name: Test VLAN + vid: 400 + state: present + + - name: Delete vlan within netbox + netbox_vlan: + netbox_url: http://netbox.local + netbox_token: thisIsMyToken + data: + name: Test VLAN + vid: 400 + state: absent + + - name: Create vlan with all information + netbox_vlan: + netbox_url: http://netbox.local + netbox_token: thisIsMyToken + data: + name: Test VLAN + vid: 400 + site: Test Site + group: Test VLAN Group + tenant: Test Tenant + status: Deprecated + vlan_role: Test VLAN Role + description: Just a test + tags: + - Schnozzberry + state: present +""" + +RETURN = r""" +vlan: + 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_L2VPNS, +) +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( + name=dict(required=True, type="str"), + type=dict(required=True, type="raw"), + identifier=dict(required=False, type="str"), + description=dict(required=False, type="str"), + tenant=dict(required=False, type="raw"), + tags=dict(required=False, type="list", elements="raw"), + custom_fields=dict(required=False, type="dict"), + ), + ), + ) + ) + required_if = [ + ("state", "present", ["name"]), + ("state", "absent", ["name"]), + ] + + module = NetboxAnsibleModule( + argument_spec=argument_spec, supports_check_mode=True, required_if=required_if + ) + + netbox_l2vpn = NetboxIpamModule(module, NB_L2VPNS) + netbox_l2vpn.run() + + +if __name__ == "__main__": # pragma: no cover + main() From 6f4cf8a99b2b638f3a5a7de5e84ea05bb2c224e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=B8dvand?= Date: Wed, 19 Oct 2022 22:07:09 +0200 Subject: [PATCH 2/7] Fully functional module --- plugins/module_utils/netbox_utils.py | 2 +- plugins/modules/netbox_l2vpn.py | 61 +++++++++++++++++----------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/plugins/module_utils/netbox_utils.py b/plugins/module_utils/netbox_utils.py index b32132326..cd32e84db 100644 --- a/plugins/module_utils/netbox_utils.py +++ b/plugins/module_utils/netbox_utils.py @@ -524,7 +524,7 @@ "circuit_type": "type", "cluster_type": "type", "cluster_group": "group", - "contact_group": "group", + "contact_group": "group", "parent_contact_group": "parent", "parent_location": "parent", "parent_interface": "parent", diff --git a/plugins/modules/netbox_l2vpn.py b/plugins/modules/netbox_l2vpn.py index a4abf8f9a..91f537386 100644 --- a/plugins/modules/netbox_l2vpn.py +++ b/plugins/modules/netbox_l2vpn.py @@ -41,26 +41,38 @@ description: - The identifier of the L2VPN required: false - type: str + type: int + import_targets: + description: + - Route targets to import + required: false + type: list + elements: raw + export_targets: + description: + - Route targets to export + required: false + type: list + elements: raw description: description: - - The description of the vlan + - The description of the L2VPN required: false type: str tenant: description: - - The tenant that the vlan will be assigned to + - The tenant that the L2VPN will be assigned to required: false type: raw tags: description: - - Any tags that the vlan may need to be associated with + - Any tags that the L2VPN may need to be associated with required: false type: list elements: raw custom_fields: description: - - must exist in NetBox + - Must exist in NetBox required: false type: dict required: true @@ -73,36 +85,37 @@ gather_facts: False tasks: - - name: Create vlan within NetBox with only required information - netbox_vlan: + - name: Create L2VPN within NetBox with only required information + netbox_l2vpn: netbox_url: http://netbox.local netbox_token: thisIsMyToken data: - name: Test VLAN - vid: 400 + name: Test L2VPN + type: vxlan state: present - - name: Delete vlan within netbox + - name: Delete L2VPN within netbox netbox_vlan: netbox_url: http://netbox.local netbox_token: thisIsMyToken data: - name: Test VLAN - vid: 400 + name: Test L2VPN + type: vxlan state: absent - - name: Create vlan with all information + - name: Create L2VPN with all required information netbox_vlan: netbox_url: http://netbox.local netbox_token: thisIsMyToken data: - name: Test VLAN - vid: 400 - site: Test Site - group: Test VLAN Group - tenant: Test Tenant - status: Deprecated - vlan_role: Test VLAN Role + name: Test L2VPN + type: vpls + identifier: 43256 + import_targets: + - "65000:1" + export_targets: + - "65000:2" + tenant: Test Tenant description: Just a test tags: - Schnozzberry @@ -110,7 +123,7 @@ """ RETURN = r""" -vlan: +l2vpn: description: Serialized object as created or already existent within NetBox returned: success (when I(state=present)) type: dict @@ -144,7 +157,9 @@ def main(): options=dict( name=dict(required=True, type="str"), type=dict(required=True, type="raw"), - identifier=dict(required=False, type="str"), + identifier=dict(required=False, type="int"), + import_targets=dict(required=False, type="list", elements="raw"), + export_targets=dict(required=False, type="list", elements="raw"), description=dict(required=False, type="str"), tenant=dict(required=False, type="raw"), tags=dict(required=False, type="list", elements="raw"), @@ -166,5 +181,5 @@ def main(): netbox_l2vpn.run() -if __name__ == "__main__": # pragma: no cover +if __name__ == "__main__": main() From 5304fc26220564af289ac557184cfc3fd06174e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=B8dvand?= Date: Wed, 19 Oct 2022 22:39:13 +0200 Subject: [PATCH 3/7] Add tests and try using action group --- meta/runtime.yml | 1 + tests/integration/targets/v3.3/tasks/main.yml | 16 +++- .../targets/v3.3/tasks/netbox_l2vpn.yml | 88 +++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml diff --git a/meta/runtime.yml b/meta/runtime.yml index 15dc42f4b..617bc34e7 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -41,6 +41,7 @@ action_groups: - netbox_inventory_item - netbox_ip_address - netbox_ipam_role + - netbox_l2vpn - netbox_location - netbox_manufacturer - netbox_platform diff --git a/tests/integration/targets/v3.3/tasks/main.yml b/tests/integration/targets/v3.3/tasks/main.yml index 5d480c591..4e7fd59de 100644 --- a/tests/integration/targets/v3.3/tasks/main.yml +++ b/tests/integration/targets/v3.3/tasks/main.yml @@ -1,4 +1,9 @@ --- +module_defaults: + group/netbox.netbox.netbox: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + - name: "NETBOX_DEVICE TESTS" include_tasks: "netbox_device.yml" @@ -235,4 +240,13 @@ tags: - netbox_webhook tags: - - netbox_webhook \ No newline at end of file + - netbox_webhook + +- name: "NETBOX_L2VPN TESTS" + include_tasks: + file: "netbox_l2vpn.yml" + apply: + tags: + - netbox_l2vpn + tags: + - netbox_l2vpn \ No newline at end of file diff --git a/tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml b/tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml new file mode 100644 index 000000000..b7543eaaf --- /dev/null +++ b/tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml @@ -0,0 +1,88 @@ +--- +## +## +### NETBOX_L2VPN +## +## +- name: "L2VPN 1: Necessary info creation" + netbox.netbox.netbox_l2vpn: + #netbox_url: http://localhost:32768 + #netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + name: Test L2VPN + type: vxlan + state: present + register: test_one + +- name: "L2VPN 1: ASSERT - Necessary info creation" + assert: + that: + - test_one is changed + - test_one['diff']['before']['state'] == "absent" + - test_one['diff']['after']['state'] == "present" + - test_one['l2vpn']['name'] == "Test L2VPN" + - test_one['l2vpn']['type'] == "vxlan" + - test_one['msg'] == "l2vpn Test L2VPN created" + +- name: "L2VPN 2: Create duplicate" + netbox.netbox.netbox_l2vpn: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + name: Test L2VPN + type: vxlan + state: present + register: test_two + +- name: "L2VPN 2: ASSERT - Create duplicate" + assert: + that: + - not test_two['changed'] + - test_two['l2vpn']['name'] == "Test L2VPN" + - test_two['l2vpn']['type'] == "vxlan" + - test_two['msg'] == "l2vpn Test L2VPN already exists" + +- name: "L2VPN 4: ASSERT - Update" + netbox.netbox.netbox_l2vpn: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + name: "Test L2VPN" + type: vxlan + tenant: "Test Tenant" + description: Updated description + tags: + - "Schnozzberry" + state: present + register: test_four + +- name: "L2VPN: ASSERT - Updated" + assert: + that: + - test_four is changed + - test_four['diff']['after']['description'] == "Updated description" + - test_four['diff']['after']['tags'][0] == 4 + - test_four['l2vpn']['name'] == "Test L2VPN" + - test_four['l2vpn']['tenant'] == 1 + - test_four['l2vpn']['description'] == "Updated description" + - test_four['l2vpn']['tags'][0] == 4 + - test_four['msg'] == "l2vpn Test L2VPN updated" + +- name: "L2VPN: ASSERT - Delete" + netbox.netbox.netbox_l2vpn: + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 + data: + name: "Test L2VPN" + state: absent + register: test_six + +- name: "L2VPN 6: ASSERT - Delete" + assert: + that: + - test_six is changed + - test_six['l2vpn']['name'] == "Test L2VPN" + - test_six['l2vpn']['tenant'] == 1 + - test_six['l2vpn']['description'] == "Updated description" + - test_six['l2vpn']['tags'][0] == 4 + - test_six['msg'] == "l2vpn Test L2VPN deleted" \ No newline at end of file From 2f620669d290c60b59284b3e875f363515d62a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=B8dvand?= Date: Wed, 19 Oct 2022 22:42:41 +0200 Subject: [PATCH 4/7] Black formatting --- plugins/module_utils/netbox_utils.py | 2 +- plugins/modules/netbox_l2vpn.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/netbox_utils.py b/plugins/module_utils/netbox_utils.py index cd32e84db..b32132326 100644 --- a/plugins/module_utils/netbox_utils.py +++ b/plugins/module_utils/netbox_utils.py @@ -524,7 +524,7 @@ "circuit_type": "type", "cluster_type": "type", "cluster_group": "group", - "contact_group": "group", + "contact_group": "group", "parent_contact_group": "parent", "parent_location": "parent", "parent_interface": "parent", diff --git a/plugins/modules/netbox_l2vpn.py b/plugins/modules/netbox_l2vpn.py index 91f537386..eb555a141 100644 --- a/plugins/modules/netbox_l2vpn.py +++ b/plugins/modules/netbox_l2vpn.py @@ -157,7 +157,7 @@ def main(): options=dict( name=dict(required=True, type="str"), type=dict(required=True, type="raw"), - identifier=dict(required=False, type="int"), + identifier=dict(required=False, type="int"), import_targets=dict(required=False, type="list", elements="raw"), export_targets=dict(required=False, type="list", elements="raw"), description=dict(required=False, type="str"), @@ -181,5 +181,5 @@ def main(): netbox_l2vpn.run() -if __name__ == "__main__": +if __name__ == "__main__": main() From fd9ff4f3e2b497a6315ff767ac86ab2c8ce42406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=B8dvand?= Date: Wed, 19 Oct 2022 22:46:51 +0200 Subject: [PATCH 5/7] License --- plugins/modules/netbox_l2vpn.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/modules/netbox_l2vpn.py b/plugins/modules/netbox_l2vpn.py index eb555a141..5fac79084 100644 --- a/plugins/modules/netbox_l2vpn.py +++ b/plugins/modules/netbox_l2vpn.py @@ -1,5 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2022, Martin Rødvand (@rodvand) +# 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 From 1a5d9fd9a87a1c2543377004d1da245083758418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=B8dvand?= Date: Wed, 19 Oct 2022 23:03:21 +0200 Subject: [PATCH 6/7] Adjust required data and tests --- plugins/modules/netbox_l2vpn.py | 4 ++-- tests/integration/inventory | 2 ++ tests/integration/targets/v3.3/tasks/main.yml | 6 +----- tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml | 8 +++++--- 4 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 tests/integration/inventory diff --git a/plugins/modules/netbox_l2vpn.py b/plugins/modules/netbox_l2vpn.py index 5fac79084..482252f2e 100644 --- a/plugins/modules/netbox_l2vpn.py +++ b/plugins/modules/netbox_l2vpn.py @@ -171,8 +171,8 @@ def main(): ) ) required_if = [ - ("state", "present", ["name"]), - ("state", "absent", ["name"]), + ("state", "present", ["name", "type"]), + ("state", "absent", ["name", "type"]), ] module = NetboxAnsibleModule( diff --git a/tests/integration/inventory b/tests/integration/inventory new file mode 100644 index 000000000..ab3861cf9 --- /dev/null +++ b/tests/integration/inventory @@ -0,0 +1,2 @@ +[testgroup] +testhost ansible_connection="local" ansible_pipelining="yes" ansible_python_interpreter="/Users/rodvand/collections/ansible_collections/netbox/netbox/venv/bin/python3.9" \ No newline at end of file diff --git a/tests/integration/targets/v3.3/tasks/main.yml b/tests/integration/targets/v3.3/tasks/main.yml index 4e7fd59de..a06782c0d 100644 --- a/tests/integration/targets/v3.3/tasks/main.yml +++ b/tests/integration/targets/v3.3/tasks/main.yml @@ -1,9 +1,5 @@ --- -module_defaults: - group/netbox.netbox.netbox: - netbox_url: http://localhost:32768 - netbox_token: 0123456789abcdef0123456789abcdef01234567 - + - name: "NETBOX_DEVICE TESTS" include_tasks: "netbox_device.yml" diff --git a/tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml b/tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml index b7543eaaf..4e2a78d9f 100644 --- a/tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml +++ b/tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml @@ -6,8 +6,8 @@ ## - name: "L2VPN 1: Necessary info creation" netbox.netbox.netbox_l2vpn: - #netbox_url: http://localhost:32768 - #netbox_token: 0123456789abcdef0123456789abcdef01234567 + netbox_url: http://localhost:32768 + netbox_token: 0123456789abcdef0123456789abcdef01234567 data: name: Test L2VPN type: vxlan @@ -73,7 +73,8 @@ netbox_url: http://localhost:32768 netbox_token: 0123456789abcdef0123456789abcdef01234567 data: - name: "Test L2VPN" + name: "Test L2VPN" + type: vxlan state: absent register: test_six @@ -83,6 +84,7 @@ - test_six is changed - test_six['l2vpn']['name'] == "Test L2VPN" - test_six['l2vpn']['tenant'] == 1 + - test_six['l2vpn']['type'] == "vxlan" - test_six['l2vpn']['description'] == "Updated description" - test_six['l2vpn']['tags'][0] == 4 - test_six['msg'] == "l2vpn Test L2VPN deleted" \ No newline at end of file From 80824e5a2c0551afd4cf1d8b22062e549c097024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=B8dvand?= Date: Wed, 19 Oct 2022 23:08:37 +0200 Subject: [PATCH 7/7] Expand tests --- .../integration/targets/v3.3/tasks/netbox_l2vpn.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml b/tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml index 4e2a78d9f..a5d7f0352 100644 --- a/tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml +++ b/tests/integration/targets/v3.3/tasks/netbox_l2vpn.yml @@ -50,7 +50,12 @@ name: "Test L2VPN" type: vxlan tenant: "Test Tenant" - description: Updated description + description: Updated description + import_targets: + - "4000:4000" + - "5000:5000" + export_targets: + - "6000:6000" tags: - "Schnozzberry" state: present @@ -61,9 +66,13 @@ that: - test_four is changed - test_four['diff']['after']['description'] == "Updated description" + - test_four['diff']['after']['import_targets'] == [1, 2] + - test_four['diff']['after']['export_targets'] == [3] - test_four['diff']['after']['tags'][0] == 4 - test_four['l2vpn']['name'] == "Test L2VPN" - - test_four['l2vpn']['tenant'] == 1 + - test_four['l2vpn']['tenant'] == 1 + - test_four['l2vpn']['import_targets'] == [1, 2] + - test_four['l2vpn']['export_targets'] == [3] - test_four['l2vpn']['description'] == "Updated description" - test_four['l2vpn']['tags'][0] == 4 - test_four['msg'] == "l2vpn Test L2VPN updated"