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,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 ###########################################################################################
@@ -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