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,316 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
This module contains general functions and tasks related to the Cisco Support APIs with Nornir.
|
4
|
+
|
5
|
+
The functions are ordered as followed:
|
6
|
+
- Helper Functions
|
7
|
+
- Static or Dynamic Nornir Serial Numbers Gathering
|
8
|
+
"""
|
9
|
+
|
10
|
+
|
11
|
+
import os
|
12
|
+
import json
|
13
|
+
import argparse
|
14
|
+
import pandas as pd
|
15
|
+
import numpy as np
|
16
|
+
import __main__
|
17
|
+
from colorama import init
|
18
|
+
from nornir.core import Nornir
|
19
|
+
from nornir_collection.cisco.configuration_management.cli.show_tasks import (
|
20
|
+
cli_get_serial_numbers,
|
21
|
+
cli_get_software_version,
|
22
|
+
)
|
23
|
+
from nornir_collection.utils import (
|
24
|
+
CustomArgParse,
|
25
|
+
CustomArgParseWidthFormatter,
|
26
|
+
print_result,
|
27
|
+
print_task_name,
|
28
|
+
task_info,
|
29
|
+
task_error,
|
30
|
+
exit_error,
|
31
|
+
load_yaml_file,
|
32
|
+
)
|
33
|
+
|
34
|
+
init(autoreset=True, strip=False)
|
35
|
+
|
36
|
+
|
37
|
+
#### Helper Functions ########################################################################################
|
38
|
+
|
39
|
+
|
40
|
+
def init_args_for_cisco_maintenance() -> argparse.Namespace:
|
41
|
+
"""
|
42
|
+
This function initialze all arguments which are needed for further script execution. The default
|
43
|
+
arguments will be supressed. Returned will be the argparse Namespace with all arguments.
|
44
|
+
"""
|
45
|
+
task_text = "ARGPARSE verify arguments"
|
46
|
+
print_task_name(text=task_text)
|
47
|
+
|
48
|
+
# Define the arguments which needs to be given to the script execution
|
49
|
+
argparser = CustomArgParse(
|
50
|
+
prog=os.path.basename(__main__.__file__),
|
51
|
+
description="Gather information dynamically with Nornir or use static provided information",
|
52
|
+
epilog="Only one of the mandatory arguments can be specified.",
|
53
|
+
argument_default=argparse.SUPPRESS,
|
54
|
+
formatter_class=CustomArgParseWidthFormatter,
|
55
|
+
)
|
56
|
+
|
57
|
+
# Create a mutually exclusive group.
|
58
|
+
# Argparse will make sure that only one of the arguments in the group is present on the command line
|
59
|
+
arg_group = argparser.add_mutually_exclusive_group(required=True)
|
60
|
+
|
61
|
+
# Add arg_group exclusive group parser arguments
|
62
|
+
arg_group.add_argument(
|
63
|
+
"--tags", type=str, metavar="<VALUE>", help="nornir inventory filter on a single tag"
|
64
|
+
)
|
65
|
+
arg_group.add_argument(
|
66
|
+
"--hosts", type=str, metavar="<VALUE>", help="nornir inventory filter on comma seperated hosts"
|
67
|
+
)
|
68
|
+
arg_group.add_argument(
|
69
|
+
"--serials", type=str, metavar="<VALUE>", help="comma seperated list of serial numbers"
|
70
|
+
)
|
71
|
+
arg_group.add_argument("--excel", type=str, metavar="<VALUE>", help="excel file with serial numbers")
|
72
|
+
|
73
|
+
# Add the optional client_key argument that is only needed if Nornir is not used
|
74
|
+
argparser.add_argument(
|
75
|
+
"--api_key", type=str, metavar="<VALUE>", help="specify Cisco support API client key"
|
76
|
+
)
|
77
|
+
# Add the optional client_key argument that is only needed if Nornir is not used
|
78
|
+
argparser.add_argument(
|
79
|
+
"--api_secret", type=str, metavar="<VALUE>", help="specify Cisco support API client secret"
|
80
|
+
)
|
81
|
+
# Add the optional tss argument
|
82
|
+
argparser.add_argument(
|
83
|
+
"--tss", type=str, default=False, metavar="<VALUE>", help="add a IBM TSS Excel report file"
|
84
|
+
)
|
85
|
+
# Add the optional verbose argument
|
86
|
+
argparser.add_argument(
|
87
|
+
"-r", "--report", action="store_true", default=False, help="create and Excel report file"
|
88
|
+
)
|
89
|
+
# Add the optional verbose argument
|
90
|
+
argparser.add_argument(
|
91
|
+
"-v", "--verbose", action="store_true", default=False, help="show extensive result details"
|
92
|
+
)
|
93
|
+
|
94
|
+
# Verify the provided arguments and print the custom argparse error message in case of an error
|
95
|
+
args = argparser.parse_args()
|
96
|
+
|
97
|
+
# Verify that --api_key and --api_secret is present when --serials or --excel is used
|
98
|
+
if ("serials" in vars(args) or "excel" in vars(args)) and (
|
99
|
+
"api_key" not in vars(args) or "api_secret" not in vars(args)
|
100
|
+
):
|
101
|
+
# Raise an ArgParse error if --api_key or --api_secret is missing
|
102
|
+
argparser.error("The --api_key and --api_secret argument is required for static provided data")
|
103
|
+
|
104
|
+
print(task_info(text=task_text, changed=False))
|
105
|
+
print(f"'{task_text}' -> ArgparseResponse <Success: True>")
|
106
|
+
|
107
|
+
if hasattr(args, "tag") or hasattr(args, "hosts"):
|
108
|
+
print("-> Gather data dynamically with Nornir")
|
109
|
+
vars(args).update(nornir=True)
|
110
|
+
else:
|
111
|
+
print("-> Use static provided data")
|
112
|
+
vars(args).update(nornir=False)
|
113
|
+
|
114
|
+
return args
|
115
|
+
|
116
|
+
|
117
|
+
#### Static or Dynamic Nornir Serial Numbers Gathering #######################################################
|
118
|
+
|
119
|
+
|
120
|
+
def _prepare_nornir_data_static_serials(serials: dict, nr: Nornir) -> dict:
|
121
|
+
"""
|
122
|
+
TBD
|
123
|
+
"""
|
124
|
+
# pylint: disable=invalid-name
|
125
|
+
|
126
|
+
if isinstance(nr.inventory.defaults.data["cisco_maintenance_report"]["static_serials"], str):
|
127
|
+
# If static_serials is a str of a file path, then load the YAML file from that string path
|
128
|
+
yaml_file = nr.inventory.defaults.data["cisco_maintenance_report"]["static_serials"]
|
129
|
+
static_serials = load_yaml_file(file=yaml_file, silent=True)
|
130
|
+
|
131
|
+
# Exit the script if the loaded dictionary from the YAML file is empty
|
132
|
+
if not static_serials:
|
133
|
+
exit_error(task_text="NORNIR cisco maintenance status", text="BAD NEWS! THE SCRIPT FAILED!")
|
134
|
+
else:
|
135
|
+
# Write the Nornir static_serials dict into a variable for consistancy
|
136
|
+
static_serials = nr.inventory.defaults.data["cisco_maintenance_report"]["static_serials"]
|
137
|
+
|
138
|
+
if isinstance(static_serials, list):
|
139
|
+
# If its list of serials, then add empty values for the hostname, switch number and the software
|
140
|
+
for serial in static_serials:
|
141
|
+
data = {serial: {"host": "", "nr_data": {}}}
|
142
|
+
data[serial]["nr_data"]["switch_num"] = ""
|
143
|
+
data[serial]["nr_data"]["current_version"] = ""
|
144
|
+
data[serial]["nr_data"]["desired_version"] = ""
|
145
|
+
|
146
|
+
# Update the serials dict with the serial dict
|
147
|
+
serials.update(data)
|
148
|
+
|
149
|
+
elif isinstance(static_serials, dict):
|
150
|
+
for serial, items in static_serials.items():
|
151
|
+
if isinstance(items, list):
|
152
|
+
# If items is a list, then add the values for the hostname, switch number and the software
|
153
|
+
# Add the hostname and Nornir data key value pair to the serial in the data dict
|
154
|
+
data = {serial: {"host": items[0] if 0 < len(items) else "", "nr_data": {}}}
|
155
|
+
# Add the switch number key value pair to the serial in the data dict
|
156
|
+
data[serial]["nr_data"]["switch_num"] = items[1] if 1 < len(items) else ""
|
157
|
+
# Add the desired and current version key value pair to the serial in the data dict
|
158
|
+
data[serial]["nr_data"]["current_version"] = items[2] if 2 < len(items) else ""
|
159
|
+
data[serial]["nr_data"]["desired_version"] = items[3] if 3 < len(items) else ""
|
160
|
+
else:
|
161
|
+
# If items is not a list, then add empty values for the serial number
|
162
|
+
data = {serial: {"host": "", "nr_data": {}}}
|
163
|
+
data[serial]["nr_data"]["switch_num"] = ""
|
164
|
+
data[serial]["nr_data"]["current_version"] = ""
|
165
|
+
data[serial]["nr_data"]["desired_version"] = ""
|
166
|
+
|
167
|
+
# Update the serials dict with the serial dict
|
168
|
+
serials.update(data)
|
169
|
+
|
170
|
+
return serials
|
171
|
+
|
172
|
+
|
173
|
+
def prepare_nornir_data(nr: Nornir, verbose: bool = False) -> dict:
|
174
|
+
"""
|
175
|
+
This function use Nornir to gather and prepare the serial numbers and more data and returns the
|
176
|
+
serials dictionary.
|
177
|
+
"""
|
178
|
+
# pylint: disable=invalid-name
|
179
|
+
|
180
|
+
# Create a dict to fill with the serial numbers and other data from all hosts
|
181
|
+
serials = {}
|
182
|
+
|
183
|
+
# Run the Nornir task cli_get_serial_number to get all serial numbers
|
184
|
+
cli_get_serial_number_result = nr.run(
|
185
|
+
task=cli_get_serial_numbers, name="NORNIR prepare serial numbers", verbose=verbose
|
186
|
+
)
|
187
|
+
# Print the Nornir task result
|
188
|
+
print_result(cli_get_serial_number_result)
|
189
|
+
# Exit the script if the Nornir tasks have been failed
|
190
|
+
if cli_get_serial_number_result.failed:
|
191
|
+
exit_error(task_text="NORNIR cisco maintenance status", text="Bad news! The script failed!")
|
192
|
+
|
193
|
+
# Run the Nornir task cli_get_software_version to get all software versions
|
194
|
+
cli_get_software_version_result = nr.run(
|
195
|
+
task=cli_get_software_version, name="NORNIR prepare software version", verbose=verbose
|
196
|
+
)
|
197
|
+
# Print the Nornir task result
|
198
|
+
print_result(cli_get_software_version_result)
|
199
|
+
# Exit the script if the Nornir tasks have been failed
|
200
|
+
if cli_get_software_version_result.failed:
|
201
|
+
exit_error(task_text="NORNIR cisco maintenance status", text="Bad news! The script failed!")
|
202
|
+
|
203
|
+
# Add dynamic serial numbers and other data to the serials dict from the Nornir task results
|
204
|
+
for host, multiresult in cli_get_serial_number_result.items():
|
205
|
+
# Get the serial number from the task result cli_get_serial_number_result attribut serial
|
206
|
+
for switch_num, serial in multiresult.serials.items():
|
207
|
+
# Add the hostname and Nornir data key value pair to the serial in the data dict
|
208
|
+
data = {serial: {"host": host, "nr_data": {}}}
|
209
|
+
# Add the switch number key value pair to the serial in the data dict
|
210
|
+
data[serial]["nr_data"]["switch_num"] = switch_num
|
211
|
+
# Add the desired and current version key value pair to the serial in the data dict
|
212
|
+
data[serial]["nr_data"]["current_version"] = cli_get_software_version_result[host].version
|
213
|
+
data[serial]["nr_data"]["desired_version"] = nr.inventory.hosts[host]["software"]["version"]
|
214
|
+
# Update the serials dict with the serial dict
|
215
|
+
serials.update(data)
|
216
|
+
|
217
|
+
if hasattr(multiresult, "add_serials"):
|
218
|
+
# Get the serial number from the task result cli_get_serial_number_result attribut add_serial
|
219
|
+
for serial, name in multiresult.add_serials.items():
|
220
|
+
# Add the name and Nornir data key value pair to the serial in the data dict
|
221
|
+
data = {serial: {"host": name, "nr_data": {}}}
|
222
|
+
# Add the switch number key value pair to the serial in the data dict
|
223
|
+
data[serial]["nr_data"]["switch_num"] = "n/a"
|
224
|
+
# Add the desired and current version key value pair to the serial in the data dict
|
225
|
+
data[serial]["nr_data"]["current_version"] = "PID without Cisco software"
|
226
|
+
data[serial]["nr_data"]["desired_version"] = "PID without Cisco software"
|
227
|
+
# Update the serials dict with the serial dict
|
228
|
+
serials.update(data)
|
229
|
+
|
230
|
+
# Add static serials to the serials dict in case there are unmanaged switches
|
231
|
+
if "static_serials" in nr.inventory.defaults.data["cisco_maintenance_report"]:
|
232
|
+
serials = _prepare_nornir_data_static_serials(serials=serials, nr=nr)
|
233
|
+
|
234
|
+
return serials
|
235
|
+
|
236
|
+
|
237
|
+
def prepare_static_serials(args: argparse.Namespace) -> tuple[dict, str, tuple]:
|
238
|
+
"""
|
239
|
+
This function prepare all static serial numbers which can be applied with the serials ArgParse argument
|
240
|
+
or within an Excel document. It returns the serials dictionary.
|
241
|
+
"""
|
242
|
+
# pylint: disable=invalid-name
|
243
|
+
|
244
|
+
task_text = "ARGPARSE verify static provided data"
|
245
|
+
print_task_name(text=task_text)
|
246
|
+
|
247
|
+
# Create a dict to fill with all serial numbers
|
248
|
+
serials = {}
|
249
|
+
|
250
|
+
# If the --serials argument is set, verify that the tag has hosts assigned to
|
251
|
+
if hasattr(args, "serials"):
|
252
|
+
print(task_info(text=task_text, changed=False))
|
253
|
+
print(f"'{task_text}' -> ArgparseResult <Success: True>")
|
254
|
+
|
255
|
+
# Add all serials from args.serials to the serials dict, as well as the hostname None
|
256
|
+
for sr_no in args.serials.split(","):
|
257
|
+
serials[sr_no.upper()] = {}
|
258
|
+
serials[sr_no.upper()]["host"] = None
|
259
|
+
|
260
|
+
print(task_info(text="PYTHON prepare static provided serial numbers", changed=False))
|
261
|
+
print("'PYTHON prepare static provided serial numbers' -> ArgparseResult <Success: True>")
|
262
|
+
if args.verbose:
|
263
|
+
print("\n" + json.dumps(serials, indent=4))
|
264
|
+
|
265
|
+
# If the --excel argument is set, verify that the tag has hosts assigned to
|
266
|
+
elif hasattr(args, "excel"):
|
267
|
+
excel_file = args.excel
|
268
|
+
# Verify that the excel file exists
|
269
|
+
if not os.path.exists(excel_file):
|
270
|
+
# If the excel don't exist -> exit the script properly
|
271
|
+
print(task_error(text=task_text, changed=False))
|
272
|
+
print(f"'{task_text}' -> ArgparseResult <Success: False>")
|
273
|
+
exit_error(
|
274
|
+
task_text=task_text,
|
275
|
+
text="ALERT: FILE NOT FOUND!",
|
276
|
+
msg=[
|
277
|
+
f"-> Excel file {excel_file} not found",
|
278
|
+
"-> Verify the file path and the --excel argument",
|
279
|
+
],
|
280
|
+
)
|
281
|
+
|
282
|
+
print(task_info(text=task_text, changed=False))
|
283
|
+
print(f"'{task_text}' -> ArgparseResult <Success: True>")
|
284
|
+
|
285
|
+
# Read the excel file into a pandas dataframe -> Row 0 is the title row
|
286
|
+
df = pd.read_excel(rf"{excel_file}", skiprows=[0], engine="openpyxl")
|
287
|
+
|
288
|
+
# Make all serial numbers written in uppercase letters
|
289
|
+
df.sr_no = df.sr_no.str.upper()
|
290
|
+
|
291
|
+
# The first fillna will replace all of (None, NAT, np.nan, etc) with Numpy's NaN, then replace
|
292
|
+
# Numpy's NaN with python's None
|
293
|
+
df = df.fillna(np.nan).replace([np.nan], [None])
|
294
|
+
|
295
|
+
# Add all serials and hostnames from pandas dataframe to the serials dict
|
296
|
+
for sr_no, host in zip(df.sr_no, df.host):
|
297
|
+
serials[sr_no] = {}
|
298
|
+
serials[sr_no]["host"] = host
|
299
|
+
|
300
|
+
# Print the static provided serial numbers
|
301
|
+
print(task_info(text="PANDAS prepare static provided Excel", changed=False))
|
302
|
+
print("'PANDAS prepare static provided Excel' -> ArgparseResult <Success: True>")
|
303
|
+
if args.verbose:
|
304
|
+
print("\n" + json.dumps(serials, indent=4))
|
305
|
+
|
306
|
+
else:
|
307
|
+
print(task_error(text=task_text, changed=False))
|
308
|
+
print(f"'{task_text}' -> ArgparseResult <Success: False>")
|
309
|
+
exit_error(
|
310
|
+
task_text="NORNIR cisco maintenance status",
|
311
|
+
text="ALERT: NOT SUPPORTET ARGPARSE ARGUMENT FOR FURTHER PROCESSING!",
|
312
|
+
msg="-> Analyse the python function for missing Argparse processing",
|
313
|
+
)
|
314
|
+
|
315
|
+
# return the serials dict
|
316
|
+
return serials
|
File without changes
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
This module contains general functions and tasks related to Fortinet.
|
4
|
+
|
5
|
+
The functions are ordered as followed:
|
6
|
+
- Helper Functions
|
7
|
+
- Single Nornir tasks
|
8
|
+
- Nornir tasks in regular function
|
9
|
+
"""
|
10
|
+
|
11
|
+
|
12
|
+
import requests
|
13
|
+
from nornir.core.task import Task
|
14
|
+
|
15
|
+
|
16
|
+
#### Helper Functions #######################################################################################
|
17
|
+
|
18
|
+
|
19
|
+
#### Nornir Tasks ###########################################################################################
|
20
|
+
|
21
|
+
|
22
|
+
def get_fgt_resources(task: Task, url: str, port: int = None) -> requests.Response:
|
23
|
+
"""
|
24
|
+
TBD
|
25
|
+
"""
|
26
|
+
# Get the API token and create the url
|
27
|
+
api_token = task.host["fortigate_api_token"][f"env_token_{task.host.name}"]
|
28
|
+
port = port if port else 4443
|
29
|
+
url = f"https://{task.host.hostname}:{port}/{url}?access_token={api_token}"
|
30
|
+
|
31
|
+
# Do the http request and return the result
|
32
|
+
headers = {"Accept": "application/json", "Content-Type": "application/json"}
|
33
|
+
return requests.get(url=url, headers=headers, verify=False, timeout=(3.05, 27)) # nosec
|
34
|
+
|
35
|
+
|
36
|
+
#### Nornir Tasks ###########################################################################################
|
nornir_collection/git.py
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
This module contains Git functions.
|
4
|
+
|
5
|
+
The functions are ordered as followed:
|
6
|
+
- Git functions in Nornir stdout style
|
7
|
+
"""
|
8
|
+
|
9
|
+
import sys
|
10
|
+
import git
|
11
|
+
from nornir_collection.utils import print_task_title, print_task_name, task_info, task_error
|
12
|
+
|
13
|
+
|
14
|
+
#### Git Functions ###########################################################################################
|
15
|
+
|
16
|
+
|
17
|
+
def git_init(repo_path: str) -> tuple:
|
18
|
+
"""
|
19
|
+
Initialize a git repo by its path specified with the repo_path argument. Returns a tuple with a GitPython
|
20
|
+
repo object and a GitPython repo_cmd object.
|
21
|
+
"""
|
22
|
+
print_task_name(text="Initialize Git repo")
|
23
|
+
|
24
|
+
try:
|
25
|
+
# Initialize the GitPython repo object
|
26
|
+
repo = git.Repo(repo_path, search_parent_directories=True)
|
27
|
+
|
28
|
+
# git.cmd.Git() raise no exception if no Git repo is found. If git_Repo() before was successful, also
|
29
|
+
# git.cmd.Git() will be successful. Initialize the GitPython CMD object
|
30
|
+
repo_cmd = git.cmd.Git(repo_path)
|
31
|
+
|
32
|
+
print(task_info(text="Initialize local Git repo", changed=False))
|
33
|
+
print(f"Local Git repo: {repo.working_tree_dir}")
|
34
|
+
print(f"GitHub origin: {repo.remotes.origin.url}")
|
35
|
+
|
36
|
+
return (repo, repo_cmd)
|
37
|
+
|
38
|
+
except git.exc.NoSuchPathError as error: # pylint: disable=no-member
|
39
|
+
# If no Git repo exists terminate the script
|
40
|
+
print(task_error(text="Initialize local Git repo", changed=False))
|
41
|
+
print(f"Git repo not found: {error}")
|
42
|
+
print("\n")
|
43
|
+
sys.exit(1)
|
44
|
+
|
45
|
+
|
46
|
+
def git_is_dirty(repo: git.Repo) -> None:
|
47
|
+
"""
|
48
|
+
Takes a GitPython repo object as argument and check if the git repo has any untracked or modified files.
|
49
|
+
Prints the results in Nornir style and returns nothing (default None).
|
50
|
+
"""
|
51
|
+
print_task_name(text="Check for local Git repo changes")
|
52
|
+
|
53
|
+
# Query the active branch whether the repository data has been modified.
|
54
|
+
if repo.is_dirty():
|
55
|
+
print(task_info(text="Check for local Git repo changes", changed=True))
|
56
|
+
print("Local Git repo is dirty")
|
57
|
+
|
58
|
+
# Query the active branch and create a list of untracked files
|
59
|
+
task_text = "Check for untracked files in local Git repo"
|
60
|
+
untracked_files = repo.untracked_files
|
61
|
+
|
62
|
+
if untracked_files:
|
63
|
+
print(task_info(text=task_text, changed=True))
|
64
|
+
print("Untracked files in local Git repo:")
|
65
|
+
for file in untracked_files:
|
66
|
+
print(f"-> {file}")
|
67
|
+
else:
|
68
|
+
print(task_info(text=task_text, changed=False))
|
69
|
+
print("No untracked files in local Git repo")
|
70
|
+
|
71
|
+
# Check differences between current files and last commit
|
72
|
+
task_text = "Check for modified files in local Git repo"
|
73
|
+
diff_to_last_commit = repo.git.diff(repo.head.commit.tree)
|
74
|
+
|
75
|
+
if diff_to_last_commit:
|
76
|
+
print(task_info(text=task_text, changed=True))
|
77
|
+
print("Modified files in local Git repo:")
|
78
|
+
|
79
|
+
# Print only the modified files from the output of git status
|
80
|
+
for line in repo.git.status().splitlines():
|
81
|
+
line = line.lstrip()
|
82
|
+
if line.startswith("modified:"):
|
83
|
+
line = line.replace("modified:", "").lstrip()
|
84
|
+
print(f"-> {line}")
|
85
|
+
else:
|
86
|
+
print(task_info(text=task_text, changed=False))
|
87
|
+
print("No modified files in local Git repo")
|
88
|
+
|
89
|
+
else:
|
90
|
+
print(task_info(text="Check for local Git repo changes", changed=False))
|
91
|
+
print("Local Git repo is clean")
|
92
|
+
|
93
|
+
|
94
|
+
def git_cmd_pull(repo_cmd) -> None:
|
95
|
+
"""
|
96
|
+
Takes a GitPython repo_cmd object as argument and executes a git pull to merge the local git repo with the
|
97
|
+
origin repo. Prints the results in Nornir style and returns nothing (default None).
|
98
|
+
"""
|
99
|
+
task_text = "Git pull from GitHub origin/main"
|
100
|
+
print_task_name(text=task_text)
|
101
|
+
|
102
|
+
try:
|
103
|
+
# Execute a git pull to merge the GitHub origin/main into local main
|
104
|
+
git_pull_result = repo_cmd.pull()
|
105
|
+
|
106
|
+
# If the local repo is already up do date -> changed=False
|
107
|
+
if git_pull_result.startswith("Already"):
|
108
|
+
print(task_info(text=task_text, changed=False))
|
109
|
+
else:
|
110
|
+
print(task_info(text=task_text, changed=True))
|
111
|
+
|
112
|
+
# Print the git pull result
|
113
|
+
print(git_pull_result)
|
114
|
+
|
115
|
+
except git.exc.GitCommandError as error: # pylint: disable=no-member
|
116
|
+
print(task_error(text=task_text, changed=False))
|
117
|
+
# pylint: disable=protected-access
|
118
|
+
print(f"Command '{error._cmdline}' failed\n")
|
119
|
+
print(error.stdout.lstrip())
|
120
|
+
print(error.stderr.lstrip())
|
121
|
+
print("\n")
|
122
|
+
sys.exit(1)
|
123
|
+
|
124
|
+
|
125
|
+
def git_cmd_add_commit(repo_cmd, commit_msg: str) -> None:
|
126
|
+
"""
|
127
|
+
Takes a GitPython repo_cmd object and a commit_msg string as argument and executed a git add followed by a
|
128
|
+
git commit to update the changes made in the local repo. Prints the results in Nornir style and returns
|
129
|
+
nothing (default None).
|
130
|
+
"""
|
131
|
+
print_task_name(text="Git commit changed to local repo")
|
132
|
+
|
133
|
+
# Execute git add to stage all changes files
|
134
|
+
repo_cmd.execute(["git", "add", "."])
|
135
|
+
task_text = "Add Git working directory to staging area"
|
136
|
+
print(task_info(text=task_text, changed=False))
|
137
|
+
print("Git add working directory to staging area")
|
138
|
+
|
139
|
+
task_text = "Git commit staging area to local repo"
|
140
|
+
try:
|
141
|
+
# Execute a git commit to commit the staging area to the local repo
|
142
|
+
commit_cmd = repo_cmd.execute(["git", "commit", f"-m '{commit_msg}'", "--no-verify"])
|
143
|
+
print(task_info(text=task_text, changed=False))
|
144
|
+
print(commit_cmd)
|
145
|
+
|
146
|
+
except git.exc.GitCommandError as error: # pylint: disable=no-member
|
147
|
+
print(task_info(text=task_text, changed=False))
|
148
|
+
message = error.stdout.replace("stdout:", "").lstrip()
|
149
|
+
message = message.replace("'", "").lstrip()
|
150
|
+
print(message)
|
151
|
+
|
152
|
+
# If the exception is another error than "Your branch is up to date ..." -> Terminate the script
|
153
|
+
if "up to date" not in message:
|
154
|
+
print("\n")
|
155
|
+
sys.exit(1)
|
156
|
+
|
157
|
+
|
158
|
+
def git_push(repo) -> None:
|
159
|
+
"""
|
160
|
+
Takes a GitPython repo object as argument and executed a git push to update the origin repo with the
|
161
|
+
changes made in the local repo. Prints the results in Nornir style and returns nothing (default None).
|
162
|
+
"""
|
163
|
+
print_task_name(text="Git push local repo to origin GitHub")
|
164
|
+
|
165
|
+
task_text = "Git push local repo to origin GitHub"
|
166
|
+
print(task_info(text=task_text, changed=False))
|
167
|
+
|
168
|
+
push = repo.remote("origin").push()
|
169
|
+
|
170
|
+
# push is a git.remote.PushInfo object with type git.util.InterableList
|
171
|
+
for info in push:
|
172
|
+
if "[up to date]" in info.summary:
|
173
|
+
print("Everything up-to-date")
|
174
|
+
|
175
|
+
else:
|
176
|
+
new_commit = str(info.summary)[9:].rstrip()
|
177
|
+
print(f"[main {new_commit}] To {repo.remotes.origin.url}")
|
178
|
+
print(f" {info.summary.rstrip()} main -> main")
|
179
|
+
|
180
|
+
|
181
|
+
def git_pull_from_origin(repo_path: str) -> None:
|
182
|
+
"""
|
183
|
+
Takes a string with a git directory path as argument and initializes two GitPython objects. Then checks
|
184
|
+
for any changes in the local repo and executes a git pull to merge the local repo with the origin repo.
|
185
|
+
Prints the results in Nornir style and returns nothing (default None).
|
186
|
+
"""
|
187
|
+
print_task_title("Update local Git repo")
|
188
|
+
|
189
|
+
# Initialize the GitPython repo and repo_cmd object. The repo_path is a string specifing the git directory
|
190
|
+
repo_init = git_init(repo_path=repo_path)
|
191
|
+
|
192
|
+
# git_init returns a tuple with a repo object [0] and a repo_cmd object [1]
|
193
|
+
repo, repo_cmd = repo_init
|
194
|
+
|
195
|
+
# Check for local git changes and print info to stdout
|
196
|
+
git_is_dirty(repo=repo)
|
197
|
+
|
198
|
+
# Execute a git pull to merge the GitHub origin/main into local main
|
199
|
+
git_cmd_pull(repo_cmd=repo_cmd)
|
200
|
+
|
201
|
+
|
202
|
+
def git_commit_push_to_origin(repo_path: str, commit_msg: str) -> None:
|
203
|
+
"""
|
204
|
+
Takes a string with a git directory path and a string with a commit message as argument and initializes
|
205
|
+
two GitPython objects. Then checks for any changes in the local repo and executes a git add and a git
|
206
|
+
commit followed by a git push to commit the local repo changes and update the origin repo with the changes
|
207
|
+
made in the local repo. Prints the results in Nornir style and returns nothing (default None).
|
208
|
+
"""
|
209
|
+
print_task_title("Update remote GitHub repo")
|
210
|
+
|
211
|
+
# Initialize the GitPython repo and repo_cmd object
|
212
|
+
repo_init = git_init(repo_path=repo_path)
|
213
|
+
|
214
|
+
# git_init returns a tuple with a repo object [0] and a repo_cmd object [1]
|
215
|
+
repo, repo_cmd = repo_init
|
216
|
+
|
217
|
+
# Check for local git changes and print info to stdout
|
218
|
+
git_is_dirty(repo=repo)
|
219
|
+
|
220
|
+
# Execute git add to stage all changes files and commit them
|
221
|
+
git_cmd_add_commit(repo_cmd=repo_cmd, commit_msg=commit_msg)
|
222
|
+
|
223
|
+
# Execute git push to update the origin
|
224
|
+
git_push(repo=repo)
|
File without changes
|