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.
- nornir_collection/__init__.py +0 -0
- nornir_collection/batfish/__init__.py +0 -0
- nornir_collection/batfish/assert_config.py +358 -0
- nornir_collection/batfish/utils.py +129 -0
- nornir_collection/cisco/__init__.py +0 -0
- nornir_collection/cisco/configuration_management/__init__.py +0 -0
- nornir_collection/cisco/configuration_management/cli/__init__.py +0 -0
- nornir_collection/cisco/configuration_management/cli/config_tasks.py +569 -0
- nornir_collection/cisco/configuration_management/cli/config_workflow.py +107 -0
- nornir_collection/cisco/configuration_management/cli/show_tasks.py +677 -0
- nornir_collection/cisco/configuration_management/netconf/__init__.py +0 -0
- nornir_collection/cisco/configuration_management/netconf/config_tasks.py +564 -0
- nornir_collection/cisco/configuration_management/netconf/config_workflow.py +298 -0
- nornir_collection/cisco/configuration_management/netconf/nr_cfg_iosxe_netconf.py +186 -0
- nornir_collection/cisco/configuration_management/netconf/ops_tasks.py +307 -0
- nornir_collection/cisco/configuration_management/processor.py +151 -0
- nornir_collection/cisco/configuration_management/pyats.py +236 -0
- nornir_collection/cisco/configuration_management/restconf/__init__.py +0 -0
- nornir_collection/cisco/configuration_management/restconf/cisco_rpc.py +514 -0
- nornir_collection/cisco/configuration_management/restconf/config_workflow.py +95 -0
- nornir_collection/cisco/configuration_management/restconf/tasks.py +325 -0
- nornir_collection/cisco/configuration_management/utils.py +511 -0
- nornir_collection/cisco/software_upgrade/__init__.py +0 -0
- nornir_collection/cisco/software_upgrade/cisco_software_upgrade.py +283 -0
- nornir_collection/cisco/software_upgrade/utils.py +794 -0
- nornir_collection/cisco/support_api/__init__.py +0 -0
- nornir_collection/cisco/support_api/api_calls.py +1173 -0
- nornir_collection/cisco/support_api/cisco_maintenance_report.py +221 -0
- nornir_collection/cisco/support_api/cisco_support.py +727 -0
- nornir_collection/cisco/support_api/reports.py +747 -0
- nornir_collection/cisco/support_api/utils.py +316 -0
- nornir_collection/fortinet/__init__.py +0 -0
- nornir_collection/fortinet/utils.py +36 -0
- nornir_collection/git.py +224 -0
- nornir_collection/netbox/__init__.py +0 -0
- nornir_collection/netbox/custom_script.py +107 -0
- nornir_collection/netbox/inventory.py +360 -0
- nornir_collection/netbox/scan_prefixes_and_update_ip_addresses.py +989 -0
- nornir_collection/netbox/set_device_status.py +67 -0
- nornir_collection/netbox/sync_datasource.py +111 -0
- nornir_collection/netbox/update_cisco_inventory_data.py +158 -0
- nornir_collection/netbox/update_cisco_support_plugin_data.py +339 -0
- nornir_collection/netbox/update_fortinet_inventory_data.py +161 -0
- nornir_collection/netbox/update_purestorage_inventory_data.py +144 -0
- nornir_collection/netbox/utils.py +261 -0
- nornir_collection/netbox/verify_device_primary_ip.py +202 -0
- nornir_collection/nornir_plugins/__init__.py +0 -0
- nornir_collection/nornir_plugins/inventory/__init__.py +0 -0
- nornir_collection/nornir_plugins/inventory/netbox.py +250 -0
- nornir_collection/nornir_plugins/inventory/staggered_yaml.py +143 -0
- nornir_collection/nornir_plugins/inventory/utils.py +277 -0
- nornir_collection/purestorage/__init__.py +0 -0
- nornir_collection/purestorage/utils.py +53 -0
- nornir_collection/utils.py +741 -0
- nornir_collection-0.0.1.dist-info/LICENSE +21 -0
- nornir_collection-0.0.1.dist-info/METADATA +136 -0
- nornir_collection-0.0.1.dist-info/RECORD +59 -0
- nornir_collection-0.0.1.dist-info/WHEEL +5 -0
- 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
|