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,107 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This module contains functions related to NetBox custom scripts.
4
+
5
+ The functions are ordered as followed:
6
+ - Regular functions
7
+ """
8
+
9
+
10
+ import os
11
+ import time
12
+ import json
13
+ import requests
14
+ from nornir_collection.utils import task_name, task_error, task_info, exit_error
15
+
16
+
17
+ #### Regular functions ######################################################################################
18
+
19
+
20
+ def run_nb_custom_script( # pylint: disable=dangerous-default-value
21
+ name: str, url: str, payload: dict = {}, verbose: bool = False
22
+ ) -> bool:
23
+ """
24
+ Runs a custom script in NetBox.
25
+ Args:
26
+ name (str): The name of the custom script to run.
27
+ url (str): The URL of the custom script to run.
28
+ payload (dict, optional): The payload to send with the request. Defaults to {}.
29
+ verbose (bool, optional): Whether to print verbose output. Defaults to False.
30
+ Returns:
31
+ bool: True if the custom script failed, False otherwise.
32
+ """
33
+ task_text = f"Run NetBox Custom Script {name}"
34
+ print(task_name(text=task_text))
35
+
36
+ # Set the NetBox token, timeout, header
37
+ timeout = (3.05, 27)
38
+ headers = {
39
+ "Accept": "application/json",
40
+ "Content-Type": "application/json",
41
+ "Authorization": f"Token {os.environ.get('NB_TOKEN')}",
42
+ }
43
+
44
+ # Do the http post request to run the NetBox custom script
45
+ response = requests.post( # nosec
46
+ url=url, headers=headers, data=json.dumps(payload), verify=False, timeout=timeout
47
+ )
48
+ # Verify the response code
49
+ if not response.status_code == 200:
50
+ msg = [
51
+ f"Failed to run NetBox custom script {url}",
52
+ f"Status code: {response.status_code}",
53
+ f"Response Text: {response.text}",
54
+ ]
55
+ # Print the error message and exit the script
56
+ exit_error(task_text=task_text, msg=msg)
57
+
58
+ # Print the start of the custom script
59
+ print(task_info(text="Started NetBox Custom Script", changed=False))
60
+ print(f"Started NetBox Custom Script {name}")
61
+ if verbose:
62
+ print(json.dumps(response.json(), indent=4))
63
+
64
+ # Create a result dict from the first API response to track the progress of the status
65
+ result = response.json()["result"]
66
+ # Do the http get request to verify the status of the running NetBox custom script
67
+ # Terminate the while loop with a timeout of max 300s (5min)
68
+ timeout_start = time.time()
69
+ while time.time() < timeout_start + 300:
70
+ if result["status"]["value"] not in ("pending", "running"):
71
+ break
72
+ result = requests.get(url=result["url"], headers=headers, verify=False, timeout=timeout) # nosec
73
+ result = result.json()
74
+ time.sleep(1)
75
+
76
+ # Print the end result after the custom script has finished or the timeout exeeded
77
+ if result["status"]["value"] not in ("completed", "failed"):
78
+ print(task_error(text="Failed NetBox Custom Script", changed=False))
79
+ print(f"Failed NetBox Custom Script {name}")
80
+ print(json.dumps(result, indent=4))
81
+
82
+ # Return True because the custom script failed
83
+ return True
84
+
85
+ # Create a list of the log messages
86
+ result_list = []
87
+ result_list.append(f"Completed NetBox Custom Script {name}\n")
88
+ for item in result["data"]["log"]:
89
+ # Space after the info emoji needed to align the text
90
+ symbol_lookup = {"debug": "🚧", "info": "ℹ️", "success": "✅", "warning": "🚨", "failure": "❌"}
91
+ symbol = symbol_lookup.get(item["status"], "❓")
92
+ result_list.append(f"{symbol} [{item['status'].capitalize()}] - {item['message']}")
93
+ if verbose:
94
+ result_list.append(json.dumps(result, indent=4))
95
+
96
+ if "failed" in result["status"]["value"]:
97
+ print(task_error(text="Completed NetBox Custom Script", changed=False))
98
+ print("\n".join(result_list))
99
+
100
+ # Return True because the custom script failed
101
+ return True
102
+
103
+ print(task_info(text="Completed NetBox Custom Script", changed=False))
104
+ print("\n".join(result_list))
105
+
106
+ # Return False because the custom script was successful
107
+ return False
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This module contains general functions and tasks related to NetBox and Nornir.
4
+
5
+ The functions are ordered as followed:
6
+ - Task Helper Functions
7
+ - Single Nornir Tasks
8
+ - Nornir Tasks in regular Function
9
+ """
10
+
11
+
12
+ from typing import Dict, List
13
+ from nornir.core import Nornir
14
+ from nornir.core.task import Task, Result
15
+ from nornir_collection.netbox.utils import get_nb_resources
16
+ from nornir_collection.utils import task_info, print_task_name, print_result, exit_error
17
+
18
+
19
+ #### Task Helper Functions ##################################################################################
20
+
21
+
22
+ #### Nornir Tasks ###########################################################################################
23
+
24
+
25
+ def _load_virtual_chassis_data(task: Task, all_devices: List[Dict]) -> Result:
26
+ """
27
+ Load NetBox virtual chassis member data.
28
+ Args:
29
+ task (Task): Nornir task object.
30
+ all_devices: (List[Dict]): List of all devices from NetBox.
31
+ Returns:
32
+ Result: Nornir result object.
33
+ """
34
+ # Set the failed flag to False
35
+ failed = False
36
+
37
+ # If the device is not part of a virtual chassis
38
+ if not task.host.get("virtual_chassis"):
39
+ # Return the Nornir object
40
+ result = (
41
+ f"'Load Virtual-Chassis data' -> NetBoxResponse <Success: {not failed}>\n"
42
+ + "-> Device is not part of a virtual-chassis in NetBox"
43
+ )
44
+ return Result(host=task.host, result=result, changed=False, failed=failed)
45
+
46
+ # Dict comprehension to include only some key-value pairs
47
+ include_keys = ["id", "url", "display", "name", "serial"]
48
+ # Add an empty list for the members of the virtual chassis
49
+ task.host["virtual_chassis"]["members"] = []
50
+ # Add the data of all members to the master
51
+ # Start with 2 as the master is number 1 and already in the lists
52
+ for i in range(2, task.host["virtual_chassis"]["member_count"] + 1):
53
+ # Get the member data
54
+ member = [x for x in all_devices if x["name"] == f"{task.host['virtual_chassis']['name']}_{i}"]
55
+ # Dict comprehension to include only some key-value pairs
56
+ member = {k: v for (k, v) in member[0].items() if k in include_keys}
57
+ member["serial"] = member["serial"] if member["serial"] else None
58
+ # Add the member data to the virtual chassis master
59
+ task.host["virtual_chassis"]["members"].append(member)
60
+
61
+ # Return the Nornir object
62
+ return Result(host=task.host)
63
+
64
+
65
+ def _load_interface_data(task: Task, all_interfaces: List[Dict], enrich_vlan_data: True) -> Result:
66
+ """
67
+ Load NetBox interface data.
68
+ Args:
69
+ task (Task): The Nornir task object.
70
+ all_interfaces: (List[Dict]): List of all interfaces from NetBox.
71
+ Returns:
72
+ Result: Nornir result object.
73
+ """
74
+ # pylint: disable=too-many-locals
75
+
76
+ # Set the failed flag to False
77
+ failed = False
78
+
79
+ # Get all interfaces from the master device
80
+ interfaces = [x for x in all_interfaces if x["device"]["id"] == task.host["id"]]
81
+ # Add all interfaces from the virtual chassis members
82
+ if task.host.get("virtual_chassis") and task.host["virtual_chassis"].get("members"):
83
+ for member in task.host["virtual_chassis"]["members"]:
84
+ interfaces.extend([x for x in all_interfaces if x["device"]["id"] == member["id"]])
85
+ # Return if the interfaces list is empty
86
+ if not interfaces:
87
+ # Return the Nornir object
88
+ result = (
89
+ f"'Load interface data' -> NetBoxResponse <Success: {not failed}>\n"
90
+ + "-> No interface data found in NetBox"
91
+ )
92
+ return Result(host=task.host, result=result, changed=False, failed=failed)
93
+
94
+ # Dict keys for comprehension to include only some key-value pairs
95
+ # fmt: off
96
+ include_keys = [
97
+ "name", "int_template", "description", "type", "lag", "count_ipaddresses",
98
+ "count_fhrp_groups", "int_peer_device", "int_config_context", "enabled",
99
+ ]
100
+ # fmt: on
101
+ # Make some normalization to the device interfaces data with the interface_filter list and others
102
+ for interface in interfaces:
103
+ # Remove the custom_fields key and make the custom_field native to Nornir
104
+ for custom_field, value in interface["custom_fields"].items():
105
+ interface[custom_field] = value
106
+ interface.pop("custom_fields")
107
+ # Dict comprehension to include only some key-value pairs
108
+ interface = {k: v for (k, v) in interface.items() if k in include_keys}
109
+ # If the interface has a peer device, add the peer device data to the interface
110
+ if enrich_vlan_data and interface.get("int_peer_device"):
111
+ # Get the interface peer device name
112
+ peer_name = interface["int_peer_device"]["name"]
113
+ # Slice the last two characters from the name to get the peer name of the virtual chassis
114
+ peer_name = peer_name if task.nornir.inventory.hosts.get(peer_name) else peer_name[:-2]
115
+ # Add the peer device data (VLAN name & VLAN ID) to the interface if 'cfg_vlans' exists
116
+ if task.nornir.inventory.hosts[peer_name].data.get("cfg_vlans"):
117
+ interface["int_peer_device"]["vlans"] = [
118
+ {k: v for (k, v) in vlan.items() if k in ["vid", "name"]}
119
+ for vlan in task.nornir.inventory.hosts[peer_name].data["cfg_vlans"]
120
+ ]
121
+ # Add an empty list if 'cfg_vlans' does not exist
122
+ else:
123
+ interface["int_peer_device"]["vlans"] = []
124
+
125
+ # Reorder the list to have all LAG interfaces first and all virtual interfaces last
126
+ lag_int = [i for i in interfaces if i["type"]["value"] == "lag"]
127
+ virt_int = [i for i in interfaces if i["type"]["value"] == "virtual"]
128
+ other_int = [i for i in interfaces if i["type"]["value"] not in ("lag", "virtual")]
129
+ # Assign the interfaces list to the device
130
+ interfaces = lag_int + other_int + virt_int
131
+
132
+ # Create an empty interfaces list in the device data
133
+ task.host["interfaces"] = []
134
+ # Dict comprehension to include only some key-value pairs
135
+ for interface in interfaces:
136
+ data = {k: v for (k, v) in interface.items() if k in include_keys}
137
+ task.host["interfaces"].append(data)
138
+
139
+ # Return the Nornir object
140
+ return Result(host=task.host)
141
+
142
+
143
+ def _load_vlan_data(task: Task, all_vlans: List[Dict]) -> Result:
144
+ """
145
+ Load NetBox VLAN data.
146
+ Args:
147
+ task (Task): The Nornir task object.
148
+ all_interfaces: (List[Dict]): List of all VLANs from NetBox.
149
+ Returns:
150
+ Result: Nornir result object.
151
+ """
152
+ # Set the failed flag to False
153
+ failed = False
154
+
155
+ # Return if the device has no VLAN groups
156
+ if not task.host.get("vlan_groups"):
157
+ # Return the Nornir object
158
+ result = (
159
+ f"'Load VLAN data' -> NetBoxResponse <Success: {not failed}>\n"
160
+ + "-> No 'vlan_groups' key found in NetBox"
161
+ )
162
+ return Result(host=task.host, result=result, changed=False, failed=failed)
163
+
164
+ # Dict keys for comprehension to include only some key-value pairs
165
+ include_keys = ["display", "name", "vid", "status", "description", "group", "role", "prefix_count"]
166
+ # Create an empty cfg_vlans dict in the device data
167
+ task.host["cfg_vlans"] = []
168
+
169
+ # Loop through the 'vlan_groups' and find VLANs that match the group and role
170
+ for group, roles in task.host["vlan_groups"].items():
171
+ # If the group_roles list is not empty, filter the VLANs to match only the role
172
+ if roles:
173
+ vlans = []
174
+ for role in roles:
175
+ # Update the VLANs to match all VLANs that match also the role
176
+ data = [x for x in all_vlans if (x["group"]["name"] == group and x["role"]["name"] == role)]
177
+ vlans.extend(data)
178
+ # Return if the data list is empty
179
+ if not data:
180
+ failed = True
181
+ break
182
+ else:
183
+ # Get all VLANs that match the group name
184
+ vlans = [x for x in all_vlans if x["group"]["name"] == group]
185
+ # Return if the vlans list is empty
186
+ if not vlans:
187
+ failed = True
188
+ break
189
+
190
+ # Add the VLANs to the cfg_vlans dict
191
+ for item in vlans:
192
+ # Dict comprehension to include only some key-value pairs
193
+ vlan = {k: v for (k, v) in item.items() if k in include_keys}
194
+ task.host["cfg_vlans"].append(vlan)
195
+
196
+ # Set the result based on the failed boolian
197
+ result = f"'Load VLAN data' -> NetBoxResponse <Success: {not failed}>\n"
198
+ if failed:
199
+ result += "-> One or more 'groups' and/or 'roles' under the 'vlan_groups' key not found in NetBox"
200
+ else:
201
+ result += "-> All 'groups' and 'roles' under the 'vlan_groups' key found in NetBox"
202
+
203
+ # Return the Nornir object
204
+ return Result(host=task.host, result=result, changed=False, failed=failed)
205
+
206
+
207
+ def _load_cisco_support_data(task: Task, all_csupp: List[Dict]) -> Result:
208
+ """
209
+ Load NetBox Cisco support data.
210
+ Args:
211
+ task (Task): The Nornir task object.
212
+ all_interfaces: (List[Dict]): List of all interfaces from NetBox.
213
+ Returns:
214
+ Result: Nornir result object.
215
+ """
216
+ # Set the failed flag to False
217
+ failed = False
218
+
219
+ # Get the Cisco Support data for the master
220
+ data = [x for x in all_csupp if x["device"]["id"] == task.host["id"]]
221
+ # Return if the data list is empty
222
+ if not data:
223
+ # Return the Nornir object
224
+ result = (
225
+ f"'Load Cisco-Support data' -> NetBoxResponse <Success: {not failed}>\n"
226
+ + "-> No Cisco-Support data found in NetBox"
227
+ )
228
+ return Result(host=task.host, result=result, changed=False, failed=failed)
229
+
230
+ # Dict keys for comprehension to exclude some key-value pairs from the response
231
+ exclude_keys = ["device"]
232
+ # Dict comprehension to exclude some key-value pairs
233
+ data = {k: v for (k, v) in data[0].items() if k not in exclude_keys}
234
+ task.host["cisco_support"] = {}
235
+ task.host["cisco_support"]["master"] = data
236
+
237
+ # Add all interfaces from the virtual chassis members
238
+ if task.host.get("virtual_chassis") and task.host["virtual_chassis"].get("members"):
239
+ task.host["cisco_support"]["members"] = []
240
+ for member in task.host["virtual_chassis"]["members"]:
241
+ data = [x for x in all_csupp if x["device"]["id"] == member["id"]]
242
+ # Dict comprehension to exclude some key-value pairs
243
+ data = {k: v for (k, v) in data[0].items() if k not in exclude_keys}
244
+ task.host["cisco_support"]["members"].append(data)
245
+
246
+ # Return the Nornir
247
+ return Result(host=task.host)
248
+
249
+
250
+ #### Nornir Tasks in regular Function #######################################################################
251
+
252
+
253
+ def load_additional_netbox_data(nr: Nornir, add_netbox_data: dict[str:bool]) -> Nornir:
254
+ """
255
+ Load additional data from NetBox into Nornir inventory based on 'add_netbox_data' options.
256
+ """
257
+ # pylint: disable=invalid-name
258
+ result_msg = []
259
+ task_text = "NORNIR load NetBox additional inventory data"
260
+
261
+ # Get the NetBox url from the inventory options
262
+ nb_url = nr.config.inventory.options["nb_url"]
263
+
264
+ # Add the virtual chassis data to the devices
265
+ if add_netbox_data.get("load_virtual_chassis_data"):
266
+ # Get all devices from NetBox
267
+ all_devices = get_nb_resources(url=f"{nb_url}/api/dcim/devices/?limit=1000")
268
+ # Run the custom Nornir task _load_virtual_chassis_data
269
+ result = nr.run(
270
+ name=task_text,
271
+ task=_load_virtual_chassis_data,
272
+ all_devices=all_devices,
273
+ on_failed=True,
274
+ )
275
+ # Exit the script if the task failed
276
+ if result.failed:
277
+ print_result(result)
278
+ exit_error(
279
+ task_text=task_text,
280
+ text="ALERT: LOAD VIRTUAL-CHASSIS DATA FAILED!",
281
+ msg="-> Analyse the Nornir Init Python function and NetBox API response",
282
+ )
283
+ # Append the result message
284
+ result_msg.append(" - Load Virtual-Chassis")
285
+
286
+ # Add the Cisco support data to the devices
287
+ if add_netbox_data.get("load_cisco_support_data"):
288
+ # Get all Cisco-Support data from NetBox
289
+ all_csupp = get_nb_resources(url=f"{nb_url}/api/plugins/device-support/cisco-device/?limit=1000")
290
+ # Run the custom Nornir task _load_cisco_support_data
291
+ result = nr.run(
292
+ name=task_text,
293
+ task=_load_cisco_support_data,
294
+ all_csupp=all_csupp,
295
+ on_failed=True,
296
+ )
297
+ # Exit the script if the task failed
298
+ if result.failed:
299
+ print_result(result)
300
+ exit_error(
301
+ task_text=task_text,
302
+ text="ALERT: LOAD CISCO-SUPPORT FAILED!",
303
+ msg="-> Analyse the Nornir Init Python function and NetBox API response",
304
+ )
305
+ # Append the result message
306
+ result_msg.append(" - Load Cisco-Support")
307
+
308
+ # Add the vlan data to the devices
309
+ if add_netbox_data.get("load_vlan_data"):
310
+ # Get all vlans from NetBox
311
+ all_vlans = get_nb_resources(url=f"{nb_url}/api/ipam/vlans/?limit=1000?")
312
+ # Run the custom Nornir task _load_vlan_data
313
+ result = nr.run(
314
+ name=task_text,
315
+ task=_load_vlan_data,
316
+ all_vlans=all_vlans,
317
+ on_failed=True,
318
+ )
319
+ # Exit the script if the task failed
320
+ if result.failed:
321
+ print_result(result)
322
+ exit_error(
323
+ task_text=task_text,
324
+ text="ALERT: LOAD VLAN DATA FAILED!",
325
+ msg="-> Analyse the Nornir Init Python function and NetBox API response",
326
+ )
327
+ # Append the result message
328
+ result_msg.append(" - Load VLANs")
329
+
330
+ # Add the interfaces data to the devices
331
+ if add_netbox_data.get("load_interface_data"):
332
+ # Get all interfaces from NetBox
333
+ all_interfaces = get_nb_resources(url=f"{nb_url}/api/dcim/interfaces/?limit=1000")
334
+ # Run the custom Nornir task _load_interface_data
335
+ # Enrich the interface data with VLAN data if the 'load_vlan_data' option is set
336
+ result = nr.run(
337
+ name=task_text,
338
+ task=_load_interface_data,
339
+ all_interfaces=all_interfaces,
340
+ enrich_vlan_data=add_netbox_data.get("load_vlan_data", False),
341
+ on_failed=True,
342
+ )
343
+ # Exit the script if the task failed
344
+ if result.failed:
345
+ print_result(result)
346
+ exit_error(
347
+ task_text=task_text,
348
+ text="ALERT: LOAD INTERFACE DATA FAILED!",
349
+ msg="-> Analyse the Nornir Init Python function and NetBox API response",
350
+ )
351
+ # Append the result message
352
+ result_msg.append(" - Load Interfaces")
353
+
354
+ # Print the result of the additional loaded data
355
+ print_task_name(text=task_text)
356
+ print(task_info(text=task_text, changed=False))
357
+ print("'Load NetBox additional inventory data' -> NornirResponse <Success: True>")
358
+ print("-> Additional loaded data:")
359
+ for msg in result_msg:
360
+ print(msg)