dar-backup 0.6.16__py3-none-any.whl → 0.6.18__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.
- dar_backup/Changelog.md +217 -0
- dar_backup/README.md +1082 -0
- dar_backup/__about__.py +1 -1
- dar_backup/cleanup.py +42 -57
- dar_backup/command_runner.py +81 -0
- dar_backup/config_settings.py +7 -1
- dar_backup/dar-backup.conf +1 -1
- dar_backup/dar_backup.py +152 -49
- dar_backup/dar_backup_systemd.py +119 -0
- dar_backup/installer.py +39 -23
- dar_backup/manager.py +74 -44
- dar_backup/rich_progress.py +101 -0
- dar_backup/util.py +29 -134
- {dar_backup-0.6.16.dist-info → dar_backup-0.6.18.dist-info}/METADATA +283 -80
- dar_backup-0.6.18.dist-info/RECORD +21 -0
- {dar_backup-0.6.16.dist-info → dar_backup-0.6.18.dist-info}/entry_points.txt +1 -0
- dar_backup-0.6.16.dist-info/RECORD +0 -16
- {dar_backup-0.6.16.dist-info → dar_backup-0.6.18.dist-info}/WHEEL +0 -0
- {dar_backup-0.6.16.dist-info → dar_backup-0.6.18.dist-info}/licenses/LICENSE +0 -0
dar_backup/util.py
CHANGED
|
@@ -19,13 +19,13 @@ import sys
|
|
|
19
19
|
import threading
|
|
20
20
|
import traceback
|
|
21
21
|
from datetime import datetime
|
|
22
|
+
from dar_backup.config_settings import ConfigSettings
|
|
22
23
|
|
|
23
24
|
from typing import NamedTuple, List
|
|
24
25
|
|
|
25
26
|
logger=None
|
|
26
27
|
secondary_logger=None
|
|
27
28
|
|
|
28
|
-
|
|
29
29
|
def setup_logging(log_file: str, command_output_log_file: str, log_level: str = "info", log_to_stdout: bool = False) -> logging.Logger:
|
|
30
30
|
"""
|
|
31
31
|
Sets up logging for the main program and a separate secondary logfile for command outputs.
|
|
@@ -98,106 +98,44 @@ def get_logger(command_output_logger: bool = False) -> logging.Logger:
|
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
def
|
|
102
|
-
"""
|
|
103
|
-
Reads lines from the subprocess pipe and logs them to multiple destinations.
|
|
101
|
+
def requirements(type: str, config_setting: ConfigSettings):
|
|
104
102
|
"""
|
|
105
|
-
|
|
106
|
-
for line in iter(pipe.readline, ''):
|
|
107
|
-
stripped_line = line.strip()
|
|
108
|
-
output_accumulator.append(stripped_line)
|
|
109
|
-
for log_func in log_funcs:
|
|
110
|
-
log_func(stripped_line) # Log the output in real-time
|
|
103
|
+
Perform PREREQ or POSTREQ requirements.
|
|
111
104
|
|
|
105
|
+
Args:
|
|
106
|
+
type (str): The type of prereq (PREREQ, POSTREQ).
|
|
107
|
+
config_settings (ConfigSettings): An instance of the ConfigSettings class.
|
|
112
108
|
|
|
109
|
+
Raises:
|
|
110
|
+
RuntimeError: If a subprocess returns anything but zero.
|
|
113
111
|
|
|
114
|
-
|
|
112
|
+
subprocess.CalledProcessError: if CalledProcessError is raised in subprocess.run(), let it bobble up.
|
|
115
113
|
"""
|
|
116
|
-
|
|
114
|
+
|
|
115
|
+
if type is None or config_setting is None:
|
|
116
|
+
raise RuntimeError(f"requirements: 'type' or config_setting is None")
|
|
117
117
|
|
|
118
|
+
allowed_types = ['PREREQ', 'POSTREQ']
|
|
119
|
+
if type not in allowed_types:
|
|
120
|
+
raise RuntimeError(f"requirements: {type} not in: {allowed_types}")
|
|
118
121
|
|
|
119
|
-
Returns:
|
|
120
|
-
A CommandResult NamedTuple with the following properties:
|
|
121
|
-
- process: subprocess.CompletedProcess
|
|
122
|
-
- stdout: str: The full standard output of the command.
|
|
123
|
-
- stderr: str: The full standard error of the command.
|
|
124
|
-
- returncode: int: The return code of the command.
|
|
125
|
-
- timeout: int: The timeout value in seconds used to run the command.
|
|
126
|
-
- command: list[str]: The command executed.
|
|
127
|
-
|
|
128
|
-
Logs:
|
|
129
|
-
- Logs standard output (`stdout`) and standard error in real-time to the
|
|
130
|
-
logger.secondary_log (that contains the command output).
|
|
131
|
-
|
|
132
|
-
Raises:
|
|
133
|
-
subprocess.TimeoutExpired: If the command execution times out (see `timeout` parameter).
|
|
134
|
-
Exception: If other exceptions occur during command execution.
|
|
135
|
-
FileNotFoundError: If the command is not found.
|
|
136
|
-
"""
|
|
137
|
-
stdout_lines, stderr_lines = [], []
|
|
138
|
-
process = None
|
|
139
|
-
stdout_thread, stderr_thread = None, None
|
|
140
122
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
stdout_thread = threading.Thread(target=_stream_reader, args=(process.stdout, log_funcs, stdout_lines))
|
|
157
|
-
stderr_thread = threading.Thread(target=_stream_reader, args=(process.stderr, err_log_funcs, stderr_lines))
|
|
158
|
-
|
|
159
|
-
stdout_thread.start()
|
|
160
|
-
stderr_thread.start()
|
|
161
|
-
|
|
162
|
-
process.wait(timeout=timeout)
|
|
163
|
-
|
|
164
|
-
except FileNotFoundError as e:
|
|
165
|
-
logger.error(f"Command not found: {command[0]}")
|
|
166
|
-
return CommandResult(
|
|
167
|
-
process=None,
|
|
168
|
-
stdout="",
|
|
169
|
-
stderr=str(e),
|
|
170
|
-
returncode=127,
|
|
171
|
-
timeout=timeout,
|
|
172
|
-
command=command
|
|
173
|
-
)
|
|
174
|
-
except subprocess.TimeoutExpired:
|
|
175
|
-
if process:
|
|
176
|
-
process.terminate()
|
|
177
|
-
logger.error(f"Command: '{command}' timed out and was terminated.")
|
|
178
|
-
raise
|
|
179
|
-
except Exception as e:
|
|
180
|
-
logger.error(f"Error running command: {command}", exc_info=True)
|
|
181
|
-
raise
|
|
182
|
-
finally:
|
|
183
|
-
if stdout_thread and stdout_thread.is_alive():
|
|
184
|
-
stdout_thread.join()
|
|
185
|
-
if stderr_thread and stderr_thread.is_alive():
|
|
186
|
-
stderr_thread.join()
|
|
187
|
-
if process:
|
|
188
|
-
if process.stdout:
|
|
189
|
-
process.stdout.close()
|
|
190
|
-
if process.stderr:
|
|
191
|
-
process.stderr.close()
|
|
123
|
+
logger.debug(f"Performing {type}")
|
|
124
|
+
if type in config_setting.config:
|
|
125
|
+
for key in sorted(config_setting.config[type].keys()):
|
|
126
|
+
script = config_setting.config[type][key]
|
|
127
|
+
try:
|
|
128
|
+
result = subprocess.run(script, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True, check=True)
|
|
129
|
+
logger.debug(f"{type} {key}: '{script}' run, return code: {result.returncode}")
|
|
130
|
+
logger.debug(f"{type} stdout:\n{result.stdout}")
|
|
131
|
+
if result.returncode != 0:
|
|
132
|
+
logger.error(f"{type} stderr:\n{result.stderr}")
|
|
133
|
+
raise RuntimeError(f"{type} {key}: '{script}' failed, return code: {result.returncode}")
|
|
134
|
+
except subprocess.CalledProcessError as e:
|
|
135
|
+
logger.error(f"Error executing {key}: '{script}': {e}")
|
|
136
|
+
raise e
|
|
192
137
|
|
|
193
|
-
|
|
194
|
-
# Combine captured stdout and stderr lines into single strings
|
|
195
|
-
stdout = "\n".join(stdout_lines)
|
|
196
|
-
stderr = "\n".join(stderr_lines)
|
|
197
138
|
|
|
198
|
-
#Build the result object
|
|
199
|
-
result = CommandResult(process=process, stdout=stdout, stderr=stderr, returncode=process.returncode, timeout=timeout, command=command)
|
|
200
|
-
return result
|
|
201
139
|
|
|
202
140
|
|
|
203
141
|
class BackupError(Exception):
|
|
@@ -234,49 +172,6 @@ class CommandResult(NamedTuple):
|
|
|
234
172
|
return f"CommandResult: [Return Code: '{self.returncode}', \nCommand: '{' '.join(map(shlex.quote, self.command))}']"
|
|
235
173
|
|
|
236
174
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def extract_error_lines(log_file_path: str, start_time: str, end_time: str):
|
|
240
|
-
"""
|
|
241
|
-
Extracts error lines from a log file within a specific time range.
|
|
242
|
-
|
|
243
|
-
Args:
|
|
244
|
-
log_file_path (str): The path to the log file.
|
|
245
|
-
start_time (str): The start time of the desired time range (unixtime).
|
|
246
|
-
end_time (str): The end time of the desired time range (unixtime).
|
|
247
|
-
|
|
248
|
-
Returns:
|
|
249
|
-
list: A list of error lines within the specified time range.
|
|
250
|
-
|
|
251
|
-
Raises:
|
|
252
|
-
ValueError: If the start or end markers are not found in the log file.
|
|
253
|
-
"""
|
|
254
|
-
with open(log_file_path, 'r') as log_file:
|
|
255
|
-
lines = log_file.readlines()
|
|
256
|
-
|
|
257
|
-
start_index = None
|
|
258
|
-
end_index = None
|
|
259
|
-
|
|
260
|
-
start_marker = f"START TIME: {start_time}"
|
|
261
|
-
end_marker = f"END TIME: {end_time}"
|
|
262
|
-
error_pattern = re.compile(r'ERROR')
|
|
263
|
-
|
|
264
|
-
# Find the start and end index for the specific run
|
|
265
|
-
for i, line in enumerate(lines):
|
|
266
|
-
if start_marker in line:
|
|
267
|
-
start_index = i
|
|
268
|
-
elif end_marker in line and start_index is not None:
|
|
269
|
-
end_index = i
|
|
270
|
-
break
|
|
271
|
-
|
|
272
|
-
if start_index is None or end_index is None:
|
|
273
|
-
raise ValueError("Could not find start or end markers in the log file")
|
|
274
|
-
|
|
275
|
-
error_lines = [line.rstrip("\n") for line in lines[start_index:end_index + 1] if error_pattern.search(line)]
|
|
276
|
-
|
|
277
|
-
return error_lines
|
|
278
|
-
|
|
279
|
-
|
|
280
175
|
def list_backups(backup_dir, backup_definition=None):
|
|
281
176
|
"""
|
|
282
177
|
List the available backups in the specified directory and their sizes in megabytes, with aligned sizes.
|