50
50
description:
51
51
- The node ID.
52
52
type: str
53
- member_interfaces :
53
+ interfaces :
54
54
description:
55
55
- The list of used Interface IDs.
56
+ - Ranges of Interface IDs can be used.
56
57
type: list
57
58
elements: str
58
59
aliases: [ members ]
74
75
suboptions:
75
76
interface_id:
76
77
description:
77
- - The interface ID to which attach a description .
78
+ - The interface ID.
78
79
type: str
79
80
description:
80
81
description:
101
102
description: My Ansible Port Channel
102
103
port_channel_interface: ansible_port_channel_interface
103
104
node: 101
104
- member_interfaces :
105
+ interfaces :
105
106
- 1/1
107
+ - 1/10-11
106
108
interface_policy_uuid: ansible_port_channel_policy
107
109
interface_descriptions:
108
110
- interface_id: 1/1
109
111
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
110
116
state: present
111
117
112
118
- name: Query an Port Channel Interface with template_name
117
123
template: ansible_fabric_resource_template
118
124
port_channel_interface: ansible_port_channel_interface
119
125
state: query
126
+ register: query_one
120
127
121
- - name: Query all IPort Channel Interfaces in the template
128
+ - name: Query all Port Channel Interfaces in the template
122
129
cisco.mso.ndo_port_channel_interface:
123
130
host: mso_host
124
131
username: admin
125
132
password: SomeSecretPassword
126
133
template: ansible_fabric_resource_template
127
134
state: query
135
+ register: query_all
128
136
129
137
- name: Delete an Port Channel Interface
130
138
cisco.mso.ndo_port_channel_interface:
140
148
"""
141
149
142
150
import copy
151
+ import re
143
152
from ansible .module_utils .basic import AnsibleModule
144
153
from ansible_collections .cisco .mso .plugins .module_utils .mso import (
145
154
MSOModule ,
151
160
)
152
161
153
162
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
+
154
183
def main ():
155
184
argument_spec = mso_argument_spec ()
156
185
argument_spec .update (
@@ -159,7 +188,7 @@ def main():
159
188
port_channel_interface_uuid = dict (type = "str" , aliases = ["uuid" , "port_channel_uuid" ]),
160
189
description = dict (type = "str" ),
161
190
node = dict (type = "str" ),
162
- member_interfaces = dict (type = "list" , elements = "str" , aliases = ["members" ]),
191
+ interfaces = dict (type = "list" , elements = "str" , aliases = ["members" ]),
163
192
interface_policy = dict (type = "str" , aliases = ["policy" , "pc_policy" ]),
164
193
interface_policy_uuid = dict (type = "str" , aliases = ["policy_uuid" , "pc_policy_uuid" ]),
165
194
interface_descriptions = dict (
@@ -177,8 +206,8 @@ def main():
177
206
argument_spec = argument_spec ,
178
207
supports_check_mode = True ,
179
208
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" ]],
182
211
],
183
212
)
184
213
@@ -189,7 +218,7 @@ def main():
189
218
port_channel_interface_uuid = module .params .get ("port_channel_interface_uuid" )
190
219
description = module .params .get ("description" )
191
220
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
193
222
interface_policy = module .params .get ("interface_policy" )
194
223
interface_policy_uuid = module .params .get ("interface_policy_uuid" )
195
224
interface_descriptions = module .params .get ("interface_descriptions" )
@@ -231,7 +260,7 @@ def main():
231
260
mso .fail_json (msg = "No existing Port Channel policy" )
232
261
233
262
if match :
234
- if match .details .get ("name" ) != port_channel_interface :
263
+ if port_channel_interface and match .details .get ("name" ) != port_channel_interface :
235
264
ops .append (
236
265
dict (
237
266
op = "replace" ,
@@ -241,7 +270,7 @@ def main():
241
270
)
242
271
match .details ["name" ] = port_channel_interface
243
272
244
- if match .details .get ("description" ) != description :
273
+ if description and match .details .get ("description" ) != description :
245
274
ops .append (
246
275
dict (
247
276
op = "replace" ,
@@ -251,7 +280,8 @@ def main():
251
280
)
252
281
match .details ["description" ] = description
253
282
254
- if match .details .get ("node" ) != node :
283
+ node_changed = False
284
+ if node and match .details .get ("node" ) != node :
255
285
ops .append (
256
286
dict (
257
287
op = "replace" ,
@@ -260,18 +290,9 @@ def main():
260
290
)
261
291
)
262
292
match .details ["node" ] = node
293
+ node_changed = True
263
294
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 :
275
296
ops .append (
276
297
dict (
277
298
op = "replace" ,
@@ -281,47 +302,103 @@ def main():
281
302
)
282
303
match .details ["policy" ] = interface_policy_uuid
283
304
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
+ )
297
316
)
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 )))
300
359
301
360
mso .sanitize (match .details )
302
361
303
362
else :
304
- payload = {
305
- "name" : port_channel_interface ,
306
- "description" : description ,
363
+ config = {
307
364
"node" : node ,
308
- "memberInterfaces" : "," . join ( member_interfaces ) ,
365
+ "memberInterfaces" : interfaces ,
309
366
"policy" : interface_policy_uuid ,
310
367
}
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 )
325
402
326
403
elif state == "absent" :
327
404
if match :
0 commit comments