2019-06-12 19:11:36 +00:00
|
|
|
import logging
|
2019-04-03 05:30:14 +00:00
|
|
|
import subprocess
|
|
|
|
|
2019-06-17 18:53:08 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2019-04-03 05:30:14 +00:00
|
|
|
|
|
|
|
|
2019-06-24 16:55:41 +00:00
|
|
|
ERROR_OUTPUT_MAX_LINE_COUNT = 25
|
2019-08-03 22:13:54 +00:00
|
|
|
BORG_ERROR_EXIT_CODE = 2
|
2019-06-24 16:55:41 +00:00
|
|
|
|
|
|
|
|
2019-06-12 20:09:04 +00:00
|
|
|
def execute_and_log_output(full_command, output_log_level, shell):
|
2019-06-24 16:55:41 +00:00
|
|
|
last_lines = []
|
2019-06-12 20:09:04 +00:00
|
|
|
process = subprocess.Popen(
|
|
|
|
full_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell
|
|
|
|
)
|
2019-06-11 23:42:04 +00:00
|
|
|
|
|
|
|
while process.poll() is None:
|
2019-06-12 20:09:04 +00:00
|
|
|
line = process.stdout.readline().rstrip().decode()
|
2019-06-13 03:56:20 +00:00
|
|
|
if not line:
|
|
|
|
continue
|
|
|
|
|
2019-06-24 16:55:41 +00:00
|
|
|
# Keep the last few lines of output in case the command errors, and we need the output for
|
|
|
|
# the exception below.
|
|
|
|
last_lines.append(line)
|
|
|
|
if len(last_lines) > ERROR_OUTPUT_MAX_LINE_COUNT:
|
|
|
|
last_lines.pop(0)
|
|
|
|
|
|
|
|
logger.log(output_log_level, line)
|
2019-06-11 23:42:04 +00:00
|
|
|
|
2019-06-12 20:09:04 +00:00
|
|
|
remaining_output = process.stdout.read().rstrip().decode()
|
2019-06-14 04:38:06 +00:00
|
|
|
if remaining_output: # pragma: no cover
|
2019-06-13 17:48:21 +00:00
|
|
|
logger.log(output_log_level, remaining_output)
|
2019-06-11 23:42:04 +00:00
|
|
|
|
2019-06-12 18:49:35 +00:00
|
|
|
exit_code = process.poll()
|
2019-09-12 23:37:43 +00:00
|
|
|
|
|
|
|
# If shell is True, assume we're running something other than Borg and should treat all non-zero
|
|
|
|
# exit codes as errors.
|
|
|
|
error = bool(exit_code != 0) if shell else bool(exit_code >= BORG_ERROR_EXIT_CODE)
|
|
|
|
|
|
|
|
if error:
|
2019-06-24 16:55:41 +00:00
|
|
|
# If an error occurs, include its output in the raised exception so that we don't
|
|
|
|
# inadvertently hide error output.
|
|
|
|
if len(last_lines) == ERROR_OUTPUT_MAX_LINE_COUNT:
|
|
|
|
last_lines.insert(0, '...')
|
|
|
|
|
|
|
|
raise subprocess.CalledProcessError(
|
|
|
|
exit_code, ' '.join(full_command), '\n'.join(last_lines)
|
|
|
|
)
|
2019-06-11 23:42:04 +00:00
|
|
|
|
|
|
|
|
2019-06-12 20:09:04 +00:00
|
|
|
def execute_command(full_command, output_log_level=logging.INFO, shell=False):
|
2019-04-03 05:30:14 +00:00
|
|
|
'''
|
2019-06-12 19:11:36 +00:00
|
|
|
Execute the given command (a sequence of command/argument strings) and log its output at the
|
2019-06-12 20:09:04 +00:00
|
|
|
given log level. If output log level is None, instead capture and return the output. If
|
|
|
|
shell is True, execute the command within a shell.
|
2019-04-03 05:30:14 +00:00
|
|
|
'''
|
|
|
|
logger.debug(' '.join(full_command))
|
|
|
|
|
2019-06-12 19:11:36 +00:00
|
|
|
if output_log_level is None:
|
2019-06-12 20:09:04 +00:00
|
|
|
output = subprocess.check_output(full_command, shell=shell)
|
2019-04-03 05:30:14 +00:00
|
|
|
return output.decode() if output is not None else None
|
|
|
|
else:
|
2019-06-12 20:09:04 +00:00
|
|
|
execute_and_log_output(full_command, output_log_level, shell=shell)
|