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,67 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This module sets the device status in NetBox by running a custom script.
4
+ The Main function is intended to import and execute by other scripts.
5
+ """
6
+
7
+
8
+ import sys
9
+ from nornir_collection.utils import exit_error, load_yaml_file
10
+ from nornir_collection.netbox.custom_script import run_nb_custom_script
11
+
12
+
13
+ __author__ = "Willi Kubny"
14
+ __maintainer__ = "Willi Kubny"
15
+ __version__ = "1.0"
16
+ __license__ = "MIT"
17
+ __email__ = "willi.kubny@dreyfusbank.ch"
18
+ __status__ = "Production"
19
+
20
+
21
+ def main(nr_config: str) -> None:
22
+ """
23
+ Main function is intended to import and execute by other scripts.
24
+ It load Nornir configuration, retrieve NetBox URL, and run a custom NetBox script.
25
+
26
+ * Args:
27
+ * nr_config (str): Path to the Nornir configuration YAML file.
28
+
29
+ * Steps:
30
+ * Loads the Nornir configuration file.
31
+ * Checks if the configuration file is loaded successfully; exits with code 1 if not.
32
+ * Retrieves the NetBox URL from the configuration.
33
+ * Runs the NetBox custom script 'set_device_status.SetDeviceStatus'.
34
+ * Checks the result of the custom script execution.
35
+
36
+ * Exits:
37
+ * Exits with code 1 if the Nornir configuration file is empty or if any task fails.
38
+ """
39
+
40
+ # Load the Nornir yaml config file as dict and print a error message
41
+ nr_config_dict = load_yaml_file(
42
+ file=nr_config, text="Load Nornir Config File", silent=False, verbose=False
43
+ )
44
+ # Check the loaded config file and exit the script with exit code 1 if the dict is empty
45
+ if not nr_config_dict:
46
+ sys.exit(1)
47
+
48
+ # Get the NetBox URL (Authentication token will be loaded as nb_token env variable)
49
+ nb_url = nr_config_dict["inventory"]["options"]["nb_url"]
50
+
51
+ # Run the NetBox custom script 'set_device_status.SetDeviceStatus'
52
+ result_failed = run_nb_custom_script(
53
+ name="set_device_status.SetDeviceStatus",
54
+ url=f"{nb_url}/api/extras/scripts/set_device_status.SetDeviceStatus/",
55
+ payload={"data": {}, "commit": True},
56
+ verbose=False,
57
+ )
58
+
59
+ # Check the result and exit the script with exit code 1 and a error message
60
+ if result_failed:
61
+ exit_error(
62
+ task_text="NetBox Custom Script Failed",
63
+ msg=[
64
+ "Check the result details in the pipeline log or directly in NetBox",
65
+ "-> One or more device primary ip-address is not reachable by ping",
66
+ ],
67
+ )
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This module sync a list of NetBox datasources and print the result.
4
+ The Main function is intended to import and execute by other scripts.
5
+ """
6
+
7
+
8
+ import time
9
+ import json
10
+ import sys
11
+ from nornir_collection.utils import load_yaml_file, task_name, task_error, task_info, exit_error
12
+ from nornir_collection.netbox.utils import get_nb_resources, post_nb_resources
13
+
14
+ __author__ = "Willi Kubny"
15
+ __maintainer__ = "Willi Kubny"
16
+ __version__ = "1.0"
17
+ __license__ = "MIT"
18
+ __email__ = "willi.kubny@dreyfusbank.ch"
19
+ __status__ = "Production"
20
+
21
+
22
+ def main(nr_config: str, datasources: list[str]) -> None:
23
+ """
24
+ Main function is intended to import and execute by other scripts.
25
+ It sync external data sources to NetBox.
26
+
27
+ * Args:
28
+ * nr_config (str): Path to the Nornir YAML configuration file.
29
+ * datasources (list[str]): List of data source names to be synced.
30
+
31
+ * Steps:
32
+ * Loads the Nornir configuration from the specified YAML file.
33
+ * Retrieves the NetBox URL from the configuration.
34
+ * Iterates over the provided data sources and attempts to sync each one with NetBox.
35
+ * For each data source, it checks if the data source exists in NetBox.
36
+ * If the data source exists, it initiates a sync operation and monitors its status.
37
+ * Logs the result of each sync operation.
38
+ * If any data sources fail to sync, it exits with an error message and a list of failed data sources.
39
+
40
+ * Exit:
41
+ * Exits with code 1 if the Nornir configuration file is empty or if any task fails.
42
+ """
43
+
44
+ # Create a empty list to fill with all datasources that fail to sync
45
+ failed_datasources = []
46
+
47
+ # Load the Nornir yaml config file from the filepath
48
+ nr_config = load_yaml_file(file=nr_config)
49
+ # Check the loaded config file and exit the script with exit code 1 if the dict is empty
50
+ if not nr_config:
51
+ sys.exit(1)
52
+ # Get the NetBox URL
53
+ nb_url = nr_config["inventory"]["options"]["nb_url"]
54
+
55
+ task_text = "NETBOX Sync External Git Datasource"
56
+ print(task_name(text=task_text))
57
+
58
+ # Loop over all datasources and sync the data into NetBox
59
+ for datasource in datasources:
60
+ # Get the NetBox datasource
61
+ query = {"name": datasource, "enabled": True}
62
+ response = get_nb_resources(url=f"{nb_url}/api/core/data-sources/", params=query)
63
+ # Verify the result (list of dict)
64
+ if not response:
65
+ failed_datasources.append(datasource)
66
+ print(task_error(text=task_text, changed=False))
67
+ print(f"'{task_text} {datasource}' -> NetBoxResponse <Success: False>")
68
+ print(f"-> Datasource '{datasource}' not found")
69
+ # Continue because the datasource sync failed
70
+ continue
71
+
72
+ # Start the NetBox datasource sync
73
+ ds_id = response[0]["id"]
74
+ response = post_nb_resources(url=f"{nb_url}/api/core/data-sources/{ds_id}/sync/", payload=[])
75
+ result = response.json()
76
+
77
+ # Do thee GET request to verify the status of the running NetBox datasource sync
78
+ # Terminate the while loop with a timeout of max 300s (5min)
79
+ timeout_start = time.time()
80
+ while time.time() < timeout_start + 300:
81
+ # Verify the NetBox datasource status
82
+ if result["status"]["value"] not in ("queued", "syncing"):
83
+ # Break out of the loop as the datasource sync was successful
84
+ break
85
+ # Get the NetBox datasource
86
+ response = get_nb_resources(url=f"{nb_url}/api/core/data-sources/", params=query)
87
+ result = response[0]
88
+ time.sleep(1)
89
+
90
+ # Print the status after the datasource sync
91
+ if result["status"]["value"] == "completed":
92
+ print(task_info(text=f"{task_text} {datasource}", changed=True))
93
+ print(f"'{task_text} {datasource}' -> NetBoxResponse <Success: True>")
94
+ print(f"-> Completed {task_text} '{datasource}'")
95
+ else:
96
+ failed_datasources.append(datasource)
97
+ print(task_error(text=f"{task_text} {datasource}", changed=False))
98
+ print(f"'{task_text} {datasource}' -> NetBoxResponse <Success: False>")
99
+ print(f"-> Failed {task_text} '{datasource}'")
100
+ print(json.dumps(result, indent=4))
101
+
102
+ # Check the result and exit the script with exit code 1 and a error message
103
+ if failed_datasources:
104
+ exit_error(
105
+ task_text=f"{task_text} Failed",
106
+ msg=[
107
+ "Check the result details in the pipeline log or directly in NetBox",
108
+ "-> The following datasources failed to sync:",
109
+ failed_datasources,
110
+ ],
111
+ )
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This module updates the serial numbers of Cisco devices in NetBox using Nornir.
4
+ The Main function is intended to import and execute by other scripts.
5
+ """
6
+
7
+
8
+ from nornir.core import Nornir
9
+ from nornir.core.filter import F
10
+ from nornir.core.task import Task, Result
11
+ from nornir_collection.nornir_plugins.inventory.utils import init_nornir
12
+ from nornir_collection.cisco.configuration_management.cli.show_tasks import _get_serial_numbers
13
+ from nornir_collection.netbox.utils import get_nb_resources, patch_nb_resources
14
+ from nornir_collection.netbox.utils import _nb_patch_resources, _nb_create_payload_patch_device_serials
15
+ from nornir_collection.utils import (
16
+ print_task_title,
17
+ print_result,
18
+ task_name,
19
+ task_info,
20
+ exit_error,
21
+ )
22
+
23
+
24
+ def update_serial_number(task: Task) -> Result:
25
+ """
26
+ Update the device serial number in NetBox based on the serial number retrieved from the Cisco device.
27
+
28
+ Args:
29
+ task (Task): The Nornir task object.
30
+
31
+ Returns:
32
+ Result: The Nornir result object.
33
+ """
34
+ task_text = "Update device serial number"
35
+
36
+ # Use the helper function _get_serial_numbers to get the software version
37
+ serials, sub_serials, verbose_result, failed = _get_serial_numbers(task=task)
38
+ # If failed is the the verbose result contains the full Nornir result string (result + verbose_result)
39
+ if failed:
40
+ # Return the Nornir result as failed
41
+ return Result(host=task.host, result=verbose_result, failed=True)
42
+
43
+ # Create the API payload to update the device serial number or return the Nornir result as failed
44
+ payload = _nb_create_payload_patch_device_serials(task=task, task_text=task_text, serials=serials)
45
+
46
+ # Update the device serial number in NetBox
47
+ url = f"{task.nornir.config.inventory.options['nb_url']}/api/dcim/devices/"
48
+ result = _nb_patch_resources(task=task, task_text=task_text, url=url, payload=payload)
49
+ result += f"\n-> Serials: {[item['serial'] for item in payload]}"
50
+
51
+ # If the add_serials dict is not empty, then the dict contains managed devices by a controller (e.g. WLC)
52
+ if not sub_serials:
53
+ return Result(host=task.host, result=result, changed=False, failed=failed)
54
+
55
+ # Create a list of dicts. Multiple dicts if its a virtual chassis
56
+ payload = []
57
+ for serial, device in sub_serials.items():
58
+ nb_device = get_nb_resources(url=f"{url}?name={device}")
59
+ if nb_device:
60
+ payload.append({"id": nb_device[0]["id"], "serial": serial})
61
+ result += f"\n-> Serial: {serial} (Sub-Device)"
62
+ else:
63
+ result += f"\n-> Serial: {serial} (Sub-Device '{device}' not in NetBox)"
64
+ failed = True
65
+
66
+ # POST request to update the Cisco Support Plugin desired release
67
+ response = patch_nb_resources(url=url, payload=payload)
68
+ # Verify the response code and return the result
69
+ if response.status_code != 200:
70
+ failed = True
71
+
72
+ return Result(host=task.host, result=result, changed=False, failed=failed)
73
+
74
+
75
+ def update_cisco_inventory_data(nr: Nornir) -> bool:
76
+ """
77
+ Update NetBox Cisco Device Inventory Data. This function runs a Nornir task to update the serial number
78
+ of Cisco devices in NetBox. The Nornir inventory is filtered by device manufacturer (Cisco), device
79
+ status (active), and device role (router, switch or wlc). If any of the tasks fail, the function returns
80
+ True, otherwise it returns False.
81
+
82
+ Args:
83
+ nr: The Nornir inventory object.
84
+
85
+ Returns:
86
+ bool: True if any of the tasks failed, False otherwise.
87
+ """
88
+ # pylint: disable=invalid-name
89
+ # Track if one of the tasks has failed
90
+ failed = False
91
+
92
+ print_task_title(title="Update NetBox Cisco Device Inventory Data")
93
+
94
+ # Filter Nornir inventory by device manufacturer, device status and device role
95
+ nr_cisco = nr.filter(
96
+ F(device_type__manufacturer__slug__contains="cisco")
97
+ & F(status__value="active")
98
+ & ~F(role__slug__any=["ap", "access-point", "srv", "server"])
99
+ )
100
+ task_text = "Filter nornir inventory"
101
+ print(task_name(text=task_text))
102
+ print(task_info(text=task_text, changed=False))
103
+ print(
104
+ f"'{task_text}' -> NornirResult <Success: True>\n"
105
+ + "-> Filter condition: Device manufacturer slug contains 'cisco'\n"
106
+ + " & Device status value is 'active'\n"
107
+ + " & Device role is not 'ap', 'access-point', 'srv' or 'server'"
108
+ )
109
+
110
+ # Run the custom Nornir task update_serial_number
111
+ task_text = "NETBOX update device serial number"
112
+ result = nr_cisco.run(name=task_text, task=update_serial_number, on_failed=True)
113
+ # Print the whole result for each host
114
+ print_result(result)
115
+ if result.failed:
116
+ failed = True
117
+
118
+ return failed
119
+
120
+
121
+ def main(nr_config: str) -> None:
122
+ """
123
+ Main function is intended to import and execute by other scripts. It initialize Nornir and update Cisco
124
+ inventory data in NetBox.
125
+
126
+ * Args:
127
+ * nr_config (str): Path to the Nornir configuration file.
128
+
129
+ * Steps:
130
+ * Initializes the Nornir inventory object using the provided configuration file.
131
+ * Runs the Nornir Cisco support plugin to update NetBox Cisco inventory data.
132
+ * Checks the result of the update task and exits the script with an error message if the task failed.
133
+
134
+ * Exit:
135
+ * Exits with code 1 if the Nornir configuration file is empty or if any task fails.
136
+ """
137
+ # pylint: disable=invalid-name
138
+
139
+ #### Initialize Nornir ##################################################################################
140
+
141
+ # Initialize, transform and filter the Nornir inventory are return the filtered Nornir object
142
+ # Define data to load from NetBox in addition to the base Nornir inventory plugin
143
+ add_netbox_data = {"load_virtual_chassis_data": True}
144
+ nr = init_nornir(config_file=nr_config, add_netbox_data=add_netbox_data)
145
+
146
+ #### Run Nornir Cisco Support Plugin Update Tasks #######################################################
147
+
148
+ # Update NetBox Cisco inventory data
149
+ result_failed = update_cisco_inventory_data(nr=nr)
150
+
151
+ # Check the result and exit the script with exit code 1 and a error message
152
+ if result_failed:
153
+ text = "Update NetBox Cisco Device Inventory Data with Nornir"
154
+ print(task_name(text=text))
155
+ exit_error(
156
+ task_text=f"{text} Failed",
157
+ msg="Check the result details for failed Nornir tasks",
158
+ )
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This module updates Cisco support plugin data in NetBox using Nornir.
4
+ The Main function is intended to import and execute by other scripts.
5
+ """
6
+
7
+
8
+ import sys
9
+ from nornir.core.filter import F
10
+ from nornir.core import Nornir
11
+ from nornir.core.task import Task, Result
12
+ from nornir_collection.nornir_plugins.inventory.utils import init_nornir
13
+ from nornir_collection.netbox.utils import patch_nb_resources
14
+ from nornir_collection.cisco.configuration_management.cli.show_tasks import _get_software_version
15
+ from nornir_collection.utils import (
16
+ print_task_title,
17
+ task_name,
18
+ task_info,
19
+ exit_error,
20
+ print_result,
21
+ load_yaml_file,
22
+ )
23
+
24
+
25
+ __author__ = "Willi Kubny"
26
+ __maintainer__ = "Willi Kubny"
27
+ __version__ = "1.0"
28
+ __license__ = "MIT"
29
+ __email__ = "willi.kubny@dreyfusbank.ch"
30
+ __status__ = "Production"
31
+
32
+
33
+ def update_cisco_support_contract_supplier(task: Task, data: dict) -> Result:
34
+ """
35
+ Update the contract supplier information for a Cisco device in NetBox using the Cisco Support Plugin API.
36
+
37
+ Args:
38
+ task (Task): The Nornir task object.
39
+ data (dict): A dictionary containing the contract supplier information for the device.
40
+
41
+ Returns:
42
+ Result: A Nornir Result object containing the result of the task.
43
+ """
44
+ # Create empty lists to fill with the payload and the serials of all devices
45
+ payload = []
46
+ serials = []
47
+
48
+ # Get the NetBox device serial number and database id for the device depending if its a virtual chassis
49
+ if "master" in task.host["cisco_support"]:
50
+ nb_serial = task.host["cisco_support"]["master"]["serial"]
51
+ nb_id = task.host["cisco_support"]["master"]["id"]
52
+ else:
53
+ nb_serial = task.host["cisco_support"]["serial"]
54
+ nb_id = task.host["cisco_support"]["id"]
55
+ # Add the serial to the serials list
56
+ serials.append(nb_serial)
57
+ # Check if the master is covered by a 3rd party support contract
58
+ if nb_serial not in data.keys():
59
+ payload.append(
60
+ {
61
+ "id": nb_id,
62
+ "contract_supplier": None,
63
+ "partner_status": None,
64
+ "partner_service_level": None,
65
+ "partner_customer_number": None,
66
+ "partner_coverage_end_date": None,
67
+ }
68
+ )
69
+ else:
70
+ payload.append(
71
+ {
72
+ "id": nb_id,
73
+ "contract_supplier": data[str(nb_serial)]["contract_supplier"],
74
+ "partner_status": data[str(nb_serial)]["partner_status"],
75
+ "partner_service_level": data[str(nb_serial)]["partner_service_level"],
76
+ "partner_customer_number": data[str(nb_serial)]["partner_customer_number"],
77
+ "partner_coverage_end_date": data[str(nb_serial)]["partner_coverage_end_date"],
78
+ }
79
+ )
80
+
81
+ # Check if the members are covered by a 3rd party support contract
82
+ if "members" in task.host["cisco_support"]:
83
+ for member in task.host["cisco_support"]["members"]:
84
+ # Get the NetBox device serial number and database id for the virtual chassis member
85
+ nb_serial = member["serial"]
86
+ nb_id = member["id"]
87
+ # Add the serial to the serials list
88
+ serials.append(nb_serial)
89
+ if nb_serial not in data.keys():
90
+ payload.append(
91
+ {
92
+ "id": nb_id,
93
+ "contract_supplier": None,
94
+ "partner_status": None,
95
+ "partner_service_level": None,
96
+ "partner_customer_number": None,
97
+ "partner_coverage_end_date": None,
98
+ }
99
+ )
100
+ else:
101
+ payload.append(
102
+ {
103
+ "id": nb_id,
104
+ "contract_supplier": data[str(nb_serial)]["contract_supplier"],
105
+ "partner_status": data[str(nb_serial)]["partner_status"],
106
+ "partner_service_level": data[str(nb_serial)]["partner_service_level"],
107
+ "partner_customer_number": data[str(nb_serial)]["partner_customer_number"],
108
+ "partner_coverage_end_date": data[str(nb_serial)]["partner_coverage_end_date"],
109
+ }
110
+ )
111
+
112
+ # Get the NetBox url from the inventory options
113
+ nb_url = task.nornir.config.inventory.options["nb_url"]
114
+ # POST request to update the Cisco Support Plugin desired release
115
+ response = patch_nb_resources(
116
+ url=f"{nb_url}/api/plugins/device-support/cisco-device/",
117
+ payload=payload,
118
+ )
119
+
120
+ # Verify the response code and set the result
121
+ if response.status_code == 200:
122
+ result = "'Update contract supplier' -> NetBoxResponse: <Success: True>\n" + f"-> Serials: {serials}"
123
+ return Result(host=task.host, result=result, changed=False, failed=False)
124
+
125
+ result = (
126
+ "'Update contract supplier' -> NetBoxResponse: <Success: True>\n"
127
+ + f"-> Response Status Code: {response.status_code}\n"
128
+ + f"-> Response Text: {response.text}\n"
129
+ + f"-> Payload: {payload}"
130
+ )
131
+ return Result(host=task.host, result=result, changed=False, failed=True)
132
+
133
+
134
+ def update_cisco_support_desired_and_current_release(task: Task) -> Result:
135
+ """
136
+ Update the current and desired release of a Cisco device in NetBox using the Cisco Support Plugin.
137
+
138
+ Args:
139
+ task (Task): A Nornir task object.
140
+
141
+ Returns:
142
+ Result: A Nornir result object with the following attributes:
143
+ - result (str): A string describing the result of the task.
144
+ - changed (bool): Whether or not the task made any changes.
145
+ - failed (bool): Whether or not the task failed.
146
+ """
147
+ # Skip hosts which have no software -> 'PID without Cisco software' (managed over a controller)
148
+ if "recommended_release" in task.host["cisco_support"]:
149
+ if "PID without Cisco software" in task.host["cisco_support"]["recommended_release"]:
150
+ result = (
151
+ "'Update device current and desired release' -> NetBoxResponse: <Success: True>\n"
152
+ + "-> Device without Cisco software (managed over a controller)"
153
+ )
154
+ return Result(host=task.host, result=result, changed=False, failed=False)
155
+
156
+ # Use the helper function _get_software_version to get the current software release
157
+ current_rel, verbose_result, failed = _get_software_version(task=task)
158
+ # If failed, the the verbose result contains the full Nornir result string (result + verbose_result)
159
+ if failed:
160
+ # Return the Nornir result as failed
161
+ return Result(host=task.host, result=verbose_result, failed=True)
162
+
163
+ try:
164
+ # Get the desired release from the host config context
165
+ desired_rel = task.host["software"]["version"]
166
+
167
+ # Create a list of dicts. Multiple dicts if its a virtual chassis
168
+ payload = []
169
+ # Add the device depending if its a virtual chassis in NetBox or not
170
+ nb_id = (
171
+ task.host["cisco_support"]["master"]["id"]
172
+ if "master" in task.host["cisco_support"]
173
+ else task.host["cisco_support"]["id"]
174
+ )
175
+ payload.append({"id": nb_id, "current_release": current_rel, "desired_release": desired_rel})
176
+
177
+ # Add all members to the payload if available
178
+ if "members" in task.host["cisco_support"]:
179
+ for member in task.host["cisco_support"]["members"]:
180
+ payload.append(
181
+ {"id": member["id"], "current_release": current_rel, "desired_release": desired_rel}
182
+ )
183
+
184
+ except KeyError as error:
185
+ result = (
186
+ f"'{task.name}' -> NornirResponse <Success: False>\n"
187
+ + f"-> Nornir inventory key {error} not found"
188
+ )
189
+ # Return the Nornir result
190
+ return Result(host=task.host, result=result, changed=False, failed=True)
191
+
192
+ # Get the NetBox url from the inventory options
193
+ nb_url = task.nornir.config.inventory.options["nb_url"]
194
+ # POST request to update the Cisco Support Plugin desired release
195
+ response = patch_nb_resources(
196
+ url=f"{nb_url}/api/plugins/device-support/cisco-device/",
197
+ payload=payload,
198
+ )
199
+
200
+ # Verify the response code and set the result
201
+ if response.status_code == 200:
202
+ result = (
203
+ "'Update device current and desired release' -> NetBoxResponse: <Success: True>\n"
204
+ + f"-> Current release: {current_rel}\n"
205
+ + f"-> Desired release: {desired_rel}"
206
+ )
207
+ return Result(host=task.host, result=result, changed=False, failed=False)
208
+
209
+ result = (
210
+ "'Update device current and desired release' -> NetBoxResponse: <Success: False>\n"
211
+ + f"-> Response Status Code: {response.status_code}\n"
212
+ + f"-> Response Text: {response.text}\n"
213
+ + f"-> Payload: {payload}"
214
+ )
215
+ return Result(host=task.host, result=result, changed=False, failed=True)
216
+
217
+
218
+ def update_cisco_support_plugin_data(nr: Nornir, partner_inv: str = None) -> bool:
219
+ """
220
+ Update NetBox Cisco Support Plugin Data which can't be retrieved from the Cisco Support API.
221
+
222
+ Args:
223
+ nr (Nornir): The Nornir object.
224
+ partner_inv (str, optional): Path to the partner inventory file. Defaults to None.
225
+
226
+ Returns:
227
+ bool: True if any of the tasks failed, False otherwise.
228
+ """
229
+
230
+ # pylint: disable=invalid-name
231
+ # Track if one of the tasks has failed
232
+ failed = False
233
+
234
+ print_task_title(title="Update NetBox Cisco Support Plugin Data")
235
+
236
+ # Filter Nornir inventory by device manufacturer, device status and device role
237
+ nr_cisco = nr.filter(
238
+ F(device_type__manufacturer__slug__contains="cisco")
239
+ & F(status__value="active")
240
+ & ~F(role__slug__any=["ap", "access-point", "srv", "server"])
241
+ )
242
+ task_text = "Filter nornir inventory"
243
+ print(task_name(text=task_text))
244
+ print(task_info(text=task_text, changed=False))
245
+ print(
246
+ f"'{task_text}' -> NornirResult <Success: True>\n"
247
+ + "-> Filter condition: Device manufacturer slug contains 'cisco'\n"
248
+ + " & Device status value is 'active'\n"
249
+ + " & Device role is not 'ap', 'access-point', 'srv' or 'server'"
250
+ )
251
+
252
+ # Run the custom Nornir task update_cisco_support_desired_and_current_release
253
+ task_text = "NETBOX update device current and desired release"
254
+ result = nr_cisco.run(
255
+ name=task_text,
256
+ task=update_cisco_support_desired_and_current_release,
257
+ on_failed=True,
258
+ )
259
+ # Print the whole result for each host
260
+ print_result(result)
261
+ if result.failed:
262
+ failed = True
263
+
264
+ if not partner_inv:
265
+ print(task_name(text=task_text))
266
+ print(task_info(text=task_text, changed=False))
267
+ print(f"'{task_text}' -> NornirResponse: <Success: True>")
268
+ print("-> No list of 3rd party Cisco support partners provided")
269
+ print("-> All devices are expected to be covered by Cisco SNTC")
270
+
271
+ return failed
272
+
273
+ # Load the partner inventory file as dict and print a error message
274
+ data = load_yaml_file(
275
+ file=partner_inv, text="Load Cisco support partner inventory file", silent=False, verbose=False
276
+ )
277
+ # Check the loaded file and exit the script with exit code 1 if the list is empty
278
+ if not data:
279
+ sys.exit(1)
280
+ # Run the custom Nornir task update_cisco_support_contract_supplier
281
+ task_text = "NETBOX update contract supplier"
282
+ result = nr_cisco.run(
283
+ name=task_text,
284
+ task=update_cisco_support_contract_supplier,
285
+ data=data,
286
+ on_failed=True,
287
+ )
288
+ # Print the whole result for each host
289
+ print_result(result)
290
+ if result.failed:
291
+ failed = True
292
+
293
+ return failed
294
+
295
+
296
+ def main(nr_config: str, partner_inv: str = None) -> None:
297
+ """
298
+ Main function is intended to import and execute by other scripts.
299
+ It initialize Nornir, filter the inventory for Cisco devices, and update the NetBox Cisco support
300
+ plugin data.
301
+
302
+ * Args:
303
+ * nr_config (str): Path to the Nornir configuration file.
304
+ * partner_inv (str, optional): Path to the partner inventory file. Defaults to None.
305
+
306
+ * Steps:
307
+ * Initializes the Nornir inventory object using the provided configuration file.
308
+ * Filters the Nornir inventory by device manufacturer (Cisco) and status (active).
309
+ * Runs the custom Nornir tasks update_cisco_support_desired_and_current_release and
310
+ update_cisco_support_contract_supplier.
311
+ * If no partner inventory file is provided, all devices are expected to be covered by Cisco SNTC.
312
+ * Checks the result of the update tasks and exits the script with an error message if any task
313
+
314
+ * Exit:
315
+ * Exits with code 1 if the Nornir configuration file is empty or if any task fails.
316
+ """
317
+
318
+ # pylint: disable=invalid-name
319
+
320
+ #### Initialize Nornir ##################################################################################
321
+
322
+ # Initialize and transform the Nornir inventory object
323
+ # Define data to load from NetBox in addition to the base Nornir inventory plugin
324
+ add_netbox_data = {"load_virtual_chassis_data": True, "load_cisco_support_data": True}
325
+ nr = init_nornir(config_file=nr_config, add_netbox_data=add_netbox_data)
326
+
327
+ #### Run Nornir Cisco Support Plugin Update Tasks #######################################################
328
+
329
+ # Update NetBox Cisco support plugin data
330
+ result_failed = update_cisco_support_plugin_data(nr=nr, partner_inv=partner_inv)
331
+
332
+ # Check the result and exit the script with exit code 1 and a error message
333
+ if result_failed:
334
+ text = "Update NetBox Cisco Support Plugin Data with Nornir"
335
+ print(task_name(text=text))
336
+ exit_error(
337
+ task_text=f"{text} Failed",
338
+ msg="Check the result details for failed Nornir tasks",
339
+ )