nornir-collection 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. nornir_collection/__init__.py +0 -0
  2. nornir_collection/batfish/__init__.py +0 -0
  3. nornir_collection/batfish/assert_config.py +358 -0
  4. nornir_collection/batfish/utils.py +129 -0
  5. nornir_collection/cisco/__init__.py +0 -0
  6. nornir_collection/cisco/configuration_management/__init__.py +0 -0
  7. nornir_collection/cisco/configuration_management/cli/__init__.py +0 -0
  8. nornir_collection/cisco/configuration_management/cli/config_tasks.py +569 -0
  9. nornir_collection/cisco/configuration_management/cli/config_workflow.py +107 -0
  10. nornir_collection/cisco/configuration_management/cli/show_tasks.py +677 -0
  11. nornir_collection/cisco/configuration_management/netconf/__init__.py +0 -0
  12. nornir_collection/cisco/configuration_management/netconf/config_tasks.py +564 -0
  13. nornir_collection/cisco/configuration_management/netconf/config_workflow.py +298 -0
  14. nornir_collection/cisco/configuration_management/netconf/nr_cfg_iosxe_netconf.py +186 -0
  15. nornir_collection/cisco/configuration_management/netconf/ops_tasks.py +307 -0
  16. nornir_collection/cisco/configuration_management/processor.py +151 -0
  17. nornir_collection/cisco/configuration_management/pyats.py +236 -0
  18. nornir_collection/cisco/configuration_management/restconf/__init__.py +0 -0
  19. nornir_collection/cisco/configuration_management/restconf/cisco_rpc.py +514 -0
  20. nornir_collection/cisco/configuration_management/restconf/config_workflow.py +95 -0
  21. nornir_collection/cisco/configuration_management/restconf/tasks.py +325 -0
  22. nornir_collection/cisco/configuration_management/utils.py +511 -0
  23. nornir_collection/cisco/software_upgrade/__init__.py +0 -0
  24. nornir_collection/cisco/software_upgrade/cisco_software_upgrade.py +283 -0
  25. nornir_collection/cisco/software_upgrade/utils.py +794 -0
  26. nornir_collection/cisco/support_api/__init__.py +0 -0
  27. nornir_collection/cisco/support_api/api_calls.py +1173 -0
  28. nornir_collection/cisco/support_api/cisco_maintenance_report.py +221 -0
  29. nornir_collection/cisco/support_api/cisco_support.py +727 -0
  30. nornir_collection/cisco/support_api/reports.py +747 -0
  31. nornir_collection/cisco/support_api/utils.py +316 -0
  32. nornir_collection/fortinet/__init__.py +0 -0
  33. nornir_collection/fortinet/utils.py +36 -0
  34. nornir_collection/git.py +224 -0
  35. nornir_collection/netbox/__init__.py +0 -0
  36. nornir_collection/netbox/custom_script.py +107 -0
  37. nornir_collection/netbox/inventory.py +360 -0
  38. nornir_collection/netbox/scan_prefixes_and_update_ip_addresses.py +989 -0
  39. nornir_collection/netbox/set_device_status.py +67 -0
  40. nornir_collection/netbox/sync_datasource.py +111 -0
  41. nornir_collection/netbox/update_cisco_inventory_data.py +158 -0
  42. nornir_collection/netbox/update_cisco_support_plugin_data.py +339 -0
  43. nornir_collection/netbox/update_fortinet_inventory_data.py +161 -0
  44. nornir_collection/netbox/update_purestorage_inventory_data.py +144 -0
  45. nornir_collection/netbox/utils.py +261 -0
  46. nornir_collection/netbox/verify_device_primary_ip.py +202 -0
  47. nornir_collection/nornir_plugins/__init__.py +0 -0
  48. nornir_collection/nornir_plugins/inventory/__init__.py +0 -0
  49. nornir_collection/nornir_plugins/inventory/netbox.py +250 -0
  50. nornir_collection/nornir_plugins/inventory/staggered_yaml.py +143 -0
  51. nornir_collection/nornir_plugins/inventory/utils.py +277 -0
  52. nornir_collection/purestorage/__init__.py +0 -0
  53. nornir_collection/purestorage/utils.py +53 -0
  54. nornir_collection/utils.py +741 -0
  55. nornir_collection-0.0.1.dist-info/LICENSE +21 -0
  56. nornir_collection-0.0.1.dist-info/METADATA +136 -0
  57. nornir_collection-0.0.1.dist-info/RECORD +59 -0
  58. nornir_collection-0.0.1.dist-info/WHEEL +5 -0
  59. nornir_collection-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,564 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This module contains NETCONF functions and tasks related to Nornir. NETCONF operation RPCs like lock,
