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,161 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This module updates the serial numbers of Fortinet 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.fortinet.utils import get_fgt_resources
13
+ from nornir_collection.netbox.utils import _nb_patch_resources, _nb_create_payload_patch_device_serials
14
+ from nornir_collection.utils import (
15
+ print_task_title,
16
+ print_result,
17
+ task_name,
18
+ task_info,
19
+ exit_error,
20
+ )
21
+
22
+
23
+ __author__ = "Willi Kubny"
24
+ __maintainer__ = "Willi Kubny"
25
+ __version__ = "1.0"
26
+ __license__ = "MIT"
27
+ __email__ = "willi.kubny@dreyfusbank.ch"
28
+ __status__ = "Production"
29
+
30
+
31
+ def update_serial_number(task: Task) -> Result:
32
+ """
33
+ Update the device serial number in NetBox based on the serial number retrieved from the Fortinet device.
34
+
35
+ Args:
36
+ task (Task): The Nornir task object.
37
+
38
+ Returns:
39
+ Result: The Nornir result object.
40
+ """
41
+ task_text = "Update device serial number"
42
+
43
+ # Get the Fortinet device serial number from the Fortinet Rest API
44
+ response = get_fgt_resources(task=task, url="api/v2/monitor/system/ha-peer/")
45
+
46
+ # Verify the response code and return the result
47
+ if response.status_code != 200:
48
+ result = (
49
+ f"'{task.name}' -> APIResponse: <Success: False>\n"
50
+ + f"-> Response Status Code: {response.status_code}\n"
51
+ + f"-> Response Text: {response.text}"
52
+ )
53
+ # Return the Nornir result
54
+ return Result(host=task.host, result=result, changed=False, failed=True)
55
+
56
+ # Create the serials dict to us in the function _nb_create_payload_patch_device_serials
57
+ serials = {}
58
+ if task.host["virtual_chassis"]:
59
+ # Create a serials dict to map the serials to the virtual chassis member hostname
60
+ response = {serial["hostname"]: serial["serial_no"] for serial in response.json()["results"]}
61
+ # Create the serials dict for a HA cluster
62
+ serials["1"] = response[f"{task.host['virtual_chassis']['master']['name']}"]
63
+ if "members" in task.host["virtual_chassis"]:
64
+ for num, member in enumerate(task.host["virtual_chassis"]["members"], start=2):
65
+ serials[str(num)] = response[f"{member['name']}"]
66
+ else:
67
+ # Create the serials dict for a single device
68
+ serials["1"] = response.json()["serial"]
69
+
70
+ # Create the API payload to update the device serial number or return the Nornir result as failed
71
+ payload = _nb_create_payload_patch_device_serials(task=task, task_text=task_text, serials=serials)
72
+
73
+ # Get the NetBox url from the inventory options and update the device serial number in NetBox
74
+ url = f"{task.nornir.config.inventory.options['nb_url']}/api/dcim/devices/"
75
+ result = _nb_patch_resources(task=task, task_text=task_text, url=url, payload=payload)
76
+ result += f"\n-> Serials: {[item['serial'] for item in payload]}"
77
+
78
+ # Return the result
79
+ return Result(host=task.host, result=result, changed=False, failed=False)
80
+
81
+
82
+ def update_fortinet_inventory_data(nr: Nornir) -> bool:
83
+ """
84
+ Update NetBox Cisco Device Inventory Data. This function runs a Nornir task to update the serial number
85
+ of Fortinet devices in NetBox. The Nornir inventory is filtered by device manufacturer (fortinet). If any
86
+ of the tasks fail, the function returns True, otherwise it returns False.
87
+
88
+ Args:
89
+ nr: The Nornir inventory object.
90
+
91
+ Returns:
92
+ bool: True if any of the tasks failed, False otherwise.
93
+ """
94
+ # pylint: disable=invalid-name
95
+ # Track if one of the tasks has failed
96
+ failed = False
97
+
98
+ print_task_title(title="Update NetBox Fortinet Device Inventory Data")
99
+
100
+ # Filter Nornir inventory by device manufacturer (a device can have only one manufacturer)
101
+ nr_fortinet = nr.filter(
102
+ F(device_type__manufacturer__slug__contains="fortinet") & F(status__value="active")
103
+ )
104
+ task_text = "Filter nornir inventory"
105
+ print(task_name(text=task_text))
106
+ print(task_info(text=task_text, changed=False))
107
+ print(
108
+ f"'{task_text}' -> NornirResult <Success: True>\n"
109
+ + "-> Filter condition: Device manufacturer slug contains 'fortinet'\n"
110
+ + " & Device status value is 'active'"
111
+ )
112
+
113
+ # Run the custom Nornir task update_serial_number
114
+ name = "NETBOX update device serial number"
115
+ result = nr_fortinet.run(name=name, task=update_serial_number, on_failed=True)
116
+ # Print the whole result for each host
117
+ print_result(result)
118
+ if result.failed:
119
+ failed = True
120
+
121
+ return failed
122
+
123
+
124
+ def main(nr_config: str) -> None:
125
+ """
126
+ Main function is intended to import and execute by other scripts.
127
+ It initialize Nornir and update Fortinet inventory data in NetBox.
128
+
129
+ * Args:
130
+ * nr_config (str): Path to the Nornir configuration file.
131
+
132
+ * Steps:
133
+ * Initializes the Nornir inventory object using the provided configuration file.
134
+ * Runs the task to update Fortinet inventory data in NetBox.
135
+ * Checks the result of the update task and exits the script with an error message if the task failed.
136
+
137
+ * Exits:
138
+ * Exits with code 1 if the Nornir configuration file is empty or if any task fails.
139
+ """
140
+ # pylint: disable=invalid-name
141
+
142
+ #### Initialize Nornir ##################################################################################
143
+
144
+ # Initialize, transform and filter the Nornir inventory are return the filtered Nornir object
145
+ # Define data to load from NetBox in addition to the base Nornir inventory plugin
146
+ add_netbox_data = {"load_virtual_chassis_data": True}
147
+ nr = init_nornir(config_file=nr_config, add_netbox_data=add_netbox_data)
148
+
149
+ #### Run Nornir Cisco Support Plugin Update Tasks #######################################################
150
+
151
+ # Update NetBox Fortigate inventory data
152
+ result_failed = update_fortinet_inventory_data(nr=nr)
153
+
154
+ # Check the result and exit the script with exit code 1 and a error message
155
+ if result_failed:
156
+ text = "Update NetBox Fortinet Device Inventory Data with Nornir"
157
+ print(task_name(text=text))
158
+ exit_error(
159
+ task_text=f"{text} Failed",
160
+ msg="Check the result details for failed Nornir tasks",
161
+ )
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This module updates the serial number of Pure Storage 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.purestorage.utils import _purestorage_get_connection
13
+ from nornir_collection.netbox.utils import _nb_patch_resources
14
+ from nornir_collection.utils import (
15
+ print_task_title,
16
+ print_result,
17
+ task_name,
18
+ task_info,
19
+ exit_error,
20
+ )
21
+
22
+
23
+ __author__ = "Willi Kubny"
24
+ __maintainer__ = "Willi Kubny"
25
+ __version__ = "1.0"
26
+ __license__ = "MIT"
27
+ __email__ = "willi.kubny@dreyfusbank.ch"
28
+ __status__ = "Production"
29
+
30
+
31
+ def update_serial_number(task: Task) -> Result:
32
+ """
33
+ Update the serial number of a device in NetBox with the one retrieved from a Pure Storage FlashArray.
34
+
35
+ Args:
36
+ task (Task): A Nornir task object representing the device to update.
37
+
38
+ Returns:
39
+ Result: A Nornir result object with the outcome of the operation.
40
+ """
41
+ task_text = "Update device serial number"
42
+
43
+ # Manually create the PureStorage connection or return the Nornir result as failed
44
+ conn = _purestorage_get_connection(task=task)
45
+
46
+ # Get all hardware details from the FlashArray
47
+ response = conn.get_hardware()
48
+ # Get the serial number of the chassis
49
+ serial = [item["serial"] for item in response.items if item["type"] == "chassis"][0]
50
+ if not serial:
51
+ # Return the Nornir result as failed
52
+ result = f"'{task_text}' -> APIResponse: <Success: False>\n-> Device serial not found in API response"
53
+ return Result(host=task.host, result=result, failed=True)
54
+
55
+ # Create the API payload to update the device serial number
56
+ payload = [{"id": task.host["id"], "serial": serial}]
57
+
58
+ # Get the NetBox url from the inventory options and update the device serial number in NetBox
59
+ url = f"{task.nornir.config.inventory.options['nb_url']}/api/dcim/devices/"
60
+ result = _nb_patch_resources(task=task, task_text=task_text, url=url, payload=payload)
61
+ result += f"\n-> Serial: {serial}"
62
+
63
+ # Return the result
64
+ return Result(host=task.host, result=result, failed=False)
65
+
66
+
67
+ def update_purestorage_inventory_data(nr: Nornir) -> bool:
68
+ """
69
+ Update Pure-Storage device inventory data in NetBox. This function runs a custom Nornir task to update
70
+ the serial number of each Pure-Storage device in NetBox.
71
+
72
+ Args:
73
+ nr (Nornir): The Nornir object representing the inventory.
74
+
75
+ Returns:
76
+ bool: True if any of the tasks failed, False otherwise.
77
+ """
78
+ # pylint: disable=invalid-name
79
+ # Track if one of the tasks has failed
80
+ failed = False
81
+
82
+ print_task_title(title="Update NetBox Pure-Storage Device Inventory Data")
83
+
84
+ # Filter Nornir inventory by device manufacturer (a device can have only one manufacturer)
85
+ nr_purestorage = nr.filter(
86
+ F(device_type__manufacturer__slug__contains="pure-storage") & F(status__value="active")
87
+ )
88
+ task_text = "Filter nornir inventory"
89
+ print(task_name(text=task_text))
90
+ print(task_info(text=task_text, changed=False))
91
+ print(
92
+ f"'{task_text}' -> NornirResult <Success: True>\n"
93
+ + "-> Filter condition: Device manufacturer slug contains 'pure-storage'",
94
+ )
95
+
96
+ # Run the custom Nornir task update_serial_number
97
+ name = "NETBOX update device serial number"
98
+ result = nr_purestorage.run(name=name, task=update_serial_number, on_failed=True)
99
+ # Print the whole result for each host
100
+ print_result(result)
101
+ if result.failed:
102
+ failed = True
103
+
104
+ return failed
105
+
106
+
107
+ def main(nr_config: str) -> None:
108
+ """
109
+ Main function is intended to import and execute by other scripts.
110
+ It initialize Nornir and update NetBox Pure-Storage inventory data.
111
+
112
+ * Args:
113
+ * nr_config (str): Path to the Nornir configuration file.
114
+
115
+ * Steps:
116
+ * Initializes the Nornir inventory object using the provided configuration file.
117
+ * Executes the task to update NetBox Pure-Storage inventory data.
118
+ * Checks the result of the update task and exits the script with an error message if the task failed.
119
+
120
+ * Exits:
121
+ * Exits with code 1 if the Nornir configuration file is empty or if any task fails.
122
+ """
123
+ # pylint: disable=invalid-name
124
+
125
+ #### Initialize Nornir ##################################################################################
126
+
127
+ # Initialize, transform and filter the Nornir inventory are return the filtered Nornir object
128
+ # Define data to load from NetBox in addition to the base Nornir inventory plugin
129
+ add_netbox_data = {"load_virtual_chassis_data": True}
130
+ nr = init_nornir(config_file=nr_config, add_netbox_data=add_netbox_data)
131
+
132
+ #### Run Nornir Cisco Support Plugin Update Tasks #######################################################
133
+
134
+ # Update NetBox Pure-Storate inventory data
135
+ result_failed = update_purestorage_inventory_data(nr=nr)
136
+
137
+ # Check the result and exit the script with exit code 1 and a error message
138
+ if result_failed:
139
+ text = "Update NetBox Pure-Storage Device Inventory Data with Nornir"
140
+ print(task_name(text=text))
141
+ exit_error(
142
+ task_text=f"{text} Failed",
143
+ msg="Check the result details for failed Nornir tasks",
144
+ )
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ This module contains general functions and tasks related to NetBox.
4
+
5
+ The functions are ordered as followed:
6
+ - Helper Functions
7
+ - Task Helper Functions
8
+ - Single Nornir tasks
9
+ - Nornir tasks in regular function
10
+ """
11
+
12
+
13
+ import os
14
+ import argparse
15
+ import json
16
+ from typing import Tuple, Union, Any, Dict, List
17
+ import __main__
18
+ import requests
19
+ import pynetbox
20
+ from nornir.core.task import Task, Result
21
+ from nornir_collection.utils import (
22
+ CustomArgParse,
23
+ CustomArgParseWidthFormatter,
24
+ get_env_vars,
25
+ print_task_name,
26
+ task_info,
27
+ )
28
+
29
+
30
+ #### Helper Functions #######################################################################################
31
+
32
+
33
+ def init_pynetbox(nb_url: str, ssl_verify=False) -> Tuple[pynetbox.api, pynetbox.api]:
34
+ """
35
+ This function instantiate a Pynetbox API object, print the result and returns the API object
36
+ """
37
+ task_text = "Initialize PyNetBox"
38
+ print_task_name(text=task_text)
39
+
40
+ # Load environment variables or raise a TypeError when is None
41
+ env_vars = get_env_vars(envs=["NB_TOKEN"], task_text=task_text)
42
+
43
+ # Instantiate the NetBox API
44
+ session = requests.Session()
45
+ session.verify = ssl_verify
46
+ nb = pynetbox.api(url=nb_url, token=env_vars["NB_TOKEN"], threading=False)
47
+ nb.http_session = session
48
+
49
+ # Print success result
50
+ print(task_info(text=task_text, changed=False))
51
+ print(f"'{task_text}' -> Pynetbox.Api <Success: True>")
52
+ print(f"-> Instantiate the PyNetBox API for '{nb_url}'")
53
+
54
+ return nb
55
+
56
+
57
+ def init_args_for_nornir_config_filepath() -> str:
58
+ """
59
+ This function initialze arguments to specify which NetBox instance and Nornir config filepath to use.
60
+ """
61
+ task_text = "Argparse verify arguments"
62
+ print_task_name(text=task_text)
63
+
64
+ # Load environment variables or raise a TypeError when is None
65
+ env_vars = get_env_vars(envs=["NR_CONFIG_PROD", "NR_CONFIG_TEST"], task_text=task_text)
66
+ nr_config_prod = env_vars["NR_CONFIG_PROD"]
67
+ nr_config_test = env_vars["NR_CONFIG_TEST"]
68
+
69
+ # Define the arguments which needs to be given to the script execution
70
+ argparser = CustomArgParse(
71
+ prog=os.path.basename(__main__.__file__),
72
+ description="Specify the NetBox PROD or TEST instance and Nornir config filepath to be used",
73
+ epilog="One of the two mandatory arguments is required.",
74
+ argument_default=argparse.SUPPRESS,
75
+ formatter_class=CustomArgParseWidthFormatter,
76
+ )
77
+ # Add a argparser group for mutual exclusive arguments
78
+ group = argparser.add_mutually_exclusive_group(required=True)
79
+ # Add the test argument
80
+ group.add_argument(
81
+ "--prod",
82
+ action="store_true",
83
+ default=False,
84
+ help=f"use the NetBox 'PROD' instance and Nornir config '{nr_config_prod}'",
85
+ )
86
+ # Add the test argument
87
+ group.add_argument(
88
+ "--test",
89
+ action="store_true",
90
+ default=False,
91
+ help=f"use the NetBox 'TEST' instance and Nornir config '{nr_config_test}'",
92
+ )
93
+ # Verify the provided arguments and print the custom argparse error message in case of an error
94
+ args = argparser.parse_args()
95
+
96
+ # Set the NetBox instance and the Nornir config file based on the arguments
97
+ nb_instance = "TEST" if args.test else "PROD"
98
+ nr_config = nr_config_test if args.test else nr_config_prod
99
+
100
+ # If argparser.parse_args() is successful -> no argparse error message
101
+ print(task_info(text=task_text, changed=False))
102
+ print(f"'{task_text}' -> ArgparseResponse <Success: True>")
103
+ print(f"-> Run on the NetBox '{nb_instance}' instance and Nornir config '{nr_config}'")
104
+
105
+ return nr_config
106
+
107
+
108
+ def get_nb_resources( # pylint: disable=dangerous-default-value
109
+ url: str, params: Dict[str, Any] = {}
110
+ ) -> List[Dict[str, Any]]:
111
+ """
112
+ TBD
113
+ """
114
+ # Define the resource list
115
+ resources: List[Dict[str, Any]] = []
116
+ headers = {
117
+ "Accept": "application/json",
118
+ "Content-Type": "application/json",
119
+ "Authorization": f"Token {os.environ.get('NB_TOKEN')}",
120
+ }
121
+ # While there is a next page continue the loop
122
+ while url:
123
+ # Do the http request
124
+ response = requests.get( # nosec
125
+ url=url, headers=headers, params=params, verify=False, timeout=(3.05, 27)
126
+ )
127
+ # Verify the response code
128
+ if not response.status_code == 200:
129
+ print(response.status_code)
130
+ print(response.text)
131
+ raise ValueError(f"Failed to get data from NetBox instance {url}")
132
+ # Extract the json data from the http response
133
+ resp = response.json()
134
+ # Add the response to the resource list
135
+ resources.extend(resp.get("results"))
136
+ # Get the url of the next page
137
+ url = resp.get("next")
138
+
139
+ # Retrun the resources list
140
+ return resources
141
+
142
+
143
+ def post_nb_resources(url: str, payload: List[Dict]) -> requests.Response:
144
+ """
145
+ TBD
146
+ """
147
+ headers = {
148
+ "Accept": "application/json",
149
+ "Content-Type": "application/json",
150
+ "Authorization": f"Token {os.environ.get('NB_TOKEN')}",
151
+ }
152
+ # Do the http request and return the result
153
+ return requests.post( # nosec
154
+ url=url, headers=headers, data=json.dumps(payload), verify=False, timeout=(3.05, 27)
155
+ )
156
+
157
+
158
+ def patch_nb_resources(url: str, payload: List[Dict]) -> requests.Response:
159
+ """
160
+ TBD
161
+ """
162
+ headers = {
163
+ "Accept": "application/json",
164
+ "Content-Type": "application/json",
165
+ "Authorization": f"Token {os.environ.get('NB_TOKEN')}",
166
+ }
167
+ # Do the http request and return the result
168
+ return requests.patch( # nosec
169
+ url=url, headers=headers, data=json.dumps(payload), verify=False, timeout=(3.05, 27)
170
+ )
171
+
172
+
173
+ def delete_nb_resources(url: str, payload: List[Dict]) -> requests.Response:
174
+ """
175
+ TBD
176
+ """
177
+ headers = {
178
+ "Accept": "application/json",
179
+ "Content-Type": "application/json",
180
+ "Authorization": f"Token {os.environ.get('NB_TOKEN')}",
181
+ }
182
+ # Do the http request and return the result
183
+ return requests.delete( # nosec
184
+ url=url, headers=headers, data=json.dumps(payload), verify=False, timeout=(3.05, 27)
185
+ )
186
+
187
+
188
+ #### Task Helper Functions ##################################################################################
189
+
190
+
191
+ def _nb_patch_resources(task: Task, task_text: str, url: str, payload: list) -> Union[str, Result]:
192
+ """
193
+ Sends a PATCH request to update the resources of a device in NetBox.
194
+ Args:
195
+ task (Task): The Nornir task object.
196
+ task_text (str): The description of the task.
197
+ payload (list): A list of dictionaries containing the resources to be updated.
198
+ Returns:
199
+ tuple: A tuple containing the result string and a boolean indicating whether the task failed.
200
+ """
201
+ # POST request to update the Cisco Support Plugin desired release
202
+ response = patch_nb_resources(url=url, payload=payload)
203
+
204
+ # Verify the response code and return the result
205
+ if response.status_code != 200:
206
+ result = (
207
+ f"'{task_text}' -> NetBoxResponse: <Success: False>\n"
208
+ + f"-> Response Status Code: {response.status_code}\n"
209
+ + f"-> Response Text: {response.text}\n"
210
+ + f"-> Payload: {payload}"
211
+ )
212
+ # Return the Nornir result
213
+ return Result(host=task.host, result=result, changed=False, failed=True)
214
+
215
+ # Return the result string
216
+ result = f"'{task_text}' -> NetBoxResponse: <Success: True>"
217
+ return result
218
+
219
+
220
+ def _nb_create_payload_patch_device_serials(task: Task, task_text: str, serials: dict) -> Union[list, Result]:
221
+ """
222
+ Create a payload for patching device serial numbers in NetBox.
223
+ Args:
224
+ task (Task): The Nornir task object.
225
+ task_text (str): The task description.
226
+ serials (dict): A dictionary containing the serial numbers for the devices.
227
+ Returns:
228
+ Union[List, Result]: A list of dictionaries containing the device IDs and serial numbers,
229
+ or a Nornir Result object if an error occurs.
230
+ """
231
+ try:
232
+ # Create a list of dicts. Multiple dicts if its a virtual chassis
233
+ payload = []
234
+
235
+ # Add the device depending if its a virtual chassis in NetBox or not
236
+ if "virtual_chassis" in task.host and task.host["virtual_chassis"] is not None:
237
+ # Add the master
238
+ payload.append({"id": task.host["virtual_chassis"]["master"]["id"], "serial": serials["1"]})
239
+
240
+ # Add all members to the payload if available
241
+ if "members" in task.host["virtual_chassis"]:
242
+ # Start enumerate with 2 as 1 is the virtual chassis master
243
+ for num, member in enumerate(task.host["virtual_chassis"]["members"], start=2):
244
+ payload.append({"id": member["id"], "serial": serials[str(num)]})
245
+ else:
246
+ # Add the device (no virtual chassis)
247
+ payload.append({"id": task.host["id"], "serial": serials["1"]})
248
+
249
+ # Return the payload
250
+ return payload
251
+
252
+ except KeyError as error:
253
+ result = f"'{task_text}' -> PythonResponse <Success: False>\n-> Dictionary key {error} not found"
254
+ # Return the Nornir result
255
+ return Result(host=task.host, result=result, changed=False, failed=True)
256
+
257
+
258
+ #### Nornir Tasks ###########################################################################################
259
+
260
+
261
+ #### Nornir Tasks ###########################################################################################