Skip to content

Commit 8a0ef1a

Browse files
committed
[ignore] Modify Documentation for ndo_port_channel_interface. Add flags to check valid interfaces and descriptions inputs.
1 parent 530274a commit 8a0ef1a

File tree

2 files changed

+265
-71
lines changed

2 files changed

+265
-71
lines changed

plugins/modules/ndo_port_channel_interface.py

Lines changed: 132 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@
5050
description:
5151
- The node ID.
5252
type: str
53-
member_interfaces:
53+
interfaces:
5454
description:
5555
- The list of used Interface IDs.
56+
- Ranges of Interface IDs can be used.
5657
type: list
5758
elements: str
5859
aliases: [ members ]
@@ -74,7 +75,7 @@
7475
suboptions:
7576
interface_id:
7677
description:
77-
- The interface ID to which attach a description.
78+
- The interface ID.
7879
type: str
7980
description:
8081
description:
@@ -101,12 +102,17 @@
101102
description: My Ansible Port Channel
102103
port_channel_interface: ansible_port_channel_interface
103104
node: 101
104-
member_interfaces:
105+
interfaces:
105106
- 1/1
107+
- 1/10-11
106108
interface_policy_uuid: ansible_port_channel_policy
107109
interface_descriptions:
108110
- interface_id: 1/1
109111
description: My first Ansible Interface
112+
- interface_id: 1/10
113+
description: My second Ansible Interface
114+
- interface_id: 1/11
115+
description: My third Ansible Interface
110116
state: present
111117
112118
- name: Query an Port Channel Interface with template_name
@@ -117,14 +123,16 @@
117123
template: ansible_fabric_resource_template
118124
port_channel_interface: ansible_port_channel_interface
119125
state: query
126+
register: query_one
120127
121-
- name: Query all IPort Channel Interfaces in the template
128+
- name: Query all Port Channel Interfaces in the template
122129
cisco.mso.ndo_port_channel_interface:
123130
host: mso_host
124131
username: admin
125132
password: SomeSecretPassword
126133
template: ansible_fabric_resource_template
127134
state: query
135+
register: query_all
128136
129137
- name: Delete an Port Channel Interface
130138
cisco.mso.ndo_port_channel_interface:
@@ -140,6 +148,7 @@
140148
"""
141149

142150
import copy
151+
import re
143152
from ansible.module_utils.basic import AnsibleModule
144153
from ansible_collections.cisco.mso.plugins.module_utils.mso import (
145154
MSOModule,
@@ -151,6 +160,26 @@
151160
)
152161

153162

163+
def lookup_valid_interfaces(interfaces):
164+
interface_ids = []
165+
errors_interfaces = []
166+
modified_interfaces = interfaces.replace(" ", "").split(",")
167+
for interface in modified_interfaces:
168+
if re.fullmatch(r"((\d+/)+\d+-\d+$)", interface):
169+
slots = interface.rsplit("/", 1)[0]
170+
range_start, range_stop = interface.rsplit("/", 1)[1].split("-")
171+
if int(range_stop) > int(range_start):
172+
for x in range(int(range_start), int(range_stop) + 1):
173+
interface_ids.append("{0}/{1}".format(slots, x))
174+
else:
175+
errors_interfaces.append(interface)
176+
elif re.fullmatch(r"((\d+/)+\d+$)", interface):
177+
interface_ids.append(interface)
178+
else:
179+
errors_interfaces.append(interface)
180+
return set(interface_ids), errors_interfaces
181+
182+
154183
def main():
155184
argument_spec = mso_argument_spec()
156185
argument_spec.update(
@@ -159,7 +188,7 @@ def main():
159188
port_channel_interface_uuid=dict(type="str", aliases=["uuid", "port_channel_uuid"]),
160189
description=dict(type="str"),
161190
node=dict(type="str"),
162-
member_interfaces=dict(type="list", elements="str", aliases=["members"]),
191+
interfaces=dict(type="list", elements="str", aliases=["members"]),
163192
interface_policy=dict(type="str", aliases=["policy", "pc_policy"]),
164193
interface_policy_uuid=dict(type="str", aliases=["policy_uuid", "pc_policy_uuid"]),
165194
interface_descriptions=dict(
@@ -177,8 +206,8 @@ def main():
177206
argument_spec=argument_spec,
178207
supports_check_mode=True,
179208
required_if=[
180-
["state", "absent", ["port_channel_interface"]],
181-
["state", "present", ["port_channel_interface"]],
209+
["state", "absent", ["template", "port_channel_interface"]],
210+
["state", "present", ["template", "port_channel_interface"]],
182211
],
183212
)
184213

@@ -189,7 +218,7 @@ def main():
189218
port_channel_interface_uuid = module.params.get("port_channel_interface_uuid")
190219
description = module.params.get("description")
191220
node = module.params.get("node")
192-
member_interfaces = module.params.get("member_interfaces")
221+
interfaces = ",".join(module.params.get("interfaces")) if module.params.get("interfaces") else None
193222
interface_policy = module.params.get("interface_policy")
194223
interface_policy_uuid = module.params.get("interface_policy_uuid")
195224
interface_descriptions = module.params.get("interface_descriptions")
@@ -231,7 +260,7 @@ def main():
231260
mso.fail_json(msg="No existing Port Channel policy")
232261

233262
if match:
234-
if match.details.get("name") != port_channel_interface:
263+
if port_channel_interface and match.details.get("name") != port_channel_interface:
235264
ops.append(
236265
dict(
237266
op="replace",
@@ -241,7 +270,7 @@ def main():
241270
)
242271
match.details["name"] = port_channel_interface
243272

244-
if match.details.get("description") != description:
273+
if description and match.details.get("description") != description:
245274
ops.append(
246275
dict(
247276
op="replace",
@@ -251,7 +280,8 @@ def main():
251280
)
252281
match.details["description"] = description
253282

254-
if match.details.get("node") != node:
283+
node_changed = False
284+
if node and match.details.get("node") != node:
255285
ops.append(
256286
dict(
257287
op="replace",
@@ -260,18 +290,9 @@ def main():
260290
)
261291
)
262292
match.details["node"] = node
293+
node_changed = True
263294

264-
if match.details.get("memberInterfaces") != member_interfaces:
265-
ops.append(
266-
dict(
267-
op="replace",
268-
path="{0}/{1}/memberInterfaces".format(path, match.index),
269-
value=",".join(member_interfaces),
270-
)
271-
)
272-
match.details["memberInterfaces"] = member_interfaces
273-
274-
if match.details.get("policy") != interface_policy_uuid:
295+
if interface_policy_uuid and match.details.get("policy") != interface_policy_uuid:
275296
ops.append(
276297
dict(
277298
op="replace",
@@ -281,47 +302,103 @@ def main():
281302
)
282303
match.details["policy"] = interface_policy_uuid
283304

284-
if interface_descriptions is not None and match.details.get("interfaceDescriptions") != interface_descriptions:
285-
ops.append(
286-
dict(
287-
op="replace",
288-
path="{0}/{1}/interfaceDescriptions".format(path, match.index),
289-
value=[
290-
{
291-
"nodeID": node,
292-
"interfaceID": interface.get("interface_id"),
293-
"description": interface.get("description"),
294-
}
295-
for interface in interface_descriptions
296-
],
305+
if interfaces and match.details.get("memberInterfaces") != interfaces:
306+
interface_ids, errors_interfaces = lookup_valid_interfaces(interfaces)
307+
if errors_interfaces:
308+
mso.fail_json(msg=("Invalid interface inputs, {0}".format(errors_interfaces)))
309+
else:
310+
ops.append(
311+
dict(
312+
op="replace",
313+
path="{0}/{1}/memberInterfaces".format(path, match.index),
314+
value=interfaces,
315+
)
297316
)
298-
)
299-
match.details["interfaceDescriptions"] = interface_descriptions
317+
match.details["memberInterfaces"] = interfaces
318+
else:
319+
interface_ids, errors_interfaces = lookup_valid_interfaces(match.details.get("memberInterfaces"))
320+
321+
if interface_descriptions or (node_changed and match.details.get("interfaceDescriptions")):
322+
if node_changed and interface_descriptions is None:
323+
interface_descriptions = [
324+
{
325+
"nodeID": node,
326+
"interfaceID": interface.get("interfaceID"),
327+
"description": interface.get("description"),
328+
}
329+
for interface in match.details["interfaceDescriptions"]
330+
]
331+
else:
332+
interface_descriptions = [
333+
{
334+
"nodeID": match.details["node"],
335+
"interfaceID": interface.get("interface_id"),
336+
"description": interface.get("description"),
337+
}
338+
for interface in interface_descriptions
339+
]
340+
error_descriptions = []
341+
for interface_description in interface_descriptions:
342+
if interface_description["interfaceID"] not in interface_ids:
343+
error_descriptions.append(interface_description["interfaceID"])
344+
if error_descriptions:
345+
mso.fail_json(
346+
msg=("Interface IDs with description {0} not in list of current interfaces {1}".format(error_descriptions, list(interface_ids)))
347+
)
348+
if interface_descriptions != match.details.get("interfaceDescriptions"):
349+
ops.append(
350+
dict(
351+
op="replace",
352+
path="{0}/{1}/interfaceDescriptions".format(path, match.index),
353+
value=interface_descriptions
354+
)
355+
)
356+
match.details["interfaceDescriptions"] = interface_descriptions
357+
elif interface_descriptions == [] and match.details["interfaceDescriptions"]:
358+
ops.append(dict(op="remove", path="{0}/{1}/interfaceDescriptions".format(path, match.index)))
300359

301360
mso.sanitize(match.details)
302361

303362
else:
304-
payload = {
305-
"name": port_channel_interface,
306-
"description": description,
363+
config = {
307364
"node": node,
308-
"memberInterfaces": ",".join(member_interfaces),
365+
"memberInterfaces": interfaces,
309366
"policy": interface_policy_uuid,
310367
}
311-
312-
if interface_descriptions:
313-
payload["interfaceDescriptions"] = [
314-
{
315-
"nodeID": node,
316-
"interfaceID": interface.get("interface_id"),
317-
"description": interface.get("description"),
318-
}
319-
for interface in interface_descriptions
320-
]
321-
322-
ops.append(dict(op="add", path="{0}/-".format(path), value=payload))
323-
324-
mso.sanitize(payload)
368+
missing_required_attributes = []
369+
for attribute_name, attribute_value in config.items():
370+
if not attribute_value:
371+
missing_required_attributes.append(attribute_name)
372+
if missing_required_attributes:
373+
mso.fail_json(msg=("Missing required attributes {0} for creating a Port Channel Interface".format(missing_required_attributes)))
374+
else:
375+
payload = {"name": port_channel_interface} | config
376+
interface_ids, errors_interfaces = lookup_valid_interfaces(payload["memberInterfaces"])
377+
if errors_interfaces:
378+
mso.fail_json(msg=("Invalid interface inputs, {0}".format(errors_interfaces)))
379+
if description:
380+
payload["description"] = description
381+
if interface_descriptions:
382+
error_descriptions = []
383+
for interface_description in interface_descriptions:
384+
if interface_description["interface_id"] not in interface_ids:
385+
error_descriptions.append(interface_description["interface_id"])
386+
if error_descriptions:
387+
mso.fail_json(
388+
msg=("Interface IDs with description {0} not in list of current interfaces {1}".format(error_descriptions, list(interface_ids)))
389+
)
390+
payload["interfaceDescriptions"] = [
391+
{
392+
"nodeID": node,
393+
"interfaceID": interface.get("interface_id"),
394+
"description": interface.get("description"),
395+
}
396+
for interface in interface_descriptions
397+
]
398+
399+
ops.append(dict(op="add", path="{0}/-".format(path), value=payload))
400+
401+
mso.sanitize(payload)
325402

326403
elif state == "absent":
327404
if match:

0 commit comments

Comments
 (0)