Skip to content

Commit e681b03

Browse files
authored
Closes #651 and #652 - Wireless modules (#678)
* Add module for Wireless LAN * Add module for Wireless LAN Group * Add module for Wireless link * Add tests for wireless modules * Add initial wireless links for CI
1 parent 9391280 commit e681b03

15 files changed

+1329
-5
lines changed

plugins/module_utils/netbox_utils.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
secrets=[],
9292
tenancy=["tenants", "tenant_groups", "contacts", "contact_groups", "contact_roles"],
9393
virtualization=["cluster_groups", "cluster_types", "clusters", "virtual_machines"],
94+
wireless=["wireless_lans", "wireless_lan_groups", "wireless_links"],
9495
)
9596

9697
# Used to normalize data for the respective query types used to find endpoints
@@ -120,6 +121,7 @@
120121
parent_region="slug",
121122
parent_site_group="slug",
122123
parent_tenant_group="slug",
124+
parent_wireless_lan_group="slug",
123125
power_panel="name",
124126
power_port="name",
125127
power_port_template="name",
@@ -151,6 +153,8 @@
151153
vlan_group="slug",
152154
vlan_role="name",
153155
vrf="name",
156+
wireless_lan="ssid",
157+
wireless_lan_group="slug",
154158
)
155159

156160
# Specifies keys within data that need to be converted to ID and the endpoint to be used when queried
@@ -183,6 +187,8 @@
183187
"import_targets": "route_targets",
184188
"installed_device": "devices",
185189
"interface": "interfaces",
190+
"interface_a": "interfaces",
191+
"interface_b": "interfaces",
186192
"interface_template": "interface_templates",
187193
"ip_addresses": "ip_addresses",
188194
"ipaddresses": "ip_addresses",
@@ -200,6 +206,7 @@
200206
"parent_region": "regions",
201207
"parent_site_group": "site_groups",
202208
"parent_tenant_group": "tenant_groups",
209+
"parent_wireless_lan_group": "wireless_lan_groups",
203210
"platforms": "platforms",
204211
"power_panel": "power_panels",
205212
"power_port": "power_ports",
@@ -242,6 +249,9 @@
242249
"vlan_group": "vlan_groups",
243250
"vlan_role": "roles",
244251
"vrf": "vrfs",
252+
"wireless_lan": "wireless_lans",
253+
"wireless_lan_group": "wireless_lan_groups",
254+
"wireless_link": "wireless_links",
245255
}
246256

247257
ENDPOINT_NAME_MAPPING = {
@@ -304,6 +314,9 @@
304314
"vlans": "vlan",
305315
"vlan_groups": "vlan_group",
306316
"vrfs": "vrf",
317+
"wireless_lans": "wireless_lan",
318+
"wireless_lan_groups": "wireless_lan_group",
319+
"wireless_links": "wireless_link",
307320
}
308321

309322
ALLOWED_QUERY_PARAMS = {
@@ -355,6 +368,8 @@
355368
"front_port_template": set(["name", "device_type", "rear_port"]),
356369
"installed_device": set(["name"]),
357370
"interface": set(["name", "device", "virtual_machine"]),
371+
"interface_a": set(["name", "device"]),
372+
"interface_b": set(["name", "device"]),
358373
"interface_template": set(["name", "device_type"]),
359374
"inventory_item": set(["name", "device"]),
360375
"ip_address": set(["address", "vrf", "device", "interface", "assigned_object"]),
@@ -408,6 +423,9 @@
408423
"vlan": set(["group", "name", "site", "tenant", "vid", "vlan_group"]),
409424
"vlan_group": set(["slug", "site", "scope"]),
410425
"vrf": set(["name", "tenant"]),
426+
"wireless_lan": set(["ssid"]),
427+
"wireless_lan_group": set(["name"]),
428+
"wireless_link": set(["interface_a", "interface_b"]),
411429
}
412430

413431
QUERY_PARAMS_IDS = set(
@@ -472,6 +490,7 @@
472490
"parent_region": "parent",
473491
"parent_site_group": "parent",
474492
"parent_tenant_group": "parent",
493+
"parent_wireless_lan_group": "parent",
475494
"power_port_template": "power_port",
476495
"prefix_role": "role",
477496
"rack_group": "group",
@@ -485,6 +504,7 @@
485504
"virtual_machine_role": "role",
486505
"vlan_role": "role",
487506
"vlan_group": "group",
507+
"wireless_lan_group": "group",
488508
}
489509

490510
# This is used to dynamically convert name to slug on endpoints requiring a slug
@@ -512,6 +532,7 @@
512532
"platforms",
513533
"providers",
514534
"vlan_groups",
535+
"wireless_lan_groups",
515536
}
516537