4
+ validate, commit, discard and unlock are not part of this module. Please take a look to ops_tasks module.
5
+
6
+ The functions are ordered as followed:
7
+ - Helper functions
8
+ - Nornir NETCONF tasks
9
+ - Nornir NETCONF tasks in regular function
10
+ """
11
+
12
+ import traceback
13
+ from typing import Union
14
+ from colorama import init
15
+ from nornir_scrapli.tasks import netconf_edit_config
16
+ from nornir.core import Nornir
17
+ from nornir.core.task import Task, Result
18
+ from nornir_collection.cisco.configuration_management.utils import (
19
+ extract_interface_name,
20
+ extract_interface_number,
21
+ render_jinja2_template,
22
+ add_interface_data,
23
+ set_restconf_result,
24
+ )
25
+ from nornir_collection.cisco.configuration_management.restconf.tasks import rc_cisco_get
26
+ from nornir_collection.utils import print_result, task_result
27
+
28
+ init(autoreset=True, strip=False)
29
+
30
+
31
+ #### Helper Functions #######################################################################################
32
+
33
+
34
+ def return_result_if_no_interfaces(task: Task) -> Union[str, None]:
35
+ """
36
+ TBD
37
+ """
38
+ if "interfaces" not in task.host:
39
+ custom_result = (
40
+ f"'{task.name}' -> NornirResponse <Success: True>\n"
41
+ '-> Host inventory key task.host["interfaces"] not found' + f"\n\n{traceback.format_exc()}"
42
+ )
43
+ return custom_result
44
+
45
+ # Return None if everything is fine
46
+ return None
47
+
48
+
49
+ def return_result_if_no_template(task: Task, is_iface: bool, tpl_startswith: str) -> Union[str, dict, list]:
50
+ """
51
+ TBD
52
+ """
53
+ # If its a interface task, return the result if no Jinja2 interface templates were found
54
+ if is_iface:
55
+ # Create a list with the templates of the interface for verification
56
+ nc_tpl = []
57
+ for i in task.host["interfaces"]:
58
+ if i["int_template"] is None:
59
+ continue
60
+ if i["int_template"].startswith(tpl_startswith):
61
+ nc_tpl.append(i["int_template"])
62
+ # Return the result if no Jinja2 templates were found
63
+ if not nc_tpl:
64
+ custom_result = (
65
+ f"'{task.name}' -> NornirResponse <Success: True>\n"
66
+ f'-> No interface template starting with "{tpl_startswith}" found'
67
+ )
68
+ return custom_result
69
+
70
+ # Return the template list if everything is fine
71
+ return nc_tpl
72
+
73
+ # If its a system task, return the result if no Jinja2 system templates were found
74
+ # Create a dict with all templates where the template key match the tpl_startswith string
75
+ nc_tpl = {}
76
+ for tpl_name, tpl_path in task.host.items():
77
+ if tpl_name.startswith(tpl_startswith):
78
+ # Add the template to the dict
79
+ nc_tpl[tpl_name] = tpl_path
80
+
81
+ # Return the result if no Jinja2 templates were found
82
+ if not nc_tpl:
83
+ custom_result = (
84
+ f"'{task.name}' -> NornirResponse <Success: False>\n"
85
+ f"-> No templates starting with '{tpl_startswith}' found"
86
+ )
87
+ return custom_result
88
+
89
+ # Return the template dict if everything is fine
90
+ return nc_tpl
91
+
92
+
93
+ def netconf_configure_jinja2_rendered_payload_template( # pylint: disable=too-many-arguments,too-many-locals
94
+ task: Task,
95
+ j2_task_text: str,
96
+ j2_tpl_path: str,
97
+ custom_result: list,
98
+ task_failed: bool,
99
+ info_msg: str = None,
100
+ verbose: bool = False,
101
+ int_name: str = None,
102
+ j2_tpl_name: str = None,
103
+ j2_kwargs: dict = None,
104
+ ) -> tuple:
105
+ """
106
+ TBD
107
+ """
108
+ try:
109
+ # Render the Jinja2 payload template
110
+ kwargs = j2_kwargs or {}
111
+ nc_config = render_jinja2_template(task=task, tpl_path=j2_tpl_path, kwargs=kwargs)
112
+
113
+ # If the Jinja2 template rendering was successful, set the Jinja2 print result item
114
+ msg = f"{task_result(text=j2_task_text, changed=False, level_name='INFO', failed=False)}\n"
115
+ if int_name:
116
+ msg += f"'{int_name} ({j2_tpl_name})' -> Jinja2Response <Success: True>\n"
117
+ elif j2_tpl_name:
118
+ msg += f"'{j2_tpl_name}' -> Jinja2Response <Success: True>\n"
119
+ else:
120
+ msg += f"'{j2_task_text}' -> Jinja2Response <Success: True>\n"
121
+ # Add the Jinja2 template name to the result string
122
+ msg += f"-> {info_msg}"
123
+ # Add the Jinja2 template result to the result string if verbose is True
124
+ custom_result.append(msg + f"\n\n{nc_config}" if verbose else msg)
125
+
126
+ except: # pylint: disable=bare-except # nosec
127
+ task_failed = True
128
+ # Set the Nornir print result item
129
+ msg = f"{task_result(text=j2_task_text, changed=False, level_name='ERROR', failed=True)}\n"
130
+ if int_name:
131
+ msg += f"'{int_name} ({j2_tpl_name})' -> Jinja2Response <Success: False>\n"
132
+ elif j2_tpl_name:
133
+ msg += f"'{j2_tpl_name}' -> Jinja2Response <Success: False>\n"
134
+ else:
135
+ msg += f"'{j2_task_text}' -> Jinja2Response <Success: False>\n"
136
+ # Add the Traceback as its an Nornir result for an exception
137
+ msg += f"-> {info_msg}\n" + f"\n{traceback.format_exc()}"
138
+ # Add the Nornir print result item to the custom_result list
139
+ custom_result.append(msg)
140
+
141
+ # Return the result as the Jinja2 template rendering failed
142
+ return custom_result, task_failed
143
+
144
+ # Configure the Jinja2 interface template
145
+ try:
146
+ # Apply config to the NETCONF candidate datastore
147
+ nc_result = task.run(task=netconf_edit_config, config=nc_config, target="candidate")
148
+
149
+ # If the task netconf_edit_config failed, set the Nornir result to return as failed
150
+ if nc_result[0].failed:
151
+ task_failed = True
152
+
153
+ # Set level_name and failed for the Nornir print result item
154
+ level_name = "ERROR" if nc_result[0].failed else "INFO"
155
+ failed = bool(nc_result[0].failed)
156
+ changed = not failed
157
+
158
+ # Set the Nornir print result item
159
+ msg = f"{task_result(text=task.name, changed=changed, level_name=level_name, failed=failed)}\n"
160
+ if int_name:
161
+ msg += f"'{int_name} ({j2_tpl_name})' -> {str(nc_result[0].scrapli_response)}"
162
+ elif j2_tpl_name:
163
+ msg += f"'{j2_tpl_name}' -> {str(nc_result[0].scrapli_response)}"
164
+ else:
165
+ msg += f"'{task.name}' -> {str(nc_result[0].scrapli_response)}"
166
+
167
+ # Add the Nornir print result item to the custom_result list
168
+ custom_result.append(
169
+ msg + f"\n\n{nc_result[0].scrapli_response.result}"
170
+ if nc_result[0].failed
171
+ else (msg + f"\n\n{nc_result[0].scrapli_response.result}" if verbose else msg)
172
+ )
173
+
174
+ except: # pylint: disable=bare-except # nosec
175
+ task_failed = True
176
+ # Set the Nornir print result item
177
+ msg = f"{task_result(text=task.name, changed=False, level_name='ERROR', failed=True)}\n"
178
+ if int_name:
179
+ msg += f"'{int_name} ({j2_tpl_name})' -> NetconfResponse <Success: False>\n"
180
+ elif j2_tpl_name:
181
+ msg += f"'{j2_tpl_name}' -> NetconfResponse <Success: False>\n"
182
+ else:
183
+ msg += f"'{task.name}' -> NetconfResponse <Success: False>\n"
184
+ # Add the Traceback as its an Nornir result for an exception
185
+ msg += f"\n{traceback.format_exc()}"
186
+ # Add the Nornir print result item to the custom_result list
187
+ custom_result.append(msg)
188
+
189
+ # Return the result as the NETCONF config failed
190
+ return custom_result, task_failed
191
+
192
+ # Return the successful custom result and the task_failed status which is False
193
+ return custom_result, task_failed
194
+
195
+
196
+ #### Nornir NETCONF Tasks ###################################################################################
197
+
198
+
199
+ def nc_edit_cleanup_portchannel(task: Task, verbose: bool = False) -> Result:
200
+ """
201
+ TBD
202
+ """
203
+ # pylint: disable=too-many-locals
204
+
205
+ # The custom_result list will be filled with the results
206
+ custom_result = []
207
+
208
+ # Return the Nornir result as failed if the host have no interfaces
209
+ iface_result = return_result_if_no_interfaces(task=task)
210
+ # If the return in not None, then it's the custom Nornir result to return
211
+ if isinstance(iface_result, str):
212
+ return Result(host=task.host, custom_result=iface_result, failed=True)
213
+
214
+ #### Get all current configured Port-channels with RESTCONF #############################################
215
+
216
+ task_text = "RESTCONF GET Portchannel interfaces"
217
+
218
+ # Set the RESTCONF port, yang query and url to get all configured SVIs
219
+ yang_query = "Cisco-IOS-XE-native:native/interface"
220
+ url = f"https://{task.host.hostname}:443/restconf/data/{yang_query}"
221
+
222
+ # RESTCONF HTTP Get for all SVIs
223
+ response = rc_cisco_get(url=url, auth=(task.host.username, task.host.password), verify=False)
224
+
225
+ # Set the RESTCONF result and return the Nornir result if the RESTCONF response status_code is not 200
226
+ custom_result = set_restconf_result(
227
+ task=Task,
228
+ task_text=task_text,
229
+ yang_query=yang_query,
230
+ response=response,
231
+ custom_result=custom_result,
232
+ verbose=verbose,
233
+ )
234
+
235
+ #### Render NETCONF payload to remove Port-channels with Jinja2 #########################################
236
+
237
+ task_text = "Render Jinja2 NETCONF payload"
238
+
239
+ # Create a empty list to add all current configured Port-channels and their associated interfaces
240
+ current_po = {}
241
+ # Loop through all Port-channels and add them and their associated interfaces as a list to the dict
242
+ for po in response["json"]["Cisco-IOS-XE-native:interface"]["Port-channel"]:
243
+ current_po[po["name"]] = []
244
+ for i_name, i_detail in response["json"]["Cisco-IOS-XE-native:interface"].items():
245
+ for i in i_detail:
246
+ if i.get("Cisco-IOS-XE-ethernet:channel-group"):
247
+ if i["Cisco-IOS-XE-ethernet:channel-group"]["number"] == po["name"]:
248
+ current_po[po["name"]].append({"name": i_name, "number": i["name"]})
249
+
250
+ # Exclude NoneType or empty string interface template names
251
+ # Select only the interface name and convert it to lowercase
252
+ interfaces = [i["name"].lower() for i in task.host["interfaces"] if i["int_template"]]
253
+ # Include interfaces name starts with 'port-channel' and replace to only have the Port-channel number
254
+ inventory_po = [int(i.replace("port-channel", "")) for i in interfaces if i.startswith("port-channel")]
255
+ # Create a dict with all configured Port-channel which are not part of the inventoryp
256
+ remove_po = {k: v for k, v in current_po.items() if k not in inventory_po}
257
+
258
+ # If there are no Port-channel to remove, return the Nornir result
259
+ if not remove_po:
260
+ custom_result.append(
261
+ f"{task_result(text=task_text, changed=False, level_name='INFO', failed=False)}\n"
262
+ + f"'{task_text}' -> NornirResponse <Success: True>\n"
263
+ + "-> No Portchannel interfaces to remove"
264
+ )
265
+ return Result(host=task.host, custom_result=custom_result, failed=False)
266
+
267
+ # Set the info message for the Nornir result
268
+ info_msg = f"Port-channel interfaces to remove: {len(remove_po)}"
269
+
270
+ # Render the Jinja2 payload template and configure the NETCONF candidate datastore
271
+ custom_result, task_failed = netconf_configure_jinja2_rendered_payload_template(
272
+ task=task,
273
+ j2_task_text=task_text,
274
+ j2_tpl_path="iosxe_netconf/tpl_sys/cleanup/cleanup_portchannel.j2",
275
+ custom_result=custom_result,
276
+ task_failed=False,
277
+ info_msg=info_msg,
278
+ verbose=verbose,
279
+ j2_kwargs={"remove_po": remove_po},
280
+ )
281
+
282
+ # Return the Nornir NETCONF result
283
+ return Result(host=task.host, custom_result=custom_result, failed=task_failed)
284
+
285
+
286
+ def nc_edit_cleanup_svi(task: Task, verbose: bool = False) -> Result:
287
+ """
288
+ TBD
289
+ """
290
+ # pylint: disable=too-many-locals
291
+
292
+ # The custom_result list will be filled with the results
293
+ custom_result = []
294
+
295
+ # Return the Nornir result as failed if the host have no interfaces
296
+ iface_result = return_result_if_no_interfaces(task=task)
297
+ # If the return in not None, then it's the custom Nornir result to return
298
+ if isinstance(iface_result, str):
299
+ return Result(host=task.host, custom_result=iface_result, failed=True)
300
+
301
+ #### Get all current configured SVIs with RESTCONF ######################################################
302
+
303
+ task_text = "RESTCONF GET VLAN interfaces"
304
+
305
+ # Set the RESTCONF port, yang query and url to get all configured SVIs
306
+ yang_query = "Cisco-IOS-XE-native:native/interface/Vlan"
307
+ url = f"https://{task.host.hostname}:443/restconf/data/{yang_query}"
308
+
309
+ # RESTCONF HTTP Get for all SVIs
310
+ response = rc_cisco_get(url=url, auth=(task.host.username, task.host.password), verify=False)
311
+
312
+ # Set the RESTCONF result and return the Nornir result if the RESTCONF response status_code is not 200
313
+ custom_result = set_restconf_result(
314
+ task=Task,
315
+ task_text=task_text,
316
+ yang_query=yang_query,
317
+ response=response,
318
+ custom_result=custom_result,
319
+ verbose=verbose,
320
+ )
321
+
322
+ # Create a list with all current configured SVIs except the default VLAN 1
323
+ current_svi = [x["name"] for x in response["json"]["Cisco-IOS-XE-native:Vlan"] if x["name"] != 1]
324
+
325
+ #### Render NETCONF payload to remove SVIs with Jinja2 ##################################################
326
+
327
+ task_text = "Render Jinja2 NETCONF payload"
328
+
329
+ # Exclude NoneType or empty string interface template names
330
+ # Select only the interface name and convert it to lowercase
331
+ interfaces = [i["name"].lower() for i in task.host["interfaces"] if i["int_template"]]
332
+ # Include interfaces which name starts with 'vlan' and replace 'vlan' to only have the SVI number
333
+ inventory_svi = [int(i.replace("vlan", "")) for i in interfaces if i.startswith("vlan")]
334
+ # Create a list with all configured SVIs which are not part of the inventoryp
335
+ remove_svi = [svi for svi in current_svi if svi not in inventory_svi]
336
+
337
+ # If there are no SVIs to remove, return the Nornir result
338
+ if not remove_svi:
339
+ custom_result.append(
340
+ f"{task_result(text=task_text, changed=False, level_name='INFO', failed=False)}\n"
341
+ + f"'{task_text}' -> NornirResponse <Success: True>\n"
342
+ + "-> No VLAN interfaces to remove"
343
+ )
344
+ return Result(host=task.host, custom_result=custom_result, failed=False)
345
+
346
+ # Set the info message for the Nornir result
347
+ info_msg = f"VLAN interfaces to remove: {len(remove_svi)}"
348
+
349
+ # Render the Jinja2 payload template and configure the NETCONF candidate datastore
350
+ custom_result, task_failed = netconf_configure_jinja2_rendered_payload_template(
351
+ task=task,
352
+ j2_task_text=task_text,
353
+ j2_tpl_path="iosxe_netconf/tpl_sys/cleanup/cleanup_svi.j2",
354
+ custom_result=custom_result,
355
+ task_failed=False,
356
+ info_msg=info_msg,
357
+ verbose=verbose,
358
+ j2_kwargs={"remove_svi": remove_svi},
359
+ )
360
+
361
+ # Return the Nornir NETCONF result
362
+ return Result(host=task.host, custom_result=custom_result, failed=task_failed)
363
+
364
+
365
+ def nc_edit_tpl_config(task: Task, tpl_startswith: str, verbose: bool = False) -> Result:
366
+ """
367
+ TBD
368
+ """
369
+ # pylint: disable=invalid-name,too-many-locals
370
+
371
+ # Return the result if no Jinja2 interface templates for a 'tpl_startswith' string were found
372
+ nc_tpl_result = return_result_if_no_template(task=task, is_iface=False, tpl_startswith=tpl_startswith)
373
+ # If the return in a string, then it's the custom Nornir result to return
374
+ if isinstance(nc_tpl_result, str):
375
+ return Result(host=task.host, custom_result=nc_tpl_result, failed=True)
376
+
377
+ #### Configure each Jinja2 rendered template ############################################################
378
+
379
+ # Track if the overall task has failed
380
+ task_failed = False
381
+ # The custom_result list will be filled with the result of each template
382
+ custom_result = []
383
+
384
+ # Gather all NETCONF templates to apply
385
+ for tpl_name, tpl_path in nc_tpl_result.items():
386
+ # Render the Jinja2 payload template and configure the NETCONF candidate datastore
387
+ custom_result, task_failed = netconf_configure_jinja2_rendered_payload_template(
388
+ task=task,
389
+ j2_task_text="Render Jinja2 NETCONF payload template",
390
+ j2_tpl_path=tpl_path,
391
+ custom_result=custom_result,
392
+ task_failed=task_failed,
393
+ info_msg=tpl_path,
394
+ verbose=verbose,
395
+ j2_tpl_name=tpl_name,
396
+ )
397
+
398
+ # Return the Nornir NETCONF result
399
+ return Result(host=task.host, custom_result=custom_result, failed=task_failed)
400
+
401
+
402
+ def nc_edit_tpl_int_config(task: Task, tpl_startswith: str, verbose: bool = False) -> Result:
403
+ """
404
+ TBD
405
+ """
406
+ # pylint: disable=too-many-locals,invalid-name
407
+
408
+ # Return the Nornir result as failed if the host have no interfaces
409
+ iface_result = return_result_if_no_interfaces(task=task)
410
+ # If the return in not None, then it's the custom Nornir result to return
411
+ if isinstance(iface_result, str):
412
+ return Result(host=task.host, custom_result=iface_result, failed=True)
413
+
414
+ # Return the result if no Jinja2 interface templates for a 'tpl_startswith' string were found
415
+ nc_tpl_result = return_result_if_no_template(task=task, is_iface=True, tpl_startswith=tpl_startswith)
416
+ # If the return in a string, then it's the custom Nornir result to return
417
+ if isinstance(nc_tpl_result, str):
418
+ return Result(host=task.host, custom_result=nc_tpl_result, failed=False)
419
+
420
+ #### Configure each interface with the Jinja2 rendered template #########################################
421
+
422
+ # Track if the overall task has failed
423
+ task_failed = False
424
+ # The custom_result list will be filled with the result of each interface
425
+ custom_result = []
426
+
427
+ # Exclude NoneType or empty string interface template names
428
+ interfaces = [i for i in task.host["interfaces"] if i["int_template"]]
429
+ # Exclude interface templates if they don't start with the tpl_startswith string
430
+ interfaces = [i for i in interfaces if i["int_template"].startswith(tpl_startswith)]
431
+
432
+ for interface in interfaces:
433
+ # Set the interface template name
434
+ tpl_name = interface["int_template"]
435
+ tpl_path = task.host[tpl_name]
436
+
437
+ # Add additional interface data for Jinja2 **kwargs
438
+ interface = add_interface_data(task=task, interface=interface)
439
+ # Extract the interface name and the interface number into a variable and add the current
440
+ # interface details for the Jinja2 **kwargs
441
+ j2_kwargs = {
442
+ "interface": {
443
+ "interface_name": extract_interface_name(interface["name"]),
444
+ "interface_number": extract_interface_number(interface["name"]),
445
+ **interface,
446
+ }
447
+ }
448
+
449
+ # Render the Jinja2 payload template and configure the NETCONF candidate datastore
450
+ custom_result, task_failed = netconf_configure_jinja2_rendered_payload_template(
451
+ task=task,
452
+ j2_task_text="Render Jinja2 NETCONF interface payload template",
453
+ j2_tpl_path=tpl_path,
454
+ custom_result=custom_result,
455
+ task_failed=task_failed,
456
+ info_msg=tpl_path,
457
+ verbose=verbose,
458
+ int_name=interface["name"],
459
+ j2_tpl_name=tpl_name,
460
+ j2_kwargs=j2_kwargs,
461
+ )
462
+
463
+ # Return the Nornir NETCONF result
464
+ return Result(host=task.host, custom_result=custom_result, failed=task_failed)
465
+
466
+
467
+ #### Nornir NETCONF Tasks in regular Function ###############################################################
468
+
469
+
470
+ def nc_cfg_cleanup(nr: Nornir, cfg_status: bool = True, verbose: bool = False) -> bool:
471
+ """
472
+ TBD
473
+ """
474
+ # pylint: disable=invalid-name
475
+
476
+ # Return False if cfg_status argument is False
477
+ if not cfg_status:
478
+ return False
479
+
480
+ tasks = {
481
+ nc_edit_cleanup_portchannel: "NETCONF portchannel cleanup",
482
+ nc_edit_cleanup_svi: "NETCONF vlan interface cleanup",
483
+ }
484
+
485
+ # Run each task from the Nornir tasks list
486
+ for task, name in tasks.items():
487
+ # Run the custom nornir task
488
+ nc_result = nr.run(
489
+ name=name,
490
+ task=task,
491
+ verbose=verbose,
492
+ on_failed=True,
493
+ )
494
+
495
+ # Print the result
496
+ print_result(result=nc_result, result_sub_list=True, attrs=["custom_result"])
497
+
498
+ # If the task failed -> nc_result.failed is True. So return False if nc_result.failed is True
499
+ if nc_result.failed:
500
+ cfg_status = False
501
+
502
+ return cfg_status
503
+
504
+
505
+ def nc_cfg_tpl(nr: Nornir, cfg_tasks: dict, cfg_status: bool = True, verbose: bool = False) -> bool:
506
+ """
507
+ TBD
508
+ """
509
+ # pylint: disable=invalid-name
510
+
511
+ # Return False if cfg_status argument is False
512
+ if not cfg_status:
513
+ return False
514
+
515
+ # Run each task from config_tasks
516
+ for tpl_startswith, task_text in cfg_tasks.items():
517
+ # Run the custom nornir task nc_edit_tpl_config
518
+ nc_result = nr.run(
519
+ name=task_text,
520
+ task=nc_edit_tpl_config,
521
+ tpl_startswith=tpl_startswith,
522
+ verbose=verbose,
523
+ on_failed=True,
524
+ )
525
+
526
+ # Print the result
527
+ print_result(result=nc_result, result_sub_list=True, attrs=["custom_result"])
528
+
529
+ # If the task failed -> nc_result.failed is True. So return False if nc_result.failed is True
530
+ if nc_result.failed:
531
+ cfg_status = False
532
+
533
+ return cfg_status
534
+
535
+
536
+ def nc_cfg_tpl_int(nr: Nornir, cfg_tasks: dict, cfg_status: bool = True, verbose: bool = False) -> bool:
537
+ """
538
+ TBD
539
+ """
540
+ # pylint: disable=invalid-name
541
+
542
+ # Return False if cfg_status argument is False
543
+ if not cfg_status:
544
+ return False
545
+
546
+ # Run each task from interface_tasks
547
+ for tpl_startswith, task_text in cfg_tasks.items():
548
+ # Run the custom nornir task nc_edit_tpl_int_config
549
+ nc_result = nr.run(
550
+ name=task_text,
551
+ task=nc_edit_tpl_int_config,
552
+ tpl_startswith=tpl_startswith,
553
+ verbose=verbose,
554
+ on_failed=True,
555
+ )
556
+
557
+ # Print the result
558
+ print_result(result=nc_result, result_sub_list=True, attrs=["custom_result"])
559
+
560
+ # If the task failed -> nc_result.failed is True. So return False if nc_result.failed is True
561
+ if nc_result.failed:
562
+ cfg_status = False
563
+
564
+ return cfg_status