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/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 _stream_reader(pipe, log_funcs, output_accumulator: List[str]):
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
- with pipe:
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
- def run_command(command: List[str], timeout: int = 30, no_output_log: bool = False):
112
+ subprocess.CalledProcessError: if CalledProcessError is raised in subprocess.run(), let it bobble up.
115
113
  """
116
- Executes a command and streams output only to the secondary log unless no_log is set to True.
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
- try:
142
- logger = get_logger(command_output_logger=False)
143
- command_logger = get_logger(command_output_logger=True)
144
-
145
- if not shutil.which(command[0]):
146
- raise FileNotFoundError(f"Command not found: {command[0]}")
147
-
148
- logger.debug(f"Running command: {command}")
149
- command_logger.info(f"Running command: {command}")
150
-
151
- process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
152
-
153
- log_funcs = [command_logger.info] if not no_output_log else []
154
- err_log_funcs = [command_logger.error] if not no_output_log else []
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.