517538
SCOPE_TO_ENDPOINT = {
@@ -954,6 +975,7 @@ def _find_app(self, endpoint):
954975
for k, v in API_APPS_ENDPOINTS.items():
955976
if endpoint in v:
956977
nb_app = k
978+
957979
return nb_app
958980

959981
def _find_ids(self, data, user_query_params):
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright: (c) 2021, Martin Rødvand (@rodvand) <mikhail.yohman@gmail.com>
3+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4+
from __future__ import absolute_import, division, print_function
5+
6+
__metaclass__ = type
7+
8+
from ansible_collections.netbox.netbox.plugins.module_utils.netbox_utils import (
9+
NetboxModule,
10+
ENDPOINT_NAME_MAPPING,
11+
SLUG_REQUIRED,
12+
)
13+
14+
15+
NB_WIRELESS_LANS = "wireless_lans"
16+
NB_WIRELESS_LAN_GROUPS = "wireless_lan_groups"
17+
NB_WIRELESS_LINKS = "wireless_links"
18+
19+
20+
class NetboxWirelessModule(NetboxModule):
21+
def __init__(self, module, endpoint):
22+
super().__init__(module, endpoint)
23+
24+
def run(self):
25+
"""
26+
This function should have all necessary code for endpoints within the application
27+
to create/update/delete the endpoint objects
28+
Supported endpoints:
29+
- wireless LAN
30+
- wireless LAN Group
31+
- wireless link
32+
"""
33+
# Used to dynamically set key when returning results
34+
endpoint_name = ENDPOINT_NAME_MAPPING[self.endpoint]
35+
36+
self.result = {"changed": False}
37+
38+
application = self._find_app(self.endpoint)
39+
nb_app = getattr(self.nb, application)
40+
nb_endpoint = getattr(nb_app, self.endpoint)
41+
user_query_params = self.module.params.get("query_params")
42+
43+
data = self.data
44+
45+
# Used for msg output
46+
if data.get("name"):
47+
name = data["name"]
48+
elif data.get("slug"):
49+
name = data["slug"]
50+
elif data.get("ssid"):
51+
name = data["ssid"]
52+
53+
if data.get("interface_a") and data.get("interface_b"):
54+
interface_a_name = self.module.params["data"]["interface_a"].get("name")
55+
interface_a_device = self.module.params["data"]["interface_a"].get("device")
56+
interface_b_name = self.module.params["data"]["interface_b"].get("name")
57+
interface_b_device = self.module.params["data"]["interface_b"].get("device")
58+
name = f"{interface_a_device} {interface_a_name} <> {interface_b_device} {interface_b_name}"
59+
60+
if self.endpoint in SLUG_REQUIRED:
61+
if not data.get("slug"):
62+
data["slug"] = self._to_slug(name)
63+
64+
object_query_params = self._build_query_params(
65+
endpoint_name, data, user_query_params
66+
)
67+
self.nb_object = self._nb_endpoint_get(nb_endpoint, object_query_params, name)
68+
69+
if self.state == "present":
70+
self._ensure_object_exists(nb_endpoint, endpoint_name, name, data)
71+
elif self.state == "absent":
72+
self._ensure_object_absent(endpoint_name, name)
73+
74+
try:
75+
serialized_object = self.nb_object.serialize()
76+
except AttributeError:
77+
serialized_object = self.nb_object
78+
79+
self.result.update({endpoint_name: serialized_object})
80+
81+
self.module.exit_json(**self.result)
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
# Copyright: (c) 2021, Martin Rødvand (@rodvand)
4+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5+
6+
from __future__ import absolute_import, division, print_function
7+
8+
__metaclass__ = type
9+
10+
DOCUMENTATION = r"""
11+
---
12+
module: netbox_wireless_lan
13+
short_description: Creates or removes Wireless LANs from NetBox
14+
description:
15+
- Creates or removes wireless LANs from NetBox
16+
notes:
17+
- This should be ran with connection C(local) and hosts C(localhost)
18+
author:
19+
- Martin Rødvand (@rodvand)
20+
requirements:
21+
- pynetbox
22+
version_added: "3.5.0"
23+
extends_documentation_fragment:
24+
- netbox.netbox.common
25+
options:
26+
data:
27+
type: dict
28+
description:
29+
- Defines the contact configuration
30+
suboptions:
31+
ssid:
32+
description:
33+
- Name of the SSID to be created
34+
required: true
35+
type: str
36+
description:
37+
description:
38+
- The description of the Wireless LAN
39+
required: false
40+
type: str
41+
wireless_lan_group:
42+
description:
43+
- The wireless LAN group
44+
required: false
45+
type: raw
46+
vlan:
47+
description:
48+
- The VLAN of the Wireless LAN
49+
required: false
50+
type: raw
51+
auth_type:
52+
description:
53+
- The authentication type of the Wireless LAN
54+
choices:
55+
- open
56+
- wep
57+
- wpa-personal
58+
- wpa-enterprise
59+
required: false
60+
type: str
61+
auth_cipher:
62+
description:
63+
- The authentication cipher of the Wireless LAN
64+
choices:
65+
- auto
66+
- tkip
67+
- aes
68+
required: false
69+
type: str
70+
auth_psk:
71+
description:
72+
- The PSK of the Wireless LAN
73+
required: false
74+
type: str
75+
tags:
76+
description:
77+
- Any tags that the Wireless LAN may need to be associated with
78+
required: false
79+
type: list
80+
elements: raw
81+
custom_fields:
82+
description:
83+
- must exist in NetBox
84+
required: false
85+
type: dict
86+
required: true
87+
"""
88+
89+
EXAMPLES = r"""
90+
- name: "Test NetBox module"
91+
connection: local
92+
hosts: localhost
93+
gather_facts: False
94+
tasks:
95+
- name: Create Wireless LAN within NetBox with only required information
96+
netbox_wireless_lan:
97+
netbox_url: http://netbox.local
98+
netbox_token: thisIsMyToken
99+
data:
100+
ssid: Wireless Network One
101+
state: present
102+
103+
- name: Delete Wireless LAN within netbox
104+
netbox_wireless_lan:
105+
netbox_url: http://netbox.local
106+
netbox_token: thisIsMyToken
107+
data:
108+
ssid: Wireless Network One
109+
state: absent
110+
111+
- name: Create Wireless LAN with all parameters
112+
netbox_wireless_lan:
113+
netbox_url: http://netbox.local
114+
netbox_token: thisIsMyToken
115+
data:
116+
ssid: Wireless Network One
117+
description: Cool Wireless Network
118+
auth_type: wpa-enterprise
119+
auth_cipher: aes
120+
auth_psk: psk123456
121+
tags:
122+
- tagA
123+
- tagB
124+
- tagC
125+
state: present
126+
"""
127+
128+
RETURN = r"""
129+
wireless_lan:
130+
description: Serialized object as created or already existent within NetBox
131+
returned: on creation
132+
type: dict
133+
msg:
134+
description: Message indicating failure or info about what has been achieved
135+
returned: always
136+
type: str
137+
"""
138+
139+
from ansible_collections.netbox.netbox.plugins.module_utils.netbox_utils import (
140+
NetboxAnsibleModule,
141+
NETBOX_ARG_SPEC,
142+
)
143+
from ansible_collections.netbox.netbox.plugins.module_utils.netbox_wireless import (
144+
NetboxWirelessModule,
145+
NB_WIRELESS_LANS,
146+
)
147+
from copy import deepcopy
148+
149+
150+
def main():
151+
"""
152+
Main entry point for module execution
153+
"""
154+
argument_spec = deepcopy(NETBOX_ARG_SPEC)
155+
argument_spec.update(
156+
dict(
157+
data=dict(
158+
type="dict",
159+
required=True,
160+
options=dict(
161+
ssid=dict(required=True, type="str"),
162+
description=dict(required=False, type="str"),
163+
wireless_lan_group=dict(required=False, type="raw"),
164+
vlan=dict(required=False, type="raw"),
165+
auth_type=dict(
166+
required=False,
167+
choices=["open", "wep", "wpa-enterprise", "wpa-personal"],
168+
type="str",
169+
),
170+
auth_cipher=dict(
171+
required=False, choices=["auto", "tkip", "aes"], type="str"
172+
),
173+
auth_psk=dict(required=False, type="str"),
174+
tags=dict(required=False, type="list", elements="raw"),
175+
custom_fields=dict(required=False, type="dict"),
176+
),
177+
),
178+
)
179+
)
180+
181+
required_if = [("state", "present", ["ssid"]), ("state", "absent", ["ssid"])]
182+
183+
module = NetboxAnsibleModule(
184+
argument_spec=argument_spec, supports_check_mode=True, required_if=required_if
185+
)
186+
187+
netbox_wireless_lan = NetboxWirelessModule(module, NB_WIRELESS_LANS)
188+
netbox_wireless_lan.run()
189+
190+
191+
if __name__ == "__main__": # pragma: no cover
192+
main()

0 commit comments

Comments
 (0)