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,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
|
+
)
|