mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-26 11:23:02 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50096296da | ||
|
|
3bc14ba364 | ||
|
|
c9c6913547 | ||
|
|
779f51f40a | ||
|
|
24b846e9ca | ||
|
|
73fe29b055 | ||
|
|
775385e688 | ||
|
|
efdbee934a | ||
|
|
49719dc309 | ||
|
|
b7e3ee8277 | ||
|
|
97fe1a2c50 | ||
|
|
66abf38b39 | ||
|
|
5baf091853 | ||
|
|
c5abcc1fdf | ||
|
|
9a9a8fd1c6 | ||
|
|
ab9e8d06ee | ||
|
|
5a2cd1b261 | ||
|
|
ffaa99ba15 | ||
|
|
5dc0b08f22 | ||
|
|
23009e22aa | ||
|
|
6cfa10fb7e |
@@ -1,3 +1,19 @@
|
||||
1.9.10
|
||||
* #966: Add a "{credential ...}" syntax for loading systemd credentials into borgmatic
|
||||
configuration files. See the documentation for more information:
|
||||
https://torsion.org/borgmatic/docs/how-to/provide-your-passwords/
|
||||
* #987: Fix a "list" action error when the "encryption_passcommand" option is set.
|
||||
* #987: When both "encryption_passcommand" and "encryption_passphrase" are configured, prefer
|
||||
"encryption_passphrase" even if it's an empty value.
|
||||
* #988: With the "max_duration" option or the "--max-duration" flag, run the archives and
|
||||
repository checks separately so they don't interfere with one another. Previously, borgmatic
|
||||
refused to run checks in this situation.
|
||||
* #989: Fix the log message code to avoid using Python 3.10+ logging features. Now borgmatic will
|
||||
work with Python 3.9 again.
|
||||
* Capture and delay any log records produced before logging is fully configured, so early log
|
||||
records don't get lost.
|
||||
* Add support for Python 3.13.
|
||||
|
||||
1.9.9
|
||||
* #635: Log the repository path or label on every relevant log message, not just some logs.
|
||||
* #961: When the "encryption_passcommand" option is set, call the command once from borgmatic to
|
||||
|
||||
@@ -56,6 +56,8 @@ borgmatic is powered by [Borg Backup](https://www.borgbackup.org/).
|
||||
|
||||
## Integrations
|
||||
|
||||
### Data
|
||||
|
||||
<a href="https://www.postgresql.org/"><img src="docs/static/postgresql.png" alt="PostgreSQL" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://www.mysql.com/"><img src="docs/static/mysql.png" alt="MySQL" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://mariadb.com/"><img src="docs/static/mariadb.png" alt="MariaDB" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
@@ -65,6 +67,11 @@ borgmatic is powered by [Borg Backup](https://www.borgbackup.org/).
|
||||
<a href="https://btrfs.readthedocs.io/"><img src="docs/static/btrfs.png" alt="Btrfs" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://sourceware.org/lvm2/"><img src="docs/static/lvm.png" alt="LVM" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://rclone.org"><img src="docs/static/rclone.png" alt="rclone" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://www.borgbase.com/?utm_source=borgmatic"><img src="docs/static/borgbase.png" alt="BorgBase" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
|
||||
|
||||
### Monitoring
|
||||
|
||||
<a href="https://healthchecks.io/"><img src="docs/static/healthchecks.png" alt="Healthchecks" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://uptime.kuma.pet/"><img src="docs/static/uptimekuma.png" alt="Uptime Kuma" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://cronitor.io/"><img src="docs/static/cronitor.png" alt="Cronitor" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
@@ -76,7 +83,11 @@ borgmatic is powered by [Borg Backup](https://www.borgbackup.org/).
|
||||
<a href="https://github.com/caronc/apprise/wiki"><img src="docs/static/apprise.png" alt="Apprise" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://www.zabbix.com/"><img src="docs/static/zabbix.png" alt="Zabbix" height="40px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://sentry.io/"><img src="docs/static/sentry.png" alt="Sentry" height="40px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://www.borgbase.com/?utm_source=borgmatic"><img src="docs/static/borgbase.png" alt="BorgBase" height="60px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
|
||||
|
||||
### Credentials
|
||||
|
||||
<a href="https://systemd.io/"><img src="docs/static/systemd.png" alt="Sentry" height="40px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -386,13 +386,12 @@ def collect_spot_check_source_paths(
|
||||
stream_processes=stream_processes,
|
||||
)
|
||||
)
|
||||
borg_environment = borgmatic.borg.environment.make_environment(config)
|
||||
working_directory = borgmatic.config.paths.get_working_directory(config)
|
||||
|
||||
paths_output = borgmatic.execute.execute_command_and_capture_output(
|
||||
create_flags + create_positional_arguments,
|
||||
capture_stderr=True,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=borgmatic.borg.environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
|
||||
@@ -34,10 +34,9 @@ def break_lock(
|
||||
+ flags.make_repository_flags(repository_path, local_borg_version)
|
||||
)
|
||||
|
||||
borg_environment = environment.make_environment(config)
|
||||
execute_command(
|
||||
full_command,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=borgmatic.config.paths.get_working_directory(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
|
||||
+42
-46
@@ -64,15 +64,11 @@ def make_check_name_flags(checks, archive_filter_flags):
|
||||
|
||||
('--repository-only',)
|
||||
|
||||
However, if both "repository" and "archives" are in checks, then omit them from the returned
|
||||
flags because Borg does both checks by default. If "data" is in checks, that implies "archives".
|
||||
However, if both "repository" and "archives" are in checks, then omit the "only" flags from the
|
||||
returned flags because Borg does both checks by default. Note that a "data" check only works
|
||||
along with an "archives" check.
|
||||
'''
|
||||
if 'data' in checks:
|
||||
data_flags = ('--verify-data',)
|
||||
checks.update({'archives'})
|
||||
else:
|
||||
data_flags = ()
|
||||
|
||||
data_flags = ('--verify-data',) if 'data' in checks else ()
|
||||
common_flags = (archive_filter_flags if 'archives' in checks else ()) + data_flags
|
||||
|
||||
if {'repository', 'archives'}.issubset(checks):
|
||||
@@ -142,51 +138,51 @@ def check_archives(
|
||||
except StopIteration:
|
||||
repository_check_config = {}
|
||||
|
||||
if check_arguments.max_duration and 'archives' in checks:
|
||||
raise ValueError('The archives check cannot run when the --max-duration flag is used')
|
||||
if repository_check_config.get('max_duration') and 'archives' in checks:
|
||||
raise ValueError(
|
||||
'The archives check cannot run when the repository check has the max_duration option set'
|
||||
)
|
||||
|
||||
max_duration = check_arguments.max_duration or repository_check_config.get('max_duration')
|
||||
|
||||
umask = config.get('umask')
|
||||
|
||||
borg_environment = environment.make_environment(config)
|
||||
borg_exit_codes = config.get('borg_exit_codes')
|
||||
|
||||
full_command = (
|
||||
(local_path, 'check')
|
||||
+ (('--repair',) if check_arguments.repair else ())
|
||||
+ (('--max-duration', str(max_duration)) if max_duration else ())
|
||||
+ make_check_name_flags(checks, archive_filter_flags)
|
||||
+ (('--remote-path', remote_path) if remote_path else ())
|
||||
+ (('--umask', str(umask)) if umask else ())
|
||||
+ (('--log-json',) if global_arguments.log_json else ())
|
||||
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
||||
+ verbosity_flags
|
||||
+ (('--progress',) if check_arguments.progress else ())
|
||||
+ (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
|
||||
+ flags.make_repository_flags(repository_path, local_borg_version)
|
||||
)
|
||||
|
||||
working_directory = borgmatic.config.paths.get_working_directory(config)
|
||||
|
||||
# The Borg repair option triggers an interactive prompt, which won't work when output is
|
||||
# captured. And progress messes with the terminal directly.
|
||||
if check_arguments.repair or check_arguments.progress:
|
||||
if 'data' in checks:
|
||||
checks.add('archives')
|
||||
|
||||
grouped_checks = (checks,)
|
||||
|
||||
# If max_duration is set, then archives and repository checks need to be run separately, as Borg
|
||||
# doesn't support --max-duration along with an archives checks.
|
||||
if max_duration and 'archives' in checks and 'repository' in checks:
|
||||
checks.remove('repository')
|
||||
grouped_checks = (checks, {'repository'})
|
||||
|
||||
for checks_subset in grouped_checks:
|
||||
full_command = (
|
||||
(local_path, 'check')
|
||||
+ (('--repair',) if check_arguments.repair else ())
|
||||
+ (
|
||||
('--max-duration', str(max_duration))
|
||||
if max_duration and 'repository' in checks_subset
|
||||
else ()
|
||||
)
|
||||
+ make_check_name_flags(checks_subset, archive_filter_flags)
|
||||
+ (('--remote-path', remote_path) if remote_path else ())
|
||||
+ (('--umask', str(umask)) if umask else ())
|
||||
+ (('--log-json',) if global_arguments.log_json else ())
|
||||
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
||||
+ verbosity_flags
|
||||
+ (('--progress',) if check_arguments.progress else ())
|
||||
+ (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
|
||||
+ flags.make_repository_flags(repository_path, local_borg_version)
|
||||
)
|
||||
|
||||
execute_command(
|
||||
full_command,
|
||||
output_file=DO_NOT_CAPTURE,
|
||||
extra_environment=borg_environment,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
)
|
||||
else:
|
||||
execute_command(
|
||||
full_command,
|
||||
extra_environment=borg_environment,
|
||||
# The Borg repair option triggers an interactive prompt, which won't work when output is
|
||||
# captured. And progress messes with the terminal directly.
|
||||
output_file=(
|
||||
DO_NOT_CAPTURE if check_arguments.repair or check_arguments.progress else None
|
||||
),
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -122,15 +122,14 @@ def collect_special_file_paths(
|
||||
config,
|
||||
local_path,
|
||||
working_directory,
|
||||
borg_environment,
|
||||
borgmatic_runtime_directory,
|
||||
):
|
||||
'''
|
||||
Given a dry-run flag, a Borg create command as a tuple, a configuration dict, a local Borg path,
|
||||
a working directory, a dict of environment variables to pass to Borg, and the borgmatic runtime
|
||||
directory, collect the paths for any special files (character devices, block devices, and named
|
||||
pipes / FIFOs) that Borg would encounter during a create. These are all paths that could cause
|
||||
Borg to hang if its --read-special flag is used.
|
||||
a working directory, and the borgmatic runtime directory, collect the paths for any special
|
||||
files (character devices, block devices, and named pipes / FIFOs) that Borg would encounter
|
||||
during a create. These are all paths that could cause Borg to hang if its --read-special flag is
|
||||
used.
|
||||
|
||||
Skip looking for special files in the given borgmatic runtime directory, as borgmatic creates
|
||||
its own special files there for database dumps. And if the borgmatic runtime directory is
|
||||
@@ -144,7 +143,7 @@ def collect_special_file_paths(
|
||||
+ ('--dry-run', '--list'),
|
||||
capture_stderr=True,
|
||||
working_directory=working_directory,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
)
|
||||
@@ -299,7 +298,6 @@ def make_base_create_command(
|
||||
logger.warning(
|
||||
'Ignoring configured "read_special" value of false, as true is needed for database hooks.'
|
||||
)
|
||||
borg_environment = environment.make_environment(config)
|
||||
working_directory = borgmatic.config.paths.get_working_directory(config)
|
||||
|
||||
logger.debug('Collecting special file paths')
|
||||
@@ -309,7 +307,6 @@ def make_base_create_command(
|
||||
config,
|
||||
local_path,
|
||||
working_directory,
|
||||
borg_environment,
|
||||
borgmatic_runtime_directory=borgmatic_runtime_directory,
|
||||
)
|
||||
|
||||
@@ -396,8 +393,6 @@ def create_archive(
|
||||
# the terminal directly.
|
||||
output_file = DO_NOT_CAPTURE if progress else None
|
||||
|
||||
borg_environment = environment.make_environment(config)
|
||||
|
||||
create_flags += (
|
||||
(('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
|
||||
+ (('--stats',) if stats and not json and not dry_run else ())
|
||||
@@ -414,7 +409,7 @@ def create_archive(
|
||||
output_log_level,
|
||||
output_file,
|
||||
working_directory=working_directory,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
)
|
||||
@@ -422,7 +417,7 @@ def create_archive(
|
||||
return execute_command_and_capture_output(
|
||||
create_flags + create_positional_arguments,
|
||||
working_directory=working_directory,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
)
|
||||
@@ -432,7 +427,7 @@ def create_archive(
|
||||
output_log_level,
|
||||
output_file,
|
||||
working_directory=working_directory,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
|
||||
import borgmatic.borg.passcommand
|
||||
import borgmatic.hooks.credential.parse
|
||||
|
||||
OPTION_TO_ENVIRONMENT_VARIABLE = {
|
||||
'borg_base_directory': 'BORG_BASE_DIR',
|
||||
@@ -14,6 +15,8 @@ OPTION_TO_ENVIRONMENT_VARIABLE = {
|
||||
'temporary_directory': 'TMPDIR',
|
||||
}
|
||||
|
||||
CREDENTIAL_OPTIONS = {'encryption_passphrase'}
|
||||
|
||||
DEFAULT_BOOL_OPTION_TO_DOWNCASE_ENVIRONMENT_VARIABLE = {
|
||||
'relocated_repo_access_is_ok': 'BORG_RELOCATED_REPO_ACCESS_IS_OK',
|
||||
'unknown_unencrypted_repo_access_is_ok': 'BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK',
|
||||
@@ -28,13 +31,19 @@ def make_environment(config):
|
||||
'''
|
||||
Given a borgmatic configuration dict, return its options converted to a Borg environment
|
||||
variable dict.
|
||||
|
||||
Do not reuse this environment across multiple Borg invocations, because it can include
|
||||
references to resources like anonymous pipes for passphrases—which can only be consumed once.
|
||||
'''
|
||||
environment = {}
|
||||
|
||||
for option_name, environment_variable_name in OPTION_TO_ENVIRONMENT_VARIABLE.items():
|
||||
value = config.get(option_name)
|
||||
|
||||
if value:
|
||||
if option_name in CREDENTIAL_OPTIONS and value is not None:
|
||||
value = borgmatic.hooks.credential.parse.resolve_credential(value)
|
||||
|
||||
if value is not None:
|
||||
environment[environment_variable_name] = str(value)
|
||||
|
||||
passphrase = borgmatic.borg.passcommand.get_passphrase_from_passcommand(config)
|
||||
|
||||
@@ -44,7 +44,6 @@ def extract_last_archive_dry_run(
|
||||
return
|
||||
|
||||
list_flag = ('--list',) if logger.isEnabledFor(logging.DEBUG) else ()
|
||||
borg_environment = environment.make_environment(config)
|
||||
full_extract_command = (
|
||||
(local_path, 'extract', '--dry-run')
|
||||
+ (('--remote-path', remote_path) if remote_path else ())
|
||||
@@ -59,7 +58,7 @@ def extract_last_archive_dry_run(
|
||||
|
||||
execute_command(
|
||||
full_extract_command,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=borgmatic.config.paths.get_working_directory(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
@@ -144,7 +143,6 @@ def extract_archive(
|
||||
+ (tuple(paths) if paths else ())
|
||||
)
|
||||
|
||||
borg_environment = environment.make_environment(config)
|
||||
borg_exit_codes = config.get('borg_exit_codes')
|
||||
full_destination_path = (
|
||||
os.path.join(working_directory or '', destination_path) if destination_path else None
|
||||
@@ -156,7 +154,7 @@ def extract_archive(
|
||||
return execute_command(
|
||||
full_command,
|
||||
output_file=DO_NOT_CAPTURE,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=full_destination_path,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -168,7 +166,7 @@ def extract_archive(
|
||||
full_command,
|
||||
output_file=subprocess.PIPE,
|
||||
run_to_completion=False,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=full_destination_path,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -178,7 +176,7 @@ def extract_archive(
|
||||
# if the restore paths don't exist in the archive.
|
||||
execute_command(
|
||||
full_command,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=full_destination_path,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -106,8 +106,6 @@ def capture_archive_listing(
|
||||
format to use for the output, and local and remote Borg paths, capture the
|
||||
output of listing that archive and return it as a list of file paths.
|
||||
'''
|
||||
borg_environment = environment.make_environment(config)
|
||||
|
||||
return tuple(
|
||||
execute_command_and_capture_output(
|
||||
make_list_command(
|
||||
@@ -126,7 +124,7 @@ def capture_archive_listing(
|
||||
local_path,
|
||||
remote_path,
|
||||
),
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=borgmatic.config.paths.get_working_directory(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
@@ -194,7 +192,6 @@ def list_archive(
|
||||
'The --json flag on the list action is not supported when using the --archive/--find flags.'
|
||||
)
|
||||
|
||||
borg_environment = environment.make_environment(config)
|
||||
borg_exit_codes = config.get('borg_exit_codes')
|
||||
|
||||
# If there are any paths to find (and there's not a single archive already selected), start by
|
||||
@@ -224,7 +221,7 @@ def list_archive(
|
||||
local_path,
|
||||
remote_path,
|
||||
),
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=borgmatic.config.paths.get_working_directory(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -260,7 +257,7 @@ def list_archive(
|
||||
execute_command(
|
||||
main_command,
|
||||
output_log_level=logging.ANSWER,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=borgmatic.config.paths.get_working_directory(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -59,7 +59,6 @@ def mount_archive(
|
||||
+ (tuple(mount_arguments.paths) if mount_arguments.paths else ())
|
||||
)
|
||||
|
||||
borg_environment = environment.make_environment(config)
|
||||
working_directory = borgmatic.config.paths.get_working_directory(config)
|
||||
|
||||
# Don't capture the output when foreground mode is used so that ctrl-C can work properly.
|
||||
@@ -67,7 +66,7 @@ def mount_archive(
|
||||
execute_command(
|
||||
full_command,
|
||||
output_file=DO_NOT_CAPTURE,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
@@ -76,7 +75,7 @@ def mount_archive(
|
||||
|
||||
execute_command(
|
||||
full_command,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
|
||||
@@ -47,4 +47,4 @@ def get_passphrase_from_passcommand(config):
|
||||
passphrase = config.get('encryption_passphrase')
|
||||
working_directory = borgmatic.config.paths.get_working_directory(config)
|
||||
|
||||
return run_passcommand(passcommand, bool(passphrase), working_directory)
|
||||
return run_passcommand(passcommand, bool(passphrase is not None), working_directory)
|
||||
|
||||
@@ -50,14 +50,13 @@ def display_repository_info(
|
||||
+ flags.make_repository_flags(repository_path, local_borg_version)
|
||||
)
|
||||
|
||||
extra_environment = environment.make_environment(config)
|
||||
working_directory = borgmatic.config.paths.get_working_directory(config)
|
||||
borg_exit_codes = config.get('borg_exit_codes')
|
||||
|
||||
if repo_info_arguments.json:
|
||||
return execute_command_and_capture_output(
|
||||
full_command,
|
||||
extra_environment=extra_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -66,7 +65,7 @@ def display_repository_info(
|
||||
execute_command(
|
||||
full_command,
|
||||
output_log_level=logging.ANSWER,
|
||||
extra_environment=extra_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -140,7 +140,6 @@ def list_repository(
|
||||
return JSON output).
|
||||
'''
|
||||
borgmatic.logger.add_custom_log_levels()
|
||||
borg_environment = environment.make_environment(config)
|
||||
|
||||
main_command = make_repo_list_command(
|
||||
repository_path,
|
||||
@@ -165,7 +164,7 @@ def list_repository(
|
||||
|
||||
json_listing = execute_command_and_capture_output(
|
||||
json_command,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -179,7 +178,7 @@ def list_repository(
|
||||
execute_command(
|
||||
main_command,
|
||||
output_log_level=logging.ANSWER,
|
||||
extra_environment=borg_environment,
|
||||
extra_environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -349,12 +349,12 @@ def make_parsers():
|
||||
global_group.add_argument(
|
||||
'--log-file-format',
|
||||
type=str,
|
||||
help='Log format string used for log messages written to the log file',
|
||||
help='Python format string used for log messages written to the log file',
|
||||
)
|
||||
global_group.add_argument(
|
||||
'--log-json',
|
||||
action='store_true',
|
||||
help='Write log messages and console output as one JSON object per log line instead of formatted text',
|
||||
help='Write Borg log messages and console output as one JSON object per log line instead of formatted text',
|
||||
)
|
||||
global_group.add_argument(
|
||||
'--override',
|
||||
|
||||
@@ -43,6 +43,7 @@ from borgmatic.logger import (
|
||||
DISABLED,
|
||||
Log_prefix,
|
||||
add_custom_log_levels,
|
||||
configure_delayed_logging,
|
||||
configure_logging,
|
||||
should_do_markup,
|
||||
)
|
||||
@@ -534,13 +535,16 @@ def run_actions(
|
||||
|
||||
def load_configurations(config_filenames, overrides=None, resolve_env=True):
|
||||
'''
|
||||
Given a sequence of configuration filenames, load and validate each configuration file. Return
|
||||
the results as a tuple of: dict of configuration filename to corresponding parsed configuration,
|
||||
a sequence of paths for all loaded configuration files (including includes), and a sequence of
|
||||
logging.LogRecord instances containing any parse errors.
|
||||
Given a sequence of configuration filenames, a sequence of configuration file override strings
|
||||
in the form of "option.suboption=value", and whether to resolve environment variables, load and
|
||||
validate each configuration file. Return the results as a tuple of: dict of configuration
|
||||
filename to corresponding parsed configuration, a sequence of paths for all loaded configuration
|
||||
files (including includes), and a sequence of logging.LogRecord instances containing any parse
|
||||
errors.
|
||||
|
||||
Log records are returned here instead of being logged directly because logging isn't yet
|
||||
initialized at this point!
|
||||
initialized at this point! (Although with the Delayed_logging_handler now in place, maybe this
|
||||
approach could change.)
|
||||
'''
|
||||
# Dict mapping from config filename to corresponding parsed config dict.
|
||||
configs = collections.OrderedDict()
|
||||
@@ -562,7 +566,10 @@ def load_configurations(config_filenames, overrides=None, resolve_env=True):
|
||||
)
|
||||
try:
|
||||
configs[config_filename], paths, parse_logs = validate.parse_configuration(
|
||||
config_filename, validate.schema_filename(), overrides, resolve_env
|
||||
config_filename,
|
||||
validate.schema_filename(),
|
||||
overrides,
|
||||
resolve_env,
|
||||
)
|
||||
config_paths.update(paths)
|
||||
logs.extend(parse_logs)
|
||||
@@ -880,6 +887,7 @@ def exit_with_help_link(): # pragma: no cover
|
||||
|
||||
def main(extra_summary_logs=[]): # pragma: no cover
|
||||
configure_signals()
|
||||
configure_delayed_logging()
|
||||
|
||||
try:
|
||||
arguments = parse_arguments(*sys.argv[1:])
|
||||
@@ -905,9 +913,12 @@ def main(extra_summary_logs=[]): # pragma: no cover
|
||||
print(borgmatic.commands.completion.fish.fish_completion())
|
||||
sys.exit(0)
|
||||
|
||||
validate = bool('validate' in arguments)
|
||||
config_filenames = tuple(collect.collect_config_filenames(global_arguments.config_paths))
|
||||
configs, config_paths, parse_logs = load_configurations(
|
||||
config_filenames, global_arguments.overrides, global_arguments.resolve_env
|
||||
config_filenames,
|
||||
global_arguments.overrides,
|
||||
resolve_env=global_arguments.resolve_env and not validate,
|
||||
)
|
||||
configuration_parse_errors = (
|
||||
(max(log.levelno for log in parse_logs) >= logging.CRITICAL) if parse_logs else False
|
||||
|
||||
@@ -69,7 +69,7 @@ def include_configuration(loader, filename_node, include_directory, config_paths
|
||||
]
|
||||
|
||||
raise ValueError(
|
||||
'!include value is not supported; use a single filename or a list of filenames'
|
||||
'The value given for the !include tag is invalid; use a single filename or a list of filenames instead'
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@ properties:
|
||||
repositories that were initialized with passphrase/repokey/keyfile
|
||||
encryption. Quote the value if it contains punctuation, so it parses
|
||||
correctly. And backslash any quote or backslash literals as well.
|
||||
Defaults to not set.
|
||||
Defaults to not set. Supports the "{credential ...}" syntax.
|
||||
example: "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||
checkpoint_interval:
|
||||
type: integer
|
||||
@@ -632,8 +632,8 @@ properties:
|
||||
long-running repository check into multiple
|
||||
partial checks. Defaults to no interruption. Only
|
||||
applies to the "repository" check, does not check
|
||||
the repository index, and is not compatible with a
|
||||
simultaneous "archives" check or "--repair" flag.
|
||||
the repository index and is not compatible with
|
||||
the "--repair" flag.
|
||||
example: 3600
|
||||
- required:
|
||||
- name
|
||||
@@ -989,13 +989,15 @@ properties:
|
||||
Username with which to connect to the database. Defaults
|
||||
to the username of the current user. You probably want
|
||||
to specify the "postgres" superuser here when the
|
||||
database name is "all".
|
||||
database name is "all". Supports the "{credential ...}"
|
||||
syntax.
|
||||
example: dbuser
|
||||
restore_username:
|
||||
type: string
|
||||
description: |
|
||||
Username with which to restore the database. Defaults to
|
||||
the "username" option.
|
||||
the "username" option. Supports the "{credential ...}"
|
||||
syntax.
|
||||
example: dbuser
|
||||
password:
|
||||
type: string
|
||||
@@ -1003,13 +1005,15 @@ properties:
|
||||
Password with which to connect to the database. Omitting
|
||||
a password will only work if PostgreSQL is configured to
|
||||
trust the configured username without a password or you
|
||||
create a ~/.pgpass file.
|
||||
create a ~/.pgpass file. Supports the "{credential ...}"
|
||||
syntax.
|
||||
example: trustsome1
|
||||
restore_password:
|
||||
type: string
|
||||
description: |
|
||||
Password with which to connect to the restore database.
|
||||
Defaults to the "password" option.
|
||||
Defaults to the "password" option. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: trustsome1
|
||||
no_owner:
|
||||
type: boolean
|
||||
@@ -1169,13 +1173,15 @@ properties:
|
||||
type: string
|
||||
description: |
|
||||
Username with which to connect to the database. Defaults
|
||||
to the username of the current user.
|
||||
to the username of the current user. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: dbuser
|
||||
restore_username:
|
||||
type: string
|
||||
description: |
|
||||
Username with which to restore the database. Defaults to
|
||||
the "username" option.
|
||||
the "username" option. Supports the "{credential ...}"
|
||||
syntax.
|
||||
example: dbuser
|
||||
password:
|
||||
type: string
|
||||
@@ -1183,6 +1189,14 @@ properties:
|
||||
Password with which to connect to the database. Omitting
|
||||
a password will only work if MariaDB is configured to
|
||||
trust the configured username without a password.
|
||||
Supports the "{credential ...}" syntax.
|
||||
example: trustsome1
|
||||
restore_password:
|
||||
type: string
|
||||
description: |
|
||||
Password with which to connect to the restore database.
|
||||
Defaults to the "password" option. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: trustsome1
|
||||
mariadb_dump_command:
|
||||
type: string
|
||||
@@ -1201,12 +1215,6 @@ properties:
|
||||
run a specific mariadb version (e.g., one inside a
|
||||
running container). Defaults to "mariadb".
|
||||
example: docker exec mariadb_container mariadb
|
||||
restore_password:
|
||||
type: string
|
||||
description: |
|
||||
Password with which to connect to the restore database.
|
||||
Defaults to the "password" option.
|
||||
example: trustsome1
|
||||
format:
|
||||
type: string
|
||||
enum: ['sql']
|
||||
@@ -1295,13 +1303,15 @@ properties:
|
||||
type: string
|
||||
description: |
|
||||
Username with which to connect to the database. Defaults
|
||||
to the username of the current user.
|
||||
to the username of the current user. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: dbuser
|
||||
restore_username:
|
||||
type: string
|
||||
description: |
|
||||
Username with which to restore the database. Defaults to
|
||||
the "username" option.
|
||||
the "username" option. Supports the "{credential ...}"
|
||||
syntax.
|
||||
example: dbuser
|
||||
password:
|
||||
type: string
|
||||
@@ -1309,12 +1319,14 @@ properties:
|
||||
Password with which to connect to the database. Omitting
|
||||
a password will only work if MySQL is configured to
|
||||
trust the configured username without a password.
|
||||
Supports the "{credential ...}" syntax.
|
||||
example: trustsome1
|
||||
restore_password:
|
||||
type: string
|
||||
description: |
|
||||
Password with which to connect to the restore database.
|
||||
Defaults to the "password" option.
|
||||
Defaults to the "password" option. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: trustsome1
|
||||
mysql_dump_command:
|
||||
type: string
|
||||
@@ -1451,25 +1463,29 @@ properties:
|
||||
type: string
|
||||
description: |
|
||||
Username with which to connect to the database. Skip it
|
||||
if no authentication is needed.
|
||||
if no authentication is needed. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: dbuser
|
||||
restore_username:
|
||||
type: string
|
||||
description: |
|
||||
Username with which to restore the database. Defaults to
|
||||
the "username" option.
|
||||
the "username" option. Supports the "{credential ...}"
|
||||
syntax.
|
||||
example: dbuser
|
||||
password:
|
||||
type: string
|
||||
description: |
|
||||
Password with which to connect to the database. Skip it
|
||||
if no authentication is needed.
|
||||
if no authentication is needed. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: trustsome1
|
||||
restore_password:
|
||||
type: string
|
||||
description: |
|
||||
Password with which to connect to the restore database.
|
||||
Defaults to the "password" option.
|
||||
Defaults to the "password" option. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: trustsome1
|
||||
authentication_database:
|
||||
type: string
|
||||
@@ -1528,18 +1544,20 @@ properties:
|
||||
username:
|
||||
type: string
|
||||
description: |
|
||||
The username used for authentication.
|
||||
The username used for authentication. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: testuser
|
||||
password:
|
||||
type: string
|
||||
description: |
|
||||
The password used for authentication.
|
||||
The password used for authentication. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: fakepassword
|
||||
access_token:
|
||||
type: string
|
||||
description: |
|
||||
An ntfy access token to authenticate with instead of
|
||||
username/password.
|
||||
username/password. Supports the "{credential ...}" syntax.
|
||||
example: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2
|
||||
start:
|
||||
type: object
|
||||
@@ -1634,14 +1652,16 @@ properties:
|
||||
token:
|
||||
type: string
|
||||
description: |
|
||||
Your application's API token.
|
||||
Your application's API token. Supports the "{credential
|
||||
...}" syntax.
|
||||
example: 7ms6TXHpTokTou2P6x4SodDeentHRa
|
||||
user:
|
||||
type: string
|
||||
description: |
|
||||
Your user/group key (or that of your target user), viewable
|
||||
when logged into your dashboard: often referred to as
|
||||
Your user/group key (or that of your target user), viewable
|
||||
when logged into your dashboard: often referred to as
|
||||
USER_KEY in Pushover documentation and code examples.
|
||||
Supports the "{credential ...}" syntax.
|
||||
example: hwRwoWsXMBWwgrSecfa9EfPey55WSN
|
||||
start:
|
||||
type: object
|
||||
@@ -1915,19 +1935,19 @@ properties:
|
||||
type: string
|
||||
description: |
|
||||
The username used for authentication. Not needed if using
|
||||
an API key.
|
||||
an API key. Supports the "{credential ...}" syntax.
|
||||
example: testuser
|
||||
password:
|
||||
type: string
|
||||
description: |
|
||||
The password used for authentication. Not needed if using
|
||||
an API key.
|
||||
an API key. Supports the "{credential ...}" syntax.
|
||||
example: fakepassword
|
||||
api_key:
|
||||
type: string
|
||||
description: |
|
||||
The API key used for authentication. Not needed if using
|
||||
an username/password.
|
||||
The API key used for authentication. Not needed if using an
|
||||
username/password. Supports the "{credential ...}" syntax.
|
||||
example: fakekey
|
||||
start:
|
||||
type: object
|
||||
@@ -2207,8 +2227,8 @@ properties:
|
||||
integration_key:
|
||||
type: string
|
||||
description: |
|
||||
PagerDuty integration key used to notify PagerDuty
|
||||
when a backup errors.
|
||||
PagerDuty integration key used to notify PagerDuty when a
|
||||
backup errors. Supports the "{credential ...}" syntax.
|
||||
example: a177cad45bd374409f78906a810a3074
|
||||
description: |
|
||||
Configuration for a monitoring integration with PagerDuty. Create an
|
||||
|
||||
@@ -88,8 +88,9 @@ def parse_configuration(config_filename, schema_filename, overrides=None, resolv
|
||||
'''
|
||||
Given the path to a config filename in YAML format, the path to a schema filename in a YAML
|
||||
rendition of JSON Schema format, a sequence of configuration file override strings in the form
|
||||
of "option.suboption=value", return the parsed configuration as a data structure of nested dicts
|
||||
and lists corresponding to the schema. Example return value:
|
||||
of "option.suboption=value", and whether to resolve environment variables, return the parsed
|
||||
configuration as a data structure of nested dicts and lists corresponding to the schema. Example
|
||||
return value:
|
||||
|
||||
{
|
||||
'source_directories': ['/home', '/etc'],
|
||||
@@ -124,6 +125,7 @@ def parse_configuration(config_filename, schema_filename, overrides=None, resolv
|
||||
validator = jsonschema.Draft7Validator(schema)
|
||||
except AttributeError: # pragma: no cover
|
||||
validator = jsonschema.Draft4Validator(schema)
|
||||
|
||||
validation_errors = tuple(validator.iter_errors(config))
|
||||
|
||||
if validation_errors:
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import functools
|
||||
import re
|
||||
|
||||
import borgmatic.hooks.dispatch
|
||||
|
||||
IS_A_HOOK = False
|
||||
|
||||
|
||||
CREDENTIAL_PATTERN = re.compile(
|
||||
r'\{credential +(?P<hook_name>[A-Za-z0-9_]+) +(?P<credential_name>[A-Za-z0-9_]+)\}'
|
||||
)
|
||||
|
||||
GENERAL_CREDENTIAL_PATTERN = re.compile(r'\{credential( +[^}]*)?\}')
|
||||
|
||||
|
||||
@functools.cache
|
||||
def resolve_credential(value):
|
||||
'''
|
||||
Given a configuration value containing a string like "{credential hookname credentialname}", resolve it by
|
||||
calling the relevant hook to get the actual credential value. If the given value does not
|
||||
actually contain a credential tag, then return it unchanged.
|
||||
|
||||
Cache the value so repeated calls to this function don't need to load the credential repeatedly.
|
||||
|
||||
Raise ValueError if the config could not be parsed or the credential could not be loaded.
|
||||
'''
|
||||
if value is None:
|
||||
return value
|
||||
|
||||
result = CREDENTIAL_PATTERN.sub(
|
||||
lambda matcher: borgmatic.hooks.dispatch.call_hook(
|
||||
'load_credential', {}, matcher.group('hook_name'), matcher.group('credential_name')
|
||||
),
|
||||
value,
|
||||
)
|
||||
|
||||
# If we've tried to parse the credential, but the parsed result still looks kind of like a
|
||||
# credential, it means it's invalid syntax.
|
||||
if GENERAL_CREDENTIAL_PATTERN.match(result):
|
||||
raise ValueError(f'Cannot load credential with invalid syntax "{value}"')
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,35 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CREDENTIAL_NAME_PATTERN = re.compile(r'^\w+$')
|
||||
|
||||
|
||||
def load_credential(hook_config, config, credential_name):
|
||||
'''
|
||||
Given the hook configuration dict, the configuration dict, and a credential name to load, read
|
||||
the credential from the corresponding systemd credential file and return it.
|
||||
|
||||
Raise ValueError if the systemd CREDENTIALS_DIRECTORY environment variable is not set, the
|
||||
credential name is invalid, or the credential file cannot be read.
|
||||
'''
|
||||
credentials_directory = os.environ.get('CREDENTIALS_DIRECTORY')
|
||||
|
||||
if not credentials_directory:
|
||||
raise ValueError(
|
||||
f'Cannot load credential "{credential_name}" because the systemd CREDENTIALS_DIRECTORY environment variable is not set'
|
||||
)
|
||||
|
||||
if not CREDENTIAL_NAME_PATTERN.match(credential_name):
|
||||
raise ValueError(f'Cannot load invalid credential name "{credential_name}"')
|
||||
|
||||
try:
|
||||
with open(os.path.join(credentials_directory, credential_name)) as credential_file:
|
||||
return credential_file.read().rstrip(os.linesep)
|
||||
except (FileNotFoundError, OSError) as error:
|
||||
logger.warning(error)
|
||||
|
||||
raise ValueError(f'Cannot load credential "{credential_name}" from file: {error.filename}')
|
||||
@@ -5,6 +5,7 @@ import shlex
|
||||
|
||||
import borgmatic.borg.pattern
|
||||
import borgmatic.config.paths
|
||||
import borgmatic.hooks.credential.parse
|
||||
from borgmatic.execute import (
|
||||
execute_command,
|
||||
execute_command_and_capture_output,
|
||||
@@ -45,7 +46,11 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (('--user', database['username']) if 'username' in database else ())
|
||||
+ (
|
||||
('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
+ ('--skip-column-names', '--batch')
|
||||
+ ('--execute', 'show schemas')
|
||||
)
|
||||
@@ -96,7 +101,11 @@ def execute_dump_command(
|
||||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (('--user', database['username']) if 'username' in database else ())
|
||||
+ (
|
||||
('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
+ ('--databases',)
|
||||
+ database_names
|
||||
+ ('--result-file', dump_filename)
|
||||
@@ -152,7 +161,11 @@ def dump_data_sources(
|
||||
|
||||
for database in databases:
|
||||
dump_path = make_dump_path(borgmatic_runtime_directory)
|
||||
extra_environment = {'MYSQL_PWD': database['password']} if 'password' in database else None
|
||||
extra_environment = (
|
||||
{'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(database['password'])}
|
||||
if 'password' in database
|
||||
else None
|
||||
)
|
||||
dump_database_names = database_names_to_dump(database, extra_environment, dry_run)
|
||||
|
||||
if not dump_database_names:
|
||||
@@ -251,11 +264,13 @@ def restore_data_source_dump(
|
||||
port = str(
|
||||
connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
|
||||
)
|
||||
username = connection_params['username'] or data_source.get(
|
||||
'restore_username', data_source.get('username')
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
)
|
||||
password = connection_params['password'] or data_source.get(
|
||||
'restore_password', data_source.get('password')
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['password']
|
||||
or data_source.get('restore_password', data_source.get('password'))
|
||||
)
|
||||
|
||||
mariadb_restore_command = tuple(
|
||||
|
||||
@@ -4,6 +4,7 @@ import shlex
|
||||
|
||||
import borgmatic.borg.pattern
|
||||
import borgmatic.config.paths
|
||||
import borgmatic.hooks.credential.parse
|
||||
from borgmatic.execute import execute_command, execute_command_with_processes
|
||||
from borgmatic.hooks.data_source import dump
|
||||
|
||||
@@ -98,8 +99,26 @@ def build_dump_command(database, dump_filename, dump_format):
|
||||
+ (('--out', shlex.quote(dump_filename)) if dump_format == 'directory' else ())
|
||||
+ (('--host', shlex.quote(database['hostname'])) if 'hostname' in database else ())
|
||||
+ (('--port', shlex.quote(str(database['port']))) if 'port' in database else ())
|
||||
+ (('--username', shlex.quote(database['username'])) if 'username' in database else ())
|
||||
+ (('--password', shlex.quote(database['password'])) if 'password' in database else ())
|
||||
+ (
|
||||
(
|
||||
'--username',
|
||||
shlex.quote(
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'])
|
||||
),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
+ (
|
||||
(
|
||||
'--password',
|
||||
shlex.quote(
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['password'])
|
||||
),
|
||||
)
|
||||
if 'password' in database
|
||||
else ()
|
||||
)
|
||||
+ (
|
||||
('--authenticationDatabase', shlex.quote(database['authentication_database']))
|
||||
if 'authentication_database' in database
|
||||
@@ -198,11 +217,11 @@ def build_restore_command(extract_process, database, dump_filename, connection_p
|
||||
'restore_hostname', database.get('hostname')
|
||||
)
|
||||
port = str(connection_params['port'] or database.get('restore_port', database.get('port', '')))
|
||||
username = connection_params['username'] or database.get(
|
||||
'restore_username', database.get('username')
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username'] or database.get('restore_username', database.get('username'))
|
||||
)
|
||||
password = connection_params['password'] or database.get(
|
||||
'restore_password', database.get('password')
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['password'] or database.get('restore_password', database.get('password'))
|
||||
)
|
||||
|
||||
command = ['mongorestore']
|
||||
|
||||
@@ -5,6 +5,7 @@ import shlex
|
||||
|
||||
import borgmatic.borg.pattern
|
||||
import borgmatic.config.paths
|
||||
import borgmatic.hooks.credential.parse
|
||||
from borgmatic.execute import (
|
||||
execute_command,
|
||||
execute_command_and_capture_output,
|
||||
@@ -45,7 +46,11 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (('--user', database['username']) if 'username' in database else ())
|
||||
+ (
|
||||
('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
+ ('--skip-column-names', '--batch')
|
||||
+ ('--execute', 'show schemas')
|
||||
)
|
||||
@@ -95,7 +100,11 @@ def execute_dump_command(
|
||||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (('--user', database['username']) if 'username' in database else ())
|
||||
+ (
|
||||
('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
+ ('--databases',)
|
||||
+ database_names
|
||||
+ ('--result-file', dump_filename)
|
||||
@@ -151,7 +160,11 @@ def dump_data_sources(
|
||||
|
||||
for database in databases:
|
||||
dump_path = make_dump_path(borgmatic_runtime_directory)
|
||||
extra_environment = {'MYSQL_PWD': database['password']} if 'password' in database else None
|
||||
extra_environment = (
|
||||
{'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(database['password'])}
|
||||
if 'password' in database
|
||||
else None
|
||||
)
|
||||
dump_database_names = database_names_to_dump(database, extra_environment, dry_run)
|
||||
|
||||
if not dump_database_names:
|
||||
@@ -250,11 +263,13 @@ def restore_data_source_dump(
|
||||
port = str(
|
||||
connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
|
||||
)
|
||||
username = connection_params['username'] or data_source.get(
|
||||
'restore_username', data_source.get('username')
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
)
|
||||
password = connection_params['password'] or data_source.get(
|
||||
'restore_password', data_source.get('password')
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['password']
|
||||
or data_source.get('restore_password', data_source.get('password'))
|
||||
)
|
||||
|
||||
mysql_restore_command = tuple(
|
||||
|
||||
@@ -7,6 +7,7 @@ import shlex
|
||||
|
||||
import borgmatic.borg.pattern
|
||||
import borgmatic.config.paths
|
||||
import borgmatic.hooks.credential.parse
|
||||
from borgmatic.execute import (
|
||||
execute_command,
|
||||
execute_command_and_capture_output,
|
||||
@@ -33,11 +34,14 @@ def make_extra_environment(database, restore_connection_params=None):
|
||||
|
||||
try:
|
||||
if restore_connection_params:
|
||||
extra['PGPASSWORD'] = restore_connection_params.get('password') or database.get(
|
||||
'restore_password', database['password']
|
||||
extra['PGPASSWORD'] = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
restore_connection_params.get('password')
|
||||
or database.get('restore_password', database['password'])
|
||||
)
|
||||
else:
|
||||
extra['PGPASSWORD'] = database['password']
|
||||
extra['PGPASSWORD'] = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password']
|
||||
)
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
|
||||
@@ -82,7 +86,14 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ ('--list', '--no-password', '--no-psqlrc', '--csv', '--tuples-only')
|
||||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--username', database['username']) if 'username' in database else ())
|
||||
+ (
|
||||
(
|
||||
'--username',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username']),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
+ (tuple(database['list_options'].split(' ')) if 'list_options' in database else ())
|
||||
)
|
||||
logger.debug('Querying for "all" PostgreSQL databases to dump')
|
||||
@@ -174,7 +185,14 @@ def dump_data_sources(
|
||||
+ (('--host', shlex.quote(database['hostname'])) if 'hostname' in database else ())
|
||||
+ (('--port', shlex.quote(str(database['port']))) if 'port' in database else ())
|
||||
+ (
|
||||
('--username', shlex.quote(database['username']))
|
||||
(
|
||||
'--username',
|
||||
shlex.quote(
|
||||
borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['username']
|
||||
)
|
||||
),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
@@ -290,8 +308,9 @@ def restore_data_source_dump(
|
||||
port = str(
|
||||
connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
|
||||
)
|
||||
username = connection_params['username'] or data_source.get(
|
||||
'restore_username', data_source.get('username')
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
)
|
||||
|
||||
all_databases = bool(data_source['name'] == 'all')
|
||||
|
||||
@@ -2,6 +2,8 @@ import logging
|
||||
|
||||
import requests
|
||||
|
||||
import borgmatic.hooks.credential.parse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -47,9 +49,20 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
'X-Tags': state_config.get('tags'),
|
||||
}
|
||||
|
||||
username = hook_config.get('username')
|
||||
password = hook_config.get('password')
|
||||
access_token = hook_config.get('access_token')
|
||||
try:
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('username')
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('password')
|
||||
)
|
||||
access_token = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('access_token')
|
||||
)
|
||||
except ValueError as error:
|
||||
logger.warning(f'Ntfy credential error: {error}')
|
||||
return
|
||||
|
||||
auth = None
|
||||
|
||||
if access_token is not None:
|
||||
|
||||
@@ -5,6 +5,7 @@ import platform
|
||||
|
||||
import requests
|
||||
|
||||
import borgmatic.hooks.credential.parse
|
||||
from borgmatic.hooks.monitoring import monitor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -39,11 +40,19 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
try:
|
||||
integration_key = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('integration_key')
|
||||
)
|
||||
except ValueError as error:
|
||||
logger.warning(f'PagerDuty credential error: {error}')
|
||||
return
|
||||
|
||||
hostname = platform.node()
|
||||
local_timestamp = datetime.datetime.now(datetime.timezone.utc).astimezone().isoformat()
|
||||
payload = json.dumps(
|
||||
{
|
||||
'routing_key': hook_config['integration_key'],
|
||||
'routing_key': integration_key,
|
||||
'event_action': 'trigger',
|
||||
'payload': {
|
||||
'summary': f'backup failed on {hostname}',
|
||||
|
||||
@@ -2,6 +2,8 @@ import logging
|
||||
|
||||
import requests
|
||||
|
||||
import borgmatic.hooks.credential.parse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -32,8 +34,12 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
|
||||
state_config = hook_config.get(state.name.lower(), {})
|
||||
|
||||
token = hook_config.get('token')
|
||||
user = hook_config.get('user')
|
||||
try:
|
||||
token = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('token'))
|
||||
user = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('user'))
|
||||
except ValueError as error:
|
||||
logger.warning(f'Pushover credential error: {error}')
|
||||
return
|
||||
|
||||
logger.info(f'Updating Pushover{dry_run_label}')
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import logging
|
||||
|
||||
import requests
|
||||
|
||||
import borgmatic.hooks.credential.parse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -34,10 +36,15 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('username'))
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('password'))
|
||||
api_key = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('api_key'))
|
||||
except ValueError as error:
|
||||
logger.warning(f'Zabbix credential error: {error}')
|
||||
return
|
||||
|
||||
server = hook_config.get('server')
|
||||
username = hook_config.get('username')
|
||||
password = hook_config.get('password')
|
||||
api_key = hook_config.get('api_key')
|
||||
itemid = hook_config.get('itemid')
|
||||
host = hook_config.get('host')
|
||||
key = hook_config.get('key')
|
||||
|
||||
+94
-25
@@ -87,11 +87,16 @@ class Multi_stream_handler(logging.Handler):
|
||||
handler.setLevel(level)
|
||||
|
||||
|
||||
class Console_no_color_formatter(logging.Formatter):
|
||||
def __init__(self, *args, **kwargs): # pragma: no cover
|
||||
super(Console_no_color_formatter, self).__init__(
|
||||
'{prefix}{message}', style='{', defaults={'prefix': ''}, *args, **kwargs
|
||||
)
|
||||
class Log_prefix_formatter(logging.Formatter):
|
||||
def __init__(self, fmt='{prefix}{message}', style='{', *args, **kwargs): # pragma: no cover
|
||||
self.prefix = None
|
||||
|
||||
super(Log_prefix_formatter, self).__init__(fmt=fmt, style=style, *args, **kwargs)
|
||||
|
||||
def format(self, record): # pragma: no cover
|
||||
record.prefix = f'{self.prefix}: ' if self.prefix else ''
|
||||
|
||||
return super(Log_prefix_formatter, self).format(record)
|
||||
|
||||
|
||||
class Color(enum.Enum):
|
||||
@@ -105,8 +110,9 @@ class Color(enum.Enum):
|
||||
|
||||
class Console_color_formatter(logging.Formatter):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.prefix = None
|
||||
super(Console_color_formatter, self).__init__(
|
||||
'{prefix}{message}', style='{', defaults={'prefix': ''}, *args, **kwargs
|
||||
'{prefix}{message}', style='{', *args, **kwargs
|
||||
)
|
||||
|
||||
def format(self, record):
|
||||
@@ -124,6 +130,7 @@ class Console_color_formatter(logging.Formatter):
|
||||
.get(record.levelno)
|
||||
.value
|
||||
)
|
||||
record.prefix = f'{self.prefix}: ' if self.prefix else ''
|
||||
|
||||
return color_text(color, super(Console_color_formatter, self).format(record))
|
||||
|
||||
@@ -188,28 +195,32 @@ def add_custom_log_levels(): # pragma: no cover
|
||||
|
||||
def get_log_prefix():
|
||||
'''
|
||||
Return the current log prefix from the defaults for the formatter on the first logging handler,
|
||||
set by set_log_prefix(). Return None if no such prefix exists.
|
||||
Return the current log prefix set by set_log_prefix(). Return None if no such prefix exists.
|
||||
|
||||
It would be a whole lot easier to use logger.Formatter(defaults=...) instead, but that argument
|
||||
doesn't exist until Python 3.10+.
|
||||
'''
|
||||
try:
|
||||
return next(
|
||||
handler.formatter._style._defaults.get('prefix').rstrip().rstrip(':')
|
||||
formatter = next(
|
||||
handler.formatter
|
||||
for handler in logging.getLogger().handlers
|
||||
if handler.formatter
|
||||
if hasattr(handler.formatter, 'prefix')
|
||||
)
|
||||
except (StopIteration, AttributeError):
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
return formatter.prefix
|
||||
|
||||
|
||||
def set_log_prefix(prefix):
|
||||
'''
|
||||
Given a log prefix as a string, set it into the defaults for the formatters on all logging
|
||||
handlers. Note that this overwrites any existing defaults.
|
||||
Given a log prefix as a string, set it into the each handler's formatter so that it can inject
|
||||
the prefix into each logged record.
|
||||
'''
|
||||
for handler in logging.getLogger().handlers:
|
||||
try:
|
||||
handler.formatter._style._defaults = {'prefix': f'{prefix}: ' if prefix else ''}
|
||||
except AttributeError:
|
||||
pass
|
||||
if handler.formatter and hasattr(handler.formatter, 'prefix'):
|
||||
handler.formatter.prefix = prefix
|
||||
|
||||
|
||||
class Log_prefix:
|
||||
@@ -252,6 +263,67 @@ class Log_prefix:
|
||||
set_log_prefix(self.original_prefix)
|
||||
|
||||
|
||||
class Delayed_logging_handler(logging.handlers.BufferingHandler):
|
||||
'''
|
||||
A logging handler that buffers logs and doesn't flush them until explicitly flushed (after
|
||||
target handlers are actually set). It's useful for holding onto messages logged before logging
|
||||
is configured, ensuring those records eventually make their way to the relevant logging
|
||||
handlers.
|
||||
|
||||
When flushing, don't forward log records to a target handler if the record's log level is below
|
||||
that of the handler. This recreates the standard logging behavior of, say, logging.DEBUG records
|
||||
getting suppressed if a handler's level is only set to logging.INFO.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
super(Delayed_logging_handler, self).__init__(capacity=0)
|
||||
|
||||
self.targets = None
|
||||
|
||||
def shouldFlush(self, record):
|
||||
return self.targets is not None
|
||||
|
||||
def flush(self):
|
||||
self.acquire()
|
||||
|
||||
try:
|
||||
if not self.targets:
|
||||
return
|
||||
|
||||
for record in self.buffer:
|
||||
for target in self.targets:
|
||||
if record.levelno >= target.level:
|
||||
target.handle(record)
|
||||
|
||||
self.buffer.clear()
|
||||
finally:
|
||||
self.release()
|
||||
|
||||
|
||||
def configure_delayed_logging(): # pragma: no cover
|
||||
'''
|
||||
Configure a delayed logging handler to buffer anything that gets logged until we're ready to
|
||||
deal with it.
|
||||
'''
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
handlers=[Delayed_logging_handler()],
|
||||
)
|
||||
|
||||
|
||||
def flush_delayed_logging(target_handlers):
|
||||
'''
|
||||
Flush any previously buffered logs to our "real" logging handlers.
|
||||
'''
|
||||
root_logger = logging.getLogger()
|
||||
|
||||
if root_logger.handlers and isinstance(root_logger.handlers[0], Delayed_logging_handler):
|
||||
delayed_handler = root_logger.handlers[0]
|
||||
delayed_handler.targets = target_handlers
|
||||
delayed_handler.flush()
|
||||
root_logger.removeHandler(delayed_handler)
|
||||
|
||||
|
||||
def configure_logging(
|
||||
console_log_level,
|
||||
syslog_log_level=None,
|
||||
@@ -296,10 +368,9 @@ def configure_logging(
|
||||
if color_enabled:
|
||||
console_handler.setFormatter(Console_color_formatter())
|
||||
else:
|
||||
console_handler.setFormatter(Console_no_color_formatter())
|
||||
console_handler.setFormatter(Log_prefix_formatter())
|
||||
|
||||
console_handler.setLevel(console_log_level)
|
||||
|
||||
handlers = [console_handler]
|
||||
|
||||
if syslog_log_level != logging.DISABLED:
|
||||
@@ -315,10 +386,8 @@ def configure_logging(
|
||||
if syslog_path:
|
||||
syslog_handler = logging.handlers.SysLogHandler(address=syslog_path)
|
||||
syslog_handler.setFormatter(
|
||||
logging.Formatter(
|
||||
Log_prefix_formatter(
|
||||
'borgmatic: {levelname} {prefix}{message}', # noqa: FS003
|
||||
style='{',
|
||||
defaults={'prefix': ''},
|
||||
)
|
||||
)
|
||||
syslog_handler.setLevel(syslog_log_level)
|
||||
@@ -327,15 +396,15 @@ def configure_logging(
|
||||
if log_file and log_file_log_level != logging.DISABLED:
|
||||
file_handler = logging.handlers.WatchedFileHandler(log_file)
|
||||
file_handler.setFormatter(
|
||||
logging.Formatter(
|
||||
Log_prefix_formatter(
|
||||
log_file_format or '[{asctime}] {levelname}: {prefix}{message}', # noqa: FS003
|
||||
style='{',
|
||||
defaults={'prefix': ''},
|
||||
)
|
||||
)
|
||||
file_handler.setLevel(log_file_log_level)
|
||||
handlers.append(file_handler)
|
||||
|
||||
flush_delayed_logging(handlers)
|
||||
|
||||
logging.basicConfig(
|
||||
level=min(handler.level for handler in handlers),
|
||||
handlers=handlers,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="generator" content="{{ eleventy.generator }}">
|
||||
<link rel="icon" href="docs/static/borgmatic.png" type="image/x-icon">
|
||||
<link rel="icon" href="https://torsion.org/borgmatic/docs/static/borgmatic.png" type="image/x-icon">
|
||||
<title>{{ subtitle + ' - ' if subtitle}}{{ title }}</title>
|
||||
{%- set css %}
|
||||
{% include 'index.css' %}
|
||||
|
||||
@@ -50,52 +50,101 @@ once per borgmatic run.
|
||||
|
||||
### Using systemd service credentials
|
||||
|
||||
Borgmatic supports using encrypted [credentials](https://systemd.io/CREDENTIALS/).
|
||||
borgmatic supports using encrypted [systemd
|
||||
credentials](https://systemd.io/CREDENTIALS/). To use this feature, start by
|
||||
saving your password as an encrypted credential to
|
||||
`/etc/credstore.encrypted/borgmatic.pw`, e.g.,
|
||||
|
||||
Save your password as an encrypted credential to `/etc/credstore.encrypted/borgmatic.pw`, e.g.,
|
||||
|
||||
```
|
||||
# systemd-ask-password -n | systemd-creds encrypt - /etc/credstore.encrypted/borgmatic.pw
|
||||
```bash
|
||||
systemd-ask-password -n | systemd-creds encrypt - /etc/credstore.encrypted/borgmatic.pw
|
||||
```
|
||||
|
||||
Then uncomment or use the following in your configuration file:
|
||||
Then use the following in your configuration file:
|
||||
|
||||
```yaml
|
||||
encryption_passcommand: "cat ${CREDENTIALS_DIRECTORY}/borgmatic.pw"
|
||||
encryption_passphrase: "{credential systemd borgmatic.pw}"
|
||||
```
|
||||
|
||||
<span class="minilink minilink-addedin">Prior to version 1.9.10</span> You can
|
||||
accomplish the same thing with this configuration:
|
||||
|
||||
```yaml
|
||||
encryption_passcommand: cat ${CREDENTIALS_DIRECTORY}/borgmatic.pw
|
||||
```
|
||||
|
||||
Note that the name `borgmatic.pw` is hardcoded in the systemd service file.
|
||||
|
||||
To use multiple different passwords, save them as encrypted credentials to `/etc/credstore.encrypted/borgmatic/`, e.g.,
|
||||
The `{credential ...}` syntax works for several different options in a borgmatic
|
||||
configuration file besides just `encryption_passphrase`. For instance, the
|
||||
username, password, and API token options within database and monitoring hooks
|
||||
support `{credential ...}`:
|
||||
|
||||
```yaml
|
||||
postgresql_databases:
|
||||
- name: invoices
|
||||
username: postgres
|
||||
password: "{credential systemd borgmatic_db1}"
|
||||
```
|
||||
# mkdir /etc/credstore.encrypted/borgmatic
|
||||
# systemd-ask-password -n | systemd-creds encrypt --name=borgmatic_backupserver1 - /etc/credstore.encrypted/borgmatic/backupserver1
|
||||
# systemd-ask-password -n | systemd-creds encrypt --name=borgmatic_pw2 - /etc/credstore.encrypted/borgmatic/pw2
|
||||
|
||||
For specifics about which options are supported, see the
|
||||
[configuration
|
||||
reference](https://torsion.org/borgmatic/docs/reference/configuration/).
|
||||
|
||||
To use these credentials, you'll need to modify the borgmatic systemd service
|
||||
file to support loading multiple credentials (assuming you need to load more
|
||||
than one or anything not named `borgmatic.pw`).
|
||||
|
||||
Start by saving each encrypted credentials to
|
||||
`/etc/credstore.encrypted/borgmatic/`. E.g.,
|
||||
|
||||
```bash
|
||||
mkdir /etc/credstore.encrypted/borgmatic
|
||||
systemd-ask-password -n | systemd-creds encrypt --name=borgmatic_backupserver1 - /etc/credstore.encrypted/borgmatic/backupserver1
|
||||
systemd-ask-password -n | systemd-creds encrypt --name=borgmatic_pw2 - /etc/credstore.encrypted/borgmatic/pw2
|
||||
...
|
||||
```
|
||||
|
||||
Ensure that the file names, (e.g. `backupserver1`) match the corresponding part of
|
||||
the `--name` option *after* the underscore (_), and that the part *before*
|
||||
Ensure that the file names, (e.g. `backupserver1`) match the corresponding part
|
||||
of the `--name` option *after* the underscore (_), and that the part *before*
|
||||
the underscore matches the directory name (e.g. `borgmatic`).
|
||||
|
||||
Then, uncomment the appropriate line in the systemd service file:
|
||||
|
||||
```
|
||||
# systemctl edit borgmatic.service
|
||||
systemctl edit borgmatic.service
|
||||
...
|
||||
# Load multiple encrypted credentials.
|
||||
LoadCredentialEncrypted=borgmatic:/etc/credstore.encrypted/borgmatic/
|
||||
```
|
||||
|
||||
Finally, use the following in your configuration file:
|
||||
Finally, use something like the following in your borgmatic configuration file
|
||||
for each option value you'd like to load from systemd:
|
||||
|
||||
```
|
||||
encryption_passcommand: "cat ${CREDENTIALS_DIRECTORY}/borgmatic_backupserver1"
|
||||
```yaml
|
||||
encryption_passphrase: "{credential systemd borgmatic_backupserver1}"
|
||||
```
|
||||
|
||||
Adjust `borgmatic_backupserver1` according to the name given to the credential
|
||||
and the directory set in the service file.
|
||||
<span class="minilink minilink-addedin">Prior to version 1.9.10</span> Use the
|
||||
following instead, but only for the `encryption_passcommand` option and
|
||||
not other options:
|
||||
|
||||
```yaml
|
||||
encryption_passcommand: cat ${CREDENTIALS_DIRECTORY}/borgmatic_backupserver1
|
||||
```
|
||||
|
||||
Adjust `borgmatic_backupserver1` according to the name of the credential and the
|
||||
directory set in the service file.
|
||||
|
||||
Be aware that when using this systemd `{credential ...}` feature, you may no
|
||||
longer be able to run certain borgmatic actions outside of the systemd service,
|
||||
as the credentials are only available from within the context of that service.
|
||||
So for instance, `borgmatic list` necessarily relies on the
|
||||
`encryption_passphrase` in order to access the Borg repository, but `list`
|
||||
shouldn't need to load any credentials for your database or monitoring hooks.
|
||||
|
||||
The one exception is `borgmatic config validate`, which doesn't actually load
|
||||
any credentials and should continue working anywhere.
|
||||
|
||||
|
||||
### Environment variable interpolation
|
||||
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 9.5 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "borgmatic"
|
||||
version = "1.9.9"
|
||||
version = "1.9.10"
|
||||
authors = [
|
||||
{ name="Dan Helfman", email="witten@torsion.org" },
|
||||
]
|
||||
|
||||
@@ -18,8 +18,7 @@ if [ -z "$TEST_CONTAINER" ]; then
|
||||
fi
|
||||
|
||||
apk add --no-cache python3 py3-pip borgbackup postgresql17-client mariadb-client mongodb-tools \
|
||||
py3-mongo py3-regex py3-ruamel.yaml py3-ruamel.yaml.clib py3-tox py3-typed-ast py3-yaml bash \
|
||||
sqlite fish
|
||||
py3-mongo py3-regex py3-ruamel.yaml py3-ruamel.yaml.clib py3-tox py3-yaml bash sqlite fish
|
||||
export COVERAGE_FILE=/tmp/.coverage
|
||||
|
||||
tox --workdir /tmp/.tox --sitepackages
|
||||
|
||||
@@ -30,4 +30,3 @@ regex
|
||||
requests==2.32.2
|
||||
ruamel.yaml>0.15.0
|
||||
toml==0.10.2
|
||||
typed-ast
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def generate_configuration(config_path, repository_path):
|
||||
'''
|
||||
Generate borgmatic configuration into a file at the config path, and update the defaults so as
|
||||
to work for testing, including updating the source directories, injecting the given repository
|
||||
path, and tacking on an encryption passphrase loaded from systemd.
|
||||
'''
|
||||
subprocess.check_call(f'borgmatic config generate --destination {config_path}'.split(' '))
|
||||
config = (
|
||||
open(config_path)
|
||||
.read()
|
||||
.replace('ssh://user@backupserver/./sourcehostname.borg', repository_path)
|
||||
.replace('- path: /mnt/backup', '')
|
||||
.replace('label: local', '')
|
||||
.replace('- /home/user/path with spaces', '')
|
||||
.replace('- /home', f'- {config_path}')
|
||||
.replace('- /etc', '')
|
||||
.replace('- /var/log/syslog*', '')
|
||||
+ '\nencryption_passphrase: "{credential systemd mycredential}"'
|
||||
)
|
||||
config_file = open(config_path, 'w')
|
||||
config_file.write(config)
|
||||
config_file.close()
|
||||
|
||||
|
||||
def test_borgmatic_command():
|
||||
# Create a Borg repository.
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
repository_path = os.path.join(temporary_directory, 'test.borg')
|
||||
extract_path = os.path.join(temporary_directory, 'extract')
|
||||
|
||||
original_working_directory = os.getcwd()
|
||||
os.mkdir(extract_path)
|
||||
os.chdir(extract_path)
|
||||
|
||||
try:
|
||||
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||
generate_configuration(config_path, repository_path)
|
||||
|
||||
credential_path = os.path.join(temporary_directory, 'mycredential')
|
||||
with open(credential_path, 'w') as credential_file:
|
||||
credential_file.write('test')
|
||||
|
||||
subprocess.check_call(
|
||||
f'borgmatic -v 2 --config {config_path} repo-create --encryption repokey'.split(' '),
|
||||
env=dict(os.environ, **{'CREDENTIALS_DIRECTORY': temporary_directory}),
|
||||
)
|
||||
|
||||
# Run borgmatic to generate a backup archive, and then list it to make sure it exists.
|
||||
subprocess.check_call(
|
||||
f'borgmatic --config {config_path}'.split(' '),
|
||||
env=dict(os.environ, **{'CREDENTIALS_DIRECTORY': temporary_directory}),
|
||||
)
|
||||
output = subprocess.check_output(
|
||||
f'borgmatic --config {config_path} list --json'.split(' '),
|
||||
env=dict(os.environ, **{'CREDENTIALS_DIRECTORY': temporary_directory}),
|
||||
).decode(sys.stdout.encoding)
|
||||
parsed_output = json.loads(output)
|
||||
|
||||
assert len(parsed_output) == 1
|
||||
assert len(parsed_output[0]['archives']) == 1
|
||||
finally:
|
||||
os.chdir(original_working_directory)
|
||||
shutil.rmtree(temporary_directory)
|
||||
+201
-74
@@ -8,13 +8,16 @@ from borgmatic.borg import check as module
|
||||
from ..test_verbosity import insert_logging_mock
|
||||
|
||||
|
||||
def insert_execute_command_mock(command, working_directory=None, borg_exit_codes=None):
|
||||
def insert_execute_command_mock(
|
||||
command, output_file=None, working_directory=None, borg_exit_codes=None
|
||||
):
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||
working_directory,
|
||||
)
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
command,
|
||||
output_file=output_file,
|
||||
extra_environment=None,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=command[0],
|
||||
@@ -250,11 +253,11 @@ def test_make_check_name_flags_without_archives_check_and_with_archive_filter_fl
|
||||
assert flags == ('--repository-only',)
|
||||
|
||||
|
||||
def test_make_check_name_flags_with_data_check_returns_flag_and_implies_archives():
|
||||
def test_make_check_name_flags_with_archives_and_data_check_returns_verify_data_flag():
|
||||
flexmock(module.feature).should_receive('available').and_return(True)
|
||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||
|
||||
flags = module.make_check_name_flags({'data'}, ())
|
||||
flags = module.make_check_name_flags({'archives', 'data'}, ())
|
||||
|
||||
assert flags == (
|
||||
'--archives-only',
|
||||
@@ -262,6 +265,15 @@ def test_make_check_name_flags_with_data_check_returns_flag_and_implies_archives
|
||||
)
|
||||
|
||||
|
||||
def test_make_check_name_flags_with_repository_and_data_check_returns_verify_data_flag():
|
||||
flexmock(module.feature).should_receive('available').and_return(True)
|
||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||
|
||||
flags = module.make_check_name_flags({'archives', 'data', 'repository'}, ())
|
||||
|
||||
assert flags == ('--verify-data',)
|
||||
|
||||
|
||||
def test_make_check_name_flags_with_extract_omits_extract_flag():
|
||||
flexmock(module.feature).should_receive('available').and_return(True)
|
||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||
@@ -271,21 +283,6 @@ def test_make_check_name_flags_with_extract_omits_extract_flag():
|
||||
assert flags == ()
|
||||
|
||||
|
||||
def test_make_check_name_flags_with_repository_and_data_checks_does_not_return_repository_only():
|
||||
flexmock(module.feature).should_receive('available').and_return(True)
|
||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||
|
||||
flags = module.make_check_name_flags(
|
||||
{
|
||||
'repository',
|
||||
'data',
|
||||
},
|
||||
(),
|
||||
)
|
||||
|
||||
assert flags == ('--verify-data',)
|
||||
|
||||
|
||||
def test_get_repository_id_with_valid_json_does_not_raise():
|
||||
config = {}
|
||||
flexmock(module.repo_info).should_receive('display_repository_info').and_return(
|
||||
@@ -336,7 +333,9 @@ def test_get_repository_id_with_missing_json_keys_raises():
|
||||
|
||||
def test_check_archives_with_progress_passes_through_to_borg():
|
||||
config = {}
|
||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(())
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
@@ -369,7 +368,9 @@ def test_check_archives_with_progress_passes_through_to_borg():
|
||||
|
||||
def test_check_archives_with_repair_passes_through_to_borg():
|
||||
config = {}
|
||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(())
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
@@ -402,12 +403,15 @@ def test_check_archives_with_repair_passes_through_to_borg():
|
||||
|
||||
def test_check_archives_with_max_duration_flag_passes_through_to_borg():
|
||||
config = {}
|
||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(())
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'check', '--max-duration', '33', 'repo'),
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
@@ -432,37 +436,17 @@ def test_check_archives_with_max_duration_flag_passes_through_to_borg():
|
||||
)
|
||||
|
||||
|
||||
def test_check_archives_with_max_duration_flag_and_archives_check_errors():
|
||||
config = {}
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.check_archives(
|
||||
repository_path='repo',
|
||||
config=config,
|
||||
local_borg_version='1.2.3',
|
||||
check_arguments=flexmock(
|
||||
progress=None,
|
||||
repair=None,
|
||||
only_checks=None,
|
||||
force=None,
|
||||
match_archives=None,
|
||||
max_duration=33,
|
||||
),
|
||||
global_arguments=flexmock(log_json=False),
|
||||
checks={'repository', 'archives'},
|
||||
archive_filter_flags=(),
|
||||
)
|
||||
|
||||
|
||||
def test_check_archives_with_max_duration_option_passes_through_to_borg():
|
||||
config = {'checks': [{'name': 'repository', 'max_duration': 33}]}
|
||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(())
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'check', '--max-duration', '33', 'repo'),
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
@@ -487,37 +471,145 @@ def test_check_archives_with_max_duration_option_passes_through_to_borg():
|
||||
)
|
||||
|
||||
|
||||
def test_check_archives_with_max_duration_option_and_archives_check_errors():
|
||||
config = {'checks': [{'name': 'repository', 'max_duration': 33}]}
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
def test_check_archives_with_max_duration_option_and_archives_check_runs_repository_check_separately():
|
||||
config = {'checks': [{'name': 'repository', 'max_duration': 33}, {'name': 'archives'}]}
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args({'archives'}, ()).and_return(
|
||||
('--archives-only',)
|
||||
)
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(('--repository-only',))
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(('borg', 'check', '--archives-only', 'repo'))
|
||||
insert_execute_command_mock(
|
||||
('borg', 'check', '--max-duration', '33', '--repository-only', 'repo')
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.check_archives(
|
||||
repository_path='repo',
|
||||
config=config,
|
||||
local_borg_version='1.2.3',
|
||||
check_arguments=flexmock(
|
||||
progress=None,
|
||||
repair=None,
|
||||
only_checks=None,
|
||||
force=None,
|
||||
match_archives=None,
|
||||
max_duration=None,
|
||||
),
|
||||
global_arguments=flexmock(log_json=False),
|
||||
checks={'repository', 'archives'},
|
||||
archive_filter_flags=(),
|
||||
)
|
||||
module.check_archives(
|
||||
repository_path='repo',
|
||||
config=config,
|
||||
local_borg_version='1.2.3',
|
||||
check_arguments=flexmock(
|
||||
progress=None,
|
||||
repair=None,
|
||||
only_checks=None,
|
||||
force=None,
|
||||
match_archives=None,
|
||||
max_duration=None,
|
||||
),
|
||||
global_arguments=flexmock(log_json=False),
|
||||
checks={'repository', 'archives'},
|
||||
archive_filter_flags=(),
|
||||
)
|
||||
|
||||
|
||||
def test_check_archives_with_max_duration_flag_and_archives_check_runs_repository_check_separately():
|
||||
config = {'checks': [{'name': 'repository'}, {'name': 'archives'}]}
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args({'archives'}, ()).and_return(
|
||||
('--archives-only',)
|
||||
)
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(('--repository-only',))
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(('borg', 'check', '--archives-only', 'repo'))
|
||||
insert_execute_command_mock(
|
||||
('borg', 'check', '--max-duration', '33', '--repository-only', 'repo')
|
||||
)
|
||||
|
||||
module.check_archives(
|
||||
repository_path='repo',
|
||||
config=config,
|
||||
local_borg_version='1.2.3',
|
||||
check_arguments=flexmock(
|
||||
progress=None,
|
||||
repair=None,
|
||||
only_checks=None,
|
||||
force=None,
|
||||
match_archives=None,
|
||||
max_duration=33,
|
||||
),
|
||||
global_arguments=flexmock(log_json=False),
|
||||
checks={'repository', 'archives'},
|
||||
archive_filter_flags=(),
|
||||
)
|
||||
|
||||
|
||||
def test_check_archives_with_max_duration_option_and_data_check_runs_repository_check_separately():
|
||||
config = {'checks': [{'name': 'repository', 'max_duration': 33}, {'name': 'data'}]}
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'data', 'archives'}, ()
|
||||
).and_return(('--archives-only', '--verify-data'))
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(('--repository-only',))
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(('borg', 'check', '--archives-only', '--verify-data', 'repo'))
|
||||
insert_execute_command_mock(
|
||||
('borg', 'check', '--max-duration', '33', '--repository-only', 'repo')
|
||||
)
|
||||
|
||||
module.check_archives(
|
||||
repository_path='repo',
|
||||
config=config,
|
||||
local_borg_version='1.2.3',
|
||||
check_arguments=flexmock(
|
||||
progress=None,
|
||||
repair=None,
|
||||
only_checks=None,
|
||||
force=None,
|
||||
match_archives=None,
|
||||
max_duration=None,
|
||||
),
|
||||
global_arguments=flexmock(log_json=False),
|
||||
checks={'repository', 'data'},
|
||||
archive_filter_flags=(),
|
||||
)
|
||||
|
||||
|
||||
def test_check_archives_with_max_duration_flag_and_data_check_runs_repository_check_separately():
|
||||
config = {'checks': [{'name': 'repository'}, {'name': 'data'}]}
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'data', 'archives'}, ()
|
||||
).and_return(('--archives-only', '--verify-data'))
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(('--repository-only',))
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(('borg', 'check', '--archives-only', '--verify-data', 'repo'))
|
||||
insert_execute_command_mock(
|
||||
('borg', 'check', '--max-duration', '33', '--repository-only', 'repo')
|
||||
)
|
||||
|
||||
module.check_archives(
|
||||
repository_path='repo',
|
||||
config=config,
|
||||
local_borg_version='1.2.3',
|
||||
check_arguments=flexmock(
|
||||
progress=None,
|
||||
repair=None,
|
||||
only_checks=None,
|
||||
force=None,
|
||||
match_archives=None,
|
||||
max_duration=33,
|
||||
),
|
||||
global_arguments=flexmock(log_json=False),
|
||||
checks={'repository', 'data'},
|
||||
archive_filter_flags=(),
|
||||
)
|
||||
|
||||
|
||||
def test_check_archives_with_max_duration_flag_overrides_max_duration_option():
|
||||
config = {'checks': [{'name': 'repository', 'max_duration': 33}]}
|
||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(())
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'check', '--max-duration', '44', 'repo'),
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
@@ -575,9 +667,37 @@ def test_check_archives_calls_borg_with_parameters(checks):
|
||||
)
|
||||
|
||||
|
||||
def test_check_archives_with_data_check_implies_archives_check_calls_borg_with_parameters():
|
||||
config = {}
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'data', 'archives'}, ()
|
||||
).and_return(())
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(('borg', 'check', 'repo'))
|
||||
|
||||
module.check_archives(
|
||||
repository_path='repo',
|
||||
config=config,
|
||||
local_borg_version='1.2.3',
|
||||
check_arguments=flexmock(
|
||||
progress=None,
|
||||
repair=None,
|
||||
only_checks=None,
|
||||
force=None,
|
||||
match_archives=None,
|
||||
max_duration=None,
|
||||
),
|
||||
global_arguments=flexmock(log_json=False),
|
||||
checks={'data'},
|
||||
archive_filter_flags=(),
|
||||
)
|
||||
|
||||
|
||||
def test_check_archives_with_log_info_passes_through_to_borg():
|
||||
config = {}
|
||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(())
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_logging_mock(logging.INFO)
|
||||
insert_execute_command_mock(('borg', 'check', '--info', 'repo'))
|
||||
@@ -602,7 +722,9 @@ def test_check_archives_with_log_info_passes_through_to_borg():
|
||||
|
||||
def test_check_archives_with_log_debug_passes_through_to_borg():
|
||||
config = {}
|
||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(())
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_logging_mock(logging.DEBUG)
|
||||
insert_execute_command_mock(('borg', 'check', '--debug', '--show-rc', 'repo'))
|
||||
@@ -806,7 +928,9 @@ def test_check_archives_with_retention_prefix():
|
||||
|
||||
def test_check_archives_with_extra_borg_options_passes_through_to_borg():
|
||||
config = {'extra_borg_options': {'check': '--extra --options'}}
|
||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(())
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(('borg', 'check', '--extra', '--options', 'repo'))
|
||||
|
||||
@@ -829,15 +953,16 @@ def test_check_archives_with_extra_borg_options_passes_through_to_borg():
|
||||
|
||||
|
||||
def test_check_archives_with_match_archives_passes_through_to_borg():
|
||||
config = {}
|
||||
flexmock(module).should_receive('make_check_name_flags').and_return(
|
||||
('--match-archives', 'foo-*')
|
||||
)
|
||||
config = {'checks': [{'name': 'archives'}]}
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'archives'}, object
|
||||
).and_return(('--match-archives', 'foo-*'))
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'check', '--match-archives', 'foo-*', 'repo'),
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
@@ -864,7 +989,9 @@ def test_check_archives_with_match_archives_passes_through_to_borg():
|
||||
|
||||
def test_check_archives_calls_borg_with_working_directory():
|
||||
config = {'working_directory': '/working/dir'}
|
||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(())
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
|
||||
@@ -185,6 +185,7 @@ def test_any_parent_directories_treats_unrelated_paths_as_non_match():
|
||||
|
||||
|
||||
def test_collect_special_file_paths_parses_special_files_from_borg_dry_run_file_list():
|
||||
flexmock(module.environment).should_receive('make_environment').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').and_return(
|
||||
'Processing files ...\n- /foo\n+ /bar\n- /baz'
|
||||
)
|
||||
@@ -198,12 +199,12 @@ def test_collect_special_file_paths_parses_special_files_from_borg_dry_run_file_
|
||||
config={},
|
||||
local_path=None,
|
||||
working_directory=None,
|
||||
borg_environment=None,
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
) == ('/foo', '/bar', '/baz')
|
||||
|
||||
|
||||
def test_collect_special_file_paths_skips_borgmatic_runtime_directory():
|
||||
flexmock(module.environment).should_receive('make_environment').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').and_return(
|
||||
'+ /foo\n- /run/borgmatic/bar\n- /baz'
|
||||
)
|
||||
@@ -225,12 +226,12 @@ def test_collect_special_file_paths_skips_borgmatic_runtime_directory():
|
||||
config={},
|
||||
local_path=None,
|
||||
working_directory=None,
|
||||
borg_environment=None,
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
) == ('/foo', '/baz')
|
||||
|
||||
|
||||
def test_collect_special_file_paths_with_borgmatic_runtime_directory_missing_from_paths_output_errors():
|
||||
flexmock(module.environment).should_receive('make_environment').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').and_return(
|
||||
'+ /foo\n- /bar\n- /baz'
|
||||
)
|
||||
@@ -245,12 +246,12 @@ def test_collect_special_file_paths_with_borgmatic_runtime_directory_missing_fro
|
||||
config={},
|
||||
local_path=None,
|
||||
working_directory=None,
|
||||
borg_environment=None,
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
)
|
||||
|
||||
|
||||
def test_collect_special_file_paths_with_dry_run_and_borgmatic_runtime_directory_missing_from_paths_output_does_not_raise():
|
||||
flexmock(module.environment).should_receive('make_environment').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').and_return(
|
||||
'+ /foo\n- /bar\n- /baz'
|
||||
)
|
||||
@@ -264,12 +265,12 @@ def test_collect_special_file_paths_with_dry_run_and_borgmatic_runtime_directory
|
||||
config={},
|
||||
local_path=None,
|
||||
working_directory=None,
|
||||
borg_environment=None,
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
) == ('/foo', '/bar', '/baz')
|
||||
|
||||
|
||||
def test_collect_special_file_paths_excludes_non_special_files():
|
||||
flexmock(module.environment).should_receive('make_environment').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').and_return(
|
||||
'+ /foo\n+ /bar\n+ /baz'
|
||||
)
|
||||
@@ -285,12 +286,12 @@ def test_collect_special_file_paths_excludes_non_special_files():
|
||||
config={},
|
||||
local_path=None,
|
||||
working_directory=None,
|
||||
borg_environment=None,
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
) == ('/foo', '/baz')
|
||||
|
||||
|
||||
def test_collect_special_file_paths_omits_exclude_no_dump_flag_from_command():
|
||||
flexmock(module.environment).should_receive('make_environment').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'create', '--dry-run', '--list'),
|
||||
capture_stderr=True,
|
||||
@@ -309,7 +310,6 @@ def test_collect_special_file_paths_omits_exclude_no_dump_flag_from_command():
|
||||
config={},
|
||||
local_path='borg',
|
||||
working_directory=None,
|
||||
borg_environment=None,
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
)
|
||||
|
||||
|
||||
@@ -24,11 +24,30 @@ def test_make_environment_with_passphrase_should_set_environment():
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
|
||||
environment = module.make_environment({'encryption_passphrase': 'pass'})
|
||||
|
||||
assert environment.get('BORG_PASSPHRASE') == 'pass'
|
||||
|
||||
|
||||
def test_make_environment_with_credential_tag_passphrase_should_load_it_and_set_environment():
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).with_args('{credential systemd pass}').and_return('pass')
|
||||
|
||||
environment = module.make_environment({'encryption_passphrase': '{credential systemd pass}'})
|
||||
|
||||
assert environment.get('BORG_PASSPHRASE') == 'pass'
|
||||
|
||||
|
||||
def test_make_environment_with_ssh_command_should_set_environment():
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
|
||||
@@ -4,6 +4,7 @@ from borgmatic.borg import passcommand as module
|
||||
|
||||
|
||||
def test_run_passcommand_with_passphrase_configured_bails():
|
||||
module.run_passcommand.cache_clear()
|
||||
flexmock(module.borgmatic.execute).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
assert (
|
||||
@@ -13,6 +14,7 @@ def test_run_passcommand_with_passphrase_configured_bails():
|
||||
|
||||
|
||||
def test_run_passcommand_without_passphrase_configured_executes_passcommand():
|
||||
module.run_passcommand.cache_clear()
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).and_return('passphrase').once()
|
||||
@@ -24,6 +26,7 @@ def test_run_passcommand_without_passphrase_configured_executes_passcommand():
|
||||
|
||||
|
||||
def test_get_passphrase_from_passcommand_with_configured_passcommand_runs_it():
|
||||
module.run_passcommand.cache_clear()
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||
'/working'
|
||||
)
|
||||
@@ -40,6 +43,7 @@ def test_get_passphrase_from_passcommand_with_configured_passcommand_runs_it():
|
||||
|
||||
|
||||
def test_get_passphrase_from_passcommand_with_configured_passphrase_and_passcommand_detects_passphrase():
|
||||
module.run_passcommand.cache_clear()
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||
'/working'
|
||||
)
|
||||
@@ -53,3 +57,36 @@ def test_get_passphrase_from_passcommand_with_configured_passphrase_and_passcomm
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_get_passphrase_from_passcommand_with_configured_blank_passphrase_and_passcommand_detects_passphrase():
|
||||
module.run_passcommand.cache_clear()
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||
'/working'
|
||||
)
|
||||
flexmock(module).should_receive('run_passcommand').with_args(
|
||||
'command', True, '/working'
|
||||
).and_return(None).once()
|
||||
|
||||
assert (
|
||||
module.get_passphrase_from_passcommand(
|
||||
{'encryption_passphrase': '', 'encryption_passcommand': 'command'},
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_run_passcommand_caches_passcommand_after_first_call():
|
||||
module.run_passcommand.cache_clear()
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).and_return('passphrase').once()
|
||||
|
||||
assert (
|
||||
module.run_passcommand('passcommand', passphrase_configured=False, working_directory=None)
|
||||
== 'passphrase'
|
||||
)
|
||||
assert (
|
||||
module.run_passcommand('passcommand', passphrase_configured=False, working_directory=None)
|
||||
== 'passphrase'
|
||||
)
|
||||
|
||||
@@ -1114,7 +1114,11 @@ def test_run_actions_runs_multiple_actions_in_argument_order():
|
||||
)
|
||||
|
||||
|
||||
def test_load_configurations_collects_parsed_configurations_and_logs():
|
||||
@pytest.mark.parametrize(
|
||||
'resolve_env',
|
||||
((True, False),),
|
||||
)
|
||||
def test_load_configurations_collects_parsed_configurations_and_logs(resolve_env):
|
||||
configuration = flexmock()
|
||||
other_configuration = flexmock()
|
||||
test_expected_logs = [flexmock(), flexmock()]
|
||||
@@ -1123,7 +1127,12 @@ def test_load_configurations_collects_parsed_configurations_and_logs():
|
||||
configuration, ['/tmp/test.yaml'], test_expected_logs
|
||||
).and_return(other_configuration, ['/tmp/other.yaml'], other_expected_logs)
|
||||
|
||||
configs, config_paths, logs = tuple(module.load_configurations(('test.yaml', 'other.yaml')))
|
||||
configs, config_paths, logs = tuple(
|
||||
module.load_configurations(
|
||||
('test.yaml', 'other.yaml'),
|
||||
resolve_env=resolve_env,
|
||||
)
|
||||
)
|
||||
|
||||
assert configs == {'test.yaml': configuration, 'other.yaml': other_configuration}
|
||||
assert config_paths == ['/tmp/other.yaml', '/tmp/test.yaml']
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.hooks.credential import parse as module
|
||||
|
||||
|
||||
def test_resolve_credential_passes_through_string_without_credential_tag():
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
|
||||
|
||||
assert module.resolve_credential('{no credentials here}') == '{no credentials here}'
|
||||
|
||||
|
||||
def test_resolve_credential_passes_through_none():
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
|
||||
|
||||
assert module.resolve_credential(None) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('invalid_value', ('{credential}', '{credential }', '{credential systemd}'))
|
||||
def test_resolve_credential_with_invalid_credential_tag_raises(invalid_value):
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.resolve_credential(invalid_value)
|
||||
|
||||
|
||||
def test_resolve_credential_with_valid_credential_tag_loads_credential():
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
|
||||
'load_credential',
|
||||
{},
|
||||
'systemd',
|
||||
'mycredential',
|
||||
).and_return('result').once()
|
||||
|
||||
assert module.resolve_credential('{credential systemd mycredential}') == 'result'
|
||||
|
||||
|
||||
def test_resolve_credential_caches_credential_after_first_call():
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
|
||||
'load_credential',
|
||||
{},
|
||||
'systemd',
|
||||
'mycredential',
|
||||
).and_return('result').once()
|
||||
|
||||
assert module.resolve_credential('{credential systemd mycredential}') == 'result'
|
||||
assert module.resolve_credential('{credential systemd mycredential}') == 'result'
|
||||
@@ -0,0 +1,51 @@
|
||||
import io
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.hooks.credential import systemd as module
|
||||
|
||||
|
||||
def test_load_credential_without_credentials_directory_raises():
|
||||
flexmock(module.os.environ).should_receive('get').with_args('CREDENTIALS_DIRECTORY').and_return(
|
||||
None
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(hook_config={}, config={}, credential_name='mycredential')
|
||||
|
||||
|
||||
def test_load_credential_with_invalid_credential_name_raises():
|
||||
flexmock(module.os.environ).should_receive('get').with_args('CREDENTIALS_DIRECTORY').and_return(
|
||||
'/var'
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(hook_config={}, config={}, credential_name='../../my!@#$credential')
|
||||
|
||||
|
||||
def test_load_credential_reads_named_credential_from_file():
|
||||
flexmock(module.os.environ).should_receive('get').with_args('CREDENTIALS_DIRECTORY').and_return(
|
||||
'/var'
|
||||
)
|
||||
credential_stream = io.StringIO('password')
|
||||
credential_stream.name = '/var/mycredential'
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/var/mycredential').and_return(credential_stream)
|
||||
|
||||
assert (
|
||||
module.load_credential(hook_config={}, config={}, credential_name='mycredential')
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_file_not_found_error_raises():
|
||||
flexmock(module.os.environ).should_receive('get').with_args('CREDENTIALS_DIRECTORY').and_return(
|
||||
'/var'
|
||||
)
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/var/mycredential').and_raise(FileNotFoundError)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(hook_config={}, config={}, credential_name='mycredential')
|
||||
@@ -25,6 +25,9 @@ def test_database_names_to_dump_bails_for_dry_run():
|
||||
|
||||
def test_database_names_to_dump_queries_mariadb_for_database_names():
|
||||
extra_environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('mariadb', '--skip-column-names', '--batch', '--execute', 'show schemas'),
|
||||
extra_environment=extra_environment,
|
||||
@@ -50,6 +53,9 @@ def test_dump_data_sources_dumps_each_database():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
processes = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
@@ -81,6 +87,9 @@ def test_dump_data_sources_dumps_with_password():
|
||||
database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
@@ -108,6 +117,9 @@ def test_dump_data_sources_dumps_all_databases_at_once():
|
||||
databases = [{'name': 'all'}]
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': 'all'},
|
||||
@@ -132,6 +144,9 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
|
||||
databases = [{'name': 'all', 'format': 'sql'}]
|
||||
processes = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
|
||||
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
@@ -199,6 +214,9 @@ def test_execute_dump_command_runs_mariadb_dump():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -231,6 +249,9 @@ def test_execute_dump_command_runs_mariadb_dump_without_add_drop_database():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -262,6 +283,9 @@ def test_execute_dump_command_runs_mariadb_dump_with_hostname_and_port():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -300,6 +324,9 @@ def test_execute_dump_command_runs_mariadb_dump_with_username_and_password():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -334,6 +361,9 @@ def test_execute_dump_command_runs_mariadb_dump_with_options():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -367,6 +397,9 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -422,6 +455,9 @@ def test_execute_dump_command_with_duplicate_dump_skips_mariadb_dump():
|
||||
def test_execute_dump_command_with_dry_run_skips_mariadb_dump():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
@@ -442,6 +478,9 @@ def test_execute_dump_command_with_dry_run_skips_mariadb_dump():
|
||||
def test_dump_data_sources_errors_for_missing_all_databases():
|
||||
databases = [{'name': 'all'}]
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@@ -461,6 +500,9 @@ def test_dump_data_sources_errors_for_missing_all_databases():
|
||||
def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
|
||||
databases = [{'name': 'all'}]
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@@ -483,6 +525,9 @@ def test_restore_data_source_dump_runs_mariadb_to_restore():
|
||||
hook_config = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch'),
|
||||
processes=[extract_process],
|
||||
@@ -511,6 +556,9 @@ def test_restore_data_source_dump_runs_mariadb_with_options():
|
||||
hook_config = [{'name': 'foo', 'restore_options': '--harder'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
@@ -541,6 +589,9 @@ def test_restore_data_source_dump_runs_non_default_mariadb_with_options():
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('custom_mariadb', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
@@ -569,6 +620,9 @@ def test_restore_data_source_dump_runs_mariadb_with_hostname_and_port():
|
||||
hook_config = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
@@ -606,6 +660,9 @@ def test_restore_data_source_dump_runs_mariadb_with_username_and_password():
|
||||
hook_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch', '--user', 'root'),
|
||||
processes=[extract_process],
|
||||
@@ -644,6 +701,9 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
@@ -695,6 +755,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
@@ -733,6 +796,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
hook_config = [{'name': 'foo'}]
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
|
||||
@@ -124,6 +124,9 @@ def test_dump_data_sources_runs_mongodump_with_username_and_password():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -242,6 +245,9 @@ def test_dump_data_sources_runs_mongodumpall_for_all_databases():
|
||||
|
||||
def test_build_dump_command_with_username_injection_attack_gets_escaped():
|
||||
database = {'name': 'test', 'username': 'bob; naughty-command'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
|
||||
command = module.build_dump_command(database, dump_filename='test', dump_format='archive')
|
||||
|
||||
@@ -254,6 +260,9 @@ def test_restore_data_source_dump_runs_mongorestore():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--archive', '--drop'],
|
||||
processes=[extract_process],
|
||||
@@ -285,6 +294,9 @@ def test_restore_data_source_dump_runs_mongorestore_with_hostname_and_port():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -330,6 +342,9 @@ def test_restore_data_source_dump_runs_mongorestore_with_username_and_password()
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -381,6 +396,9 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -436,6 +454,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -479,6 +500,9 @@ def test_restore_data_source_dump_runs_mongorestore_with_options():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--archive', '--drop', '--harder'],
|
||||
processes=[extract_process],
|
||||
@@ -508,6 +532,9 @@ def test_restore_databases_dump_runs_mongorestore_with_schemas():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -545,6 +572,9 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--archive'],
|
||||
processes=[extract_process],
|
||||
@@ -573,6 +603,9 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -596,6 +629,9 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--dir', '/dump/path', '--drop'],
|
||||
processes=[],
|
||||
|
||||
@@ -16,6 +16,9 @@ def test_database_names_to_dump_passes_through_name():
|
||||
|
||||
def test_database_names_to_dump_bails_for_dry_run():
|
||||
extra_environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=True)
|
||||
@@ -25,6 +28,9 @@ def test_database_names_to_dump_bails_for_dry_run():
|
||||
|
||||
def test_database_names_to_dump_queries_mysql_for_database_names():
|
||||
extra_environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('mysql', '--skip-column-names', '--batch', '--execute', 'show schemas'),
|
||||
extra_environment=extra_environment,
|
||||
@@ -81,6 +87,9 @@ def test_dump_data_sources_dumps_with_password():
|
||||
database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
@@ -199,6 +208,9 @@ def test_execute_dump_command_runs_mysqldump():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -231,6 +243,9 @@ def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -262,6 +277,9 @@ def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -300,6 +318,9 @@ def test_execute_dump_command_runs_mysqldump_with_username_and_password():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -334,6 +355,9 @@ def test_execute_dump_command_runs_mysqldump_with_options():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -367,6 +391,9 @@ def test_execute_dump_command_runs_non_default_mysqldump():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -420,6 +447,9 @@ def test_execute_dump_command_with_duplicate_dump_skips_mysqldump():
|
||||
def test_execute_dump_command_with_dry_run_skips_mysqldump():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
@@ -440,6 +470,9 @@ def test_execute_dump_command_with_dry_run_skips_mysqldump():
|
||||
def test_dump_data_sources_errors_for_missing_all_databases():
|
||||
databases = [{'name': 'all'}]
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@@ -459,6 +492,9 @@ def test_dump_data_sources_errors_for_missing_all_databases():
|
||||
def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
|
||||
databases = [{'name': 'all'}]
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@@ -481,6 +517,9 @@ def test_restore_data_source_dump_runs_mysql_to_restore():
|
||||
hook_config = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch'),
|
||||
processes=[extract_process],
|
||||
@@ -509,6 +548,9 @@ def test_restore_data_source_dump_runs_mysql_with_options():
|
||||
hook_config = [{'name': 'foo', 'restore_options': '--harder'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
@@ -537,6 +579,9 @@ def test_restore_data_source_dump_runs_non_default_mysql_with_options():
|
||||
hook_config = [{'name': 'foo', 'mysql_command': 'custom_mysql', 'restore_options': '--harder'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('custom_mysql', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
@@ -565,6 +610,9 @@ def test_restore_data_source_dump_runs_mysql_with_hostname_and_port():
|
||||
hook_config = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
@@ -602,6 +650,9 @@ def test_restore_data_source_dump_runs_mysql_with_username_and_password():
|
||||
hook_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch', '--user', 'root'),
|
||||
processes=[extract_process],
|
||||
@@ -640,6 +691,9 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
@@ -691,6 +745,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
@@ -729,6 +786,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
hook_config = [{'name': 'foo'}]
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
|
||||
@@ -24,6 +24,9 @@ def test_make_extra_environment_maps_options_to_environment():
|
||||
'PGSSLROOTCERT': 'root.crt',
|
||||
'PGSSLCRL': 'crl.crl',
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
|
||||
extra_env = module.make_extra_environment(database)
|
||||
|
||||
@@ -32,6 +35,9 @@ def test_make_extra_environment_maps_options_to_environment():
|
||||
|
||||
def test_make_extra_environment_with_cli_password_sets_correct_password():
|
||||
database = {'name': 'foo', 'restore_password': 'trustsome1', 'password': 'anotherpassword'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
|
||||
extra = module.make_extra_environment(
|
||||
database, restore_connection_params={'password': 'clipassword'}
|
||||
@@ -78,6 +84,9 @@ def test_database_names_to_dump_passes_through_all_without_format():
|
||||
|
||||
def test_database_names_to_dump_with_all_and_format_and_dry_run_bails():
|
||||
database = {'name': 'all', 'format': 'custom'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=True) == ()
|
||||
@@ -85,6 +94,9 @@ def test_database_names_to_dump_with_all_and_format_and_dry_run_bails():
|
||||
|
||||
def test_database_names_to_dump_with_all_and_format_lists_databases():
|
||||
database = {'name': 'all', 'format': 'custom'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').and_return(
|
||||
'foo,test,\nbar,test,"stuff and such"'
|
||||
)
|
||||
@@ -97,6 +109,9 @@ def test_database_names_to_dump_with_all_and_format_lists_databases():
|
||||
|
||||
def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostname_and_port():
|
||||
database = {'name': 'all', 'format': 'custom', 'hostname': 'localhost', 'port': 1234}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'psql',
|
||||
@@ -121,6 +136,9 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostnam
|
||||
|
||||
def test_database_names_to_dump_with_all_and_format_lists_databases_with_username():
|
||||
database = {'name': 'all', 'format': 'custom', 'username': 'postgres'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'psql',
|
||||
@@ -143,6 +161,9 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_usernam
|
||||
|
||||
def test_database_names_to_dump_with_all_and_format_lists_databases_with_options():
|
||||
database = {'name': 'all', 'format': 'custom', 'list_options': '--harder'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('psql', '--list', '--no-password', '--no-psqlrc', '--csv', '--tuples-only', '--harder'),
|
||||
extra_environment=object,
|
||||
@@ -156,6 +177,9 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_options
|
||||
|
||||
def test_database_names_to_dump_with_all_and_format_excludes_particular_databases():
|
||||
database = {'name': 'all', 'format': 'custom'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').and_return(
|
||||
'foo,test,\ntemplate0,test,blah'
|
||||
)
|
||||
@@ -169,6 +193,9 @@ def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
|
||||
'format': 'custom',
|
||||
'psql_command': 'docker exec --workdir * mycontainer psql',
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'docker',
|
||||
@@ -219,6 +246,9 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
|
||||
'databases/localhost/foo'
|
||||
).and_return('databases/localhost/bar')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
@@ -323,6 +353,9 @@ def test_dump_data_sources_with_dry_run_skips_pg_dump():
|
||||
'databases/localhost/foo'
|
||||
).and_return('databases/localhost/bar')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
|
||||
@@ -349,6 +382,9 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
|
||||
'databases/database.example.org/foo'
|
||||
)
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -394,6 +430,9 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password():
|
||||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -437,6 +476,9 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped():
|
||||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -477,6 +519,9 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
|
||||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_parent_directory_for_dump')
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
|
||||
@@ -519,6 +564,9 @@ def test_dump_data_sources_runs_pg_dump_with_options():
|
||||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -559,6 +607,9 @@ def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
|
||||
'databases/localhost/all'
|
||||
)
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -588,6 +639,9 @@ def test_dump_data_sources_runs_non_default_pg_dump():
|
||||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -623,6 +677,9 @@ def test_restore_data_source_dump_runs_pg_restore():
|
||||
hook_config = [{'name': 'foo', 'schemas': None}, {'name': 'bar'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@@ -677,6 +734,9 @@ def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@@ -739,6 +799,9 @@ def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
{'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
@@ -810,6 +873,9 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
{'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
@@ -889,6 +955,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
{'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
@@ -962,6 +1031,9 @@ def test_restore_data_source_dump_runs_pg_restore_with_options():
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@@ -1016,6 +1088,9 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
|
||||
hook_config = [{'name': 'all', 'schemas': None}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@@ -1055,6 +1130,9 @@ def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
|
||||
hook_config = [{'name': 'foo', 'format': 'plain', 'schemas': None}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@@ -1106,6 +1184,9 @@ def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@@ -1167,6 +1248,9 @@ def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
|
||||
def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
hook_config = [{'name': 'foo', 'schemas': None}]
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@@ -1191,6 +1275,9 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
def test_restore_data_source_dump_without_extract_process_restores_from_disk():
|
||||
hook_config = [{'name': 'foo', 'schemas': None}]
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
|
||||
@@ -1243,6 +1330,9 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
|
||||
def test_restore_data_source_dump_with_schemas_restores_schemas():
|
||||
hook_config = [{'name': 'foo', 'schemas': ['bar', 'baz']}]
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
|
||||
|
||||
@@ -36,6 +36,9 @@ def return_default_message_headers(state=Enum):
|
||||
|
||||
def test_ping_monitor_minimal_config_hits_hosted_ntfy_on_fail():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@@ -57,6 +60,9 @@ def test_ping_monitor_with_access_token_hits_hosted_ntfy_on_fail():
|
||||
'topic': topic,
|
||||
'access_token': 'abc123',
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@@ -80,6 +86,9 @@ def test_ping_monitor_with_username_password_and_access_token_ignores_username_p
|
||||
'password': 'fakepassword',
|
||||
'access_token': 'abc123',
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@@ -103,6 +112,9 @@ def test_ping_monitor_with_username_password_hits_hosted_ntfy_on_fail():
|
||||
'username': 'testuser',
|
||||
'password': 'fakepassword',
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@@ -121,6 +133,9 @@ def test_ping_monitor_with_username_password_hits_hosted_ntfy_on_fail():
|
||||
|
||||
def test_ping_monitor_with_password_but_no_username_warns():
|
||||
hook_config = {'topic': topic, 'password': 'fakepassword'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@@ -140,6 +155,9 @@ def test_ping_monitor_with_password_but_no_username_warns():
|
||||
|
||||
def test_ping_monitor_with_username_but_no_password_warns():
|
||||
hook_config = {'topic': topic, 'username': 'testuser'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@@ -159,6 +177,9 @@ def test_ping_monitor_with_username_but_no_password_warns():
|
||||
|
||||
def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_start():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -173,6 +194,9 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_start():
|
||||
|
||||
def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_finish():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -187,6 +211,9 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_finish():
|
||||
|
||||
def test_ping_monitor_minimal_config_hits_selfhosted_ntfy_on_fail():
|
||||
hook_config = {'topic': topic, 'server': custom_base_url}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{custom_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@@ -205,6 +232,9 @@ def test_ping_monitor_minimal_config_hits_selfhosted_ntfy_on_fail():
|
||||
|
||||
def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_fail_dry_run():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -219,6 +249,9 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_fail_dry_run():
|
||||
|
||||
def test_ping_monitor_custom_message_hits_hosted_ntfy_on_fail():
|
||||
hook_config = {'topic': topic, 'fail': custom_message_config}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}', headers=custom_message_headers, auth=None
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
@@ -235,6 +268,9 @@ def test_ping_monitor_custom_message_hits_hosted_ntfy_on_fail():
|
||||
|
||||
def test_ping_monitor_custom_state_hits_hosted_ntfy_on_start():
|
||||
hook_config = {'topic': topic, 'states': ['start', 'fail']}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.START),
|
||||
@@ -253,6 +289,9 @@ def test_ping_monitor_custom_state_hits_hosted_ntfy_on_start():
|
||||
|
||||
def test_ping_monitor_with_connection_error_logs_warning():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@@ -270,8 +309,29 @@ def test_ping_monitor_with_connection_error_logs_warning():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_with_credential_error_logs_warning():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_raise(ValueError)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
{},
|
||||
'config.yaml',
|
||||
borgmatic.hooks.monitoring.monitor.State.FAIL,
|
||||
monitoring_log_level=1,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_with_other_error_logs_warning():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
response = flexmock(ok=False)
|
||||
response.should_receive('raise_for_status').and_raise(
|
||||
module.requests.exceptions.RequestException
|
||||
|
||||
@@ -4,6 +4,9 @@ from borgmatic.hooks.monitoring import pagerduty as module
|
||||
|
||||
|
||||
def test_ping_monitor_ignores_start_state():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -17,6 +20,9 @@ def test_ping_monitor_ignores_start_state():
|
||||
|
||||
|
||||
def test_ping_monitor_ignores_finish_state():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -30,6 +36,9 @@ def test_ping_monitor_ignores_finish_state():
|
||||
|
||||
|
||||
def test_ping_monitor_calls_api_for_fail_state():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True))
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -43,6 +52,9 @@ def test_ping_monitor_calls_api_for_fail_state():
|
||||
|
||||
|
||||
def test_ping_monitor_dry_run_does_not_call_api():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -56,6 +68,9 @@ def test_ping_monitor_dry_run_does_not_call_api():
|
||||
|
||||
|
||||
def test_ping_monitor_with_connection_error_logs_warning():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').and_raise(
|
||||
module.requests.exceptions.ConnectionError
|
||||
)
|
||||
@@ -71,8 +86,28 @@ def test_ping_monitor_with_connection_error_logs_warning():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_with_credential_error_logs_warning():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_raise(ValueError)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module.logger).should_receive('warning')
|
||||
|
||||
module.ping_monitor(
|
||||
{'integration_key': 'abc123'},
|
||||
{},
|
||||
'config.yaml',
|
||||
module.monitor.State.FAIL,
|
||||
monitoring_log_level=1,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_with_other_error_logs_warning():
|
||||
response = flexmock(ok=False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
response.should_receive('raise_for_status').and_raise(
|
||||
module.requests.exceptions.RequestException
|
||||
)
|
||||
|
||||
@@ -11,6 +11,9 @@ def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_
|
||||
should be auto populated with the default value which is the state name.
|
||||
'''
|
||||
hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@@ -38,6 +41,9 @@ def test_ping_monitor_config_with_minimum_config_start_state_backup_not_send_to_
|
||||
'start' state. Only the 'fail' state is enabled by default.
|
||||
'''
|
||||
hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -63,6 +69,9 @@ def test_ping_monitor_start_state_backup_default_message_successfully_send_to_pu
|
||||
'user': '983hfe0of902lkjfa2amanfgui',
|
||||
'states': {'start', 'fail', 'finish'},
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@@ -96,6 +105,9 @@ def test_ping_monitor_start_state_backup_custom_message_successfully_send_to_pus
|
||||
'states': {'start', 'fail', 'finish'},
|
||||
'start': {'message': 'custom start message'},
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@@ -128,6 +140,9 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
|
||||
'states': {'start', 'fail', 'finish'},
|
||||
'start': {'priority': 2},
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@@ -163,6 +178,9 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
|
||||
'states': {'start', 'fail', 'finish'},
|
||||
'start': {'priority': 2, 'expire': 600},
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@@ -198,6 +216,9 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
|
||||
'states': {'start', 'fail', 'finish'},
|
||||
'start': {'priority': 2, 'retry': 30},
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@@ -236,6 +257,9 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_high_decl
|
||||
'start': {'priority': 1, 'expire': 30, 'retry': 30},
|
||||
}
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -288,6 +312,9 @@ def test_ping_monitor_start_state_backup_based_on_documentation_advanced_example
|
||||
'url_title': 'Login to ticketing system',
|
||||
},
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@@ -351,6 +378,9 @@ def test_ping_monitor_fail_state_backup_based_on_documentation_advanced_example_
|
||||
'url_title': 'Login to ticketing system',
|
||||
},
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@@ -419,6 +449,9 @@ def test_ping_monitor_finish_state_backup_based_on_documentation_advanced_exampl
|
||||
'url_title': 'Login to ticketing system',
|
||||
},
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@@ -446,12 +479,15 @@ def test_ping_monitor_finish_state_backup_based_on_documentation_advanced_exampl
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_send_to_pushover_dryrun():
|
||||
def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_send_to_pushover_dry_run():
|
||||
'''
|
||||
This test should be the minimum working configuration. The "message"
|
||||
should be auto populated with the default value which is the state name.
|
||||
'''
|
||||
hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True)).never()
|
||||
|
||||
@@ -473,6 +509,9 @@ def test_ping_monitor_config_incorrect_state_exit_early():
|
||||
'token': 'ksdjfwoweijfvwoeifvjmwghagy92',
|
||||
'user': '983hfe0of902lkjfa2amanfgui',
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True)).never()
|
||||
|
||||
@@ -486,7 +525,7 @@ def test_ping_monitor_config_incorrect_state_exit_early():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_push_post_error_exits_early():
|
||||
def test_ping_monitor_push_post_error_bails():
|
||||
'''
|
||||
This test simulates the Pushover servers not responding with a 200 OK. We
|
||||
should raise for status and warn then exit.
|
||||
@@ -496,6 +535,9 @@ def test_ping_monitor_push_post_error_exits_early():
|
||||
'user': '983hfe0of902lkjfa2amanfgui',
|
||||
}
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
push_response = flexmock(ok=False)
|
||||
push_response.should_receive('raise_for_status').and_raise(
|
||||
module.requests.ConnectionError
|
||||
@@ -520,3 +562,25 @@ def test_ping_monitor_push_post_error_exits_early():
|
||||
monitoring_log_level=1,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_credential_error_bails():
|
||||
hook_config = hook_config = {
|
||||
'token': 'ksdjfwoweijfvwoeifvjmwghagy92',
|
||||
'user': '983hfe0of902lkjfa2amanfgui',
|
||||
}
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_raise(ValueError)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
{},
|
||||
'config.yaml',
|
||||
borgmatic.hooks.monitoring.monitor.State.FAIL,
|
||||
monitoring_log_level=1,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
@@ -57,7 +57,7 @@ AUTH_HEADERS_API_KEY = {
|
||||
AUTH_HEADERS_USERNAME_PASSWORD = {'Content-Type': 'application/json-rpc'}
|
||||
|
||||
|
||||
def test_ping_monitor_with_non_matching_state_exits_early():
|
||||
def test_ping_monitor_with_non_matching_state_bails():
|
||||
hook_config = {'api_key': API_KEY}
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -71,10 +71,13 @@ def test_ping_monitor_with_non_matching_state_exits_early():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_with_api_key_only_exit_early():
|
||||
def test_ping_monitor_config_with_api_key_only_bails():
|
||||
# This test should exit early since only providing an API KEY is not enough
|
||||
# for the hook to work
|
||||
hook_config = {'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -88,10 +91,13 @@ def test_ping_monitor_config_with_api_key_only_exit_early():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_with_host_only_exit_early():
|
||||
def test_ping_monitor_config_with_host_only_bails():
|
||||
# This test should exit early since only providing a HOST is not enough
|
||||
# for the hook to work
|
||||
hook_config = {'host': HOST}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -105,10 +111,13 @@ def test_ping_monitor_config_with_host_only_exit_early():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_with_key_only_exit_early():
|
||||
def test_ping_monitor_config_with_key_only_bails():
|
||||
# This test should exit early since only providing a KEY is not enough
|
||||
# for the hook to work
|
||||
hook_config = {'key': KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -122,10 +131,13 @@ def test_ping_monitor_config_with_key_only_exit_early():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_with_server_only_exit_early():
|
||||
def test_ping_monitor_config_with_server_only_bails():
|
||||
# This test should exit early since only providing a SERVER is not enough
|
||||
# for the hook to work
|
||||
hook_config = {'server': SERVER}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -139,9 +151,12 @@ def test_ping_monitor_config_with_server_only_exit_early():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_user_password_no_zabbix_data_exit_early():
|
||||
def test_ping_monitor_config_user_password_no_zabbix_data_bails():
|
||||
# This test should exit early since there are HOST/KEY or ITEMID provided to publish data to
|
||||
hook_config = {'server': SERVER, 'username': USERNAME, 'password': PASSWORD}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -155,9 +170,12 @@ def test_ping_monitor_config_user_password_no_zabbix_data_exit_early():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_api_key_no_zabbix_data_exit_early():
|
||||
def test_ping_monitor_config_api_key_no_zabbix_data_bails():
|
||||
# This test should exit early since there are HOST/KEY or ITEMID provided to publish data to
|
||||
hook_config = {'server': SERVER, 'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -171,10 +189,13 @@ def test_ping_monitor_config_api_key_no_zabbix_data_exit_early():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_itemid_no_auth_data_exit_early():
|
||||
def test_ping_monitor_config_itemid_no_auth_data_bails():
|
||||
# This test should exit early since there is no authentication provided
|
||||
# and Zabbix requires authentication to use it's API
|
||||
hook_config = {'server': SERVER, 'itemid': ITEMID}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -188,10 +209,13 @@ def test_ping_monitor_config_itemid_no_auth_data_exit_early():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_host_and_key_no_auth_data_exit_early():
|
||||
def test_ping_monitor_config_host_and_key_no_auth_data_bails():
|
||||
# This test should exit early since there is no authentication provided
|
||||
# and Zabbix requires authentication to use it's API
|
||||
hook_config = {'server': SERVER, 'host': HOST, 'key': KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -209,6 +233,9 @@ def test_ping_monitor_config_host_and_key_with_api_key_auth_data_successful():
|
||||
# This test should simulate a successful POST to a Zabbix server. This test uses API_KEY
|
||||
# to authenticate and HOST/KEY to know which item to populate in Zabbix.
|
||||
hook_config = {'server': SERVER, 'host': HOST, 'key': KEY, 'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_API_KEY,
|
||||
@@ -226,8 +253,11 @@ def test_ping_monitor_config_host_and_key_with_api_key_auth_data_successful():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_host_and_missing_key_exits_early():
|
||||
def test_ping_monitor_config_host_and_missing_key_bails():
|
||||
hook_config = {'server': SERVER, 'host': HOST, 'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -241,8 +271,11 @@ def test_ping_monitor_config_host_and_missing_key_exits_early():
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_key_and_missing_host_exits_early():
|
||||
def test_ping_monitor_config_key_and_missing_host_bails():
|
||||
hook_config = {'server': SERVER, 'key': KEY, 'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -267,6 +300,9 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_succe
|
||||
'password': PASSWORD,
|
||||
}
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
auth_response = flexmock(ok=True)
|
||||
auth_response.should_receive('json').and_return(
|
||||
{'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
|
||||
@@ -296,7 +332,7 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_succe
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_auth_post_error_exits_early():
|
||||
def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_auth_post_error_bails():
|
||||
hook_config = {
|
||||
'server': SERVER,
|
||||
'host': HOST,
|
||||
@@ -305,6 +341,9 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_a
|
||||
'password': PASSWORD,
|
||||
}
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
auth_response = flexmock(ok=False)
|
||||
auth_response.should_receive('json').and_return(
|
||||
{'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
|
||||
@@ -335,7 +374,7 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_a
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_host_and_key_with_username_and_missing_password_exits_early():
|
||||
def test_ping_monitor_config_host_and_key_with_username_and_missing_password_bails():
|
||||
hook_config = {
|
||||
'server': SERVER,
|
||||
'host': HOST,
|
||||
@@ -343,6 +382,9 @@ def test_ping_monitor_config_host_and_key_with_username_and_missing_password_exi
|
||||
'username': USERNAME,
|
||||
}
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -356,7 +398,7 @@ def test_ping_monitor_config_host_and_key_with_username_and_missing_password_exi
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_host_and_key_with_passing_and_missing_username_exits_early():
|
||||
def test_ping_monitor_config_host_and_key_with_password_and_missing_username_bails():
|
||||
hook_config = {
|
||||
'server': SERVER,
|
||||
'host': HOST,
|
||||
@@ -364,6 +406,9 @@ def test_ping_monitor_config_host_and_key_with_passing_and_missing_username_exit
|
||||
'password': PASSWORD,
|
||||
}
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@@ -381,6 +426,9 @@ def test_ping_monitor_config_itemid_with_api_key_auth_data_successful():
|
||||
# This test should simulate a successful POST to a Zabbix server. This test uses API_KEY
|
||||
# to authenticate and HOST/KEY to know which item to populate in Zabbix.
|
||||
hook_config = {'server': SERVER, 'itemid': ITEMID, 'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_API_KEY,
|
||||
@@ -403,6 +451,9 @@ def test_ping_monitor_config_itemid_with_username_password_auth_data_successful(
|
||||
# to authenticate and HOST/KEY to know which item to populate in Zabbix.
|
||||
hook_config = {'server': SERVER, 'itemid': ITEMID, 'username': USERNAME, 'password': PASSWORD}
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
auth_response = flexmock(ok=True)
|
||||
auth_response.should_receive('json').and_return(
|
||||
{'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
|
||||
@@ -432,9 +483,12 @@ def test_ping_monitor_config_itemid_with_username_password_auth_data_successful(
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_itemid_with_username_password_auth_data_and_push_post_error_exits_early():
|
||||
def test_ping_monitor_config_itemid_with_username_password_auth_data_and_push_post_error_bails():
|
||||
hook_config = {'server': SERVER, 'itemid': ITEMID, 'username': USERNAME, 'password': PASSWORD}
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
auth_response = flexmock(ok=True)
|
||||
auth_response.should_receive('json').and_return(
|
||||
{'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
|
||||
@@ -466,3 +520,22 @@ def test_ping_monitor_config_itemid_with_username_password_auth_data_and_push_po
|
||||
monitoring_log_level=1,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_with_credential_error_bails():
|
||||
hook_config = {'server': SERVER, 'itemid': ITEMID, 'username': USERNAME, 'password': PASSWORD}
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_raise(ValueError)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
{},
|
||||
'config.yaml',
|
||||
borgmatic.hooks.monitoring.monitor.State.FAIL,
|
||||
monitoring_log_level=1,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
+118
-33
@@ -228,16 +228,12 @@ def test_add_logging_level_skips_global_setting_if_already_set():
|
||||
module.add_logging_level('PLAID', 99)
|
||||
|
||||
|
||||
def test_get_log_prefix_gets_prefix_from_first_handler():
|
||||
def test_get_log_prefix_gets_prefix_from_first_handler_formatter_with_prefix():
|
||||
flexmock(module.logging).should_receive('getLogger').and_return(
|
||||
flexmock(
|
||||
handlers=[
|
||||
flexmock(
|
||||
formatter=flexmock(
|
||||
_style=flexmock(_defaults=flexmock(get=lambda name: 'myprefix: '))
|
||||
)
|
||||
),
|
||||
flexmock(),
|
||||
flexmock(formatter=flexmock()),
|
||||
flexmock(formatter=flexmock(prefix='myprefix')),
|
||||
],
|
||||
removeHandler=lambda handler: None,
|
||||
)
|
||||
@@ -261,8 +257,8 @@ def test_get_log_prefix_with_no_formatters_does_not_raise():
|
||||
flexmock(module.logging).should_receive('getLogger').and_return(
|
||||
flexmock(
|
||||
handlers=[
|
||||
flexmock(),
|
||||
flexmock(),
|
||||
flexmock(formatter=None),
|
||||
flexmock(formatter=None),
|
||||
],
|
||||
removeHandler=lambda handler: None,
|
||||
)
|
||||
@@ -276,9 +272,8 @@ def test_get_log_prefix_with_no_prefix_does_not_raise():
|
||||
flexmock(
|
||||
handlers=[
|
||||
flexmock(
|
||||
formatter=flexmock(_style=flexmock(_defaults=flexmock(get=lambda name: None)))
|
||||
formatter=flexmock(),
|
||||
),
|
||||
flexmock(),
|
||||
],
|
||||
removeHandler=lambda handler: None,
|
||||
)
|
||||
@@ -287,24 +282,20 @@ def test_get_log_prefix_with_no_prefix_does_not_raise():
|
||||
assert module.get_log_prefix() is None
|
||||
|
||||
|
||||
def test_set_log_prefix_updates_all_handlers():
|
||||
styles = (
|
||||
flexmock(_defaults=None),
|
||||
flexmock(_defaults=None),
|
||||
def test_set_log_prefix_updates_all_handler_formatters():
|
||||
formatters = (
|
||||
flexmock(prefix=None),
|
||||
flexmock(prefix=None),
|
||||
)
|
||||
|
||||
flexmock(module.logging).should_receive('getLogger').and_return(
|
||||
flexmock(
|
||||
handlers=[
|
||||
flexmock(
|
||||
formatter=flexmock(
|
||||
_style=styles[0],
|
||||
)
|
||||
formatter=formatters[0],
|
||||
),
|
||||
flexmock(
|
||||
formatter=flexmock(
|
||||
_style=styles[1],
|
||||
)
|
||||
formatter=formatters[1],
|
||||
),
|
||||
],
|
||||
removeHandler=lambda handler: None,
|
||||
@@ -313,12 +304,12 @@ def test_set_log_prefix_updates_all_handlers():
|
||||
|
||||
module.set_log_prefix('myprefix')
|
||||
|
||||
for style in styles:
|
||||
assert style._defaults == {'prefix': 'myprefix: '}
|
||||
for formatter in formatters:
|
||||
assert formatter.prefix == 'myprefix'
|
||||
|
||||
|
||||
def test_set_log_prefix_skips_handlers_without_a_formatter():
|
||||
style = flexmock(_defaults=None)
|
||||
formatter = flexmock(prefix=None)
|
||||
|
||||
flexmock(module.logging).should_receive('getLogger').and_return(
|
||||
flexmock(
|
||||
@@ -326,11 +317,8 @@ def test_set_log_prefix_skips_handlers_without_a_formatter():
|
||||
flexmock(
|
||||
formatter=None,
|
||||
),
|
||||
flexmock(),
|
||||
flexmock(
|
||||
formatter=flexmock(
|
||||
_style=style,
|
||||
)
|
||||
formatter=formatter,
|
||||
),
|
||||
],
|
||||
removeHandler=lambda handler: None,
|
||||
@@ -339,7 +327,7 @@ def test_set_log_prefix_skips_handlers_without_a_formatter():
|
||||
|
||||
module.set_log_prefix('myprefix')
|
||||
|
||||
assert style._defaults == {'prefix': 'myprefix: '}
|
||||
assert formatter.prefix == 'myprefix'
|
||||
|
||||
|
||||
def test_log_prefix_sets_prefix_and_then_restores_no_prefix_after():
|
||||
@@ -360,6 +348,94 @@ def test_log_prefix_sets_prefix_and_then_restores_original_prefix_after():
|
||||
pass
|
||||
|
||||
|
||||
def test_delayed_logging_handler_should_flush_without_targets_returns_false():
|
||||
handler = module.Delayed_logging_handler()
|
||||
|
||||
assert handler.shouldFlush(flexmock()) is False
|
||||
|
||||
|
||||
def test_delayed_logging_handler_should_flush_with_targets_returns_true():
|
||||
handler = module.Delayed_logging_handler()
|
||||
handler.targets = [flexmock()]
|
||||
|
||||
assert handler.shouldFlush(flexmock()) is True
|
||||
|
||||
|
||||
def test_delayed_logging_handler_flush_without_targets_does_not_raise():
|
||||
handler = module.Delayed_logging_handler()
|
||||
flexmock(handler).should_receive('acquire')
|
||||
flexmock(handler).should_receive('release')
|
||||
|
||||
handler.flush()
|
||||
|
||||
|
||||
def test_delayed_logging_handler_flush_with_empty_buffer_does_not_raise():
|
||||
handler = module.Delayed_logging_handler()
|
||||
flexmock(handler).should_receive('acquire')
|
||||
flexmock(handler).should_receive('release')
|
||||
handler.targets = [flexmock()]
|
||||
|
||||
handler.flush()
|
||||
|
||||
|
||||
def test_delayed_logging_handler_flush_forwards_each_record_to_each_target():
|
||||
handler = module.Delayed_logging_handler()
|
||||
flexmock(handler).should_receive('acquire')
|
||||
flexmock(handler).should_receive('release')
|
||||
handler.targets = [flexmock(level=logging.DEBUG), flexmock(level=logging.DEBUG)]
|
||||
handler.buffer = [flexmock(levelno=logging.DEBUG), flexmock(levelno=logging.DEBUG)]
|
||||
handler.targets[0].should_receive('handle').with_args(handler.buffer[0]).once()
|
||||
handler.targets[1].should_receive('handle').with_args(handler.buffer[0]).once()
|
||||
handler.targets[0].should_receive('handle').with_args(handler.buffer[1]).once()
|
||||
handler.targets[1].should_receive('handle').with_args(handler.buffer[1]).once()
|
||||
|
||||
handler.flush()
|
||||
|
||||
assert handler.buffer == []
|
||||
|
||||
|
||||
def test_delayed_logging_handler_flush_skips_forwarding_when_log_record_is_too_low_for_target():
|
||||
handler = module.Delayed_logging_handler()
|
||||
flexmock(handler).should_receive('acquire')
|
||||
flexmock(handler).should_receive('release')
|
||||
handler.targets = [flexmock(level=logging.INFO), flexmock(level=logging.DEBUG)]
|
||||
handler.buffer = [flexmock(levelno=logging.DEBUG), flexmock(levelno=logging.INFO)]
|
||||
handler.targets[0].should_receive('handle').with_args(handler.buffer[0]).never()
|
||||
handler.targets[1].should_receive('handle').with_args(handler.buffer[0]).once()
|
||||
handler.targets[0].should_receive('handle').with_args(handler.buffer[1]).once()
|
||||
handler.targets[1].should_receive('handle').with_args(handler.buffer[1]).once()
|
||||
|
||||
handler.flush()
|
||||
|
||||
assert handler.buffer == []
|
||||
|
||||
|
||||
def test_flush_delayed_logging_without_handlers_does_not_raise():
|
||||
root_logger = flexmock(handlers=[])
|
||||
root_logger.should_receive('removeHandler')
|
||||
flexmock(module.logging).should_receive('getLogger').and_return(root_logger)
|
||||
|
||||
module.flush_delayed_logging([flexmock()])
|
||||
|
||||
|
||||
def test_flush_delayed_logging_without_delayed_logging_handler_does_not_raise():
|
||||
root_logger = flexmock(handlers=[flexmock()])
|
||||
root_logger.should_receive('removeHandler')
|
||||
flexmock(module.logging).should_receive('getLogger').and_return(root_logger)
|
||||
|
||||
module.flush_delayed_logging([flexmock()])
|
||||
|
||||
|
||||
def test_flush_delayed_logging_flushes_delayed_logging_handler():
|
||||
delayed_logging_handler = module.Delayed_logging_handler()
|
||||
root_logger = flexmock(handlers=[delayed_logging_handler])
|
||||
flexmock(module.logging).should_receive('getLogger').and_return(root_logger)
|
||||
flexmock(delayed_logging_handler).should_receive('flush').once()
|
||||
root_logger.should_receive('removeHandler')
|
||||
|
||||
module.flush_delayed_logging([flexmock()])
|
||||
|
||||
|
||||
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_linux():
|
||||
flexmock(module).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.ANSWER
|
||||
@@ -369,6 +445,7 @@ def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_linux(
|
||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||
flexmock(module).should_receive('flush_delayed_logging')
|
||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||
level=logging.DEBUG, handlers=list
|
||||
)
|
||||
@@ -390,6 +467,7 @@ def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_macos(
|
||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||
flexmock(module).should_receive('flush_delayed_logging')
|
||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||
level=logging.DEBUG, handlers=list
|
||||
)
|
||||
@@ -412,6 +490,7 @@ def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_freebs
|
||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||
flexmock(module).should_receive('flush_delayed_logging')
|
||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||
level=logging.DEBUG, handlers=list
|
||||
)
|
||||
@@ -434,6 +513,7 @@ def test_configure_logging_without_syslog_log_level_skips_syslog():
|
||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||
flexmock(module).should_receive('flush_delayed_logging')
|
||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||
level=logging.INFO, handlers=list
|
||||
)
|
||||
@@ -451,6 +531,7 @@ def test_configure_logging_skips_syslog_if_not_found():
|
||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||
flexmock(module).should_receive('flush_delayed_logging')
|
||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||
level=logging.INFO, handlers=list
|
||||
)
|
||||
@@ -469,6 +550,7 @@ def test_configure_logging_skips_log_file_if_log_file_logging_is_disabled():
|
||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||
|
||||
flexmock(module).should_receive('flush_delayed_logging')
|
||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||
level=logging.INFO, handlers=list
|
||||
)
|
||||
@@ -490,6 +572,7 @@ def test_configure_logging_to_log_file_instead_of_syslog():
|
||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||
|
||||
flexmock(module).should_receive('flush_delayed_logging')
|
||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||
level=logging.DEBUG, handlers=list
|
||||
)
|
||||
@@ -517,6 +600,7 @@ def test_configure_logging_to_both_log_file_and_syslog():
|
||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||
|
||||
flexmock(module).should_receive('flush_delayed_logging')
|
||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||
level=logging.DEBUG, handlers=list
|
||||
)
|
||||
@@ -541,10 +625,8 @@ def test_configure_logging_to_both_log_file_and_syslog():
|
||||
def test_configure_logging_to_log_file_formats_with_custom_log_format():
|
||||
flexmock(module).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.ANSWER
|
||||
flexmock(module.logging).should_receive('Formatter').with_args(
|
||||
flexmock(module).should_receive('Log_prefix_formatter').with_args(
|
||||
'{message}', # noqa: FS003
|
||||
style='{',
|
||||
defaults={'prefix': ''},
|
||||
).once()
|
||||
fake_formatter = flexmock()
|
||||
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
||||
@@ -553,6 +635,7 @@ def test_configure_logging_to_log_file_formats_with_custom_log_format():
|
||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||
|
||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||
flexmock(module).should_receive('flush_delayed_logging')
|
||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||
level=logging.DEBUG, handlers=list
|
||||
)
|
||||
@@ -580,6 +663,7 @@ def test_configure_logging_skips_log_file_if_argument_is_none():
|
||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||
|
||||
flexmock(module).should_receive('flush_delayed_logging')
|
||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||
level=logging.INFO, handlers=list
|
||||
)
|
||||
@@ -594,11 +678,12 @@ def test_configure_logging_uses_console_no_color_formatter_if_color_disabled():
|
||||
flexmock(module.logging).ANSWER = module.ANSWER
|
||||
fake_formatter = flexmock()
|
||||
flexmock(module).should_receive('Console_color_formatter').never()
|
||||
flexmock(module).should_receive('Console_no_color_formatter').and_return(fake_formatter)
|
||||
flexmock(module).should_receive('Log_prefix_formatter').and_return(fake_formatter)
|
||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||
|
||||
flexmock(module).should_receive('flush_delayed_logging')
|
||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||
level=logging.INFO, handlers=list
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user