mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-25 19:23:00 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f88018558 | ||
|
|
3642687ab5 | ||
|
|
5d9c111910 | ||
|
|
3cf19dd1b0 | ||
|
|
ad3392ca15 | ||
|
|
087b7f5c7b | ||
|
|
34bb09e9be | ||
|
|
a61eba8c79 | ||
|
|
2280bb26b6 | ||
|
|
4ee2603fef | ||
|
|
cc2ede70ac | ||
|
|
02d8ecd66e | ||
|
|
9ba78fa33b | ||
|
|
a3e34d63e9 | ||
|
|
bc25ac4eea | ||
|
|
e69c686abf | ||
|
|
0210bf76bc | ||
|
|
e69cce7e51 | ||
|
|
3655e8784a | ||
|
|
58aed0892c | ||
|
|
0e65169503 | ||
|
|
07ecc0ffd6 | ||
|
|
37ad398aff | ||
|
|
056dfc6d33 | ||
|
|
bf850b9d38 | ||
|
|
7f22612bf1 | ||
|
|
e02a0e6322 | ||
|
|
2ca23b629c | ||
|
|
b283e379d0 | ||
|
|
5dda9c8ee5 | ||
|
|
653d8c0946 | ||
|
|
92e87d839d | ||
|
|
d6cf48544a | ||
|
|
8745b9939d | ||
|
|
5661b67cde | ||
|
|
aa4a9de3b2 | ||
|
|
f9ea45493d | ||
|
|
a0ba5b673b |
@@ -1,3 +1,22 @@
|
||||
1.9.11
|
||||
* #795: Add credential loading from file, KeePassXC, and Docker/Podman secrets. See the
|
||||
documentation for more information:
|
||||
https://torsion.org/borgmatic/docs/how-to/provide-your-passwords/
|
||||
* #996: Fix the "create" action to omit the repository label prefix from Borg's output when
|
||||
databases are enabled.
|
||||
* #998: Send the "encryption_passphrase" option to Borg via an anonymous pipe, which is more secure
|
||||
than using an environment variable.
|
||||
* #999: Fix a runtime directory error from a conflict between "extra_borg_options" and special file
|
||||
detection.
|
||||
* #1001: For the ZFS, Btrfs, and LVM hooks, only make snapshots for root patterns that come from
|
||||
a borgmatic configuration option (e.g. "source_directories")—not from other hooks within
|
||||
borgmatic.
|
||||
* #1001: Fix a ZFS/LVM error due to colliding snapshot mount points for nested datasets or logical
|
||||
volumes.
|
||||
* #1001: Don't try to snapshot ZFS datasets that have the "canmount=off" property.
|
||||
* Fix another error in the Btrfs hook when a subvolume mounted at "/" is configured in borgmatic's
|
||||
source directories.
|
||||
|
||||
1.9.10
|
||||
* #966: Add a "{credential ...}" syntax for loading systemd credentials into borgmatic
|
||||
configuration files. See the documentation for more information:
|
||||
|
||||
@@ -88,6 +88,9 @@ borgmatic is powered by [Borg Backup](https://www.borgbackup.org/).
|
||||
### Credentials
|
||||
|
||||
<a href="https://systemd.io/"><img src="docs/static/systemd.png" alt="Sentry" height="40px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://www.docker.com/"><img src="docs/static/docker.png" alt="Docker" height="40px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://podman.io/"><img src="docs/static/podman.png" alt="Podman" height="40px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://keepassxc.org/"><img src="docs/static/keepassxc.png" alt="Podman" height="40px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -16,7 +16,7 @@ def run_change_passphrase(
|
||||
remote_path,
|
||||
):
|
||||
'''
|
||||
Run the "key change-passprhase" action for the given repository.
|
||||
Run the "key change-passphrase" action for the given repository.
|
||||
'''
|
||||
if (
|
||||
change_passphrase_arguments.repository is None
|
||||
|
||||
@@ -391,7 +391,7 @@ def collect_spot_check_source_paths(
|
||||
paths_output = borgmatic.execute.execute_command_and_capture_output(
|
||||
create_flags + create_positional_arguments,
|
||||
capture_stderr=True,
|
||||
extra_environment=borgmatic.borg.environment.make_environment(config),
|
||||
environment=borgmatic.borg.environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
|
||||
@@ -36,6 +36,7 @@ def parse_pattern(pattern_line, default_style=borgmatic.borg.pattern.Pattern_sty
|
||||
path,
|
||||
borgmatic.borg.pattern.Pattern_type(pattern_type),
|
||||
borgmatic.borg.pattern.Pattern_style(pattern_style),
|
||||
source=borgmatic.borg.pattern.Pattern_source.CONFIG,
|
||||
)
|
||||
|
||||
|
||||
@@ -51,7 +52,9 @@ def collect_patterns(config):
|
||||
try:
|
||||
return (
|
||||
tuple(
|
||||
borgmatic.borg.pattern.Pattern(source_directory)
|
||||
borgmatic.borg.pattern.Pattern(
|
||||
source_directory, source=borgmatic.borg.pattern.Pattern_source.CONFIG
|
||||
)
|
||||
for source_directory in config.get('source_directories', ())
|
||||
)
|
||||
+ tuple(
|
||||
@@ -144,6 +147,7 @@ def expand_patterns(patterns, working_directory=None, skip_paths=None):
|
||||
pattern.type,
|
||||
pattern.style,
|
||||
pattern.device,
|
||||
pattern.source,
|
||||
)
|
||||
for expanded_path in expand_directory(pattern.path, working_directory)
|
||||
)
|
||||
@@ -178,6 +182,7 @@ def device_map_patterns(patterns, working_directory=None):
|
||||
and os.path.exists(full_path)
|
||||
else None
|
||||
),
|
||||
source=pattern.source,
|
||||
)
|
||||
for pattern in patterns
|
||||
for full_path in (os.path.join(working_directory or '', pattern.path),)
|
||||
|
||||
@@ -61,7 +61,7 @@ def run_arbitrary_borg(
|
||||
tuple(shlex.quote(part) for part in full_command),
|
||||
output_file=DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment=dict(
|
||||
environment=dict(
|
||||
(environment.make_environment(config) or {}),
|
||||
**{
|
||||
'BORG_REPO': repository_path,
|
||||
|
||||
@@ -36,7 +36,7 @@ def break_lock(
|
||||
|
||||
execute_command(
|
||||
full_command,
|
||||
extra_environment=environment.make_environment(config),
|
||||
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'),
|
||||
|
||||
@@ -56,7 +56,7 @@ def change_passphrase(
|
||||
full_command,
|
||||
output_file=borgmatic.execute.DO_NOT_CAPTURE,
|
||||
output_log_level=logging.ANSWER,
|
||||
extra_environment=environment.make_environment(config_without_passphrase),
|
||||
environment=environment.make_environment(config_without_passphrase),
|
||||
working_directory=borgmatic.config.paths.get_working_directory(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
|
||||
@@ -182,7 +182,7 @@ def check_archives(
|
||||
output_file=(
|
||||
DO_NOT_CAPTURE if check_arguments.repair or check_arguments.progress else None
|
||||
),
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -49,7 +49,7 @@ def compact_segments(
|
||||
execute_command(
|
||||
full_command,
|
||||
output_log_level=logging.INFO,
|
||||
extra_environment=environment.make_environment(config),
|
||||
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'),
|
||||
|
||||
+26
-13
@@ -132,41 +132,53 @@ def collect_special_file_paths(
|
||||
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
|
||||
configured to be excluded from the files Borg backs up, error, because this means Borg won't be
|
||||
able to consume any database dumps and therefore borgmatic will hang.
|
||||
its own special files there for database dumps and we don't want those omitted.
|
||||
|
||||
Additionally, if the borgmatic runtime directory is not contained somewhere in the files Borg
|
||||
plans to backup, that means the user must have excluded the runtime directory (e.g. via
|
||||
"exclude_patterns" or similar). Therefore, raise, because this means Borg won't be able to
|
||||
consume any database dumps and therefore borgmatic will hang when it tries to do so.
|
||||
'''
|
||||
# Omit "--exclude-nodump" from the Borg dry run command, because that flag causes Borg to open
|
||||
# files including any named pipe we've created.
|
||||
# files including any named pipe we've created. And omit "--filter" because that can break the
|
||||
# paths output parsing below such that path lines no longer start with th expected "- ".
|
||||
paths_output = execute_command_and_capture_output(
|
||||
tuple(argument for argument in create_command if argument != '--exclude-nodump')
|
||||
flags.omit_flag_and_value(flags.omit_flag(create_command, '--exclude-nodump'), '--filter')
|
||||
+ ('--dry-run', '--list'),
|
||||
capture_stderr=True,
|
||||
working_directory=working_directory,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
)
|
||||
|
||||
# These are all the individual files that Borg is planning to backup as determined by the Borg
|
||||
# create dry run above.
|
||||
paths = tuple(
|
||||
path_line.split(' ', 1)[1]
|
||||
for path_line in paths_output.split('\n')
|
||||
if path_line and path_line.startswith('- ') or path_line.startswith('+ ')
|
||||
)
|
||||
skip_paths = {}
|
||||
|
||||
# These are the subset of those files that contain the borgmatic runtime directory.
|
||||
paths_containing_runtime_directory = {}
|
||||
|
||||
if os.path.exists(borgmatic_runtime_directory):
|
||||
skip_paths = {
|
||||
paths_containing_runtime_directory = {
|
||||
path for path in paths if any_parent_directories(path, (borgmatic_runtime_directory,))
|
||||
}
|
||||
|
||||
if not skip_paths and not dry_run:
|
||||
# If no paths to backup contain the runtime directory, it must've been excluded.
|
||||
if not paths_containing_runtime_directory and not dry_run:
|
||||
raise ValueError(
|
||||
f'The runtime directory {os.path.normpath(borgmatic_runtime_directory)} overlaps with the configured excludes or patterns with excludes. Please ensure the runtime directory is not excluded.'
|
||||
)
|
||||
|
||||
return tuple(
|
||||
path for path in paths if special_file(path, working_directory) if path not in skip_paths
|
||||
path
|
||||
for path in paths
|
||||
if special_file(path, working_directory)
|
||||
if path not in paths_containing_runtime_directory
|
||||
)
|
||||
|
||||
|
||||
@@ -325,6 +337,7 @@ def make_base_create_command(
|
||||
special_file_path,
|
||||
borgmatic.borg.pattern.Pattern_type.NO_RECURSE,
|
||||
borgmatic.borg.pattern.Pattern_style.FNMATCH,
|
||||
source=borgmatic.borg.pattern.Pattern_source.INTERNAL,
|
||||
)
|
||||
for special_file_path in special_file_paths
|
||||
),
|
||||
@@ -409,7 +422,7 @@ def create_archive(
|
||||
output_log_level,
|
||||
output_file,
|
||||
working_directory=working_directory,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
)
|
||||
@@ -417,7 +430,7 @@ def create_archive(
|
||||
return execute_command_and_capture_output(
|
||||
create_flags + create_positional_arguments,
|
||||
working_directory=working_directory,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
)
|
||||
@@ -427,7 +440,7 @@ def create_archive(
|
||||
output_log_level,
|
||||
output_file,
|
||||
working_directory=working_directory,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
)
|
||||
|
||||
@@ -128,7 +128,7 @@ def delete_archives(
|
||||
borgmatic.execute.execute_command(
|
||||
command,
|
||||
output_log_level=logging.ANSWER,
|
||||
extra_environment=borgmatic.borg.environment.make_environment(config),
|
||||
environment=borgmatic.borg.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'),
|
||||
|
||||
@@ -10,13 +10,10 @@ OPTION_TO_ENVIRONMENT_VARIABLE = {
|
||||
'borg_files_cache_ttl': 'BORG_FILES_CACHE_TTL',
|
||||
'borg_security_directory': 'BORG_SECURITY_DIR',
|
||||
'borg_keys_directory': 'BORG_KEYS_DIR',
|
||||
'encryption_passphrase': 'BORG_PASSPHRASE',
|
||||
'ssh_command': 'BORG_RSH',
|
||||
'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',
|
||||
@@ -29,27 +26,50 @@ DEFAULT_BOOL_OPTION_TO_UPPERCASE_ENVIRONMENT_VARIABLE = {
|
||||
|
||||
def make_environment(config):
|
||||
'''
|
||||
Given a borgmatic configuration dict, return its options converted to a Borg environment
|
||||
variable dict.
|
||||
Given a borgmatic configuration dict, convert it to a Borg environment variable dict, merge it
|
||||
with a copy of the current environment variables, and return the result.
|
||||
|
||||
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.
|
||||
|
||||
Here's how native Borg precedence works for a few of the environment variables:
|
||||
|
||||
1. BORG_PASSPHRASE, if set, is used first.
|
||||
2. BORG_PASSCOMMAND is used only if BORG_PASSPHRASE isn't set.
|
||||
3. BORG_PASSPHRASE_FD is used only if neither of the above are set.
|
||||
|
||||
In borgmatic, we want to simulate this precedence order, but there are some additional
|
||||
complications. First, values can come from either configuration or from environment variables
|
||||
set outside borgmatic; configured options should take precedence. Second, when borgmatic gets a
|
||||
passphrase—directly from configuration or indirectly via a credential hook or a passcommand—we
|
||||
want to pass that passphrase to Borg via an anonymous pipe (+ BORG_PASSPHRASE_FD), since that's
|
||||
more secure than using an environment variable (BORG_PASSPHRASE).
|
||||
'''
|
||||
environment = {}
|
||||
environment = dict(os.environ)
|
||||
|
||||
for option_name, environment_variable_name in OPTION_TO_ENVIRONMENT_VARIABLE.items():
|
||||
value = config.get(option_name)
|
||||
|
||||
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)
|
||||
if 'encryption_passphrase' in config:
|
||||
environment.pop('BORG_PASSPHRASE', None)
|
||||
environment.pop('BORG_PASSCOMMAND', None)
|
||||
|
||||
# If the passcommand produced a passphrase, send it to Borg via an anonymous pipe.
|
||||
if passphrase:
|
||||
if 'encryption_passcommand' in config:
|
||||
environment.pop('BORG_PASSCOMMAND', None)
|
||||
|
||||
passphrase = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
config.get('encryption_passphrase'), config
|
||||
)
|
||||
|
||||
if passphrase is None:
|
||||
passphrase = borgmatic.borg.passcommand.get_passphrase_from_passcommand(config)
|
||||
|
||||
# If there's a passphrase (from configuration, from a configured credential, or from a
|
||||
# configured passcommand), send it to Borg via an anonymous pipe.
|
||||
if passphrase is not None:
|
||||
read_file_descriptor, write_file_descriptor = os.pipe()
|
||||
os.write(write_file_descriptor, passphrase.encode('utf-8'))
|
||||
os.close(write_file_descriptor)
|
||||
|
||||
@@ -67,7 +67,7 @@ def export_key(
|
||||
full_command,
|
||||
output_file=output_file,
|
||||
output_log_level=logging.ANSWER,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
|
||||
@@ -70,7 +70,7 @@ def export_tar_archive(
|
||||
full_command,
|
||||
output_file=DO_NOT_CAPTURE if destination_path == '-' else None,
|
||||
output_log_level=output_log_level,
|
||||
extra_environment=environment.make_environment(config),
|
||||
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'),
|
||||
|
||||
@@ -58,7 +58,7 @@ def extract_last_archive_dry_run(
|
||||
|
||||
execute_command(
|
||||
full_extract_command,
|
||||
extra_environment=environment.make_environment(config),
|
||||
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'),
|
||||
@@ -154,7 +154,7 @@ def extract_archive(
|
||||
return execute_command(
|
||||
full_command,
|
||||
output_file=DO_NOT_CAPTURE,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=full_destination_path,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -166,7 +166,7 @@ def extract_archive(
|
||||
full_command,
|
||||
output_file=subprocess.PIPE,
|
||||
run_to_completion=False,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=full_destination_path,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -176,7 +176,7 @@ def extract_archive(
|
||||
# if the restore paths don't exist in the archive.
|
||||
execute_command(
|
||||
full_command,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=full_destination_path,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -156,3 +156,44 @@ def warn_for_aggressive_archive_flags(json_command, json_output):
|
||||
logger.debug(f'Cannot parse JSON output from archive command: {error}')
|
||||
except (TypeError, KeyError):
|
||||
logger.debug('Cannot parse JSON output from archive command: No "archives" key found')
|
||||
|
||||
|
||||
def omit_flag(arguments, flag):
|
||||
'''
|
||||
Given a sequence of Borg command-line arguments, return them with the given (valueless) flag
|
||||
omitted. For instance, if the flag is "--flag" and arguments is:
|
||||
|
||||
('borg', 'create', '--flag', '--other-flag')
|
||||
|
||||
... then return:
|
||||
|
||||
('borg', 'create', '--other-flag')
|
||||
'''
|
||||
return tuple(argument for argument in arguments if argument != flag)
|
||||
|
||||
|
||||
def omit_flag_and_value(arguments, flag):
|
||||
'''
|
||||
Given a sequence of Borg command-line arguments, return them with the given flag and its
|
||||
corresponding value omitted. For instance, if the flag is "--flag" and arguments is:
|
||||
|
||||
('borg', 'create', '--flag', 'value', '--other-flag')
|
||||
|
||||
... or:
|
||||
|
||||
('borg', 'create', '--flag=value', '--other-flag')
|
||||
|
||||
... then return:
|
||||
|
||||
('borg', 'create', '--other-flag')
|
||||
'''
|
||||
# This works by zipping together a list of overlapping pairwise arguments. E.g., ('one', 'two',
|
||||
# 'three', 'four') becomes ((None, 'one'), ('one, 'two'), ('two', 'three'), ('three', 'four')).
|
||||
# This makes it easy to "look back" at the previous arguments so we can exclude both a flag and
|
||||
# its value.
|
||||
return tuple(
|
||||
argument
|
||||
for (previous_argument, argument) in zip((None,) + arguments, arguments)
|
||||
if flag not in (previous_argument, argument)
|
||||
if not argument.startswith(f'{flag}=')
|
||||
)
|
||||
|
||||
@@ -102,7 +102,7 @@ def display_archives_info(
|
||||
|
||||
json_info = execute_command_and_capture_output(
|
||||
json_command,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -116,7 +116,7 @@ def display_archives_info(
|
||||
execute_command(
|
||||
main_command,
|
||||
output_log_level=logging.ANSWER,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -124,7 +124,7 @@ def capture_archive_listing(
|
||||
local_path,
|
||||
remote_path,
|
||||
),
|
||||
extra_environment=environment.make_environment(config),
|
||||
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'),
|
||||
@@ -221,7 +221,7 @@ def list_archive(
|
||||
local_path,
|
||||
remote_path,
|
||||
),
|
||||
extra_environment=environment.make_environment(config),
|
||||
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,
|
||||
@@ -257,7 +257,7 @@ def list_archive(
|
||||
execute_command(
|
||||
main_command,
|
||||
output_log_level=logging.ANSWER,
|
||||
extra_environment=environment.make_environment(config),
|
||||
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,
|
||||
|
||||
@@ -66,7 +66,7 @@ def mount_archive(
|
||||
execute_command(
|
||||
full_command,
|
||||
output_file=DO_NOT_CAPTURE,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
@@ -75,7 +75,7 @@ def mount_archive(
|
||||
|
||||
execute_command(
|
||||
full_command,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=config.get('borg_exit_codes'),
|
||||
|
||||
@@ -9,21 +9,14 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@functools.cache
|
||||
def run_passcommand(passcommand, passphrase_configured, working_directory):
|
||||
def run_passcommand(passcommand, working_directory):
|
||||
'''
|
||||
Run the given passcommand using the given working directory and return the passphrase produced
|
||||
by the command. But bail first if a passphrase is already configured; this mimics Borg's
|
||||
behavior.
|
||||
by the command.
|
||||
|
||||
Cache the results so that the passcommand only needs to run—and potentially prompt the user—once
|
||||
per borgmatic invocation.
|
||||
'''
|
||||
if passcommand and passphrase_configured:
|
||||
logger.warning(
|
||||
'Ignoring the "encryption_passcommand" option because "encryption_passphrase" is set'
|
||||
)
|
||||
return None
|
||||
|
||||
return borgmatic.execute.execute_command_and_capture_output(
|
||||
shlex.split(passcommand),
|
||||
working_directory=working_directory,
|
||||
@@ -44,7 +37,4 @@ def get_passphrase_from_passcommand(config):
|
||||
if not passcommand:
|
||||
return None
|
||||
|
||||
passphrase = config.get('encryption_passphrase')
|
||||
working_directory = borgmatic.config.paths.get_working_directory(config)
|
||||
|
||||
return run_passcommand(passcommand, bool(passphrase is not None), working_directory)
|
||||
return run_passcommand(passcommand, borgmatic.config.paths.get_working_directory(config))
|
||||
|
||||
@@ -20,12 +20,31 @@ class Pattern_style(enum.Enum):
|
||||
PATH_FULL_MATCH = 'pf'
|
||||
|
||||
|
||||
class Pattern_source(enum.Enum):
|
||||
'''
|
||||
Where the pattern came from within borgmatic. This is important because certain use cases (like
|
||||
filesystem snapshotting) only want to consider patterns that the user actually put in a
|
||||
configuration file and not patterns from other sources.
|
||||
'''
|
||||
|
||||
# The pattern is from a borgmatic configuration option, e.g. listed in "source_directories".
|
||||
CONFIG = 'config'
|
||||
|
||||
# The pattern is generated internally within borgmatic, e.g. for special file excludes.
|
||||
INTERNAL = 'internal'
|
||||
|
||||
# The pattern originates from within a borgmatic hook, e.g. a database hook that adds its dump
|
||||
# directory.
|
||||
HOOK = 'hook'
|
||||
|
||||
|
||||
Pattern = collections.namedtuple(
|
||||
'Pattern',
|
||||
('path', 'type', 'style', 'device'),
|
||||
('path', 'type', 'style', 'device', 'source'),
|
||||
defaults=(
|
||||
Pattern_type.ROOT,
|
||||
Pattern_style.NONE,
|
||||
None,
|
||||
Pattern_source.HOOK,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -96,7 +96,7 @@ def prune_archives(
|
||||
execute_command(
|
||||
full_command,
|
||||
output_log_level=output_log_level,
|
||||
extra_environment=environment.make_environment(config),
|
||||
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'),
|
||||
|
||||
@@ -98,7 +98,7 @@ def create_repository(
|
||||
execute_command(
|
||||
repo_create_command,
|
||||
output_file=DO_NOT_CAPTURE,
|
||||
extra_environment=environment.make_environment(config),
|
||||
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'),
|
||||
|
||||
@@ -88,7 +88,7 @@ def delete_repository(
|
||||
if repo_delete_arguments.force or repo_delete_arguments.cache_only
|
||||
else borgmatic.execute.DO_NOT_CAPTURE
|
||||
),
|
||||
extra_environment=borgmatic.borg.environment.make_environment(config),
|
||||
environment=borgmatic.borg.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'),
|
||||
|
||||
@@ -56,7 +56,7 @@ def display_repository_info(
|
||||
if repo_info_arguments.json:
|
||||
return execute_command_and_capture_output(
|
||||
full_command,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -65,7 +65,7 @@ def display_repository_info(
|
||||
execute_command(
|
||||
full_command,
|
||||
output_log_level=logging.ANSWER,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -49,7 +49,7 @@ def resolve_archive_name(
|
||||
|
||||
output = execute_command_and_capture_output(
|
||||
full_command,
|
||||
extra_environment=environment.make_environment(config),
|
||||
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'),
|
||||
@@ -164,7 +164,7 @@ def list_repository(
|
||||
|
||||
json_listing = execute_command_and_capture_output(
|
||||
json_command,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -178,7 +178,7 @@ def list_repository(
|
||||
execute_command(
|
||||
main_command,
|
||||
output_log_level=logging.ANSWER,
|
||||
extra_environment=environment.make_environment(config),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
borg_local_path=local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -57,7 +57,7 @@ def transfer_archives(
|
||||
full_command,
|
||||
output_log_level=logging.ANSWER,
|
||||
output_file=DO_NOT_CAPTURE if transfer_arguments.progress else None,
|
||||
extra_environment=environment.make_environment(config),
|
||||
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'),
|
||||
|
||||
@@ -21,7 +21,7 @@ def local_borg_version(config, local_path='borg'):
|
||||
)
|
||||
output = execute_command_and_capture_output(
|
||||
full_command,
|
||||
extra_environment=environment.make_environment(config),
|
||||
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'),
|
||||
|
||||
@@ -2402,3 +2402,25 @@ properties:
|
||||
description: |
|
||||
Configuration for integration with Linux LVM (Logical Volume
|
||||
Manager).
|
||||
container:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
secrets_directory:
|
||||
type: string
|
||||
description: |
|
||||
Secrets directory to use instead of "/run/secrets".
|
||||
example: /path/to/secrets
|
||||
description: |
|
||||
Configuration for integration with Docker or Podman secrets.
|
||||
keepassxc:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
keepassxc_cli_command:
|
||||
type: string
|
||||
description: |
|
||||
Command to use instead of "keepassxc-cli".
|
||||
example: /usr/local/bin/keepassxc-cli
|
||||
description: |
|
||||
Configuration for integration with the KeePassXC password manager.
|
||||
|
||||
+47
-42
@@ -1,7 +1,6 @@
|
||||
import collections
|
||||
import enum
|
||||
import logging
|
||||
import os
|
||||
import select
|
||||
import subprocess
|
||||
import textwrap
|
||||
@@ -243,6 +242,9 @@ def mask_command_secrets(full_command):
|
||||
MAX_LOGGED_COMMAND_LENGTH = 1000
|
||||
|
||||
|
||||
PREFIXES_OF_ENVIRONMENT_VARIABLES_TO_LOG = ('BORG_', 'PG', 'MARIADB_', 'MYSQL_')
|
||||
|
||||
|
||||
def log_command(full_command, input_file=None, output_file=None, environment=None):
|
||||
'''
|
||||
Log the given command (a sequence of command/argument strings), along with its input/output file
|
||||
@@ -251,7 +253,14 @@ def log_command(full_command, input_file=None, output_file=None, environment=Non
|
||||
logger.debug(
|
||||
textwrap.shorten(
|
||||
' '.join(
|
||||
tuple(f'{key}=***' for key in (environment or {}).keys())
|
||||
tuple(
|
||||
f'{key}=***'
|
||||
for key in (environment or {}).keys()
|
||||
if any(
|
||||
key.startswith(prefix)
|
||||
for prefix in PREFIXES_OF_ENVIRONMENT_VARIABLES_TO_LOG
|
||||
)
|
||||
)
|
||||
+ mask_command_secrets(full_command)
|
||||
),
|
||||
width=MAX_LOGGED_COMMAND_LENGTH,
|
||||
@@ -274,7 +283,7 @@ def execute_command(
|
||||
output_file=None,
|
||||
input_file=None,
|
||||
shell=False,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path=None,
|
||||
borg_exit_codes=None,
|
||||
@@ -284,18 +293,17 @@ def execute_command(
|
||||
Execute the given command (a sequence of command/argument strings) and log its output at the
|
||||
given log level. If an open output file object is given, then write stdout to the file and only
|
||||
log stderr. If an open input file object is given, then read stdin from the file. If shell is
|
||||
True, execute the command within a shell. If an extra environment dict is given, then use it to
|
||||
augment the current environment, and pass the result into the command. If a working directory is
|
||||
given, use that as the present working directory when running the command. If a Borg local path
|
||||
is given, and the command matches it (regardless of arguments), treat exit code 1 as a warning
|
||||
instead of an error. But if Borg exit codes are given as a sequence of exit code configuration
|
||||
dicts, then use that configuration to decide what's an error and what's a warning. If run to
|
||||
completion is False, then return the process for the command without executing it to completion.
|
||||
True, execute the command within a shell. If an environment variables dict is given, then pass
|
||||
it into the command. If a working directory is given, use that as the present working directory
|
||||
when running the command. If a Borg local path is given, and the command matches it (regardless
|
||||
of arguments), treat exit code 1 as a warning instead of an error. But if Borg exit codes are
|
||||
given as a sequence of exit code configuration dicts, then use that configuration to decide
|
||||
what's an error and what's a warning. If run to completion is False, then return the process for
|
||||
the command without executing it to completion.
|
||||
|
||||
Raise subprocesses.CalledProcessError if an error occurs while running the command.
|
||||
'''
|
||||
log_command(full_command, input_file, output_file, extra_environment)
|
||||
environment = {**os.environ, **extra_environment} if extra_environment else None
|
||||
log_command(full_command, input_file, output_file, environment)
|
||||
do_not_capture = bool(output_file is DO_NOT_CAPTURE)
|
||||
command = ' '.join(full_command) if shell else full_command
|
||||
|
||||
@@ -308,7 +316,7 @@ def execute_command(
|
||||
env=environment,
|
||||
cwd=working_directory,
|
||||
# Necessary for the passcommand credential hook to work.
|
||||
close_fds=not bool((extra_environment or {}).get('BORG_PASSPHRASE_FD')),
|
||||
close_fds=not bool((environment or {}).get('BORG_PASSPHRASE_FD')),
|
||||
)
|
||||
if not run_to_completion:
|
||||
return process
|
||||
@@ -327,7 +335,7 @@ def execute_command_and_capture_output(
|
||||
full_command,
|
||||
capture_stderr=False,
|
||||
shell=False,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path=None,
|
||||
borg_exit_codes=None,
|
||||
@@ -335,18 +343,16 @@ def execute_command_and_capture_output(
|
||||
'''
|
||||
Execute the given command (a sequence of command/argument strings), capturing and returning its
|
||||
output (stdout). If capture stderr is True, then capture and return stderr in addition to
|
||||
stdout. If shell is True, execute the command within a shell. If an extra environment dict is
|
||||
given, then use it to augment the current environment, and pass the result into the command. If
|
||||
a working directory is given, use that as the present working directory when running the
|
||||
command. If a Borg local path is given, and the command matches it (regardless of arguments),
|
||||
treat exit code 1 as a warning instead of an error. But if Borg exit codes are given as a
|
||||
sequence of exit code configuration dicts, then use that configuration to decide what's an error
|
||||
and what's a warning.
|
||||
stdout. If shell is True, execute the command within a shell. If an environment variables dict
|
||||
is given, then pass it into the command. If a working directory is given, use that as the
|
||||
present working directory when running the command. If a Borg local path is given, and the
|
||||
command matches it (regardless of arguments), treat exit code 1 as a warning instead of an
|
||||
error. But if Borg exit codes are given as a sequence of exit code configuration dicts, then use
|
||||
that configuration to decide what's an error and what's a warning.
|
||||
|
||||
Raise subprocesses.CalledProcessError if an error occurs while running the command.
|
||||
'''
|
||||
log_command(full_command, environment=extra_environment)
|
||||
environment = {**os.environ, **extra_environment} if extra_environment else None
|
||||
log_command(full_command, environment=environment)
|
||||
command = ' '.join(full_command) if shell else full_command
|
||||
|
||||
try:
|
||||
@@ -357,7 +363,7 @@ def execute_command_and_capture_output(
|
||||
env=environment,
|
||||
cwd=working_directory,
|
||||
# Necessary for the passcommand credential hook to work.
|
||||
close_fds=not bool((extra_environment or {}).get('BORG_PASSPHRASE_FD')),
|
||||
close_fds=not bool((environment or {}).get('BORG_PASSPHRASE_FD')),
|
||||
)
|
||||
except subprocess.CalledProcessError as error:
|
||||
if (
|
||||
@@ -377,7 +383,7 @@ def execute_command_with_processes(
|
||||
output_file=None,
|
||||
input_file=None,
|
||||
shell=False,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path=None,
|
||||
borg_exit_codes=None,
|
||||
@@ -391,19 +397,17 @@ def execute_command_with_processes(
|
||||
If an open output file object is given, then write stdout to the file and only log stderr. But
|
||||
if output log level is None, instead suppress logging and return the captured output for (only)
|
||||
the given command. If an open input file object is given, then read stdin from the file. If
|
||||
shell is True, execute the command within a shell. If an extra environment dict is given, then
|
||||
use it to augment the current environment, and pass the result into the command. If a working
|
||||
directory is given, use that as the present working directory when running the command. If a
|
||||
Borg local path is given, then for any matching command or process (regardless of arguments),
|
||||
treat exit code 1 as a warning instead of an error. But if Borg exit codes are given as a
|
||||
sequence of exit code configuration dicts, then use that configuration to decide what's an error
|
||||
and what's a warning.
|
||||
shell is True, execute the command within a shell. If an environment variables dict is given,
|
||||
then pass it into the command. If a working directory is given, use that as the present working
|
||||
directory when running the command. If a Borg local path is given, then for any matching command
|
||||
or process (regardless of arguments), treat exit code 1 as a warning instead of an error. But if
|
||||
Borg exit codes are given as a sequence of exit code configuration dicts, then use that
|
||||
configuration to decide what's an error and what's a warning.
|
||||
|
||||
Raise subprocesses.CalledProcessError if an error occurs while running the command or in the
|
||||
upstream process.
|
||||
'''
|
||||
log_command(full_command, input_file, output_file, extra_environment)
|
||||
environment = {**os.environ, **extra_environment} if extra_environment else None
|
||||
log_command(full_command, input_file, output_file, environment)
|
||||
do_not_capture = bool(output_file is DO_NOT_CAPTURE)
|
||||
command = ' '.join(full_command) if shell else full_command
|
||||
|
||||
@@ -419,7 +423,7 @@ def execute_command_with_processes(
|
||||
env=environment,
|
||||
cwd=working_directory,
|
||||
# Necessary for the passcommand credential hook to work.
|
||||
close_fds=not bool((extra_environment or {}).get('BORG_PASSPHRASE_FD')),
|
||||
close_fds=not bool((environment or {}).get('BORG_PASSPHRASE_FD')),
|
||||
)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
# Something has gone wrong. So vent each process' output buffer to prevent it from hanging.
|
||||
@@ -430,13 +434,14 @@ def execute_command_with_processes(
|
||||
process.kill()
|
||||
raise
|
||||
|
||||
captured_outputs = log_outputs(
|
||||
tuple(processes) + (command_process,),
|
||||
(input_file, output_file),
|
||||
output_log_level,
|
||||
borg_local_path,
|
||||
borg_exit_codes,
|
||||
)
|
||||
with borgmatic.logger.Log_prefix(None): # Log command output without any prefix.
|
||||
captured_outputs = log_outputs(
|
||||
tuple(processes) + (command_process,),
|
||||
(input_file, output_file),
|
||||
output_log_level,
|
||||
borg_local_path,
|
||||
borg_exit_codes,
|
||||
)
|
||||
|
||||
if output_log_level is None:
|
||||
return captured_outputs.get(command_process)
|
||||
|
||||
@@ -30,16 +30,18 @@ def interpolate_context(hook_description, command, context):
|
||||
|
||||
def make_environment(current_environment, sys_module=sys):
|
||||
'''
|
||||
Given the existing system environment as a map from environment variable name to value, return
|
||||
(in the same form) any extra environment variables that should be used when running command
|
||||
hooks.
|
||||
Given the existing system environment as a map from environment variable name to value, return a
|
||||
copy of it, augmented with any extra environment variables that should be used when running
|
||||
command hooks.
|
||||
'''
|
||||
environment = dict(current_environment)
|
||||
|
||||
# Detect whether we're running within a PyInstaller bundle. If so, set or clear LD_LIBRARY_PATH
|
||||
# based on the value of LD_LIBRARY_PATH_ORIG. This prevents library version information errors.
|
||||
if getattr(sys_module, 'frozen', False) and hasattr(sys_module, '_MEIPASS'):
|
||||
return {'LD_LIBRARY_PATH': current_environment.get('LD_LIBRARY_PATH_ORIG', '')}
|
||||
environment['LD_LIBRARY_PATH'] = environment.get('LD_LIBRARY_PATH_ORIG', '')
|
||||
|
||||
return {}
|
||||
return environment
|
||||
|
||||
|
||||
def execute_hook(commands, umask, config_filename, description, dry_run, **context):
|
||||
@@ -85,7 +87,7 @@ def execute_hook(commands, umask, config_filename, description, dry_run, **conte
|
||||
[command],
|
||||
output_log_level=(logging.ERROR if description == 'on-error' else logging.WARNING),
|
||||
shell=True,
|
||||
extra_environment=make_environment(os.environ),
|
||||
environment=make_environment(os.environ),
|
||||
)
|
||||
finally:
|
||||
if original_umask:
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SECRET_NAME_PATTERN = re.compile(r'^\w+$')
|
||||
DEFAULT_SECRETS_DIRECTORY = '/run/secrets'
|
||||
|
||||
|
||||
def load_credential(hook_config, config, credential_parameters):
|
||||
'''
|
||||
Given the hook configuration dict, the configuration dict, and a credential parameters tuple
|
||||
containing a secret name to load, read the secret from the corresponding container secrets file
|
||||
and return it.
|
||||
|
||||
Raise ValueError if the credential parameters is not one element, the secret name is invalid, or
|
||||
the secret file cannot be read.
|
||||
'''
|
||||
try:
|
||||
(secret_name,) = credential_parameters
|
||||
except ValueError:
|
||||
raise ValueError(f'Cannot load invalid secret name: "{' '.join(credential_parameters)}"')
|
||||
|
||||
if not SECRET_NAME_PATTERN.match(secret_name):
|
||||
raise ValueError(f'Cannot load invalid secret name: "{secret_name}"')
|
||||
|
||||
try:
|
||||
with open(
|
||||
os.path.join(
|
||||
config.get('working_directory', ''),
|
||||
(hook_config or {}).get('secrets_directory', DEFAULT_SECRETS_DIRECTORY),
|
||||
secret_name,
|
||||
)
|
||||
) as secret_file:
|
||||
return secret_file.read().rstrip(os.linesep)
|
||||
except (FileNotFoundError, OSError) as error:
|
||||
logger.warning(error)
|
||||
|
||||
raise ValueError(f'Cannot load secret "{secret_name}" from file: {error.filename}')
|
||||
@@ -0,0 +1,28 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_credential(hook_config, config, credential_parameters):
|
||||
'''
|
||||
Given the hook configuration dict, the configuration dict, and a credential parameters tuple
|
||||
containing a credential path to load, load the credential from file and return it.
|
||||
|
||||
Raise ValueError if the credential parameters is not one element or the secret file cannot be
|
||||
read.
|
||||
'''
|
||||
try:
|
||||
(credential_path,) = credential_parameters
|
||||
except ValueError:
|
||||
raise ValueError(f'Cannot load invalid credential: "{' '.join(credential_parameters)}"')
|
||||
|
||||
try:
|
||||
with open(
|
||||
os.path.join(config.get('working_directory', ''), credential_path)
|
||||
) as credential_file:
|
||||
return credential_file.read().rstrip(os.linesep)
|
||||
except (FileNotFoundError, OSError) as error:
|
||||
logger.warning(error)
|
||||
|
||||
raise ValueError(f'Cannot load credential file: {error.filename}')
|
||||
@@ -0,0 +1,40 @@
|
||||
import logging
|
||||
import os
|
||||
import shlex
|
||||
|
||||
import borgmatic.execute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_credential(hook_config, config, credential_parameters):
|
||||
'''
|
||||
Given the hook configuration dict, the configuration dict, and a credential parameters tuple
|
||||
containing a KeePassXC database path and an attribute name to load, run keepassxc-cli to fetch
|
||||
the corresponidng KeePassXC credential and return it.
|
||||
|
||||
Raise ValueError if keepassxc-cli can't retrieve the credential.
|
||||
'''
|
||||
try:
|
||||
(database_path, attribute_name) = credential_parameters
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f'Cannot load credential with invalid KeePassXC database path and attribute name: "{' '.join(credential_parameters)}"'
|
||||
)
|
||||
|
||||
if not os.path.exists(database_path):
|
||||
raise ValueError(
|
||||
f'Cannot load credential because KeePassXC database path does not exist: {database_path}'
|
||||
)
|
||||
|
||||
return borgmatic.execute.execute_command_and_capture_output(
|
||||
tuple(shlex.split((hook_config or {}).get('keepassxc_cli_command', 'keepassxc-cli')))
|
||||
+ (
|
||||
'show',
|
||||
'--show-protected',
|
||||
'--attributes',
|
||||
'Password',
|
||||
database_path,
|
||||
attribute_name,
|
||||
)
|
||||
).rstrip(os.linesep)
|
||||
@@ -1,42 +1,124 @@
|
||||
import functools
|
||||
import re
|
||||
import shlex
|
||||
|
||||
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):
|
||||
class Hash_adapter:
|
||||
'''
|
||||
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.
|
||||
A Hash_adapter instance wraps an unhashable object and pretends it's hashable. This is intended
|
||||
for passing to a @functools.cache-decorated function to prevent it from complaining that an
|
||||
argument is unhashable. It should only be used for arguments that you don't want to actually
|
||||
impact the cache hashing, because Hash_adapter doesn't actually hash the object's contents.
|
||||
|
||||
Cache the value so repeated calls to this function don't need to load the credential repeatedly.
|
||||
Example usage:
|
||||
|
||||
@functools.cache
|
||||
def func(a, b):
|
||||
print(a, b.actual_value)
|
||||
return a
|
||||
|
||||
func(5, Hash_adapter({1: 2, 3: 4})) # Calls func(), prints, and returns.
|
||||
func(5, Hash_adapter({1: 2, 3: 4})) # Hits the cache and just returns the value.
|
||||
func(5, Hash_adapter({5: 6, 7: 8})) # Also uses cache, since the Hash_adapter is ignored.
|
||||
|
||||
In the above function, the "b" value is one that has been wrapped with Hash_adappter, and
|
||||
therefore "b.actual_value" is necessary to access the original value.
|
||||
'''
|
||||
|
||||
def __init__(self, actual_value):
|
||||
self.actual_value = actual_value
|
||||
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
|
||||
def __hash__(self):
|
||||
return 0
|
||||
|
||||
|
||||
UNHASHABLE_TYPES = (dict, list, set)
|
||||
|
||||
|
||||
def cache_ignoring_unhashable_arguments(function):
|
||||
'''
|
||||
A function decorator that caches calls to the decorated function but ignores any unhashable
|
||||
arguments when performing cache lookups. This is intended to be a drop-in replacement for
|
||||
functools.cache.
|
||||
|
||||
Example usage:
|
||||
|
||||
@cache_ignoring_unhashable_arguments
|
||||
def func(a, b):
|
||||
print(a, b)
|
||||
return a
|
||||
|
||||
func(5, {1: 2, 3: 4}) # Calls func(), prints, and returns.
|
||||
func(5, {1: 2, 3: 4}) # Hits the cache and just returns the value.
|
||||
func(5, {5: 6, 7: 8}) # Also uses cache, since the unhashable value (the dict) is ignored.
|
||||
'''
|
||||
|
||||
@functools.cache
|
||||
def cached_function(*args, **kwargs):
|
||||
return function(
|
||||
*(arg.actual_value if isinstance(arg, Hash_adapter) else arg for arg in args),
|
||||
**{
|
||||
key: value.actual_value if isinstance(value, Hash_adapter) else value
|
||||
for (key, value) in kwargs.items()
|
||||
},
|
||||
)
|
||||
|
||||
@functools.wraps(function)
|
||||
def wrapper_function(*args, **kwargs):
|
||||
return cached_function(
|
||||
*(Hash_adapter(arg) if isinstance(arg, UNHASHABLE_TYPES) else arg for arg in args),
|
||||
**{
|
||||
key: Hash_adapter(value) if isinstance(value, UNHASHABLE_TYPES) else value
|
||||
for (key, value) in kwargs.items()
|
||||
},
|
||||
)
|
||||
|
||||
wrapper_function.cache_clear = cached_function.cache_clear
|
||||
|
||||
return wrapper_function
|
||||
|
||||
|
||||
CREDENTIAL_PATTERN = re.compile(r'\{credential( +(?P<hook_and_parameters>.*))?\}')
|
||||
|
||||
|
||||
@cache_ignoring_unhashable_arguments
|
||||
def resolve_credential(value, config):
|
||||
'''
|
||||
Given a configuration value containing a string like "{credential hookname credentialname}" and
|
||||
a configuration dict, resolve the credential 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 (ignoring the config for purposes of caching), 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,
|
||||
)
|
||||
matcher = CREDENTIAL_PATTERN.match(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):
|
||||
if not matcher:
|
||||
return value
|
||||
|
||||
hook_and_parameters = matcher.group('hook_and_parameters')
|
||||
|
||||
if not hook_and_parameters:
|
||||
raise ValueError(f'Cannot load credential with invalid syntax "{value}"')
|
||||
|
||||
return result
|
||||
(hook_name, *credential_parameters) = shlex.split(hook_and_parameters)
|
||||
|
||||
if not credential_parameters:
|
||||
raise ValueError(f'Cannot load credential with invalid syntax "{value}"')
|
||||
|
||||
return borgmatic.hooks.dispatch.call_hook(
|
||||
'load_credential', config, hook_name, tuple(credential_parameters)
|
||||
)
|
||||
|
||||
@@ -8,14 +8,22 @@ logger = logging.getLogger(__name__)
|
||||
CREDENTIAL_NAME_PATTERN = re.compile(r'^\w+$')
|
||||
|
||||
|
||||
def load_credential(hook_config, config, credential_name):
|
||||
def load_credential(hook_config, config, credential_parameters):
|
||||
'''
|
||||
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.
|
||||
Given the hook configuration dict, the configuration dict, and a credential parameters tuple
|
||||
containing 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.
|
||||
'''
|
||||
try:
|
||||
(credential_name,) = credential_parameters
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f'Cannot load invalid credential name: "{' '.join(credential_parameters)}"'
|
||||
)
|
||||
|
||||
credentials_directory = os.environ.get('CREDENTIALS_DIRECTORY')
|
||||
|
||||
if not credentials_directory:
|
||||
|
||||
@@ -55,9 +55,17 @@ def dump_data_sources(
|
||||
manifest_file,
|
||||
)
|
||||
|
||||
patterns.extend(borgmatic.borg.pattern.Pattern(config_path) for config_path in config_paths)
|
||||
patterns.extend(
|
||||
borgmatic.borg.pattern.Pattern(
|
||||
config_path, source=borgmatic.borg.pattern.Pattern_source.HOOK
|
||||
)
|
||||
for config_path in config_paths
|
||||
)
|
||||
patterns.append(
|
||||
borgmatic.borg.pattern.Pattern(os.path.join(borgmatic_runtime_directory, 'bootstrap'))
|
||||
borgmatic.borg.pattern.Pattern(
|
||||
os.path.join(borgmatic_runtime_directory, 'bootstrap'),
|
||||
source=borgmatic.borg.pattern.Pattern_source.HOOK,
|
||||
)
|
||||
)
|
||||
|
||||
return []
|
||||
|
||||
@@ -54,7 +54,9 @@ def get_subvolumes(btrfs_command, findmnt_command, patterns=None):
|
||||
between the current Btrfs filesystem and subvolume mount points and the paths of any patterns.
|
||||
The idea is that these pattern paths represent the requested subvolumes to snapshot.
|
||||
|
||||
If patterns is None, then return all subvolumes, sorted by path.
|
||||
Only include subvolumes that contain at least one root pattern sourced from borgmatic
|
||||
configuration (as opposed to generated elsewhere in borgmatic). But if patterns is None, then
|
||||
return all subvolumes instead, sorted by path.
|
||||
|
||||
Return the result as a sequence of matching subvolume mount points.
|
||||
'''
|
||||
@@ -73,7 +75,12 @@ def get_subvolumes(btrfs_command, findmnt_command, patterns=None):
|
||||
mount_point, candidate_patterns
|
||||
),
|
||||
)
|
||||
if patterns is None or contained_patterns
|
||||
if patterns is None
|
||||
or any(
|
||||
pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
|
||||
and pattern.source == borgmatic.borg.pattern.Pattern_source.CONFIG
|
||||
for pattern in contained_patterns
|
||||
)
|
||||
)
|
||||
|
||||
return tuple(sorted(subvolumes, key=lambda subvolume: subvolume.path))
|
||||
@@ -121,6 +128,7 @@ def make_snapshot_exclude_pattern(subvolume_path): # pragma: no cover
|
||||
),
|
||||
borgmatic.borg.pattern.Pattern_type.NO_RECURSE,
|
||||
borgmatic.borg.pattern.Pattern_style.FNMATCH,
|
||||
source=borgmatic.borg.pattern.Pattern_source.HOOK,
|
||||
)
|
||||
|
||||
|
||||
@@ -153,6 +161,7 @@ def make_borg_snapshot_pattern(subvolume_path, pattern):
|
||||
pattern.type,
|
||||
pattern.style,
|
||||
pattern.device,
|
||||
source=borgmatic.borg.pattern.Pattern_source.HOOK,
|
||||
)
|
||||
|
||||
|
||||
@@ -198,7 +207,8 @@ def dump_data_sources(
|
||||
dry_run_label = ' (dry run; not actually snapshotting anything)' if dry_run else ''
|
||||
logger.info(f'Snapshotting Btrfs subvolumes{dry_run_label}')
|
||||
|
||||
# Based on the configured patterns, determine Btrfs subvolumes to backup.
|
||||
# Based on the configured patterns, determine Btrfs subvolumes to backup. Only consider those
|
||||
# patterns that came from actual user configuration (as opposed to, say, other hooks).
|
||||
btrfs_command = hook_config.get('btrfs_command', 'btrfs')
|
||||
findmnt_command = hook_config.get('findmnt_command', 'findmnt')
|
||||
subvolumes = get_subvolumes(btrfs_command, findmnt_command, patterns)
|
||||
@@ -299,9 +309,12 @@ def remove_data_source_dumps(hook_config, config, borgmatic_runtime_directory, d
|
||||
logger.debug(error)
|
||||
return
|
||||
|
||||
# Strip off the subvolume path from the end of the snapshot path and then delete the
|
||||
# resulting directory.
|
||||
shutil.rmtree(snapshot_path.rsplit(subvolume.path, 1)[0])
|
||||
# Remove the snapshot parent directory if it still exists. (It might not exist if the
|
||||
# snapshot was for "/".)
|
||||
snapshot_parent_dir = snapshot_path.rsplit(subvolume.path, 1)[0]
|
||||
|
||||
if os.path.isdir(snapshot_parent_dir):
|
||||
shutil.rmtree(snapshot_parent_dir)
|
||||
|
||||
|
||||
def make_data_source_dump_patterns(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import collections
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -33,7 +34,9 @@ def get_logical_volumes(lsblk_command, patterns=None):
|
||||
between the current LVM logical volume mount points and the paths of any patterns. The idea is
|
||||
that these pattern paths represent the requested logical volumes to snapshot.
|
||||
|
||||
If patterns is None, include all logical volume mounts points, not just those in patterns.
|
||||
Only include logical volumes that contain at least one root pattern sourced from borgmatic
|
||||
configuration (as opposed to generated elsewhere in borgmatic). But if patterns is None, include
|
||||
all logical volume mounts points instead, not just those in patterns.
|
||||
|
||||
Return the result as a sequence of Logical_volume instances.
|
||||
'''
|
||||
@@ -72,7 +75,12 @@ def get_logical_volumes(lsblk_command, patterns=None):
|
||||
device['mountpoint'], candidate_patterns
|
||||
),
|
||||
)
|
||||
if not patterns or contained_patterns
|
||||
if not patterns
|
||||
or any(
|
||||
pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
|
||||
and pattern.source == borgmatic.borg.pattern.Pattern_source.CONFIG
|
||||
for pattern in contained_patterns
|
||||
)
|
||||
)
|
||||
except KeyError as error:
|
||||
raise ValueError(f'Invalid {lsblk_command} output: Missing key "{error}"')
|
||||
@@ -124,10 +132,14 @@ def mount_snapshot(mount_command, snapshot_device, snapshot_mount_path): # prag
|
||||
)
|
||||
|
||||
|
||||
def make_borg_snapshot_pattern(pattern, normalized_runtime_directory):
|
||||
MOUNT_POINT_HASH_LENGTH = 10
|
||||
|
||||
|
||||
def make_borg_snapshot_pattern(pattern, logical_volume, normalized_runtime_directory):
|
||||
'''
|
||||
Given a Borg pattern as a borgmatic.borg.pattern.Pattern instance, return a new Pattern with its
|
||||
path rewritten to be in a snapshot directory based on the given runtime directory.
|
||||
Given a Borg pattern as a borgmatic.borg.pattern.Pattern instance and a Logical_volume
|
||||
containing it, return a new Pattern with its path rewritten to be in a snapshot directory based
|
||||
on both the given runtime directory and the given Logical_volume's mount point.
|
||||
|
||||
Move any initial caret in a regular expression pattern path to the beginning, so as not to break
|
||||
the regular expression.
|
||||
@@ -142,6 +154,13 @@ def make_borg_snapshot_pattern(pattern, normalized_runtime_directory):
|
||||
rewritten_path = initial_caret + os.path.join(
|
||||
normalized_runtime_directory,
|
||||
'lvm_snapshots',
|
||||
# Including this hash prevents conflicts between snapshot patterns for different logical
|
||||
# volumes. For instance, without this, snapshotting a logical volume at /var and another at
|
||||
# /var/spool would result in overlapping snapshot patterns and therefore colliding mount
|
||||
# attempts.
|
||||
hashlib.shake_256(logical_volume.mount_point.encode('utf-8')).hexdigest(
|
||||
MOUNT_POINT_HASH_LENGTH
|
||||
),
|
||||
'.', # Borg 1.4+ "slashdot" hack.
|
||||
# Included so that the source directory ends up in the Borg archive at its "original" path.
|
||||
pattern.path.lstrip('^').lstrip(os.path.sep),
|
||||
@@ -152,6 +171,7 @@ def make_borg_snapshot_pattern(pattern, normalized_runtime_directory):
|
||||
pattern.type,
|
||||
pattern.style,
|
||||
pattern.device,
|
||||
source=borgmatic.borg.pattern.Pattern_source.HOOK,
|
||||
)
|
||||
|
||||
|
||||
@@ -180,7 +200,8 @@ def dump_data_sources(
|
||||
dry_run_label = ' (dry run; not actually snapshotting anything)' if dry_run else ''
|
||||
logger.info(f'Snapshotting LVM logical volumes{dry_run_label}')
|
||||
|
||||
# List logical volumes to get their mount points.
|
||||
# List logical volumes to get their mount points, but only consider those patterns that came
|
||||
# from actual user configuration (as opposed to, say, other hooks).
|
||||
lsblk_command = hook_config.get('lsblk_command', 'lsblk')
|
||||
requested_logical_volumes = get_logical_volumes(lsblk_command, patterns)
|
||||
|
||||
@@ -218,6 +239,9 @@ def dump_data_sources(
|
||||
snapshot_mount_path = os.path.join(
|
||||
normalized_runtime_directory,
|
||||
'lvm_snapshots',
|
||||
hashlib.shake_256(logical_volume.mount_point.encode('utf-8')).hexdigest(
|
||||
MOUNT_POINT_HASH_LENGTH
|
||||
),
|
||||
logical_volume.mount_point.lstrip(os.path.sep),
|
||||
)
|
||||
|
||||
@@ -233,7 +257,9 @@ def dump_data_sources(
|
||||
)
|
||||
|
||||
for pattern in logical_volume.contained_patterns:
|
||||
snapshot_pattern = make_borg_snapshot_pattern(pattern, normalized_runtime_directory)
|
||||
snapshot_pattern = make_borg_snapshot_pattern(
|
||||
pattern, logical_volume, normalized_runtime_directory
|
||||
)
|
||||
|
||||
# Attempt to update the pattern in place, since pattern order matters to Borg.
|
||||
try:
|
||||
@@ -337,6 +363,7 @@ def remove_data_source_dumps(hook_config, config, borgmatic_runtime_directory, d
|
||||
os.path.normpath(borgmatic_runtime_directory),
|
||||
),
|
||||
'lvm_snapshots',
|
||||
'*',
|
||||
)
|
||||
logger.debug(f'Looking for snapshots to remove in {snapshots_glob}{dry_run_label}')
|
||||
umount_command = hook_config.get('umount_command', 'umount')
|
||||
@@ -349,7 +376,10 @@ def remove_data_source_dumps(hook_config, config, borgmatic_runtime_directory, d
|
||||
snapshot_mount_path = os.path.join(
|
||||
snapshots_directory, logical_volume.mount_point.lstrip(os.path.sep)
|
||||
)
|
||||
if not os.path.isdir(snapshot_mount_path):
|
||||
|
||||
# If the snapshot mount path is empty, this is probably just a "shadow" of a nested
|
||||
# logical volume and therefore there's nothing to unmount.
|
||||
if not os.path.isdir(snapshot_mount_path) or not os.listdir(snapshot_mount_path):
|
||||
continue
|
||||
|
||||
# This might fail if the directory is already mounted, but we swallow errors here since
|
||||
@@ -374,7 +404,7 @@ def remove_data_source_dumps(hook_config, config, borgmatic_runtime_directory, d
|
||||
return
|
||||
except subprocess.CalledProcessError as error:
|
||||
logger.debug(error)
|
||||
return
|
||||
continue
|
||||
|
||||
if not dry_run:
|
||||
shutil.rmtree(snapshots_directory)
|
||||
|
||||
@@ -26,11 +26,11 @@ def make_dump_path(base_directory): # pragma: no cover
|
||||
SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
|
||||
|
||||
|
||||
def database_names_to_dump(database, extra_environment, dry_run):
|
||||
def database_names_to_dump(database, config, environment, dry_run):
|
||||
'''
|
||||
Given a requested database config, return the corresponding sequence of database names to dump.
|
||||
In the case of "all", query for the names of databases on the configured host and return them,
|
||||
excluding any system databases that will cause problems during restore.
|
||||
Given a requested database config and a configuration dict, return the corresponding sequence of
|
||||
database names to dump. In the case of "all", query for the names of databases on the configured
|
||||
host and return them, excluding any system databases that will cause problems during restore.
|
||||
'''
|
||||
if database['name'] != 'all':
|
||||
return (database['name'],)
|
||||
@@ -47,7 +47,10 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (
|
||||
('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
@@ -55,9 +58,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ ('--execute', 'show schemas')
|
||||
)
|
||||
logger.debug('Querying for "all" MariaDB databases to dump')
|
||||
show_output = execute_command_and_capture_output(
|
||||
show_command, extra_environment=extra_environment
|
||||
)
|
||||
show_output = execute_command_and_capture_output(show_command, environment=environment)
|
||||
|
||||
return tuple(
|
||||
show_name
|
||||
@@ -67,7 +68,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
|
||||
|
||||
def execute_dump_command(
|
||||
database, dump_path, database_names, extra_environment, dry_run, dry_run_label
|
||||
database, config, dump_path, database_names, environment, dry_run, dry_run_label
|
||||
):
|
||||
'''
|
||||
Kick off a dump for the given MariaDB database (provided as a configuration dict) to a named
|
||||
@@ -102,7 +103,10 @@ def execute_dump_command(
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (
|
||||
('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
@@ -119,7 +123,7 @@ def execute_dump_command(
|
||||
|
||||
return execute_command(
|
||||
dump_command,
|
||||
extra_environment=extra_environment,
|
||||
environment=environment,
|
||||
run_to_completion=False,
|
||||
)
|
||||
|
||||
@@ -161,12 +165,19 @@ def dump_data_sources(
|
||||
|
||||
for database in databases:
|
||||
dump_path = make_dump_path(borgmatic_runtime_directory)
|
||||
extra_environment = (
|
||||
{'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(database['password'])}
|
||||
if 'password' in database
|
||||
else None
|
||||
environment = dict(
|
||||
os.environ,
|
||||
**(
|
||||
{
|
||||
'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password'], config
|
||||
)
|
||||
}
|
||||
if 'password' in database
|
||||
else {}
|
||||
),
|
||||
)
|
||||
dump_database_names = database_names_to_dump(database, extra_environment, dry_run)
|
||||
dump_database_names = database_names_to_dump(database, config, environment, dry_run)
|
||||
|
||||
if not dump_database_names:
|
||||
if dry_run:
|
||||
@@ -181,9 +192,10 @@ def dump_data_sources(
|
||||
processes.append(
|
||||
execute_dump_command(
|
||||
renamed_database,
|
||||
config,
|
||||
dump_path,
|
||||
(dump_name,),
|
||||
extra_environment,
|
||||
environment,
|
||||
dry_run,
|
||||
dry_run_label,
|
||||
)
|
||||
@@ -192,9 +204,10 @@ def dump_data_sources(
|
||||
processes.append(
|
||||
execute_dump_command(
|
||||
database,
|
||||
config,
|
||||
dump_path,
|
||||
dump_database_names,
|
||||
extra_environment,
|
||||
environment,
|
||||
dry_run,
|
||||
dry_run_label,
|
||||
)
|
||||
@@ -203,7 +216,8 @@ def dump_data_sources(
|
||||
if not dry_run:
|
||||
patterns.append(
|
||||
borgmatic.borg.pattern.Pattern(
|
||||
os.path.join(borgmatic_runtime_directory, 'mariadb_databases')
|
||||
os.path.join(borgmatic_runtime_directory, 'mariadb_databases'),
|
||||
source=borgmatic.borg.pattern.Pattern_source.HOOK,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -265,12 +279,18 @@ def restore_data_source_dump(
|
||||
connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
|
||||
)
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['password']
|
||||
or data_source.get('restore_password', data_source.get('password'))
|
||||
(
|
||||
connection_params['password']
|
||||
or data_source.get('restore_password', data_source.get('password'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
|
||||
mariadb_restore_command = tuple(
|
||||
@@ -289,7 +309,7 @@ def restore_data_source_dump(
|
||||
+ (('--protocol', 'tcp') if hostname or port else ())
|
||||
+ (('--user', username) if username else ())
|
||||
)
|
||||
extra_environment = {'MYSQL_PWD': password} if password else None
|
||||
environment = dict(os.environ, **({'MYSQL_PWD': password} if password else {}))
|
||||
|
||||
logger.debug(f"Restoring MariaDB database {data_source['name']}{dry_run_label}")
|
||||
if dry_run:
|
||||
@@ -302,5 +322,5 @@ def restore_data_source_dump(
|
||||
[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=extra_environment,
|
||||
environment=environment,
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ def dump_data_sources(
|
||||
if dry_run:
|
||||
continue
|
||||
|
||||
command = build_dump_command(database, dump_filename, dump_format)
|
||||
command = build_dump_command(database, config, dump_filename, dump_format)
|
||||
|
||||
if dump_format == 'directory':
|
||||
dump.create_parent_directory_for_dump(dump_filename)
|
||||
@@ -81,14 +81,15 @@ def dump_data_sources(
|
||||
if not dry_run:
|
||||
patterns.append(
|
||||
borgmatic.borg.pattern.Pattern(
|
||||
os.path.join(borgmatic_runtime_directory, 'mongodb_databases')
|
||||
os.path.join(borgmatic_runtime_directory, 'mongodb_databases'),
|
||||
source=borgmatic.borg.pattern.Pattern_source.HOOK,
|
||||
)
|
||||
)
|
||||
|
||||
return processes
|
||||
|
||||
|
||||
def build_dump_command(database, dump_filename, dump_format):
|
||||
def build_dump_command(database, config, dump_filename, dump_format):
|
||||
'''
|
||||
Return the mongodump command from a single database configuration.
|
||||
'''
|
||||
@@ -103,7 +104,9 @@ def build_dump_command(database, dump_filename, dump_format):
|
||||
(
|
||||
'--username',
|
||||
shlex.quote(
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'])
|
||||
borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['username'], config
|
||||
)
|
||||
),
|
||||
)
|
||||
if 'username' in database
|
||||
@@ -113,7 +116,9 @@ def build_dump_command(database, dump_filename, dump_format):
|
||||
(
|
||||
'--password',
|
||||
shlex.quote(
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['password'])
|
||||
borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password'], config
|
||||
)
|
||||
),
|
||||
)
|
||||
if 'password' in database
|
||||
@@ -192,7 +197,7 @@ def restore_data_source_dump(
|
||||
data_source.get('hostname'),
|
||||
)
|
||||
restore_command = build_restore_command(
|
||||
extract_process, data_source, dump_filename, connection_params
|
||||
extract_process, data_source, config, dump_filename, connection_params
|
||||
)
|
||||
|
||||
logger.debug(f"Restoring MongoDB database {data_source['name']}{dry_run_label}")
|
||||
@@ -209,7 +214,7 @@ def restore_data_source_dump(
|
||||
)
|
||||
|
||||
|
||||
def build_restore_command(extract_process, database, dump_filename, connection_params):
|
||||
def build_restore_command(extract_process, database, config, dump_filename, connection_params):
|
||||
'''
|
||||
Return the mongorestore command from a single database configuration.
|
||||
'''
|
||||
@@ -218,10 +223,18 @@ def build_restore_command(extract_process, database, dump_filename, connection_p
|
||||
)
|
||||
port = str(connection_params['port'] or database.get('restore_port', database.get('port', '')))
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username'] or database.get('restore_username', database.get('username'))
|
||||
(
|
||||
connection_params['username']
|
||||
or database.get('restore_username', database.get('username'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['password'] or database.get('restore_password', database.get('password'))
|
||||
(
|
||||
connection_params['password']
|
||||
or database.get('restore_password', database.get('password'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
|
||||
command = ['mongorestore']
|
||||
|
||||
@@ -26,11 +26,11 @@ def make_dump_path(base_directory): # pragma: no cover
|
||||
SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
|
||||
|
||||
|
||||
def database_names_to_dump(database, extra_environment, dry_run):
|
||||
def database_names_to_dump(database, config, environment, dry_run):
|
||||
'''
|
||||
Given a requested database config, return the corresponding sequence of database names to dump.
|
||||
In the case of "all", query for the names of databases on the configured host and return them,
|
||||
excluding any system databases that will cause problems during restore.
|
||||
Given a requested database config and a configuration dict, return the corresponding sequence of
|
||||
database names to dump. In the case of "all", query for the names of databases on the configured
|
||||
host and return them, excluding any system databases that will cause problems during restore.
|
||||
'''
|
||||
if database['name'] != 'all':
|
||||
return (database['name'],)
|
||||
@@ -47,7 +47,10 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (
|
||||
('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
@@ -55,9 +58,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ ('--execute', 'show schemas')
|
||||
)
|
||||
logger.debug('Querying for "all" MySQL databases to dump')
|
||||
show_output = execute_command_and_capture_output(
|
||||
show_command, extra_environment=extra_environment
|
||||
)
|
||||
show_output = execute_command_and_capture_output(show_command, environment=environment)
|
||||
|
||||
return tuple(
|
||||
show_name
|
||||
@@ -67,7 +68,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
|
||||
|
||||
def execute_dump_command(
|
||||
database, dump_path, database_names, extra_environment, dry_run, dry_run_label
|
||||
database, config, dump_path, database_names, environment, dry_run, dry_run_label
|
||||
):
|
||||
'''
|
||||
Kick off a dump for the given MySQL/MariaDB database (provided as a configuration dict) to a
|
||||
@@ -101,7 +102,10 @@ def execute_dump_command(
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (
|
||||
('--user', borgmatic.hooks.credential.parse.resolve_credential(database['username']))
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
@@ -118,7 +122,7 @@ def execute_dump_command(
|
||||
|
||||
return execute_command(
|
||||
dump_command,
|
||||
extra_environment=extra_environment,
|
||||
environment=environment,
|
||||
run_to_completion=False,
|
||||
)
|
||||
|
||||
@@ -160,12 +164,19 @@ def dump_data_sources(
|
||||
|
||||
for database in databases:
|
||||
dump_path = make_dump_path(borgmatic_runtime_directory)
|
||||
extra_environment = (
|
||||
{'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(database['password'])}
|
||||
if 'password' in database
|
||||
else None
|
||||
environment = dict(
|
||||
os.environ,
|
||||
**(
|
||||
{
|
||||
'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password'], config
|
||||
)
|
||||
}
|
||||
if 'password' in database
|
||||
else {}
|
||||
),
|
||||
)
|
||||
dump_database_names = database_names_to_dump(database, extra_environment, dry_run)
|
||||
dump_database_names = database_names_to_dump(database, config, environment, dry_run)
|
||||
|
||||
if not dump_database_names:
|
||||
if dry_run:
|
||||
@@ -180,9 +191,10 @@ def dump_data_sources(
|
||||
processes.append(
|
||||
execute_dump_command(
|
||||
renamed_database,
|
||||
config,
|
||||
dump_path,
|
||||
(dump_name,),
|
||||
extra_environment,
|
||||
environment,
|
||||
dry_run,
|
||||
dry_run_label,
|
||||
)
|
||||
@@ -191,9 +203,10 @@ def dump_data_sources(
|
||||
processes.append(
|
||||
execute_dump_command(
|
||||
database,
|
||||
config,
|
||||
dump_path,
|
||||
dump_database_names,
|
||||
extra_environment,
|
||||
environment,
|
||||
dry_run,
|
||||
dry_run_label,
|
||||
)
|
||||
@@ -202,7 +215,8 @@ def dump_data_sources(
|
||||
if not dry_run:
|
||||
patterns.append(
|
||||
borgmatic.borg.pattern.Pattern(
|
||||
os.path.join(borgmatic_runtime_directory, 'mysql_databases')
|
||||
os.path.join(borgmatic_runtime_directory, 'mysql_databases'),
|
||||
source=borgmatic.borg.pattern.Pattern_source.HOOK,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -264,12 +278,18 @@ def restore_data_source_dump(
|
||||
connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
|
||||
)
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['password']
|
||||
or data_source.get('restore_password', data_source.get('password'))
|
||||
(
|
||||
connection_params['password']
|
||||
or data_source.get('restore_password', data_source.get('password'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
|
||||
mysql_restore_command = tuple(
|
||||
@@ -288,7 +308,7 @@ def restore_data_source_dump(
|
||||
+ (('--protocol', 'tcp') if hostname or port else ())
|
||||
+ (('--user', username) if username else ())
|
||||
)
|
||||
extra_environment = {'MYSQL_PWD': password} if password else None
|
||||
environment = dict(os.environ, **({'MYSQL_PWD': password} if password else {}))
|
||||
|
||||
logger.debug(f"Restoring MySQL database {data_source['name']}{dry_run_label}")
|
||||
if dry_run:
|
||||
@@ -301,5 +321,5 @@ def restore_data_source_dump(
|
||||
[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=extra_environment,
|
||||
environment=environment,
|
||||
)
|
||||
|
||||
@@ -25,49 +25,52 @@ def make_dump_path(base_directory): # pragma: no cover
|
||||
return dump.make_data_source_dump_path(base_directory, 'postgresql_databases')
|
||||
|
||||
|
||||
def make_extra_environment(database, restore_connection_params=None):
|
||||
def make_environment(database, config, restore_connection_params=None):
|
||||
'''
|
||||
Make the extra_environment dict from the given database configuration. If restore connection
|
||||
params are given, this is for a restore operation.
|
||||
Make an environment dict from the current environment variables and the given database
|
||||
configuration. If restore connection params are given, this is for a restore operation.
|
||||
'''
|
||||
extra = dict()
|
||||
environment = dict(os.environ)
|
||||
|
||||
try:
|
||||
if restore_connection_params:
|
||||
extra['PGPASSWORD'] = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
restore_connection_params.get('password')
|
||||
or database.get('restore_password', database['password'])
|
||||
environment['PGPASSWORD'] = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
(
|
||||
restore_connection_params.get('password')
|
||||
or database.get('restore_password', database['password'])
|
||||
),
|
||||
config,
|
||||
)
|
||||
else:
|
||||
extra['PGPASSWORD'] = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password']
|
||||
environment['PGPASSWORD'] = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password'], config
|
||||
)
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
|
||||
if 'ssl_mode' in database:
|
||||
extra['PGSSLMODE'] = database['ssl_mode']
|
||||
environment['PGSSLMODE'] = database['ssl_mode']
|
||||
if 'ssl_cert' in database:
|
||||
extra['PGSSLCERT'] = database['ssl_cert']
|
||||
environment['PGSSLCERT'] = database['ssl_cert']
|
||||
if 'ssl_key' in database:
|
||||
extra['PGSSLKEY'] = database['ssl_key']
|
||||
environment['PGSSLKEY'] = database['ssl_key']
|
||||
if 'ssl_root_cert' in database:
|
||||
extra['PGSSLROOTCERT'] = database['ssl_root_cert']
|
||||
environment['PGSSLROOTCERT'] = database['ssl_root_cert']
|
||||
if 'ssl_crl' in database:
|
||||
extra['PGSSLCRL'] = database['ssl_crl']
|
||||
environment['PGSSLCRL'] = database['ssl_crl']
|
||||
|
||||
return extra
|
||||
return environment
|
||||
|
||||
|
||||
EXCLUDED_DATABASE_NAMES = ('template0', 'template1')
|
||||
|
||||
|
||||
def database_names_to_dump(database, extra_environment, dry_run):
|
||||
def database_names_to_dump(database, config, environment, dry_run):
|
||||
'''
|
||||
Given a requested database config, return the corresponding sequence of database names to dump.
|
||||
In the case of "all" when a database format is given, query for the names of databases on the
|
||||
configured host and return them. For "all" without a database format, just return a sequence
|
||||
containing "all".
|
||||
Given a requested database config and a configuration dict, return the corresponding sequence of
|
||||
database names to dump. In the case of "all" when a database format is given, query for the
|
||||
names of databases on the configured host and return them. For "all" without a database format,
|
||||
just return a sequence containing "all".
|
||||
'''
|
||||
requested_name = database['name']
|
||||
|
||||
@@ -89,7 +92,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ (
|
||||
(
|
||||
'--username',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username']),
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
@@ -97,9 +100,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ (tuple(database['list_options'].split(' ')) if 'list_options' in database else ())
|
||||
)
|
||||
logger.debug('Querying for "all" PostgreSQL databases to dump')
|
||||
list_output = execute_command_and_capture_output(
|
||||
list_command, extra_environment=extra_environment
|
||||
)
|
||||
list_output = execute_command_and_capture_output(list_command, environment=environment)
|
||||
|
||||
return tuple(
|
||||
row[0]
|
||||
@@ -146,9 +147,9 @@ def dump_data_sources(
|
||||
logger.info(f'Dumping PostgreSQL databases{dry_run_label}')
|
||||
|
||||
for database in databases:
|
||||
extra_environment = make_extra_environment(database)
|
||||
environment = make_environment(database, config)
|
||||
dump_path = make_dump_path(borgmatic_runtime_directory)
|
||||
dump_database_names = database_names_to_dump(database, extra_environment, dry_run)
|
||||
dump_database_names = database_names_to_dump(database, config, environment, dry_run)
|
||||
|
||||
if not dump_database_names:
|
||||
if dry_run:
|
||||
@@ -189,7 +190,7 @@ def dump_data_sources(
|
||||
'--username',
|
||||
shlex.quote(
|
||||
borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['username']
|
||||
database['username'], config
|
||||
)
|
||||
),
|
||||
)
|
||||
@@ -222,7 +223,7 @@ def dump_data_sources(
|
||||
execute_command(
|
||||
command,
|
||||
shell=True,
|
||||
extra_environment=extra_environment,
|
||||
environment=environment,
|
||||
)
|
||||
else:
|
||||
dump.create_named_pipe_for_dump(dump_filename)
|
||||
@@ -230,7 +231,7 @@ def dump_data_sources(
|
||||
execute_command(
|
||||
command,
|
||||
shell=True,
|
||||
extra_environment=extra_environment,
|
||||
environment=environment,
|
||||
run_to_completion=False,
|
||||
)
|
||||
)
|
||||
@@ -238,7 +239,8 @@ def dump_data_sources(
|
||||
if not dry_run:
|
||||
patterns.append(
|
||||
borgmatic.borg.pattern.Pattern(
|
||||
os.path.join(borgmatic_runtime_directory, 'postgresql_databases')
|
||||
os.path.join(borgmatic_runtime_directory, 'postgresql_databases'),
|
||||
source=borgmatic.borg.pattern.Pattern_source.HOOK,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -309,8 +311,11 @@ def restore_data_source_dump(
|
||||
connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
|
||||
)
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
|
||||
all_databases = bool(data_source['name'] == 'all')
|
||||
@@ -363,9 +368,7 @@ def restore_data_source_dump(
|
||||
)
|
||||
)
|
||||
|
||||
extra_environment = make_extra_environment(
|
||||
data_source, restore_connection_params=connection_params
|
||||
)
|
||||
environment = make_environment(data_source, config, restore_connection_params=connection_params)
|
||||
|
||||
logger.debug(f"Restoring PostgreSQL database {data_source['name']}{dry_run_label}")
|
||||
if dry_run:
|
||||
@@ -378,6 +381,6 @@ def restore_data_source_dump(
|
||||
[extract_process] if extract_process else [],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout if extract_process else None,
|
||||
extra_environment=extra_environment,
|
||||
environment=environment,
|
||||
)
|
||||
execute_command(analyze_command, extra_environment=extra_environment)
|
||||
execute_command(analyze_command, environment=environment)
|
||||
|
||||
@@ -11,10 +11,10 @@ def get_contained_patterns(parent_directory, candidate_patterns):
|
||||
paths, but there's a parent directory (logical volume, dataset, subvolume, etc.) at /var, then
|
||||
/var is what we want to snapshot.
|
||||
|
||||
For this to work, a candidate pattern path can't have any globs or other non-literal characters
|
||||
in the initial portion of the path that matches the parent directory. For instance, a parent
|
||||
directory of /var would match a candidate pattern path of /var/log/*/data, but not a pattern
|
||||
path like /v*/log/*/data.
|
||||
For this function to work, a candidate pattern path can't have any globs or other non-literal
|
||||
characters in the initial portion of the path that matches the parent directory. For instance, a
|
||||
parent directory of /var would match a candidate pattern path of /var/log/*/data, but not a
|
||||
pattern path like /v*/log/*/data.
|
||||
|
||||
The one exception is that if a regular expression pattern path starts with "^", that will get
|
||||
stripped off for purposes of matching against a parent directory.
|
||||
@@ -31,8 +31,10 @@ def get_contained_patterns(parent_directory, candidate_patterns):
|
||||
candidate
|
||||
for candidate in candidate_patterns
|
||||
for candidate_path in (pathlib.PurePath(candidate.path.lstrip('^')),)
|
||||
if pathlib.PurePath(parent_directory) == candidate_path
|
||||
or pathlib.PurePath(parent_directory) in candidate_path.parents
|
||||
if (
|
||||
pathlib.PurePath(parent_directory) == candidate_path
|
||||
or pathlib.PurePath(parent_directory) in candidate_path.parents
|
||||
)
|
||||
)
|
||||
candidate_patterns -= set(contained_patterns)
|
||||
|
||||
|
||||
@@ -90,7 +90,8 @@ def dump_data_sources(
|
||||
if not dry_run:
|
||||
patterns.append(
|
||||
borgmatic.borg.pattern.Pattern(
|
||||
os.path.join(borgmatic_runtime_directory, 'sqlite_databases')
|
||||
os.path.join(borgmatic_runtime_directory, 'sqlite_databases'),
|
||||
source=borgmatic.borg.pattern.Pattern_source.HOOK,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import collections
|
||||
import glob
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
@@ -38,6 +39,9 @@ def get_datasets_to_backup(zfs_command, patterns):
|
||||
pattern paths represent the requested datasets to snapshot. But also include any datasets tagged
|
||||
with a borgmatic-specific user property, whether or not they appear in the patterns.
|
||||
|
||||
Only include datasets that contain at least one root pattern sourced from borgmatic
|
||||
configuration (as opposed to generated elsewhere in borgmatic).
|
||||
|
||||
Return the result as a sequence of Dataset instances, sorted by mount point.
|
||||
'''
|
||||
list_output = borgmatic.execute.execute_command_and_capture_output(
|
||||
@@ -48,7 +52,7 @@ def get_datasets_to_backup(zfs_command, patterns):
|
||||
'-t',
|
||||
'filesystem',
|
||||
'-o',
|
||||
f'name,mountpoint,{BORGMATIC_USER_PROPERTY}',
|
||||
f'name,mountpoint,canmount,{BORGMATIC_USER_PROPERTY}',
|
||||
)
|
||||
)
|
||||
|
||||
@@ -60,7 +64,12 @@ def get_datasets_to_backup(zfs_command, patterns):
|
||||
(
|
||||
Dataset(dataset_name, mount_point, (user_property_value == 'auto'), ())
|
||||
for line in list_output.splitlines()
|
||||
for (dataset_name, mount_point, user_property_value) in (line.rstrip().split('\t'),)
|
||||
for (dataset_name, mount_point, can_mount, user_property_value) in (
|
||||
line.rstrip().split('\t'),
|
||||
)
|
||||
# Skip datasets that are marked "canmount=off", because mounting their snapshots will
|
||||
# result in completely empty mount points—thereby preventing us from backing them up.
|
||||
if can_mount == 'on'
|
||||
),
|
||||
key=lambda dataset: dataset.mount_point,
|
||||
reverse=True,
|
||||
@@ -83,7 +92,12 @@ def get_datasets_to_backup(zfs_command, patterns):
|
||||
for contained_patterns in (
|
||||
(
|
||||
(
|
||||
(borgmatic.borg.pattern.Pattern(dataset.mount_point),)
|
||||
(
|
||||
borgmatic.borg.pattern.Pattern(
|
||||
dataset.mount_point,
|
||||
source=borgmatic.borg.pattern.Pattern_source.HOOK,
|
||||
),
|
||||
)
|
||||
if dataset.auto_backup
|
||||
else ()
|
||||
)
|
||||
@@ -92,7 +106,12 @@ def get_datasets_to_backup(zfs_command, patterns):
|
||||
)
|
||||
),
|
||||
)
|
||||
if contained_patterns
|
||||
if dataset.auto_backup
|
||||
or any(
|
||||
pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
|
||||
and pattern.source == borgmatic.borg.pattern.Pattern_source.CONFIG
|
||||
for pattern in contained_patterns
|
||||
)
|
||||
),
|
||||
key=lambda dataset: dataset.mount_point,
|
||||
)
|
||||
@@ -155,10 +174,14 @@ def mount_snapshot(mount_command, full_snapshot_name, snapshot_mount_path): # p
|
||||
)
|
||||
|
||||
|
||||
def make_borg_snapshot_pattern(pattern, normalized_runtime_directory):
|
||||
MOUNT_POINT_HASH_LENGTH = 10
|
||||
|
||||
|
||||
def make_borg_snapshot_pattern(pattern, dataset, normalized_runtime_directory):
|
||||
'''
|
||||
Given a Borg pattern as a borgmatic.borg.pattern.Pattern instance, return a new Pattern with its
|
||||
path rewritten to be in a snapshot directory based on the given runtime directory.
|
||||
Given a Borg pattern as a borgmatic.borg.pattern.Pattern instance and the Dataset containing it,
|
||||
return a new Pattern with its path rewritten to be in a snapshot directory based on both the
|
||||
given runtime directory and the given Dataset's mount point.
|
||||
|
||||
Move any initial caret in a regular expression pattern path to the beginning, so as not to break
|
||||
the regular expression.
|
||||
@@ -173,6 +196,10 @@ def make_borg_snapshot_pattern(pattern, normalized_runtime_directory):
|
||||
rewritten_path = initial_caret + os.path.join(
|
||||
normalized_runtime_directory,
|
||||
'zfs_snapshots',
|
||||
# Including this hash prevents conflicts between snapshot patterns for different datasets.
|
||||
# For instance, without this, snapshotting a dataset at /var and another at /var/spool would
|
||||
# result in overlapping snapshot patterns and therefore colliding mount attempts.
|
||||
hashlib.shake_256(dataset.mount_point.encode('utf-8')).hexdigest(MOUNT_POINT_HASH_LENGTH),
|
||||
'.', # Borg 1.4+ "slashdot" hack.
|
||||
# Included so that the source directory ends up in the Borg archive at its "original" path.
|
||||
pattern.path.lstrip('^').lstrip(os.path.sep),
|
||||
@@ -183,6 +210,7 @@ def make_borg_snapshot_pattern(pattern, normalized_runtime_directory):
|
||||
pattern.type,
|
||||
pattern.style,
|
||||
pattern.device,
|
||||
source=borgmatic.borg.pattern.Pattern_source.HOOK,
|
||||
)
|
||||
|
||||
|
||||
@@ -209,7 +237,8 @@ def dump_data_sources(
|
||||
dry_run_label = ' (dry run; not actually snapshotting anything)' if dry_run else ''
|
||||
logger.info(f'Snapshotting ZFS datasets{dry_run_label}')
|
||||
|
||||
# List ZFS datasets to get their mount points.
|
||||
# List ZFS datasets to get their mount points, but only consider those patterns that came from
|
||||
# actual user configuration (as opposed to, say, other hooks).
|
||||
zfs_command = hook_config.get('zfs_command', 'zfs')
|
||||
requested_datasets = get_datasets_to_backup(zfs_command, patterns)
|
||||
|
||||
@@ -234,6 +263,9 @@ def dump_data_sources(
|
||||
snapshot_mount_path = os.path.join(
|
||||
normalized_runtime_directory,
|
||||
'zfs_snapshots',
|
||||
hashlib.shake_256(dataset.mount_point.encode('utf-8')).hexdigest(
|
||||
MOUNT_POINT_HASH_LENGTH
|
||||
),
|
||||
dataset.mount_point.lstrip(os.path.sep),
|
||||
)
|
||||
|
||||
@@ -249,7 +281,9 @@ def dump_data_sources(
|
||||
)
|
||||
|
||||
for pattern in dataset.contained_patterns:
|
||||
snapshot_pattern = make_borg_snapshot_pattern(pattern, normalized_runtime_directory)
|
||||
snapshot_pattern = make_borg_snapshot_pattern(
|
||||
pattern, dataset, normalized_runtime_directory
|
||||
)
|
||||
|
||||
# Attempt to update the pattern in place, since pattern order matters to Borg.
|
||||
try:
|
||||
@@ -334,6 +368,7 @@ def remove_data_source_dumps(hook_config, config, borgmatic_runtime_directory, d
|
||||
os.path.normpath(borgmatic_runtime_directory),
|
||||
),
|
||||
'zfs_snapshots',
|
||||
'*',
|
||||
)
|
||||
logger.debug(f'Looking for snapshots to remove in {snapshots_glob}{dry_run_label}')
|
||||
umount_command = hook_config.get('umount_command', 'umount')
|
||||
@@ -346,7 +381,10 @@ def remove_data_source_dumps(hook_config, config, borgmatic_runtime_directory, d
|
||||
# child datasets before the shorter mount point paths of parent datasets.
|
||||
for mount_point in reversed(dataset_mount_points):
|
||||
snapshot_mount_path = os.path.join(snapshots_directory, mount_point.lstrip(os.path.sep))
|
||||
if not os.path.isdir(snapshot_mount_path):
|
||||
|
||||
# If the snapshot mount path is empty, this is probably just a "shadow" of a nested
|
||||
# dataset and therefore there's nothing to unmount.
|
||||
if not os.path.isdir(snapshot_mount_path) or not os.listdir(snapshot_mount_path):
|
||||
continue
|
||||
|
||||
# This might fail if the path is already mounted, but we swallow errors here since we'll
|
||||
@@ -370,7 +408,7 @@ def remove_data_source_dumps(hook_config, config, borgmatic_runtime_directory, d
|
||||
return
|
||||
except subprocess.CalledProcessError as error:
|
||||
logger.debug(error)
|
||||
return
|
||||
continue
|
||||
|
||||
if not dry_run:
|
||||
shutil.rmtree(snapshots_directory)
|
||||
|
||||
@@ -51,13 +51,13 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
|
||||
try:
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('username')
|
||||
hook_config.get('username'), config
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('password')
|
||||
hook_config.get('password'), config
|
||||
)
|
||||
access_token = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('access_token')
|
||||
hook_config.get('access_token'), config
|
||||
)
|
||||
except ValueError as error:
|
||||
logger.warning(f'Ntfy credential error: {error}')
|
||||
|
||||
@@ -42,7 +42,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
|
||||
try:
|
||||
integration_key = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('integration_key')
|
||||
hook_config.get('integration_key'), config
|
||||
)
|
||||
except ValueError as error:
|
||||
logger.warning(f'PagerDuty credential error: {error}')
|
||||
|
||||
@@ -35,8 +35,10 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
state_config = hook_config.get(state.name.lower(), {})
|
||||
|
||||
try:
|
||||
token = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('token'))
|
||||
user = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('user'))
|
||||
token = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('token'), config
|
||||
)
|
||||
user = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('user'), config)
|
||||
except ValueError as error:
|
||||
logger.warning(f'Pushover credential error: {error}')
|
||||
return
|
||||
|
||||
@@ -37,9 +37,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'))
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('username'), config
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('password'), config
|
||||
)
|
||||
api_key = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('api_key'), config
|
||||
)
|
||||
except ValueError as error:
|
||||
logger.warning(f'Zabbix credential error: {error}')
|
||||
return
|
||||
|
||||
@@ -710,7 +710,8 @@ zabbix:
|
||||
- fail
|
||||
```
|
||||
|
||||
This hook requires the Zabbix server be running version 7.0+
|
||||
This hook requires the Zabbix server be running version 7.0. ([Support for Zabbix
|
||||
7.2+](https://projects.torsion.org/borgmatic-collective/borgmatic/issues/1003) is planned.)
|
||||
|
||||
|
||||
### Authentication methods
|
||||
|
||||
@@ -19,6 +19,7 @@ encryption_passphrase: yourpassphrase
|
||||
But if you'd rather store them outside of borgmatic, whether for convenience
|
||||
or security reasons, read on.
|
||||
|
||||
|
||||
### Delegating to another application
|
||||
|
||||
borgmatic supports calling another application such as a password manager to
|
||||
@@ -31,26 +32,17 @@ to provide the passphrase:
|
||||
encryption_passcommand: pass path/to/borg-passphrase
|
||||
```
|
||||
|
||||
Another example for [KeePassXC](https://keepassxc.org/):
|
||||
|
||||
```yaml
|
||||
encryption_passcommand: keepassxc-cli show --show-protected --attributes Password credentials.kdbx borg_passphrase
|
||||
```
|
||||
|
||||
... where `borg_passphrase` is the title of the KeePassXC entry containing your
|
||||
Borg encryption passphrase in its `Password` field.
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.9</span> Instead of
|
||||
letting Borg run the passcommand—potentially multiple times since borgmatic runs
|
||||
Borg multiple times—borgmatic now runs the passcommand itself and passes the
|
||||
resulting passprhase securely to Borg via an anonymous pipe. This means you
|
||||
resulting passphrase securely to Borg via an anonymous pipe. This means you
|
||||
should only ever get prompted for your password manager's passphrase at most
|
||||
once per borgmatic run.
|
||||
|
||||
|
||||
### Using systemd service credentials
|
||||
### systemd service credentials
|
||||
|
||||
borgmatic supports using encrypted [systemd
|
||||
borgmatic supports reading 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.,
|
||||
@@ -146,13 +138,172 @@ The one exception is `borgmatic config validate`, which doesn't actually load
|
||||
any credentials and should continue working anywhere.
|
||||
|
||||
|
||||
### Container secrets
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.11</span> When
|
||||
running inside a container, borgmatic can read [Docker
|
||||
secrets](https://docs.docker.com/compose/how-tos/use-secrets/) and [Podman
|
||||
secrets](https://www.redhat.com/en/blog/new-podman-secrets-command). Creating
|
||||
those secrets and passing them into your borgmatic container is outside the
|
||||
scope of this documentation, but here's a simple example of that with [Docker
|
||||
Compose](https://docs.docker.com/compose/):
|
||||
|
||||
```yaml
|
||||
services:
|
||||
borgmatic:
|
||||
# Use the actual image name of your borgmatic container here.
|
||||
image: borgmatic:latest
|
||||
secrets:
|
||||
- borgmatic_passphrase
|
||||
secrets:
|
||||
borgmatic_passphrase:
|
||||
file: /etc/borgmatic/passphrase.txt
|
||||
```
|
||||
|
||||
This assumes there's a file on the host at `/etc/borgmatic/passphrase.txt`
|
||||
containing your passphrase. Docker or Podman mounts the contents of that file
|
||||
into a secret named `borgmatic_passphrase` in the borgmatic container at
|
||||
`/run/secrets/`.
|
||||
|
||||
Once your container secret is in place, you can consume it within your borgmatic
|
||||
configuration file:
|
||||
|
||||
```yaml
|
||||
encryption_passphrase: "{credential container borgmatic_passphrase}"
|
||||
```
|
||||
|
||||
This reads the secret securely from a file mounted at
|
||||
`/run/secrets/borgmatic_passphrase` within the borgmatic container.
|
||||
|
||||
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 container borgmatic_db1}"
|
||||
```
|
||||
|
||||
For specifics about which options are supported, see the
|
||||
[configuration
|
||||
reference](https://torsion.org/borgmatic/docs/reference/configuration/).
|
||||
|
||||
You can also optionally override the `/run/secrets` directory that borgmatic reads secrets from
|
||||
inside a container:
|
||||
|
||||
```yaml
|
||||
container:
|
||||
secrets_directory: /path/to/secrets
|
||||
```
|
||||
|
||||
But you should only need to do this for development or testing purposes.
|
||||
|
||||
|
||||
### KeePassXC passwords
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.11</span> borgmatic
|
||||
supports reading passwords from the [KeePassXC](https://keepassxc.org/) password
|
||||
manager. To use this feature, start by creating an entry in your KeePassXC
|
||||
database, putting your password into the "Password" field of that entry and
|
||||
making sure it's saved.
|
||||
|
||||
Then, you can consume that password in your borgmatic configuration file. For
|
||||
instance, if the entry's title is "borgmatic" and your KeePassXC database is
|
||||
located at `/etc/keys.kdbx`, do this:
|
||||
|
||||
```yaml
|
||||
encryption_passphrase: "{credential keepassxc /etc/keys.kdbx borgmatic}"
|
||||
```
|
||||
|
||||
But if the entry's title is multiple words like `borg pw`, you'll
|
||||
need to quote it:
|
||||
|
||||
```yaml
|
||||
encryption_passphrase: "{credential keepassxc /etc/keys.kdbx 'borg pw'}"
|
||||
```
|
||||
|
||||
With this in place, borgmatic runs the `keepassxc-cli` command to retrieve the
|
||||
passphrase on demand. But note that `keepassxc-cli` will prompt for its own
|
||||
passphrase in order to unlock its database, so be prepared to enter it when
|
||||
running borgmatic.
|
||||
|
||||
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 keepassxc /etc/keys.kdbx database}"
|
||||
```
|
||||
|
||||
For specifics about which options are supported, see the
|
||||
[configuration
|
||||
reference](https://torsion.org/borgmatic/docs/reference/configuration/).
|
||||
|
||||
You can also optionally override the `keepassxc-cli` command that borgmatic calls to load
|
||||
passwords:
|
||||
|
||||
```yaml
|
||||
keepassxc:
|
||||
keepassxc_cli_command: /usr/local/bin/keepassxc-cli
|
||||
```
|
||||
|
||||
|
||||
### File-based credentials
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.11</span> borgmatic
|
||||
supports reading credentials from arbitrary file paths. To use this feature,
|
||||
start by writing your credential into a file that borgmatic has permission to
|
||||
read. Take care not to include anything in the file other than your credential.
|
||||
(borgmatic is smart enough to strip off a trailing newline though.)
|
||||
|
||||
You can consume that credential file in your borgmatic configuration. For
|
||||
instance, if your credential file is at `/credentials/borgmatic.txt`, do this:
|
||||
|
||||
```yaml
|
||||
encryption_passphrase: "{credential file /credentials/borgmatic.txt}"
|
||||
```
|
||||
|
||||
With this in place, borgmatic reads the credential from the file path.
|
||||
|
||||
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 file /credentials/database.txt}"
|
||||
```
|
||||
|
||||
For specifics about which options are supported, see the
|
||||
[configuration
|
||||
reference](https://torsion.org/borgmatic/docs/reference/configuration/).
|
||||
|
||||
|
||||
### Environment variable interpolation
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.6.4</span> borgmatic
|
||||
supports interpolating arbitrary environment variables directly into option
|
||||
values in your configuration file. That means you can instruct borgmatic to
|
||||
pull your repository passphrase, your database passwords, or any other option
|
||||
values from environment variables. For instance:
|
||||
values from environment variables.
|
||||
|
||||
Be aware though that environment variables may be less secure than some of the
|
||||
other approaches above for getting credentials into borgmatic. That's because
|
||||
environment variables may be visible from within child processes and/or OS-level
|
||||
process metadata.
|
||||
|
||||
Here's an example of using an environment variable from borgmatic's
|
||||
configuration file:
|
||||
|
||||
```yaml
|
||||
encryption_passphrase: ${YOUR_PASSPHRASE}
|
||||
@@ -214,6 +365,7 @@ can escape it with a backslash. For instance, if your password is literally
|
||||
encryption_passphrase: \${A}@!
|
||||
```
|
||||
|
||||
|
||||
## Related features
|
||||
|
||||
Another way to override particular options within a borgmatic configuration
|
||||
@@ -226,9 +378,3 @@ Additionally, borgmatic action hooks support their own [variable
|
||||
interpolation](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/#variable-interpolation),
|
||||
although in that case it's for particular borgmatic runtime values rather than
|
||||
(only) environment variables.
|
||||
|
||||
Lastly, if you do want to specify your passhprase directly within borgmatic
|
||||
configuration, but you'd like to keep it in a separate file from your main
|
||||
configuration, you can [use a configuration include or a merge
|
||||
include](https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/#configuration-includes)
|
||||
to pull in an external password.
|
||||
|
||||
@@ -54,8 +54,8 @@ You have a couple of options for borgmatic to find and backup your ZFS datasets:
|
||||
* For any dataset you'd like backed up, add its mount point to borgmatic's
|
||||
`source_directories` option.
|
||||
* <span class="minilink minilink-addedin">New in version 1.9.6</span> Or
|
||||
include the mount point with borgmatic's `patterns` or `patterns_from`
|
||||
options.
|
||||
include the mount point as a root pattern with borgmatic's `patterns` or
|
||||
`patterns_from` options.
|
||||
* Or set the borgmatic-specific user property
|
||||
`org.torsion.borgmatic:backup=auto` onto your dataset, e.g. by running `zfs
|
||||
set org.torsion.borgmatic:backup=auto datasetname`. Then borgmatic can find
|
||||
@@ -65,6 +65,11 @@ If you have multiple borgmatic configuration files with ZFS enabled, and you'd
|
||||
like particular datasets to be backed up only for particular configuration
|
||||
files, use the `source_directories` option instead of the user property.
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.11</span> borgmatic
|
||||
won't snapshot datasets with the `canmount=off` property, which is often set on
|
||||
datasets that only serve as a container for other datasets. Use `zfs get
|
||||
canmount datasetname` to see the `canmount` value for a dataset.
|
||||
|
||||
During a backup, borgmatic automatically snapshots these discovered datasets
|
||||
(non-recursively), temporarily mounts the snapshots within its [runtime
|
||||
directory](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/#runtime-directory),
|
||||
@@ -147,7 +152,8 @@ For any subvolume you'd like backed up, add its path to borgmatic's
|
||||
`source_directories` option.
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.6</span> Or include
|
||||
the mount point with borgmatic's `patterns` or `patterns_from` options.
|
||||
the mount point as a root pattern with borgmatic's `patterns` or `patterns_from`
|
||||
options.
|
||||
|
||||
During a backup, borgmatic snapshots these subvolumes (non-recursively) and
|
||||
includes the snapshotted files in the paths sent to Borg. borgmatic is also
|
||||
@@ -252,7 +258,8 @@ For any logical volume you'd like backed up, add its mount point to
|
||||
borgmatic's `source_directories` option.
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.6</span> Or include
|
||||
the mount point with borgmatic's `patterns` or `patterns_from` options.
|
||||
the mount point as a root pattern with borgmatic's `patterns` or `patterns_from`
|
||||
options.
|
||||
|
||||
During a backup, borgmatic automatically snapshots these discovered logical volumes
|
||||
(non-recursively), temporarily mounts the snapshots within its [runtime
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Vendored
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.9 KiB |
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "borgmatic"
|
||||
version = "1.9.10"
|
||||
version = "1.9.11"
|
||||
authors = [
|
||||
{ name="Dan Helfman", email="witten@torsion.org" },
|
||||
]
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
def parse_arguments(*unparsed_arguments):
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('command')
|
||||
parser.add_argument('--show-protected', action='store_true')
|
||||
parser.add_argument('--attributes')
|
||||
parser.add_argument('database_path')
|
||||
parser.add_argument('attribute_name')
|
||||
|
||||
return parser.parse_args(unparsed_arguments)
|
||||
|
||||
|
||||
def main():
|
||||
arguments = parse_arguments(*sys.argv[1:])
|
||||
|
||||
assert arguments.command == 'show'
|
||||
assert arguments.show_protected
|
||||
assert arguments.attributes == 'Password'
|
||||
assert arguments.database_path.endswith('.kdbx')
|
||||
assert arguments.attribute_name
|
||||
|
||||
print('test')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -27,6 +27,7 @@ BUILTIN_DATASETS = (
|
||||
'used': '256K',
|
||||
'avail': '23.7M',
|
||||
'refer': '25K',
|
||||
'canmount': 'on',
|
||||
'mountpoint': '/pool',
|
||||
},
|
||||
{
|
||||
@@ -34,6 +35,7 @@ BUILTIN_DATASETS = (
|
||||
'used': '256K',
|
||||
'avail': '23.7M',
|
||||
'refer': '25K',
|
||||
'canmount': 'on',
|
||||
'mountpoint': '/pool/dataset',
|
||||
},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def generate_configuration(config_path, repository_path, secrets_directory):
|
||||
'''
|
||||
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 container secrets in the given secrets
|
||||
directory.
|
||||
'''
|
||||
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 container mysecret}"'
|
||||
+ f'\ncontainer:\n secrets_directory: {secrets_directory}'
|
||||
)
|
||||
config_file = open(config_path, 'w')
|
||||
config_file.write(config)
|
||||
config_file.close()
|
||||
|
||||
|
||||
def test_container_secret():
|
||||
# Create a Borg repository.
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
repository_path = os.path.join(temporary_directory, 'test.borg')
|
||||
|
||||
original_working_directory = os.getcwd()
|
||||
os.chdir(temporary_directory)
|
||||
|
||||
try:
|
||||
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||
generate_configuration(config_path, repository_path, secrets_directory=temporary_directory)
|
||||
|
||||
secret_path = os.path.join(temporary_directory, 'mysecret')
|
||||
with open(secret_path, 'w') as secret_file:
|
||||
secret_file.write('test')
|
||||
|
||||
subprocess.check_call(
|
||||
f'borgmatic -v 2 --config {config_path} repo-create --encryption repokey'.split(' '),
|
||||
)
|
||||
|
||||
# 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(' '),
|
||||
)
|
||||
output = subprocess.check_output(
|
||||
f'borgmatic --config {config_path} list --json'.split(' '),
|
||||
).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)
|
||||
@@ -0,0 +1,68 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def generate_configuration(config_path, repository_path, credential_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 file at the given credential path.
|
||||
'''
|
||||
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 file '
|
||||
+ credential_path
|
||||
+ '}"'
|
||||
)
|
||||
config_file = open(config_path, 'w')
|
||||
config_file.write(config)
|
||||
config_file.close()
|
||||
|
||||
|
||||
def test_file_credential():
|
||||
# Create a Borg repository.
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
repository_path = os.path.join(temporary_directory, 'test.borg')
|
||||
|
||||
original_working_directory = os.getcwd()
|
||||
os.chdir(temporary_directory)
|
||||
|
||||
try:
|
||||
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||
credential_path = os.path.join(temporary_directory, 'mycredential')
|
||||
generate_configuration(config_path, repository_path, credential_path)
|
||||
|
||||
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(' '),
|
||||
)
|
||||
|
||||
# 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(' '),
|
||||
)
|
||||
output = subprocess.check_output(
|
||||
f'borgmatic --config {config_path} list --json'.split(' '),
|
||||
).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)
|
||||
@@ -0,0 +1,67 @@
|
||||
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 keepassxc-cli.
|
||||
'''
|
||||
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 keepassxc keys.kdbx mypassword}"'
|
||||
+ '\nkeepassxc:\n keepassxc_cli_command: python3 /app/tests/end-to-end/commands/fake_keepassxc_cli.py'
|
||||
)
|
||||
config_file = open(config_path, 'w')
|
||||
config_file.write(config)
|
||||
config_file.close()
|
||||
|
||||
|
||||
def test_keepassxc_password():
|
||||
# Create a Borg repository.
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
repository_path = os.path.join(temporary_directory, 'test.borg')
|
||||
|
||||
original_working_directory = os.getcwd()
|
||||
os.chdir(temporary_directory)
|
||||
|
||||
try:
|
||||
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||
generate_configuration(config_path, repository_path)
|
||||
|
||||
database_path = os.path.join(temporary_directory, 'keys.kdbx')
|
||||
with open(database_path, 'w') as database_file:
|
||||
database_file.write('fake KeePassXC database to pacify file existence check')
|
||||
|
||||
subprocess.check_call(
|
||||
f'borgmatic -v 2 --config {config_path} repo-create --encryption repokey'.split(' '),
|
||||
)
|
||||
|
||||
# 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(' '),
|
||||
)
|
||||
output = subprocess.check_output(
|
||||
f'borgmatic --config {config_path} list --json'.split(' '),
|
||||
).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)
|
||||
@@ -30,15 +30,13 @@ def generate_configuration(config_path, repository_path):
|
||||
config_file.close()
|
||||
|
||||
|
||||
def test_borgmatic_command():
|
||||
def test_systemd_credential():
|
||||
# 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)
|
||||
os.chdir(temporary_directory)
|
||||
|
||||
try:
|
||||
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||
|
||||
@@ -5,16 +5,21 @@ import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.actions import create as module
|
||||
from borgmatic.borg.pattern import Pattern, Pattern_style, Pattern_type
|
||||
from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'pattern_line,expected_pattern',
|
||||
(
|
||||
('R /foo', Pattern('/foo')),
|
||||
('P sh', Pattern('sh', Pattern_type.PATTERN_STYLE)),
|
||||
('+ /foo*', Pattern('/foo*', Pattern_type.INCLUDE)),
|
||||
('+ sh:/foo*', Pattern('/foo*', Pattern_type.INCLUDE, Pattern_style.SHELL)),
|
||||
('R /foo', Pattern('/foo', source=Pattern_source.CONFIG)),
|
||||
('P sh', Pattern('sh', Pattern_type.PATTERN_STYLE, source=Pattern_source.CONFIG)),
|
||||
('+ /foo*', Pattern('/foo*', Pattern_type.INCLUDE, source=Pattern_source.CONFIG)),
|
||||
(
|
||||
'+ sh:/foo*',
|
||||
Pattern(
|
||||
'/foo*', Pattern_type.INCLUDE, Pattern_style.SHELL, source=Pattern_source.CONFIG
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_parse_pattern_transforms_pattern_line_to_instance(pattern_line, expected_pattern):
|
||||
@@ -28,8 +33,8 @@ def test_parse_pattern_with_invalid_pattern_line_errors():
|
||||
|
||||
def test_collect_patterns_converts_source_directories():
|
||||
assert module.collect_patterns({'source_directories': ['/foo', '/bar']}) == (
|
||||
Pattern('/foo'),
|
||||
Pattern('/bar'),
|
||||
Pattern('/foo', source=Pattern_source.CONFIG),
|
||||
Pattern('/bar', source=Pattern_source.CONFIG),
|
||||
)
|
||||
|
||||
|
||||
@@ -48,9 +53,15 @@ def test_collect_patterns_parses_config_patterns():
|
||||
|
||||
def test_collect_patterns_converts_exclude_patterns():
|
||||
assert module.collect_patterns({'exclude_patterns': ['/foo', '/bar', 'sh:**/baz']}) == (
|
||||
Pattern('/foo', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH),
|
||||
Pattern('/bar', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH),
|
||||
Pattern('**/baz', Pattern_type.NO_RECURSE, Pattern_style.SHELL),
|
||||
Pattern(
|
||||
'/foo', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH, source=Pattern_source.CONFIG
|
||||
),
|
||||
Pattern(
|
||||
'/bar', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH, source=Pattern_source.CONFIG
|
||||
),
|
||||
Pattern(
|
||||
'**/baz', Pattern_type.NO_RECURSE, Pattern_style.SHELL, source=Pattern_source.CONFIG
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ def test_run_arbitrary_borg_calls_borg_with_flags():
|
||||
('borg', 'break-lock', '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -41,7 +41,7 @@ def test_run_arbitrary_borg_with_log_info_calls_borg_with_info_flag():
|
||||
('borg', 'break-lock', '--info', '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -66,7 +66,7 @@ def test_run_arbitrary_borg_with_log_debug_calls_borg_with_debug_flag():
|
||||
('borg', 'break-lock', '--debug', '--show-rc', '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -94,7 +94,7 @@ def test_run_arbitrary_borg_with_lock_wait_calls_borg_with_lock_wait_flags():
|
||||
('borg', 'break-lock', '--lock-wait', '5', '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -118,7 +118,7 @@ def test_run_arbitrary_borg_with_archive_calls_borg_with_archive_flag():
|
||||
('borg', 'break-lock', "'::$ARCHIVE'"),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': 'archive'},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': 'archive'},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -143,7 +143,7 @@ def test_run_arbitrary_borg_with_local_path_calls_borg_via_local_path():
|
||||
('borg1', 'break-lock', '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg1',
|
||||
borg_exit_codes=None,
|
||||
@@ -169,7 +169,7 @@ def test_run_arbitrary_borg_with_exit_codes_calls_borg_using_them():
|
||||
('borg', 'break-lock', '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -195,7 +195,7 @@ def test_run_arbitrary_borg_with_remote_path_calls_borg_with_remote_path_flags()
|
||||
('borg', 'break-lock', '--remote-path', 'borg1', '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -222,7 +222,7 @@ def test_run_arbitrary_borg_with_remote_path_injection_attack_gets_escaped():
|
||||
('borg', 'break-lock', '--remote-path', "'borg1; naughty-command'", '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -247,7 +247,7 @@ def test_run_arbitrary_borg_passes_borg_specific_flags_to_borg():
|
||||
('borg', 'list', '--progress', '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -271,7 +271,7 @@ def test_run_arbitrary_borg_omits_dash_dash_in_flags_passed_to_borg():
|
||||
('borg', 'break-lock', '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -295,7 +295,7 @@ def test_run_arbitrary_borg_without_borg_specific_flags_does_not_raise():
|
||||
('borg',),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -319,7 +319,7 @@ def test_run_arbitrary_borg_passes_key_sub_command_to_borg_before_injected_flags
|
||||
('borg', 'key', 'export', '--info', '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -344,7 +344,7 @@ def test_run_arbitrary_borg_passes_debug_sub_command_to_borg_before_injected_fla
|
||||
('borg', 'debug', 'dump-manifest', '--info', '::', 'path'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -371,7 +371,7 @@ def test_run_arbitrary_borg_calls_borg_with_working_directory():
|
||||
('borg', 'break-lock', '::'),
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
shell=True,
|
||||
extra_environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
environment={'BORG_REPO': 'repo', 'ARCHIVE': ''},
|
||||
working_directory='/working/dir',
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
|
||||
@@ -14,7 +14,7 @@ def insert_execute_command_mock(command, working_directory=None, borg_exit_codes
|
||||
)
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
command,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=command[0],
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -25,7 +25,7 @@ def insert_execute_command_mock(
|
||||
command,
|
||||
output_file=output_file,
|
||||
output_log_level=module.logging.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=command[0],
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -18,7 +18,7 @@ def insert_execute_command_mock(
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
command,
|
||||
output_file=output_file,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=command[0],
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -342,7 +342,7 @@ def test_check_archives_with_progress_passes_through_to_borg():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'check', '--progress', 'repo'),
|
||||
output_file=module.DO_NOT_CAPTURE,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -377,7 +377,7 @@ def test_check_archives_with_repair_passes_through_to_borg():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'check', '--repair', 'repo'),
|
||||
output_file=module.DO_NOT_CAPTURE,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -412,7 +412,7 @@ def test_check_archives_with_max_duration_flag_passes_through_to_borg():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'check', '--max-duration', '33', 'repo'),
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -447,7 +447,7 @@ def test_check_archives_with_max_duration_option_passes_through_to_borg():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'check', '--max-duration', '33', 'repo'),
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -610,7 +610,7 @@ def test_check_archives_with_max_duration_flag_overrides_max_duration_option():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'check', '--max-duration', '44', 'repo'),
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -963,7 +963,7 @@ def test_check_archives_with_match_archives_passes_through_to_borg():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'check', '--match-archives', 'foo-*', 'repo'),
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
|
||||
@@ -17,7 +17,7 @@ def insert_execute_command_mock(
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
compact_command,
|
||||
output_log_level=output_log_level,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=compact_command[0],
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -4,7 +4,7 @@ import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.borg import create as module
|
||||
from borgmatic.borg.pattern import Pattern, Pattern_style, Pattern_type
|
||||
from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
|
||||
|
||||
from ..test_verbosity import insert_logging_mock
|
||||
|
||||
@@ -185,6 +185,12 @@ 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.flags).should_receive('omit_flag').replace_with(
|
||||
lambda arguments, flag: arguments
|
||||
)
|
||||
flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
|
||||
lambda arguments, flag: arguments
|
||||
)
|
||||
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'
|
||||
@@ -204,6 +210,12 @@ def test_collect_special_file_paths_parses_special_files_from_borg_dry_run_file_
|
||||
|
||||
|
||||
def test_collect_special_file_paths_skips_borgmatic_runtime_directory():
|
||||
flexmock(module.flags).should_receive('omit_flag').replace_with(
|
||||
lambda arguments, flag: arguments
|
||||
)
|
||||
flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
|
||||
lambda arguments, flag: arguments
|
||||
)
|
||||
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'
|
||||
@@ -231,6 +243,12 @@ def test_collect_special_file_paths_skips_borgmatic_runtime_directory():
|
||||
|
||||
|
||||
def test_collect_special_file_paths_with_borgmatic_runtime_directory_missing_from_paths_output_errors():
|
||||
flexmock(module.flags).should_receive('omit_flag').replace_with(
|
||||
lambda arguments, flag: arguments
|
||||
)
|
||||
flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
|
||||
lambda arguments, flag: arguments
|
||||
)
|
||||
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'
|
||||
@@ -251,6 +269,12 @@ def test_collect_special_file_paths_with_borgmatic_runtime_directory_missing_fro
|
||||
|
||||
|
||||
def test_collect_special_file_paths_with_dry_run_and_borgmatic_runtime_directory_missing_from_paths_output_does_not_raise():
|
||||
flexmock(module.flags).should_receive('omit_flag').replace_with(
|
||||
lambda arguments, flag: arguments
|
||||
)
|
||||
flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
|
||||
lambda arguments, flag: arguments
|
||||
)
|
||||
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'
|
||||
@@ -270,6 +294,12 @@ def test_collect_special_file_paths_with_dry_run_and_borgmatic_runtime_directory
|
||||
|
||||
|
||||
def test_collect_special_file_paths_excludes_non_special_files():
|
||||
flexmock(module.flags).should_receive('omit_flag').replace_with(
|
||||
lambda arguments, flag: arguments
|
||||
)
|
||||
flexmock(module.flags).should_receive('omit_flag_and_value').replace_with(
|
||||
lambda arguments, flag: arguments
|
||||
)
|
||||
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'
|
||||
@@ -290,30 +320,6 @@ def test_collect_special_file_paths_excludes_non_special_files():
|
||||
) == ('/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,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
).and_return('Processing files ...\n- /foo\n+ /bar\n- /baz').once()
|
||||
flexmock(module).should_receive('special_file').and_return(True)
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module).should_receive('any_parent_directories').never()
|
||||
|
||||
module.collect_special_file_paths(
|
||||
dry_run=False,
|
||||
create_command=('borg', 'create', '--exclude-nodump'),
|
||||
config={},
|
||||
local_path='borg',
|
||||
working_directory=None,
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
)
|
||||
|
||||
|
||||
DEFAULT_ARCHIVE_NAME = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}' # noqa: FS003
|
||||
REPO_ARCHIVE = (f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||
|
||||
@@ -663,6 +669,7 @@ def test_make_base_create_command_with_stream_processes_ignores_read_special_fal
|
||||
'/dev/null',
|
||||
Pattern_type.NO_RECURSE,
|
||||
Pattern_style.FNMATCH,
|
||||
source=Pattern_source.INTERNAL,
|
||||
),
|
||||
),
|
||||
'/run/borgmatic',
|
||||
@@ -713,6 +720,7 @@ def test_make_base_create_command_without_patterns_and_with_stream_processes_ign
|
||||
'/dev/null',
|
||||
Pattern_type.NO_RECURSE,
|
||||
Pattern_style.FNMATCH,
|
||||
source=Pattern_source.INTERNAL,
|
||||
),
|
||||
),
|
||||
'/run/borgmatic',
|
||||
@@ -969,7 +977,7 @@ def test_create_archive_calls_borg_with_parameters():
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
|
||||
module.create_archive(
|
||||
@@ -1003,7 +1011,7 @@ def test_create_archive_calls_borg_with_environment():
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
extra_environment=environment,
|
||||
environment=environment,
|
||||
)
|
||||
|
||||
module.create_archive(
|
||||
@@ -1036,7 +1044,7 @@ def test_create_archive_with_log_info_calls_borg_with_info_parameter():
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
insert_logging_mock(logging.INFO)
|
||||
|
||||
@@ -1066,7 +1074,7 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'create', '--json') + REPO_ARCHIVE,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
)
|
||||
@@ -1103,7 +1111,7 @@ def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
insert_logging_mock(logging.DEBUG)
|
||||
|
||||
@@ -1133,7 +1141,7 @@ def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'create', '--json') + REPO_ARCHIVE,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
)
|
||||
@@ -1172,7 +1180,7 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats():
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
insert_logging_mock(logging.INFO)
|
||||
|
||||
@@ -1209,7 +1217,7 @@ def test_create_archive_with_working_directory_calls_borg_with_working_directory
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory='/working/dir',
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
|
||||
module.create_archive(
|
||||
@@ -1244,7 +1252,7 @@ def test_create_archive_with_exit_codes_calls_borg_using_them():
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
|
||||
module.create_archive(
|
||||
@@ -1278,7 +1286,7 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_answer_ou
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
|
||||
module.create_archive(
|
||||
@@ -1316,7 +1324,7 @@ def test_create_archive_with_files_calls_borg_with_answer_output_log_level():
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
|
||||
module.create_archive(
|
||||
@@ -1350,7 +1358,7 @@ def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_para
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
insert_logging_mock(logging.INFO)
|
||||
|
||||
@@ -1385,7 +1393,7 @@ def test_create_archive_with_progress_calls_borg_with_progress_parameter():
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
|
||||
module.create_archive(
|
||||
@@ -1431,7 +1439,7 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
create_command,
|
||||
@@ -1441,7 +1449,7 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
|
||||
module.create_archive(
|
||||
@@ -1472,7 +1480,7 @@ def test_create_archive_with_json_calls_borg_with_json_flag():
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'create', '--json') + REPO_ARCHIVE,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
).and_return('[]')
|
||||
@@ -1506,7 +1514,7 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_flag():
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'create', '--json') + REPO_ARCHIVE,
|
||||
working_directory=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
).and_return('[]')
|
||||
@@ -1547,7 +1555,7 @@ def test_create_archive_calls_borg_with_working_directory():
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
working_directory='/working/dir',
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
)
|
||||
|
||||
module.create_archive(
|
||||
|
||||
@@ -370,9 +370,9 @@ def test_delete_archives_calls_borg_delete_with_working_directory():
|
||||
flexmock(module.borgmatic.borg.repo_delete).should_receive('delete_repository').never()
|
||||
command = flexmock()
|
||||
flexmock(module).should_receive('make_delete_command').and_return(command)
|
||||
extra_environment = flexmock()
|
||||
environment = flexmock()
|
||||
flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
|
||||
extra_environment
|
||||
environment
|
||||
)
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||
'/working/dir'
|
||||
@@ -380,7 +380,7 @@ def test_delete_archives_calls_borg_delete_with_working_directory():
|
||||
flexmock(module.borgmatic.execute).should_receive('execute_command').with_args(
|
||||
command,
|
||||
output_log_level=logging.ANSWER,
|
||||
extra_environment=extra_environment,
|
||||
environment=environment,
|
||||
working_directory='/working/dir',
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
|
||||
@@ -4,6 +4,12 @@ from borgmatic.borg import environment as module
|
||||
|
||||
|
||||
def test_make_environment_with_passcommand_should_call_it_and_set_passphrase_file_descriptor_in_environment():
|
||||
flexmock(module.os).should_receive('environ').and_return(
|
||||
{'USER': 'root', 'BORG_PASSCOMMAND': 'nope'}
|
||||
)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
).and_return('passphrase')
|
||||
@@ -14,132 +20,153 @@ def test_make_environment_with_passcommand_should_call_it_and_set_passphrase_fil
|
||||
|
||||
environment = module.make_environment({'encryption_passcommand': 'command'})
|
||||
|
||||
assert not environment.get('BORG_PASSCOMMAND')
|
||||
assert environment.get('BORG_PASSPHRASE') is None
|
||||
assert environment.get('BORG_PASSCOMMAND') is None
|
||||
assert environment.get('BORG_PASSPHRASE_FD') == '3'
|
||||
|
||||
|
||||
def test_make_environment_with_passphrase_should_set_environment():
|
||||
def test_make_environment_with_passphrase_should_set_passphrase_file_descriptor_in_environment():
|
||||
flexmock(module.os).should_receive('environ').and_return(
|
||||
{'USER': 'root', 'BORG_PASSPHRASE': 'nope', 'BORG_PASSCOMMAND': 'nope'}
|
||||
)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
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'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module.os).should_receive('pipe').and_return((3, 4))
|
||||
flexmock(module.os).should_receive('write')
|
||||
flexmock(module.os).should_receive('close')
|
||||
flexmock(module.os).should_receive('set_inheritable')
|
||||
|
||||
environment = module.make_environment({'encryption_passphrase': 'pass'})
|
||||
|
||||
assert environment.get('BORG_PASSPHRASE') == 'pass'
|
||||
assert environment.get('BORG_PASSPHRASE') is None
|
||||
assert environment.get('BORG_PASSCOMMAND') is None
|
||||
assert environment.get('BORG_PASSPHRASE_FD') == '3'
|
||||
|
||||
|
||||
def test_make_environment_with_credential_tag_passphrase_should_load_it_and_set_environment():
|
||||
def test_make_environment_with_credential_tag_passphrase_should_load_it_and_set_passphrase_file_descriptor_in_environment():
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
config = {'encryption_passphrase': '{credential systemd pass}'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential',
|
||||
).with_args('{credential systemd pass}', config).and_return('pass')
|
||||
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')
|
||||
).never()
|
||||
flexmock(module.os).should_receive('pipe').and_return((3, 4))
|
||||
flexmock(module.os).should_receive('write')
|
||||
flexmock(module.os).should_receive('close')
|
||||
flexmock(module.os).should_receive('set_inheritable')
|
||||
|
||||
environment = module.make_environment({'encryption_passphrase': '{credential systemd pass}'})
|
||||
environment = module.make_environment(config)
|
||||
|
||||
assert environment.get('BORG_PASSPHRASE') == 'pass'
|
||||
assert environment.get('BORG_PASSPHRASE') is None
|
||||
assert environment.get('BORG_PASSPHRASE_FD') == '3'
|
||||
|
||||
|
||||
def test_make_environment_with_ssh_command_should_set_environment():
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
||||
environment = module.make_environment({'ssh_command': 'ssh -C'})
|
||||
|
||||
assert environment.get('BORG_RSH') == 'ssh -C'
|
||||
|
||||
|
||||
def test_make_environment_without_configuration_sets_certain_environment_variables():
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
||||
environment = module.make_environment({})
|
||||
|
||||
# Default environment variables.
|
||||
assert environment == {
|
||||
'USER': 'root',
|
||||
'BORG_EXIT_CODES': 'modern',
|
||||
'BORG_RELOCATED_REPO_ACCESS_IS_OK': 'no',
|
||||
'BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK': 'no',
|
||||
}
|
||||
|
||||
|
||||
def test_make_environment_without_configuration_does_not_set_certain_environment_variables_if_already_set():
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
def test_make_environment_without_configuration_passes_through_default_environment_variables_untouched():
|
||||
flexmock(module.os).should_receive('environ').and_return(
|
||||
{
|
||||
'USER': 'root',
|
||||
'BORG_RELOCATED_REPO_ACCESS_IS_OK': 'yup',
|
||||
'BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK': 'nah',
|
||||
}
|
||||
)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
flexmock(module.os.environ).should_receive('get').with_args(
|
||||
'BORG_RELOCATED_REPO_ACCESS_IS_OK'
|
||||
).and_return('yup')
|
||||
flexmock(module.os.environ).should_receive('get').with_args(
|
||||
'BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK'
|
||||
).and_return('nah')
|
||||
environment = module.make_environment({})
|
||||
|
||||
assert environment == {'BORG_EXIT_CODES': 'modern'}
|
||||
assert environment == {
|
||||
'USER': 'root',
|
||||
'BORG_RELOCATED_REPO_ACCESS_IS_OK': 'yup',
|
||||
'BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK': 'nah',
|
||||
'BORG_EXIT_CODES': 'modern',
|
||||
}
|
||||
|
||||
|
||||
def test_make_environment_with_relocated_repo_access_true_should_set_environment_yes():
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
||||
environment = module.make_environment({'relocated_repo_access_is_ok': True})
|
||||
|
||||
assert environment.get('BORG_RELOCATED_REPO_ACCESS_IS_OK') == 'yes'
|
||||
|
||||
|
||||
def test_make_environment_with_relocated_repo_access_false_should_set_environment_no():
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
||||
environment = module.make_environment({'relocated_repo_access_is_ok': False})
|
||||
|
||||
assert environment.get('BORG_RELOCATED_REPO_ACCESS_IS_OK') == 'no'
|
||||
|
||||
|
||||
def test_make_environment_check_i_know_what_i_am_doing_true_should_set_environment_YES():
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
||||
environment = module.make_environment({'check_i_know_what_i_am_doing': True})
|
||||
|
||||
assert environment.get('BORG_CHECK_I_KNOW_WHAT_I_AM_DOING') == 'YES'
|
||||
|
||||
|
||||
def test_make_environment_check_i_know_what_i_am_doing_false_should_set_environment_NO():
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
||||
environment = module.make_environment({'check_i_know_what_i_am_doing': False})
|
||||
|
||||
assert environment.get('BORG_CHECK_I_KNOW_WHAT_I_AM_DOING') == 'NO'
|
||||
|
||||
|
||||
def test_make_environment_with_integer_variable_value():
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
||||
environment = module.make_environment({'borg_files_cache_ttl': 40})
|
||||
|
||||
assert environment.get('BORG_FILES_CACHE_TTL') == '40'
|
||||
|
||||
@@ -22,7 +22,7 @@ def insert_execute_command_mock(
|
||||
command,
|
||||
output_file=output_file,
|
||||
output_log_level=module.logging.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=command[0],
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -23,7 +23,7 @@ def insert_execute_command_mock(
|
||||
command,
|
||||
output_file=None if capture else module.DO_NOT_CAPTURE,
|
||||
output_log_level=output_log_level,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=borg_local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -12,7 +12,7 @@ def insert_execute_command_mock(command, destination_path=None, borg_exit_codes=
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
command,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=destination_path,
|
||||
borg_local_path=command[0],
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -587,7 +587,7 @@ def test_extract_archive_calls_borg_with_progress_parameter():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'extract', '--progress', 'repo::archive'),
|
||||
output_file=module.DO_NOT_CAPTURE,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -639,7 +639,7 @@ def test_extract_archive_calls_borg_with_stdout_parameter_and_returns_process():
|
||||
('borg', 'extract', '--stdout', 'repo::archive'),
|
||||
output_file=module.subprocess.PIPE,
|
||||
run_to_completion=False,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -674,7 +674,7 @@ def test_extract_archive_skips_abspath_for_remote_repository():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'extract', 'server:repo::archive'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
|
||||
@@ -285,3 +285,45 @@ def test_warn_for_aggressive_archive_flags_with_glob_archives_and_json_missing_a
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
|
||||
module.warn_for_aggressive_archive_flags(('borg', '--glob-archives', 'foo*'), '{}')
|
||||
|
||||
|
||||
def test_omit_flag_removes_flag_from_arguments():
|
||||
assert module.omit_flag(('borg', 'create', '--flag', '--other'), '--flag') == (
|
||||
'borg',
|
||||
'create',
|
||||
'--other',
|
||||
)
|
||||
|
||||
|
||||
def test_omit_flag_without_flag_present_passes_through_arguments():
|
||||
assert module.omit_flag(('borg', 'create', '--other'), '--flag') == (
|
||||
'borg',
|
||||
'create',
|
||||
'--other',
|
||||
)
|
||||
|
||||
|
||||
def test_omit_flag_and_value_removes_flag_and_value_from_arguments():
|
||||
assert module.omit_flag_and_value(
|
||||
('borg', 'create', '--flag', 'value', '--other'), '--flag'
|
||||
) == (
|
||||
'borg',
|
||||
'create',
|
||||
'--other',
|
||||
)
|
||||
|
||||
|
||||
def test_omit_flag_and_value_with_equals_sign_removes_flag_and_value_from_arguments():
|
||||
assert module.omit_flag_and_value(('borg', 'create', '--flag=value', '--other'), '--flag') == (
|
||||
'borg',
|
||||
'create',
|
||||
'--other',
|
||||
)
|
||||
|
||||
|
||||
def test_omit_flag_and_value_without_flag_present_passes_through_arguments():
|
||||
assert module.omit_flag_and_value(('borg', 'create', '--other'), '--flag') == (
|
||||
'borg',
|
||||
'create',
|
||||
'--other',
|
||||
)
|
||||
|
||||
@@ -514,7 +514,7 @@ def test_display_archives_info_calls_borg_with_working_directory():
|
||||
)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
full_command=object,
|
||||
extra_environment=object,
|
||||
environment=object,
|
||||
working_directory='/working/dir',
|
||||
borg_local_path=object,
|
||||
borg_exit_codes=object,
|
||||
@@ -523,7 +523,7 @@ def test_display_archives_info_calls_borg_with_working_directory():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
full_command=object,
|
||||
output_log_level=object,
|
||||
extra_environment=object,
|
||||
environment=object,
|
||||
working_directory='/working/dir',
|
||||
borg_local_path=object,
|
||||
borg_exit_codes=object,
|
||||
|
||||
@@ -353,7 +353,7 @@ def test_list_archive_calls_borg_with_flags():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'list', 'repo::archive'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -419,7 +419,7 @@ def test_list_archive_calls_borg_with_local_path():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg2', 'list', 'repo::archive'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg2',
|
||||
borg_exit_codes=None,
|
||||
@@ -469,7 +469,7 @@ def test_list_archive_calls_borg_using_exit_codes():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'list', 'repo::archive'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -507,7 +507,7 @@ def test_list_archive_calls_borg_multiple_times_with_find_paths():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'list', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -520,7 +520,7 @@ def test_list_archive_calls_borg_multiple_times_with_find_paths():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'list', 'repo::archive1') + glob_paths,
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -528,7 +528,7 @@ def test_list_archive_calls_borg_multiple_times_with_find_paths():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'list', 'repo::archive2') + glob_paths,
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -576,7 +576,7 @@ def test_list_archive_calls_borg_with_archive():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'list', 'repo::archive'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -699,7 +699,7 @@ def test_list_archive_with_archive_ignores_archive_filter_flag(
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'list', 'repo::archive'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -759,7 +759,7 @@ def test_list_archive_with_find_paths_allows_archive_filter_flag_but_only_passes
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-list', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -808,7 +808,7 @@ def test_list_archive_with_find_paths_allows_archive_filter_flag_but_only_passes
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'list', '--repo', 'repo', 'archive1') + glob_paths,
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -816,7 +816,7 @@ def test_list_archive_with_find_paths_allows_archive_filter_flag_but_only_passes
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'list', '--repo', 'repo', 'archive2') + glob_paths,
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -875,7 +875,7 @@ def test_list_archive_calls_borg_with_working_directory():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'list', 'repo::archive'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory='/working/dir',
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
|
||||
@@ -14,7 +14,7 @@ def insert_execute_command_mock(command, working_directory=None, borg_exit_codes
|
||||
)
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
command,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=command[0],
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -262,7 +262,7 @@ def test_mount_archive_calls_borg_with_foreground_parameter():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'mount', '--foreground', 'repo::archive', '/mnt'),
|
||||
output_file=module.DO_NOT_CAPTURE,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -337,7 +337,7 @@ def test_mount_archive_with_date_based_matching_calls_borg_with_date_based_flags
|
||||
'repo',
|
||||
'/mnt',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
|
||||
@@ -3,26 +3,13 @@ from flexmock import flexmock
|
||||
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 (
|
||||
module.run_passcommand('passcommand', passphrase_configured=True, working_directory=None)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_run_passcommand_without_passphrase_configured_executes_passcommand():
|
||||
def test_run_passcommand_does_not_raise():
|
||||
module.run_passcommand.cache_clear()
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).and_return('passphrase').once()
|
||||
).and_return('passphrase')
|
||||
|
||||
assert (
|
||||
module.run_passcommand('passcommand', passphrase_configured=False, working_directory=None)
|
||||
== 'passphrase'
|
||||
)
|
||||
assert module.run_passcommand('passcommand', working_directory=None) == 'passphrase'
|
||||
|
||||
|
||||
def test_get_passphrase_from_passcommand_with_configured_passcommand_runs_it():
|
||||
@@ -30,9 +17,9 @@ def test_get_passphrase_from_passcommand_with_configured_passcommand_runs_it():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||
'/working'
|
||||
)
|
||||
flexmock(module).should_receive('run_passcommand').with_args(
|
||||
'command', False, '/working'
|
||||
).and_return('passphrase').once()
|
||||
flexmock(module).should_receive('run_passcommand').with_args('command', '/working').and_return(
|
||||
'passphrase'
|
||||
).once()
|
||||
|
||||
assert (
|
||||
module.get_passphrase_from_passcommand(
|
||||
@@ -42,38 +29,10 @@ 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'
|
||||
)
|
||||
flexmock(module).should_receive('run_passcommand').with_args(
|
||||
'command', True, '/working'
|
||||
).and_return(None).once()
|
||||
def test_get_passphrase_from_passcommand_without_configured_passcommand_bails():
|
||||
flexmock(module).should_receive('run_passcommand').never()
|
||||
|
||||
assert (
|
||||
module.get_passphrase_from_passcommand(
|
||||
{'encryption_passphrase': 'passphrase', 'encryption_passcommand': 'command'},
|
||||
)
|
||||
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
|
||||
)
|
||||
assert module.get_passphrase_from_passcommand({}) is None
|
||||
|
||||
|
||||
def test_run_passcommand_caches_passcommand_after_first_call():
|
||||
@@ -82,11 +41,5 @@ def test_run_passcommand_caches_passcommand_after_first_call():
|
||||
'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'
|
||||
)
|
||||
assert module.run_passcommand('passcommand', working_directory=None) == 'passphrase'
|
||||
assert module.run_passcommand('passcommand', working_directory=None) == 'passphrase'
|
||||
|
||||
@@ -17,7 +17,7 @@ def insert_execute_command_mock(
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
prune_command,
|
||||
output_log_level=output_log_level,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=prune_command[0],
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -497,7 +497,7 @@ def test_prune_archives_with_date_based_matching_calls_borg_with_date_based_flag
|
||||
'repo',
|
||||
),
|
||||
output_log_level=logging.INFO,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
|
||||
@@ -36,7 +36,7 @@ def insert_repo_create_command_mock(
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
repo_create_command,
|
||||
output_file=module.DO_NOT_CAPTURE,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=repo_create_command[0],
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -290,7 +290,7 @@ def test_delete_repository_with_defaults_does_not_capture_output():
|
||||
command,
|
||||
output_log_level=module.logging.ANSWER,
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
extra_environment=object,
|
||||
environment=object,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -319,7 +319,7 @@ def test_delete_repository_with_force_captures_output():
|
||||
command,
|
||||
output_log_level=module.logging.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=object,
|
||||
environment=object,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -348,7 +348,7 @@ def test_delete_repository_with_cache_only_captures_output():
|
||||
command,
|
||||
output_log_level=module.logging.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=object,
|
||||
environment=object,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -379,7 +379,7 @@ def test_delete_repository_calls_borg_with_working_directory():
|
||||
command,
|
||||
output_log_level=module.logging.ANSWER,
|
||||
output_file=module.borgmatic.execute.DO_NOT_CAPTURE,
|
||||
extra_environment=object,
|
||||
environment=object,
|
||||
working_directory='/working/dir',
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
|
||||
@@ -24,7 +24,7 @@ def test_display_repository_info_calls_borg_with_flags():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -33,7 +33,7 @@ def test_display_repository_info_calls_borg_with_flags():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'repo-info', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -60,7 +60,7 @@ def test_display_repository_info_without_borg_features_calls_borg_with_info_sub_
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--json', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -69,7 +69,7 @@ def test_display_repository_info_without_borg_features_calls_borg_with_info_sub_
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'info', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -101,7 +101,7 @@ def test_display_repository_info_with_log_info_calls_borg_with_info_flag():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--info', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -110,7 +110,7 @@ def test_display_repository_info_with_log_info_calls_borg_with_info_flag():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'repo-info', '--info', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -142,7 +142,7 @@ def test_display_repository_info_with_log_info_and_json_suppresses_most_borg_out
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -178,7 +178,7 @@ def test_display_repository_info_with_log_debug_calls_borg_with_debug_flag():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--debug', '--show-rc', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -187,7 +187,7 @@ def test_display_repository_info_with_log_debug_calls_borg_with_debug_flag():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'repo-info', '--debug', '--show-rc', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -220,7 +220,7 @@ def test_display_repository_info_with_log_debug_and_json_suppresses_most_borg_ou
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -256,7 +256,7 @@ def test_display_repository_info_with_json_calls_borg_with_json_flag():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -291,7 +291,7 @@ def test_display_repository_info_with_local_path_calls_borg_via_local_path():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg1', 'repo-info', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -300,7 +300,7 @@ def test_display_repository_info_with_local_path_calls_borg_via_local_path():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg1', 'repo-info', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg1',
|
||||
borg_exit_codes=None,
|
||||
@@ -334,7 +334,7 @@ def test_display_repository_info_with_exit_codes_calls_borg_using_them():
|
||||
borg_exit_codes = flexmock()
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -343,7 +343,7 @@ def test_display_repository_info_with_exit_codes_calls_borg_using_them():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'repo-info', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -375,7 +375,7 @@ def test_display_repository_info_with_remote_path_calls_borg_with_remote_path_fl
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--remote-path', 'borg1', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -384,7 +384,7 @@ def test_display_repository_info_with_remote_path_calls_borg_with_remote_path_fl
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'repo-info', '--remote-path', 'borg1', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -417,7 +417,7 @@ def test_display_repository_info_with_umask_calls_borg_with_umask_flags():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--umask', '077', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -426,7 +426,7 @@ def test_display_repository_info_with_umask_calls_borg_with_umask_flags():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'repo-info', '--umask', '077', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -462,7 +462,7 @@ def test_display_repository_info_with_log_json_calls_borg_with_log_json_flags():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--log-json', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -471,7 +471,7 @@ def test_display_repository_info_with_log_json_calls_borg_with_log_json_flags():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'repo-info', '--log-json', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -504,7 +504,7 @@ def test_display_repository_info_with_lock_wait_calls_borg_with_lock_wait_flags(
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--lock-wait', '5', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -513,7 +513,7 @@ def test_display_repository_info_with_lock_wait_calls_borg_with_lock_wait_flags(
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'repo-info', '--lock-wait', '5', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -547,7 +547,7 @@ def test_display_repository_info_calls_borg_with_working_directory():
|
||||
)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'repo-info', '--json', '--repo', 'repo'),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory='/working/dir',
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -556,7 +556,7 @@ def test_display_repository_info_calls_borg_with_working_directory():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('borg', 'repo-info', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory='/working/dir',
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
|
||||
@@ -39,7 +39,7 @@ def test_resolve_archive_name_calls_borg_with_flags():
|
||||
('borg', 'list') + BORG_LIST_LATEST_ARGUMENTS,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
).and_return(expected_archive + '\n')
|
||||
|
||||
@@ -61,7 +61,7 @@ def test_resolve_archive_name_with_log_info_calls_borg_without_info_flag():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'list') + BORG_LIST_LATEST_ARGUMENTS,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -86,7 +86,7 @@ def test_resolve_archive_name_with_log_debug_calls_borg_without_debug_flag():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'list') + BORG_LIST_LATEST_ARGUMENTS,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -111,7 +111,7 @@ def test_resolve_archive_name_with_local_path_calls_borg_via_local_path():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg1', 'list') + BORG_LIST_LATEST_ARGUMENTS,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg1',
|
||||
borg_exit_codes=None,
|
||||
@@ -137,7 +137,7 @@ def test_resolve_archive_name_with_exit_codes_calls_borg_using_them():
|
||||
borg_exit_codes = flexmock()
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'list') + BORG_LIST_LATEST_ARGUMENTS,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -161,7 +161,7 @@ def test_resolve_archive_name_with_remote_path_calls_borg_with_remote_path_flags
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'list', '--remote-path', 'borg1') + BORG_LIST_LATEST_ARGUMENTS,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -186,7 +186,7 @@ def test_resolve_archive_name_with_umask_calls_borg_with_umask_flags():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'list', '--umask', '077') + BORG_LIST_LATEST_ARGUMENTS,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -209,7 +209,7 @@ def test_resolve_archive_name_without_archives_raises():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'list') + BORG_LIST_LATEST_ARGUMENTS,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -232,7 +232,7 @@ def test_resolve_archive_name_with_log_json_calls_borg_with_log_json_flags():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'list', '--log-json') + BORG_LIST_LATEST_ARGUMENTS,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -257,7 +257,7 @@ def test_resolve_archive_name_with_lock_wait_calls_borg_with_lock_wait_flags():
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('borg', 'list', '--lock-wait', 'okay') + BORG_LIST_LATEST_ARGUMENTS,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -285,7 +285,7 @@ def test_resolve_archive_name_calls_borg_with_working_directory():
|
||||
('borg', 'list') + BORG_LIST_LATEST_ARGUMENTS,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory='/working/dir',
|
||||
).and_return(expected_archive + '\n')
|
||||
|
||||
@@ -773,7 +773,7 @@ def test_list_repository_calls_borg_with_working_directory():
|
||||
)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
full_command=object,
|
||||
extra_environment=object,
|
||||
environment=object,
|
||||
working_directory='/working/dir',
|
||||
borg_local_path=object,
|
||||
borg_exit_codes=object,
|
||||
@@ -782,7 +782,7 @@ def test_list_repository_calls_borg_with_working_directory():
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
full_command=object,
|
||||
output_log_level=object,
|
||||
extra_environment=object,
|
||||
environment=object,
|
||||
working_directory='/working/dir',
|
||||
borg_local_path=object,
|
||||
borg_exit_codes=object,
|
||||
|
||||
@@ -21,7 +21,7 @@ def test_transfer_archives_calls_borg_with_flags():
|
||||
('borg', 'transfer', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -55,7 +55,7 @@ def test_transfer_archives_with_dry_run_calls_borg_with_dry_run_flag():
|
||||
('borg', 'transfer', '--repo', 'repo', '--dry-run'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -86,7 +86,7 @@ def test_transfer_archives_with_log_info_calls_borg_with_info_flag():
|
||||
('borg', 'transfer', '--info', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -117,7 +117,7 @@ def test_transfer_archives_with_log_debug_calls_borg_with_debug_flag():
|
||||
('borg', 'transfer', '--debug', '--show-rc', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -151,7 +151,7 @@ def test_transfer_archives_with_archive_calls_borg_with_match_archives_flag():
|
||||
('borg', 'transfer', '--match-archives', 'archive', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -184,7 +184,7 @@ def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_fl
|
||||
('borg', 'transfer', '--match-archives', 'sh:foo*', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -217,7 +217,7 @@ def test_transfer_archives_with_archive_name_format_calls_borg_with_match_archiv
|
||||
('borg', 'transfer', '--match-archives', 'sh:bar-*', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -248,7 +248,7 @@ def test_transfer_archives_with_local_path_calls_borg_via_local_path():
|
||||
('borg2', 'transfer', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg2',
|
||||
borg_exit_codes=None,
|
||||
@@ -281,7 +281,7 @@ def test_transfer_archives_with_exit_codes_calls_borg_using_them():
|
||||
('borg', 'transfer', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
@@ -315,7 +315,7 @@ def test_transfer_archives_with_remote_path_calls_borg_with_remote_path_flags():
|
||||
('borg', 'transfer', '--remote-path', 'borg2', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -349,7 +349,7 @@ def test_transfer_archives_with_umask_calls_borg_with_umask_flags():
|
||||
('borg', 'transfer', '--umask', '077', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -383,7 +383,7 @@ def test_transfer_archives_with_log_json_calls_borg_with_log_json_flags():
|
||||
('borg', 'transfer', '--log-json', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -418,7 +418,7 @@ def test_transfer_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
|
||||
('borg', 'transfer', '--lock-wait', '5', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -449,7 +449,7 @@ def test_transfer_archives_with_progress_calls_borg_with_progress_flag():
|
||||
('borg', 'transfer', '--progress', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=module.DO_NOT_CAPTURE,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -484,7 +484,7 @@ def test_transfer_archives_passes_through_arguments_to_borg(argument_name):
|
||||
('borg', 'transfer', flag_name, 'value', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -521,7 +521,7 @@ def test_transfer_archives_with_source_repository_calls_borg_with_other_repo_fla
|
||||
('borg', 'transfer', '--repo', 'repo', '--other-repo', 'other'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -566,7 +566,7 @@ def test_transfer_archives_with_date_based_matching_calls_borg_with_date_based_f
|
||||
),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=None,
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
@@ -605,7 +605,7 @@ def test_transfer_archives_calls_borg_with_working_directory():
|
||||
('borg', 'transfer', '--repo', 'repo'),
|
||||
output_log_level=module.borgmatic.logger.ANSWER,
|
||||
output_file=None,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory='/working/dir',
|
||||
borg_local_path='borg',
|
||||
borg_exit_codes=None,
|
||||
|
||||
@@ -23,7 +23,7 @@ def insert_execute_command_and_capture_output_mock(
|
||||
)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
command,
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
working_directory=working_directory,
|
||||
borg_local_path=borg_local_path,
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import io
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.hooks.credential import container as module
|
||||
|
||||
|
||||
@pytest.mark.parametrize('credential_parameters', ((), ('foo', 'bar')))
|
||||
def test_load_credential_with_invalid_credential_parameters_raises(credential_parameters):
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=credential_parameters
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_invalid_secret_name_raises():
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('this is invalid',)
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_reads_named_secret_from_file():
|
||||
credential_stream = io.StringIO('password')
|
||||
credential_stream.name = '/run/secrets/mysecret'
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/run/secrets/mysecret').and_return(credential_stream)
|
||||
|
||||
assert (
|
||||
module.load_credential(hook_config={}, config={}, credential_parameters=('mysecret',))
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_custom_secrets_directory_looks_there_for_secret_file():
|
||||
config = {'container': {'secrets_directory': '/secrets'}}
|
||||
credential_stream = io.StringIO('password')
|
||||
credential_stream.name = '/secrets/mysecret'
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/secrets/mysecret').and_return(credential_stream)
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config=config['container'], config=config, credential_parameters=('mysecret',)
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_custom_secrets_directory_prefixes_it_with_working_directory():
|
||||
config = {'container': {'secrets_directory': 'secrets'}, 'working_directory': '/working'}
|
||||
credential_stream = io.StringIO('password')
|
||||
credential_stream.name = '/working/secrets/mysecret'
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/working/secrets/mysecret').and_return(
|
||||
credential_stream
|
||||
)
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config=config['container'], config=config, credential_parameters=('mysecret',)
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_file_not_found_error_raises():
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/run/secrets/mysecret').and_raise(FileNotFoundError)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(hook_config={}, config={}, credential_parameters=('mysecret',))
|
||||
@@ -0,0 +1,68 @@
|
||||
import io
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.hooks.credential import file as module
|
||||
|
||||
|
||||
@pytest.mark.parametrize('credential_parameters', ((), ('foo', 'bar')))
|
||||
def test_load_credential_with_invalid_credential_parameters_raises(credential_parameters):
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=credential_parameters
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_invalid_credential_name_raises():
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('this is invalid',)
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_reads_named_credential_from_file():
|
||||
credential_stream = io.StringIO('password')
|
||||
credential_stream.name = '/credentials/mycredential'
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/credentials/mycredential').and_return(
|
||||
credential_stream
|
||||
)
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('/credentials/mycredential',)
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_reads_named_credential_from_file_using_working_directory():
|
||||
credential_stream = io.StringIO('password')
|
||||
credential_stream.name = '/working/credentials/mycredential'
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/working/credentials/mycredential').and_return(
|
||||
credential_stream
|
||||
)
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config={},
|
||||
config={'working_directory': '/working'},
|
||||
credential_parameters=('credentials/mycredential',),
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_file_not_found_error_raises():
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/credentials/mycredential').and_raise(
|
||||
FileNotFoundError
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('/credentials/mycredential',)
|
||||
)
|
||||
@@ -0,0 +1,80 @@
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.hooks.credential import keepassxc as module
|
||||
|
||||
|
||||
@pytest.mark.parametrize('credential_parameters', ((), ('foo',), ('foo', 'bar', 'baz')))
|
||||
def test_load_credential_with_invalid_credential_parameters_raises(credential_parameters):
|
||||
flexmock(module.borgmatic.execute).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=credential_parameters
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_missing_database_raises():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.execute).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('database.kdbx', 'mypassword')
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_present_database_fetches_password_from_keepassxc():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).with_args(
|
||||
(
|
||||
'keepassxc-cli',
|
||||
'show',
|
||||
'--show-protected',
|
||||
'--attributes',
|
||||
'Password',
|
||||
'database.kdbx',
|
||||
'mypassword',
|
||||
)
|
||||
).and_return(
|
||||
'password'
|
||||
).once()
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('database.kdbx', 'mypassword')
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_custom_keepassxc_cli_command_calls_it():
|
||||
config = {'keepassxc': {'keepassxc_cli_command': '/usr/local/bin/keepassxc-cli --some-option'}}
|
||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).with_args(
|
||||
(
|
||||
'/usr/local/bin/keepassxc-cli',
|
||||
'--some-option',
|
||||
'show',
|
||||
'--show-protected',
|
||||
'--attributes',
|
||||
'Password',
|
||||
'database.kdbx',
|
||||
'mypassword',
|
||||
)
|
||||
).and_return(
|
||||
'password'
|
||||
).once()
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config=config['keepassxc'],
|
||||
config=config,
|
||||
credential_parameters=('database.kdbx', 'mypassword'),
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
@@ -4,39 +4,82 @@ from flexmock import flexmock
|
||||
from borgmatic.hooks.credential import parse as module
|
||||
|
||||
|
||||
def test_resolve_credential_passes_through_string_without_credential_tag():
|
||||
def test_hash_adapter_is_always_equal():
|
||||
assert module.Hash_adapter({1: 2}) == module.Hash_adapter({3: 4})
|
||||
|
||||
|
||||
def test_hash_adapter_alwaysh_hashes_the_same():
|
||||
assert hash(module.Hash_adapter({1: 2})) == hash(module.Hash_adapter({3: 4}))
|
||||
|
||||
|
||||
def test_cache_ignoring_unhashable_arguments_caches_arguments_after_first_call():
|
||||
hashable = 3
|
||||
unhashable = {1, 2}
|
||||
calls = 0
|
||||
|
||||
@module.cache_ignoring_unhashable_arguments
|
||||
def function(first, second, third):
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
|
||||
assert first == hashable
|
||||
assert second == unhashable
|
||||
assert third == unhashable
|
||||
|
||||
return first
|
||||
|
||||
assert function(hashable, unhashable, third=unhashable) == hashable
|
||||
assert calls == 1
|
||||
|
||||
assert function(hashable, unhashable, third=unhashable) == hashable
|
||||
assert calls == 1
|
||||
|
||||
|
||||
def test_resolve_credential_passes_through_string_without_credential():
|
||||
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}'
|
||||
assert module.resolve_credential('{no credentials here}', config={}) == '{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
|
||||
assert module.resolve_credential(None, config={}) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('invalid_value', ('{credential}', '{credential }', '{credential systemd}'))
|
||||
def test_resolve_credential_with_invalid_credential_tag_raises(invalid_value):
|
||||
def test_resolve_credential_with_invalid_credential_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)
|
||||
module.resolve_credential(invalid_value, config={})
|
||||
|
||||
|
||||
def test_resolve_credential_with_valid_credential_tag_loads_credential():
|
||||
def test_resolve_credential_with_valid_credential_loads_credential():
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
|
||||
'load_credential',
|
||||
{},
|
||||
'systemd',
|
||||
'mycredential',
|
||||
('mycredential',),
|
||||
).and_return('result').once()
|
||||
|
||||
assert module.resolve_credential('{credential systemd mycredential}') == 'result'
|
||||
assert module.resolve_credential('{credential systemd mycredential}', config={}) == 'result'
|
||||
|
||||
|
||||
def test_resolve_credential_with_valid_credential_and_quoted_parameters_loads_credential():
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
|
||||
'load_credential',
|
||||
{},
|
||||
'systemd',
|
||||
('my credential',),
|
||||
).and_return('result').once()
|
||||
|
||||
assert module.resolve_credential('{credential systemd "my credential"}', config={}) == 'result'
|
||||
|
||||
|
||||
def test_resolve_credential_caches_credential_after_first_call():
|
||||
@@ -45,8 +88,8 @@ def test_resolve_credential_caches_credential_after_first_call():
|
||||
'load_credential',
|
||||
{},
|
||||
'systemd',
|
||||
'mycredential',
|
||||
('mycredential',),
|
||||
).and_return('result').once()
|
||||
|
||||
assert module.resolve_credential('{credential systemd mycredential}') == 'result'
|
||||
assert module.resolve_credential('{credential systemd mycredential}') == 'result'
|
||||
assert module.resolve_credential('{credential systemd mycredential}', config={}) == 'result'
|
||||
assert module.resolve_credential('{credential systemd mycredential}', config={}) == 'result'
|
||||
|
||||
@@ -7,13 +7,23 @@ from flexmock import flexmock
|
||||
from borgmatic.hooks.credential import systemd as module
|
||||
|
||||
|
||||
@pytest.mark.parametrize('credential_parameters', ((), ('foo', 'bar')))
|
||||
def test_load_credential_with_invalid_credential_parameters_raises(credential_parameters):
|
||||
flexmock(module.os.environ).should_receive('get').never()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=credential_parameters
|
||||
)
|
||||
|
||||
|
||||
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')
|
||||
module.load_credential(hook_config={}, config={}, credential_parameters=('mycredential',))
|
||||
|
||||
|
||||
def test_load_credential_with_invalid_credential_name_raises():
|
||||
@@ -22,7 +32,9 @@ def test_load_credential_with_invalid_credential_name_raises():
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(hook_config={}, config={}, credential_name='../../my!@#$credential')
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('../../my!@#$credential',)
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_reads_named_credential_from_file():
|
||||
@@ -35,7 +47,7 @@ def test_load_credential_reads_named_credential_from_file():
|
||||
builtins.should_receive('open').with_args('/var/mycredential').and_return(credential_stream)
|
||||
|
||||
assert (
|
||||
module.load_credential(hook_config={}, config={}, credential_name='mycredential')
|
||||
module.load_credential(hook_config={}, config={}, credential_parameters=('mycredential',))
|
||||
== 'password'
|
||||
)
|
||||
|
||||
@@ -48,4 +60,4 @@ def test_load_credential_with_file_not_found_error_raises():
|
||||
builtins.should_receive('open').with_args('/var/mycredential').and_raise(FileNotFoundError)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(hook_config={}, config={}, credential_name='mycredential')
|
||||
module.load_credential(hook_config={}, config={}, credential_parameters=('mycredential',))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.borg.pattern import Pattern, Pattern_style, Pattern_type
|
||||
from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
|
||||
from borgmatic.hooks.data_source import btrfs as module
|
||||
|
||||
|
||||
@@ -52,9 +52,14 @@ def test_get_subvolume_mount_points_with_findmnt_json_missing_filesystems_errors
|
||||
def test_get_subvolumes_collects_subvolumes_matching_patterns():
|
||||
flexmock(module).should_receive('get_subvolume_mount_points').and_return(('/mnt1', '/mnt2'))
|
||||
|
||||
contained_pattern = Pattern(
|
||||
'/mnt1',
|
||||
type=Pattern_type.ROOT,
|
||||
source=Pattern_source.CONFIG,
|
||||
)
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args('/mnt1', object).and_return((Pattern('/mnt1'),))
|
||||
).with_args('/mnt1', object).and_return((contained_pattern,))
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args('/mnt2', object).and_return(())
|
||||
@@ -66,7 +71,69 @@ def test_get_subvolumes_collects_subvolumes_matching_patterns():
|
||||
Pattern('/mnt1'),
|
||||
Pattern('/mnt3'),
|
||||
],
|
||||
) == (module.Subvolume('/mnt1', contained_patterns=(Pattern('/mnt1'),)),)
|
||||
) == (module.Subvolume('/mnt1', contained_patterns=(contained_pattern,)),)
|
||||
|
||||
|
||||
def test_get_subvolumes_skips_non_root_patterns():
|
||||
flexmock(module).should_receive('get_subvolume_mount_points').and_return(('/mnt1', '/mnt2'))
|
||||
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args('/mnt1', object).and_return(
|
||||
(
|
||||
Pattern(
|
||||
'/mnt1',
|
||||
type=Pattern_type.EXCLUDE,
|
||||
source=Pattern_source.CONFIG,
|
||||
),
|
||||
)
|
||||
)
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args('/mnt2', object).and_return(())
|
||||
|
||||
assert (
|
||||
module.get_subvolumes(
|
||||
'btrfs',
|
||||
'findmnt',
|
||||
patterns=[
|
||||
Pattern('/mnt1'),
|
||||
Pattern('/mnt3'),
|
||||
],
|
||||
)
|
||||
== ()
|
||||
)
|
||||
|
||||
|
||||
def test_get_subvolumes_skips_non_config_patterns():
|
||||
flexmock(module).should_receive('get_subvolume_mount_points').and_return(('/mnt1', '/mnt2'))
|
||||
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args('/mnt1', object).and_return(
|
||||
(
|
||||
Pattern(
|
||||
'/mnt1',
|
||||
type=Pattern_type.ROOT,
|
||||
source=Pattern_source.HOOK,
|
||||
),
|
||||
)
|
||||
)
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args('/mnt2', object).and_return(())
|
||||
|
||||
assert (
|
||||
module.get_subvolumes(
|
||||
'btrfs',
|
||||
'findmnt',
|
||||
patterns=[
|
||||
Pattern('/mnt1'),
|
||||
Pattern('/mnt3'),
|
||||
],
|
||||
)
|
||||
== ()
|
||||
)
|
||||
|
||||
|
||||
def test_get_subvolumes_without_patterns_collects_all_subvolumes():
|
||||
@@ -520,6 +587,18 @@ def test_remove_data_source_dumps_deletes_snapshots():
|
||||
flexmock(module).should_receive('delete_snapshot').with_args(
|
||||
'btrfs', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
|
||||
).never()
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/mnt/subvol1/.borgmatic-1234'
|
||||
).and_return(True)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/mnt/subvol1/.borgmatic-5678'
|
||||
).and_return(True)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/mnt/subvol2/.borgmatic-1234'
|
||||
).and_return(True)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/mnt/subvol2/.borgmatic-5678'
|
||||
).and_return(True)
|
||||
flexmock(module.shutil).should_receive('rmtree').with_args(
|
||||
'/mnt/subvol1/.borgmatic-1234'
|
||||
).once()
|
||||
@@ -846,3 +925,48 @@ def test_remove_data_source_dumps_with_delete_snapshot_called_process_error_bail
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
|
||||
def test_remove_data_source_dumps_with_root_subvolume_skips_duplicate_removal():
|
||||
config = {'btrfs': {}}
|
||||
flexmock(module).should_receive('get_subvolumes').and_return(
|
||||
(module.Subvolume('/', contained_patterns=(Pattern('/etc'),)),)
|
||||
)
|
||||
|
||||
flexmock(module).should_receive('make_snapshot_path').with_args('/').and_return(
|
||||
'/.borgmatic-1234'
|
||||
)
|
||||
|
||||
flexmock(module.borgmatic.config.paths).should_receive(
|
||||
'replace_temporary_subdirectory_with_glob'
|
||||
).with_args(
|
||||
'/.borgmatic-1234',
|
||||
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
|
||||
).and_return(
|
||||
'/.borgmatic-*'
|
||||
)
|
||||
|
||||
flexmock(module.glob).should_receive('glob').with_args('/.borgmatic-*').and_return(
|
||||
('/.borgmatic-1234', '/.borgmatic-5678')
|
||||
)
|
||||
|
||||
flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-1234').and_return(
|
||||
True
|
||||
).and_return(False)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-5678').and_return(
|
||||
True
|
||||
).and_return(False)
|
||||
|
||||
flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-1234').once()
|
||||
flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-5678').once()
|
||||
|
||||
flexmock(module.os.path).should_receive('isdir').with_args('').and_return(False)
|
||||
|
||||
flexmock(module.shutil).should_receive('rmtree').never()
|
||||
|
||||
module.remove_data_source_dumps(
|
||||
hook_config=config['btrfs'],
|
||||
config=config,
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.borg.pattern import Pattern, Pattern_style, Pattern_type
|
||||
from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
|
||||
from borgmatic.hooks.data_source import lvm as module
|
||||
|
||||
|
||||
@@ -37,14 +37,20 @@ def test_get_logical_volumes_filters_by_patterns():
|
||||
}
|
||||
'''
|
||||
)
|
||||
contained = {Pattern('/mnt/lvolume'), Pattern('/mnt/lvolume/subdir')}
|
||||
contained = {
|
||||
Pattern('/mnt/lvolume', source=Pattern_source.CONFIG),
|
||||
Pattern('/mnt/lvolume/subdir', source=Pattern_source.CONFIG),
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args(None, contained).never()
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args('/mnt/lvolume', contained).and_return(
|
||||
(Pattern('/mnt/lvolume'), Pattern('/mnt/lvolume/subdir'))
|
||||
(
|
||||
Pattern('/mnt/lvolume', source=Pattern_source.CONFIG),
|
||||
Pattern('/mnt/lvolume/subdir', source=Pattern_source.CONFIG),
|
||||
)
|
||||
)
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
@@ -54,17 +60,116 @@ def test_get_logical_volumes_filters_by_patterns():
|
||||
).with_args('/mnt/notlvm', contained).never()
|
||||
|
||||
assert module.get_logical_volumes(
|
||||
'lsblk', patterns=(Pattern('/mnt/lvolume'), Pattern('/mnt/lvolume/subdir'))
|
||||
'lsblk',
|
||||
patterns=(
|
||||
Pattern('/mnt/lvolume', source=Pattern_source.CONFIG),
|
||||
Pattern('/mnt/lvolume/subdir', source=Pattern_source.CONFIG),
|
||||
),
|
||||
) == (
|
||||
module.Logical_volume(
|
||||
name='vgroup-lvolume',
|
||||
device_path='/dev/mapper/vgroup-lvolume',
|
||||
mount_point='/mnt/lvolume',
|
||||
contained_patterns=(Pattern('/mnt/lvolume'), Pattern('/mnt/lvolume/subdir')),
|
||||
contained_patterns=(
|
||||
Pattern('/mnt/lvolume', source=Pattern_source.CONFIG),
|
||||
Pattern('/mnt/lvolume/subdir', source=Pattern_source.CONFIG),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_get_logical_volumes_skips_non_root_patterns():
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).and_return(
|
||||
'''
|
||||
{
|
||||
"blockdevices": [
|
||||
{
|
||||
"name": "vgroup-lvolume",
|
||||
"path": "/dev/mapper/vgroup-lvolume",
|
||||
"mountpoint": "/mnt/lvolume",
|
||||
"type": "lvm"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
)
|
||||
contained = {
|
||||
Pattern('/mnt/lvolume', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG),
|
||||
Pattern('/mnt/lvolume/subdir', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG),
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args(None, contained).never()
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args('/mnt/lvolume', contained).and_return(
|
||||
(
|
||||
Pattern('/mnt/lvolume', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG),
|
||||
Pattern('/mnt/lvolume/subdir', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG),
|
||||
)
|
||||
)
|
||||
|
||||
assert (
|
||||
module.get_logical_volumes(
|
||||
'lsblk',
|
||||
patterns=(
|
||||
Pattern('/mnt/lvolume', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG),
|
||||
Pattern(
|
||||
'/mnt/lvolume/subdir', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG
|
||||
),
|
||||
),
|
||||
)
|
||||
== ()
|
||||
)
|
||||
|
||||
|
||||
def test_get_logical_volumes_skips_non_config_patterns():
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).and_return(
|
||||
'''
|
||||
{
|
||||
"blockdevices": [
|
||||
{
|
||||
"name": "vgroup-lvolume",
|
||||
"path": "/dev/mapper/vgroup-lvolume",
|
||||
"mountpoint": "/mnt/lvolume",
|
||||
"type": "lvm"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
)
|
||||
contained = {
|
||||
Pattern('/mnt/lvolume', source=Pattern_source.HOOK),
|
||||
Pattern('/mnt/lvolume/subdir', source=Pattern_source.HOOK),
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args(None, contained).never()
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).with_args('/mnt/lvolume', contained).and_return(
|
||||
(
|
||||
Pattern('/mnt/lvolume', source=Pattern_source.HOOK),
|
||||
Pattern('/mnt/lvolume/subdir', source=Pattern_source.HOOK),
|
||||
)
|
||||
)
|
||||
|
||||
assert (
|
||||
module.get_logical_volumes(
|
||||
'lsblk',
|
||||
patterns=(
|
||||
Pattern('/mnt/lvolume', source=Pattern_source.HOOK),
|
||||
Pattern('/mnt/lvolume/subdir', source=Pattern_source.HOOK),
|
||||
),
|
||||
)
|
||||
== ()
|
||||
)
|
||||
|
||||
|
||||
def test_get_logical_volumes_with_invalid_lsblk_json_errors():
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
@@ -138,13 +243,13 @@ def test_snapshot_logical_volume_with_non_percentage_snapshot_name_uses_lvcreate
|
||||
(
|
||||
(
|
||||
Pattern('/foo/bar/baz'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/./foo/bar/baz'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/b33f/./foo/bar/baz'),
|
||||
),
|
||||
(Pattern('/foo/bar'), Pattern('/run/borgmatic/lvm_snapshots/./foo/bar')),
|
||||
(Pattern('/foo/bar'), Pattern('/run/borgmatic/lvm_snapshots/b33f/./foo/bar')),
|
||||
(
|
||||
Pattern('^/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
|
||||
Pattern(
|
||||
'^/run/borgmatic/lvm_snapshots/./foo/bar',
|
||||
'^/run/borgmatic/lvm_snapshots/b33f/./foo/bar',
|
||||
Pattern_type.INCLUDE,
|
||||
Pattern_style.REGULAR_EXPRESSION,
|
||||
),
|
||||
@@ -152,40 +257,48 @@ def test_snapshot_logical_volume_with_non_percentage_snapshot_name_uses_lvcreate
|
||||
(
|
||||
Pattern('/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
|
||||
Pattern(
|
||||
'/run/borgmatic/lvm_snapshots/./foo/bar',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/./foo/bar',
|
||||
Pattern_type.INCLUDE,
|
||||
Pattern_style.REGULAR_EXPRESSION,
|
||||
),
|
||||
),
|
||||
(Pattern('/foo'), Pattern('/run/borgmatic/lvm_snapshots/./foo')),
|
||||
(Pattern('/'), Pattern('/run/borgmatic/lvm_snapshots/./')),
|
||||
(Pattern('/foo'), Pattern('/run/borgmatic/lvm_snapshots/b33f/./foo')),
|
||||
(Pattern('/'), Pattern('/run/borgmatic/lvm_snapshots/b33f/./')),
|
||||
),
|
||||
)
|
||||
def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_path(
|
||||
pattern, expected_pattern
|
||||
):
|
||||
assert module.make_borg_snapshot_pattern(pattern, '/run/borgmatic') == expected_pattern
|
||||
flexmock(module.hashlib).should_receive('shake_256').and_return(
|
||||
flexmock(hexdigest=lambda length: 'b33f')
|
||||
)
|
||||
|
||||
assert (
|
||||
module.make_borg_snapshot_pattern(
|
||||
pattern, flexmock(mount_point='/something'), '/run/borgmatic'
|
||||
)
|
||||
== expected_pattern
|
||||
)
|
||||
|
||||
|
||||
def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
|
||||
config = {'lvm': {}}
|
||||
patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
|
||||
flexmock(module).should_receive('get_logical_volumes').and_return(
|
||||
(
|
||||
module.Logical_volume(
|
||||
name='lvolume1',
|
||||
device_path='/dev/lvolume1',
|
||||
mount_point='/mnt/lvolume1',
|
||||
contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
|
||||
),
|
||||
module.Logical_volume(
|
||||
name='lvolume2',
|
||||
device_path='/dev/lvolume2',
|
||||
mount_point='/mnt/lvolume2',
|
||||
contained_patterns=(Pattern('/mnt/lvolume2'),),
|
||||
),
|
||||
)
|
||||
logical_volumes = (
|
||||
module.Logical_volume(
|
||||
name='lvolume1',
|
||||
device_path='/dev/lvolume1',
|
||||
mount_point='/mnt/lvolume1',
|
||||
contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
|
||||
),
|
||||
module.Logical_volume(
|
||||
name='lvolume2',
|
||||
device_path='/dev/lvolume2',
|
||||
mount_point='/mnt/lvolume2',
|
||||
contained_patterns=(Pattern('/mnt/lvolume2'),),
|
||||
),
|
||||
)
|
||||
flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
|
||||
flexmock(module.os).should_receive('getpid').and_return(1234)
|
||||
flexmock(module).should_receive('snapshot_logical_volume').with_args(
|
||||
'lvcreate', 'lvolume1_borgmatic-1234', '/dev/lvolume1', module.DEFAULT_SNAPSHOT_SIZE
|
||||
@@ -203,18 +316,21 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
|
||||
).and_return(
|
||||
(module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
|
||||
)
|
||||
flexmock(module.hashlib).should_receive('shake_256').and_return(
|
||||
flexmock(hexdigest=lambda length: 'b33f')
|
||||
)
|
||||
flexmock(module).should_receive('mount_snapshot').with_args(
|
||||
'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/mnt/lvolume1'
|
||||
'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
|
||||
).once()
|
||||
flexmock(module).should_receive('mount_snapshot').with_args(
|
||||
'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/mnt/lvolume2'
|
||||
'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
|
||||
).once()
|
||||
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
|
||||
Pattern('/mnt/lvolume1/subdir'), '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume1/subdir'))
|
||||
Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
|
||||
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
|
||||
Pattern('/mnt/lvolume2'), '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume2'))
|
||||
Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
|
||||
|
||||
assert (
|
||||
module.dump_data_sources(
|
||||
@@ -229,8 +345,8 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
|
||||
)
|
||||
|
||||
assert patterns == [
|
||||
Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume1/subdir'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume2'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
|
||||
]
|
||||
|
||||
|
||||
@@ -259,22 +375,21 @@ def test_dump_data_sources_with_no_logical_volumes_skips_snapshots():
|
||||
def test_dump_data_sources_uses_snapshot_size_for_snapshot():
|
||||
config = {'lvm': {'snapshot_size': '1000PB'}}
|
||||
patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
|
||||
flexmock(module).should_receive('get_logical_volumes').and_return(
|
||||
(
|
||||
module.Logical_volume(
|
||||
name='lvolume1',
|
||||
device_path='/dev/lvolume1',
|
||||
mount_point='/mnt/lvolume1',
|
||||
contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
|
||||
),
|
||||
module.Logical_volume(
|
||||
name='lvolume2',
|
||||
device_path='/dev/lvolume2',
|
||||
mount_point='/mnt/lvolume2',
|
||||
contained_patterns=(Pattern('/mnt/lvolume2'),),
|
||||
),
|
||||
)
|
||||
logical_volumes = (
|
||||
module.Logical_volume(
|
||||
name='lvolume1',
|
||||
device_path='/dev/lvolume1',
|
||||
mount_point='/mnt/lvolume1',
|
||||
contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
|
||||
),
|
||||
module.Logical_volume(
|
||||
name='lvolume2',
|
||||
device_path='/dev/lvolume2',
|
||||
mount_point='/mnt/lvolume2',
|
||||
contained_patterns=(Pattern('/mnt/lvolume2'),),
|
||||
),
|
||||
)
|
||||
flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
|
||||
flexmock(module.os).should_receive('getpid').and_return(1234)
|
||||
flexmock(module).should_receive('snapshot_logical_volume').with_args(
|
||||
'lvcreate',
|
||||
@@ -298,18 +413,21 @@ def test_dump_data_sources_uses_snapshot_size_for_snapshot():
|
||||
).and_return(
|
||||
(module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
|
||||
)
|
||||
flexmock(module.hashlib).should_receive('shake_256').and_return(
|
||||
flexmock(hexdigest=lambda length: 'b33f')
|
||||
)
|
||||
flexmock(module).should_receive('mount_snapshot').with_args(
|
||||
'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/mnt/lvolume1'
|
||||
'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
|
||||
).once()
|
||||
flexmock(module).should_receive('mount_snapshot').with_args(
|
||||
'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/mnt/lvolume2'
|
||||
'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
|
||||
).once()
|
||||
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
|
||||
Pattern('/mnt/lvolume1/subdir'), '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume1/subdir'))
|
||||
Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
|
||||
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
|
||||
Pattern('/mnt/lvolume2'), '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume2'))
|
||||
Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
|
||||
|
||||
assert (
|
||||
module.dump_data_sources(
|
||||
@@ -324,8 +442,8 @@ def test_dump_data_sources_uses_snapshot_size_for_snapshot():
|
||||
)
|
||||
|
||||
assert patterns == [
|
||||
Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume1/subdir'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume2'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
|
||||
]
|
||||
|
||||
|
||||
@@ -339,22 +457,21 @@ def test_dump_data_sources_uses_custom_commands():
|
||||
},
|
||||
}
|
||||
patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
|
||||
flexmock(module).should_receive('get_logical_volumes').and_return(
|
||||
(
|
||||
module.Logical_volume(
|
||||
name='lvolume1',
|
||||
device_path='/dev/lvolume1',
|
||||
mount_point='/mnt/lvolume1',
|
||||
contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
|
||||
),
|
||||
module.Logical_volume(
|
||||
name='lvolume2',
|
||||
device_path='/dev/lvolume2',
|
||||
mount_point='/mnt/lvolume2',
|
||||
contained_patterns=(Pattern('/mnt/lvolume2'),),
|
||||
),
|
||||
)
|
||||
logical_volumes = (
|
||||
module.Logical_volume(
|
||||
name='lvolume1',
|
||||
device_path='/dev/lvolume1',
|
||||
mount_point='/mnt/lvolume1',
|
||||
contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
|
||||
),
|
||||
module.Logical_volume(
|
||||
name='lvolume2',
|
||||
device_path='/dev/lvolume2',
|
||||
mount_point='/mnt/lvolume2',
|
||||
contained_patterns=(Pattern('/mnt/lvolume2'),),
|
||||
),
|
||||
)
|
||||
flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
|
||||
flexmock(module.os).should_receive('getpid').and_return(1234)
|
||||
flexmock(module).should_receive('snapshot_logical_volume').with_args(
|
||||
'/usr/local/bin/lvcreate',
|
||||
@@ -378,18 +495,25 @@ def test_dump_data_sources_uses_custom_commands():
|
||||
).and_return(
|
||||
(module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
|
||||
)
|
||||
flexmock(module.hashlib).should_receive('shake_256').and_return(
|
||||
flexmock(hexdigest=lambda length: 'b33f')
|
||||
)
|
||||
flexmock(module).should_receive('mount_snapshot').with_args(
|
||||
'/usr/local/bin/mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/mnt/lvolume1'
|
||||
'/usr/local/bin/mount',
|
||||
'/dev/lvolume1_snap',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
|
||||
).once()
|
||||
flexmock(module).should_receive('mount_snapshot').with_args(
|
||||
'/usr/local/bin/mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/mnt/lvolume2'
|
||||
'/usr/local/bin/mount',
|
||||
'/dev/lvolume2_snap',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
|
||||
).once()
|
||||
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
|
||||
Pattern('/mnt/lvolume1/subdir'), '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume1/subdir'))
|
||||
Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
|
||||
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
|
||||
Pattern('/mnt/lvolume2'), '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume2'))
|
||||
Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
|
||||
|
||||
assert (
|
||||
module.dump_data_sources(
|
||||
@@ -404,8 +528,8 @@ def test_dump_data_sources_uses_custom_commands():
|
||||
)
|
||||
|
||||
assert patterns == [
|
||||
Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume1/subdir'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume2'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
|
||||
]
|
||||
|
||||
|
||||
@@ -463,22 +587,21 @@ def test_dump_data_sources_with_dry_run_skips_snapshots_and_does_not_touch_patte
|
||||
def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained_patterns():
|
||||
config = {'lvm': {}}
|
||||
patterns = [Pattern('/hmm')]
|
||||
flexmock(module).should_receive('get_logical_volumes').and_return(
|
||||
(
|
||||
module.Logical_volume(
|
||||
name='lvolume1',
|
||||
device_path='/dev/lvolume1',
|
||||
mount_point='/mnt/lvolume1',
|
||||
contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
|
||||
),
|
||||
module.Logical_volume(
|
||||
name='lvolume2',
|
||||
device_path='/dev/lvolume2',
|
||||
mount_point='/mnt/lvolume2',
|
||||
contained_patterns=(Pattern('/mnt/lvolume2'),),
|
||||
),
|
||||
)
|
||||
logical_volumes = (
|
||||
module.Logical_volume(
|
||||
name='lvolume1',
|
||||
device_path='/dev/lvolume1',
|
||||
mount_point='/mnt/lvolume1',
|
||||
contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
|
||||
),
|
||||
module.Logical_volume(
|
||||
name='lvolume2',
|
||||
device_path='/dev/lvolume2',
|
||||
mount_point='/mnt/lvolume2',
|
||||
contained_patterns=(Pattern('/mnt/lvolume2'),),
|
||||
),
|
||||
)
|
||||
flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
|
||||
flexmock(module.os).should_receive('getpid').and_return(1234)
|
||||
flexmock(module).should_receive('snapshot_logical_volume').with_args(
|
||||
'lvcreate', 'lvolume1_borgmatic-1234', '/dev/lvolume1', module.DEFAULT_SNAPSHOT_SIZE
|
||||
@@ -496,18 +619,21 @@ def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained
|
||||
).and_return(
|
||||
(module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
|
||||
)
|
||||
flexmock(module.hashlib).should_receive('shake_256').and_return(
|
||||
flexmock(hexdigest=lambda length: 'b33f')
|
||||
)
|
||||
flexmock(module).should_receive('mount_snapshot').with_args(
|
||||
'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/mnt/lvolume1'
|
||||
'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
|
||||
).once()
|
||||
flexmock(module).should_receive('mount_snapshot').with_args(
|
||||
'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/mnt/lvolume2'
|
||||
'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
|
||||
).once()
|
||||
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
|
||||
Pattern('/mnt/lvolume1/subdir'), '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume1/subdir'))
|
||||
Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
|
||||
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
|
||||
Pattern('/mnt/lvolume2'), '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume2'))
|
||||
Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
|
||||
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
|
||||
|
||||
assert (
|
||||
module.dump_data_sources(
|
||||
@@ -523,8 +649,8 @@ def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained
|
||||
|
||||
assert patterns == [
|
||||
Pattern('/hmm'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume1/subdir'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/./mnt/lvolume2'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
|
||||
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
|
||||
]
|
||||
|
||||
|
||||
@@ -694,16 +820,19 @@ def test_remove_data_source_dumps_unmounts_and_remove_snapshots():
|
||||
flexmock(module.borgmatic.config.paths).should_receive(
|
||||
'replace_temporary_subdirectory_with_glob'
|
||||
).and_return('/run/borgmatic')
|
||||
flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
|
||||
flexmock(module.glob).should_receive('glob').replace_with(
|
||||
lambda path: [path.replace('*', 'b33f')]
|
||||
)
|
||||
flexmock(module.os.path).should_receive('isdir').and_return(True)
|
||||
flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
|
||||
flexmock(module.shutil).should_receive('rmtree')
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume1',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
|
||||
).once()
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume2',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
|
||||
).once()
|
||||
flexmock(module).should_receive('get_snapshots').and_return(
|
||||
(
|
||||
@@ -799,9 +928,11 @@ def test_remove_data_source_dumps_with_missing_snapshot_directory_skips_unmount(
|
||||
flexmock(module.borgmatic.config.paths).should_receive(
|
||||
'replace_temporary_subdirectory_with_glob'
|
||||
).and_return('/run/borgmatic')
|
||||
flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
|
||||
flexmock(module.glob).should_receive('glob').replace_with(
|
||||
lambda path: [path.replace('*', 'b33f')]
|
||||
)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots'
|
||||
'/run/borgmatic/lvm_snapshots/b33f'
|
||||
).and_return(False)
|
||||
flexmock(module.shutil).should_receive('rmtree').never()
|
||||
flexmock(module).should_receive('unmount_snapshot').never()
|
||||
@@ -843,24 +974,92 @@ def test_remove_data_source_dumps_with_missing_snapshot_mount_path_skips_unmount
|
||||
flexmock(module.borgmatic.config.paths).should_receive(
|
||||
'replace_temporary_subdirectory_with_glob'
|
||||
).and_return('/run/borgmatic')
|
||||
flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
|
||||
flexmock(module.glob).should_receive('glob').replace_with(
|
||||
lambda path: [path.replace('*', 'b33f')]
|
||||
)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots'
|
||||
'/run/borgmatic/lvm_snapshots/b33f'
|
||||
).and_return(True)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume1'
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
|
||||
).and_return(False)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume2'
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
|
||||
).and_return(True)
|
||||
flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
|
||||
flexmock(module.shutil).should_receive('rmtree')
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume1',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
|
||||
).never()
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume2',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
|
||||
).once()
|
||||
flexmock(module).should_receive('get_snapshots').and_return(
|
||||
(
|
||||
module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
|
||||
module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
|
||||
),
|
||||
)
|
||||
flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
|
||||
flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
|
||||
|
||||
module.remove_data_source_dumps(
|
||||
hook_config=config['lvm'],
|
||||
config=config,
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
|
||||
def test_remove_data_source_dumps_with_empty_snapshot_mount_path_skips_unmount():
|
||||
config = {'lvm': {}}
|
||||
flexmock(module).should_receive('get_logical_volumes').and_return(
|
||||
(
|
||||
module.Logical_volume(
|
||||
name='lvolume1',
|
||||
device_path='/dev/lvolume1',
|
||||
mount_point='/mnt/lvolume1',
|
||||
contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
|
||||
),
|
||||
module.Logical_volume(
|
||||
name='lvolume2',
|
||||
device_path='/dev/lvolume2',
|
||||
mount_point='/mnt/lvolume2',
|
||||
contained_patterns=(Pattern('/mnt/lvolume2'),),
|
||||
),
|
||||
)
|
||||
)
|
||||
flexmock(module.borgmatic.config.paths).should_receive(
|
||||
'replace_temporary_subdirectory_with_glob'
|
||||
).and_return('/run/borgmatic')
|
||||
flexmock(module.glob).should_receive('glob').replace_with(
|
||||
lambda path: [path.replace('*', 'b33f')]
|
||||
)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots/b33f'
|
||||
).and_return(True)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
|
||||
).and_return(True)
|
||||
flexmock(module.os).should_receive('listdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
|
||||
).and_return([])
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
|
||||
).and_return(True)
|
||||
flexmock(module.os).should_receive('listdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
|
||||
).and_return(['file.txt'])
|
||||
flexmock(module.shutil).should_receive('rmtree')
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
|
||||
).never()
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
|
||||
).once()
|
||||
flexmock(module).should_receive('get_snapshots').and_return(
|
||||
(
|
||||
@@ -900,24 +1099,27 @@ def test_remove_data_source_dumps_with_successful_mount_point_removal_skips_unmo
|
||||
flexmock(module.borgmatic.config.paths).should_receive(
|
||||
'replace_temporary_subdirectory_with_glob'
|
||||
).and_return('/run/borgmatic')
|
||||
flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
|
||||
flexmock(module.glob).should_receive('glob').replace_with(
|
||||
lambda path: [path.replace('*', 'b33f')]
|
||||
)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots'
|
||||
'/run/borgmatic/lvm_snapshots/b33f'
|
||||
).and_return(True)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume1'
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
|
||||
).and_return(True).and_return(False)
|
||||
flexmock(module.os.path).should_receive('isdir').with_args(
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume2'
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
|
||||
).and_return(True).and_return(True)
|
||||
flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
|
||||
flexmock(module.shutil).should_receive('rmtree')
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume1',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
|
||||
).never()
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume2',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
|
||||
).once()
|
||||
flexmock(module).should_receive('get_snapshots').and_return(
|
||||
(
|
||||
@@ -957,16 +1159,19 @@ def test_remove_data_source_dumps_bails_for_missing_umount_command():
|
||||
flexmock(module.borgmatic.config.paths).should_receive(
|
||||
'replace_temporary_subdirectory_with_glob'
|
||||
).and_return('/run/borgmatic')
|
||||
flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
|
||||
flexmock(module.glob).should_receive('glob').replace_with(
|
||||
lambda path: [path.replace('*', 'b33f')]
|
||||
)
|
||||
flexmock(module.os.path).should_receive('isdir').and_return(True)
|
||||
flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
|
||||
flexmock(module.shutil).should_receive('rmtree')
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume1',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
|
||||
).and_raise(FileNotFoundError)
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume2',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
|
||||
).never()
|
||||
flexmock(module).should_receive('get_snapshots').never()
|
||||
flexmock(module).should_receive('remove_snapshot').never()
|
||||
@@ -979,7 +1184,7 @@ def test_remove_data_source_dumps_bails_for_missing_umount_command():
|
||||
)
|
||||
|
||||
|
||||
def test_remove_data_source_dumps_bails_for_umount_command_error():
|
||||
def test_remove_data_source_dumps_swallows_umount_command_error():
|
||||
config = {'lvm': {}}
|
||||
flexmock(module).should_receive('get_logical_volumes').and_return(
|
||||
(
|
||||
@@ -1000,19 +1205,28 @@ def test_remove_data_source_dumps_bails_for_umount_command_error():
|
||||
flexmock(module.borgmatic.config.paths).should_receive(
|
||||
'replace_temporary_subdirectory_with_glob'
|
||||
).and_return('/run/borgmatic')
|
||||
flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
|
||||
flexmock(module.glob).should_receive('glob').replace_with(
|
||||
lambda path: [path.replace('*', 'b33f')]
|
||||
)
|
||||
flexmock(module.os.path).should_receive('isdir').and_return(True)
|
||||
flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
|
||||
flexmock(module.shutil).should_receive('rmtree')
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume1',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
|
||||
).and_raise(module.subprocess.CalledProcessError(1, 'wtf'))
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume2',
|
||||
).never()
|
||||
flexmock(module).should_receive('get_snapshots').never()
|
||||
flexmock(module).should_receive('remove_snapshot').never()
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
|
||||
).once()
|
||||
flexmock(module).should_receive('get_snapshots').and_return(
|
||||
(
|
||||
module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
|
||||
module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
|
||||
),
|
||||
)
|
||||
flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
|
||||
flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
|
||||
|
||||
module.remove_data_source_dumps(
|
||||
hook_config=config['lvm'],
|
||||
@@ -1043,16 +1257,19 @@ def test_remove_data_source_dumps_bails_for_missing_lvs_command():
|
||||
flexmock(module.borgmatic.config.paths).should_receive(
|
||||
'replace_temporary_subdirectory_with_glob'
|
||||
).and_return('/run/borgmatic')
|
||||
flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
|
||||
flexmock(module.glob).should_receive('glob').replace_with(
|
||||
lambda path: [path.replace('*', 'b33f')]
|
||||
)
|
||||
flexmock(module.os.path).should_receive('isdir').and_return(True)
|
||||
flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
|
||||
flexmock(module.shutil).should_receive('rmtree')
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume1',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
|
||||
).once()
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume2',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
|
||||
).once()
|
||||
flexmock(module).should_receive('get_snapshots').and_raise(FileNotFoundError)
|
||||
flexmock(module).should_receive('remove_snapshot').never()
|
||||
@@ -1086,16 +1303,19 @@ def test_remove_data_source_dumps_bails_for_lvs_command_error():
|
||||
flexmock(module.borgmatic.config.paths).should_receive(
|
||||
'replace_temporary_subdirectory_with_glob'
|
||||
).and_return('/run/borgmatic')
|
||||
flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
|
||||
flexmock(module.glob).should_receive('glob').replace_with(
|
||||
lambda path: [path.replace('*', 'b33f')]
|
||||
)
|
||||
flexmock(module.os.path).should_receive('isdir').and_return(True)
|
||||
flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
|
||||
flexmock(module.shutil).should_receive('rmtree')
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume1',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
|
||||
).once()
|
||||
flexmock(module).should_receive('unmount_snapshot').with_args(
|
||||
'umount',
|
||||
'/run/borgmatic/lvm_snapshots/mnt/lvolume2',
|
||||
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
|
||||
).once()
|
||||
flexmock(module).should_receive('get_snapshots').and_raise(
|
||||
module.subprocess.CalledProcessError(1, 'wtf')
|
||||
@@ -1133,6 +1353,7 @@ def test_remove_data_source_with_dry_run_skips_snapshot_unmount_and_delete():
|
||||
).and_return('/run/borgmatic')
|
||||
flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
|
||||
flexmock(module.os.path).should_receive('isdir').and_return(True)
|
||||
flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
|
||||
flexmock(module.shutil).should_receive('rmtree').never()
|
||||
flexmock(module).should_receive('unmount_snapshot').never()
|
||||
flexmock(module).should_receive('get_snapshots').and_return(
|
||||
|
||||
@@ -7,33 +7,33 @@ from borgmatic.hooks.data_source import mariadb as module
|
||||
|
||||
|
||||
def test_database_names_to_dump_passes_through_name():
|
||||
extra_environment = flexmock()
|
||||
environment = flexmock()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'foo'}, extra_environment, dry_run=False)
|
||||
names = module.database_names_to_dump({'name': 'foo'}, {}, environment, dry_run=False)
|
||||
|
||||
assert names == ('foo',)
|
||||
|
||||
|
||||
def test_database_names_to_dump_bails_for_dry_run():
|
||||
extra_environment = flexmock()
|
||||
environment = flexmock()
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=True)
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, environment, dry_run=True)
|
||||
|
||||
assert names == ()
|
||||
|
||||
|
||||
def test_database_names_to_dump_queries_mariadb_for_database_names():
|
||||
extra_environment = flexmock()
|
||||
environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('mariadb', '--skip-column-names', '--batch', '--execute', 'show schemas'),
|
||||
extra_environment=extra_environment,
|
||||
environment=environment,
|
||||
).and_return('foo\nbar\nmysql\n').once()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=False)
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, environment, dry_run=False)
|
||||
|
||||
assert names == ('foo', 'bar')
|
||||
|
||||
@@ -53,9 +53,10 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
@@ -63,9 +64,10 @@ def test_dump_data_sources_dumps_each_database():
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': name},
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
extra_environment=object,
|
||||
environment={'USER': 'root'},
|
||||
dry_run=object,
|
||||
dry_run_label=object,
|
||||
).and_return(process).once()
|
||||
@@ -87,18 +89,20 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database=database,
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=('foo',),
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
|
||||
dry_run=object,
|
||||
dry_run_label=object,
|
||||
).and_return(process).once()
|
||||
@@ -117,15 +121,17 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: 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'},
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=('foo', 'bar'),
|
||||
extra_environment=object,
|
||||
environment={'USER': 'root'},
|
||||
dry_run=object,
|
||||
dry_run_label=object,
|
||||
).and_return(process).once()
|
||||
@@ -144,17 +150,19 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
|
||||
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': name, 'format': 'sql'},
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
extra_environment=object,
|
||||
environment={'USER': 'root'},
|
||||
dry_run=object,
|
||||
dry_run_label=object,
|
||||
).and_return(process).once()
|
||||
@@ -183,10 +191,10 @@ def test_database_names_to_dump_runs_mariadb_with_list_options():
|
||||
'--execute',
|
||||
'show schemas',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
).and_return(('foo\nbar')).once()
|
||||
|
||||
assert module.database_names_to_dump(database, None, '') == ('foo', 'bar')
|
||||
assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_non_default_mariadb_with_list_options():
|
||||
@@ -196,7 +204,7 @@ def test_database_names_to_dump_runs_non_default_mariadb_with_list_options():
|
||||
'mariadb_command': 'custom_mariadb',
|
||||
}
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
full_command=(
|
||||
'custom_mariadb', # Custom MariaDB command
|
||||
'--defaults-extra-file=mariadb.cnf',
|
||||
@@ -207,7 +215,7 @@ def test_database_names_to_dump_runs_non_default_mariadb_with_list_options():
|
||||
),
|
||||
).and_return(('foo\nbar')).once()
|
||||
|
||||
assert module.database_names_to_dump(database, None, '') == ('foo', 'bar')
|
||||
assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_execute_dump_command_runs_mariadb_dump():
|
||||
@@ -216,7 +224,7 @@ def test_execute_dump_command_runs_mariadb_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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -228,16 +236,17 @@ def test_execute_dump_command_runs_mariadb_dump():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -251,7 +260,7 @@ def test_execute_dump_command_runs_mariadb_dump_without_add_drop_database():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -262,16 +271,17 @@ def test_execute_dump_command_runs_mariadb_dump_without_add_drop_database():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'add_drop_database': False},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -285,7 +295,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_hostname_and_port():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -303,16 +313,17 @@ def test_execute_dump_command_runs_mariadb_dump_with_hostname_and_port():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -326,7 +337,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_username_and_password():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -340,16 +351,17 @@ def test_execute_dump_command_runs_mariadb_dump_with_username_and_password():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'MYSQL_PWD': 'trustsome1'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'MYSQL_PWD': 'trustsome1'},
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -363,7 +375,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_options():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -376,16 +388,17 @@ def test_execute_dump_command_runs_mariadb_dump_with_options():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'options': '--stuff=such'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -399,7 +412,7 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -412,7 +425,7 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -423,9 +436,10 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
|
||||
'mariadb_dump_command': 'custom_mariadb_dump',
|
||||
'options': '--stuff=such',
|
||||
}, # Custom MariaDB dump command specified
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -442,9 +456,10 @@ def test_execute_dump_command_with_duplicate_dump_skips_mariadb_dump():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=True,
|
||||
dry_run_label='SO DRY',
|
||||
)
|
||||
@@ -457,7 +472,7 @@ def test_execute_dump_command_with_dry_run_skips_mariadb_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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
@@ -465,9 +480,10 @@ def test_execute_dump_command_with_dry_run_skips_mariadb_dump():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=True,
|
||||
dry_run_label='SO DRY',
|
||||
)
|
||||
@@ -478,9 +494,10 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@@ -500,9 +517,10 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@@ -527,13 +545,14 @@ def test_restore_data_source_dump_runs_mariadb_to_restore():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -558,13 +577,14 @@ def test_restore_data_source_dump_runs_mariadb_with_options():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -591,13 +611,14 @@ def test_restore_data_source_dump_runs_non_default_mariadb_with_options():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('custom_mariadb', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -622,7 +643,8 @@ def test_restore_data_source_dump_runs_mariadb_with_hostname_and_port():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
@@ -637,7 +659,7 @@ def test_restore_data_source_dump_runs_mariadb_with_hostname_and_port():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -662,13 +684,14 @@ def test_restore_data_source_dump_runs_mariadb_with_username_and_password():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch', '--user', 'root'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -703,7 +726,8 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
@@ -720,7 +744,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'MYSQL_PWD': 'clipassword'},
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'clipassword'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -757,7 +781,8 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
@@ -774,7 +799,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'MYSQL_PWD': 'restorepass'},
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'restorepass'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -798,7 +823,8 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
|
||||
@@ -126,7 +126,7 @@ def test_dump_data_sources_runs_mongodump_with_username_and_password():
|
||||
)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -247,9 +247,9 @@ 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)
|
||||
).replace_with(lambda value, config: value)
|
||||
|
||||
command = module.build_dump_command(database, dump_filename='test', dump_format='archive')
|
||||
command = module.build_dump_command(database, {}, dump_filename='test', dump_format='archive')
|
||||
|
||||
assert "'bob; naughty-command'" in command
|
||||
|
||||
@@ -262,7 +262,7 @@ def test_restore_data_source_dump_runs_mongorestore():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--archive', '--drop'],
|
||||
processes=[extract_process],
|
||||
@@ -296,7 +296,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_hostname_and_port():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -344,7 +344,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_username_and_password()
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -398,7 +398,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -456,7 +456,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -502,7 +502,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_options():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--archive', '--drop', '--harder'],
|
||||
processes=[extract_process],
|
||||
@@ -534,7 +534,7 @@ def test_restore_databases_dump_runs_mongorestore_with_schemas():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -574,7 +574,7 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--archive'],
|
||||
processes=[extract_process],
|
||||
@@ -605,7 +605,7 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -631,7 +631,7 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--dir', '/dump/path', '--drop'],
|
||||
processes=[],
|
||||
|
||||
@@ -7,36 +7,36 @@ from borgmatic.hooks.data_source import mysql as module
|
||||
|
||||
|
||||
def test_database_names_to_dump_passes_through_name():
|
||||
extra_environment = flexmock()
|
||||
environment = flexmock()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'foo'}, extra_environment, dry_run=False)
|
||||
names = module.database_names_to_dump({'name': 'foo'}, {}, environment, dry_run=False)
|
||||
|
||||
assert names == ('foo',)
|
||||
|
||||
|
||||
def test_database_names_to_dump_bails_for_dry_run():
|
||||
extra_environment = flexmock()
|
||||
environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=True)
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, environment, dry_run=True)
|
||||
|
||||
assert names == ()
|
||||
|
||||
|
||||
def test_database_names_to_dump_queries_mysql_for_database_names():
|
||||
extra_environment = flexmock()
|
||||
environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('mysql', '--skip-column-names', '--batch', '--execute', 'show schemas'),
|
||||
extra_environment=extra_environment,
|
||||
environment=environment,
|
||||
).and_return('foo\nbar\nmysql\n').once()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=False)
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, environment, dry_run=False)
|
||||
|
||||
assert names == ('foo', 'bar')
|
||||
|
||||
@@ -56,6 +56,7 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
@@ -63,9 +64,10 @@ def test_dump_data_sources_dumps_each_database():
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': name},
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
extra_environment=object,
|
||||
environment={'USER': 'root'},
|
||||
dry_run=object,
|
||||
dry_run_label=object,
|
||||
).and_return(process).once()
|
||||
@@ -87,18 +89,20 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database=database,
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=('foo',),
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
|
||||
dry_run=object,
|
||||
dry_run_label=object,
|
||||
).and_return(process).once()
|
||||
@@ -117,12 +121,14 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': 'all'},
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=('foo', 'bar'),
|
||||
extra_environment=object,
|
||||
environment={'USER': 'root'},
|
||||
dry_run=object,
|
||||
dry_run_label=object,
|
||||
).and_return(process).once()
|
||||
@@ -141,14 +147,16 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
|
||||
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': name, 'format': 'sql'},
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
extra_environment=object,
|
||||
environment={'USER': 'root'},
|
||||
dry_run=object,
|
||||
dry_run_label=object,
|
||||
).and_return(process).once()
|
||||
@@ -177,10 +185,10 @@ def test_database_names_to_dump_runs_mysql_with_list_options():
|
||||
'--execute',
|
||||
'show schemas',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
).and_return(('foo\nbar')).once()
|
||||
|
||||
assert module.database_names_to_dump(database, None, '') == ('foo', 'bar')
|
||||
assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_non_default_mysql_with_list_options():
|
||||
@@ -190,7 +198,7 @@ def test_database_names_to_dump_runs_non_default_mysql_with_list_options():
|
||||
'mysql_command': 'custom_mysql',
|
||||
}
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
full_command=(
|
||||
'custom_mysql', # Custom MySQL command
|
||||
'--defaults-extra-file=my.cnf',
|
||||
@@ -201,7 +209,7 @@ def test_database_names_to_dump_runs_non_default_mysql_with_list_options():
|
||||
),
|
||||
).and_return(('foo\nbar')).once()
|
||||
|
||||
assert module.database_names_to_dump(database, None, '') == ('foo', 'bar')
|
||||
assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_execute_dump_command_runs_mysqldump():
|
||||
@@ -210,7 +218,7 @@ def test_execute_dump_command_runs_mysqldump():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -222,16 +230,17 @@ def test_execute_dump_command_runs_mysqldump():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -245,7 +254,7 @@ def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -256,16 +265,17 @@ def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'add_drop_database': False},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -279,7 +289,7 @@ def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -297,16 +307,17 @@ def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -320,7 +331,7 @@ def test_execute_dump_command_runs_mysqldump_with_username_and_password():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -334,16 +345,17 @@ def test_execute_dump_command_runs_mysqldump_with_username_and_password():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'MYSQL_PWD': 'trustsome1'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'MYSQL_PWD': 'trustsome1'},
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -357,7 +369,7 @@ def test_execute_dump_command_runs_mysqldump_with_options():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -370,16 +382,17 @@ def test_execute_dump_command_runs_mysqldump_with_options():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'options': '--stuff=such'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -393,7 +406,7 @@ def test_execute_dump_command_runs_non_default_mysqldump():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -405,7 +418,7 @@ def test_execute_dump_command_runs_non_default_mysqldump():
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -415,9 +428,10 @@ def test_execute_dump_command_runs_non_default_mysqldump():
|
||||
'name': 'foo',
|
||||
'mysql_dump_command': 'custom_mysqldump',
|
||||
}, # Custom MySQL dump command specified
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -434,9 +448,10 @@ def test_execute_dump_command_with_duplicate_dump_skips_mysqldump():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=True,
|
||||
dry_run_label='SO DRY',
|
||||
)
|
||||
@@ -449,7 +464,7 @@ def test_execute_dump_command_with_dry_run_skips_mysqldump():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
@@ -457,9 +472,10 @@ def test_execute_dump_command_with_dry_run_skips_mysqldump():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
environment=None,
|
||||
dry_run=True,
|
||||
dry_run_label='SO DRY',
|
||||
)
|
||||
@@ -470,9 +486,10 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@@ -492,9 +509,10 @@ 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.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@@ -519,13 +537,14 @@ def test_restore_data_source_dump_runs_mysql_to_restore():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -550,13 +569,14 @@ def test_restore_data_source_dump_runs_mysql_with_options():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -581,13 +601,14 @@ def test_restore_data_source_dump_runs_non_default_mysql_with_options():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('custom_mysql', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -612,7 +633,8 @@ def test_restore_data_source_dump_runs_mysql_with_hostname_and_port():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
@@ -627,7 +649,7 @@ def test_restore_data_source_dump_runs_mysql_with_hostname_and_port():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -652,13 +674,14 @@ def test_restore_data_source_dump_runs_mysql_with_username_and_password():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch', '--user', 'root'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -693,7 +716,8 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
@@ -710,7 +734,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'MYSQL_PWD': 'clipassword'},
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'clipassword'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -747,7 +771,8 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
@@ -764,7 +789,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'MYSQL_PWD': 'restorepass'},
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'restorepass'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -788,7 +813,8 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
|
||||
@@ -6,7 +6,7 @@ from flexmock import flexmock
|
||||
from borgmatic.hooks.data_source import postgresql as module
|
||||
|
||||
|
||||
def test_make_extra_environment_maps_options_to_environment():
|
||||
def test_make_environment_maps_options_to_environment():
|
||||
database = {
|
||||
'name': 'foo',
|
||||
'password': 'pass',
|
||||
@@ -17,6 +17,7 @@ def test_make_extra_environment_maps_options_to_environment():
|
||||
'ssl_crl': 'crl.crl',
|
||||
}
|
||||
expected = {
|
||||
'USER': 'root',
|
||||
'PGPASSWORD': 'pass',
|
||||
'PGSSLMODE': 'require',
|
||||
'PGSSLCERT': 'cert.crt',
|
||||
@@ -24,84 +25,84 @@ def test_make_extra_environment_maps_options_to_environment():
|
||||
'PGSSLROOTCERT': 'root.crt',
|
||||
'PGSSLCRL': 'crl.crl',
|
||||
}
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
|
||||
extra_env = module.make_extra_environment(database)
|
||||
|
||||
assert extra_env == expected
|
||||
assert module.make_environment(database, {}) == expected
|
||||
|
||||
|
||||
def test_make_extra_environment_with_cli_password_sets_correct_password():
|
||||
def test_make_environment_with_cli_password_sets_correct_password():
|
||||
database = {'name': 'foo', 'restore_password': 'trustsome1', 'password': 'anotherpassword'}
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
|
||||
extra = module.make_extra_environment(
|
||||
database, restore_connection_params={'password': 'clipassword'}
|
||||
environment = module.make_environment(
|
||||
database, {}, restore_connection_params={'password': 'clipassword'}
|
||||
)
|
||||
|
||||
assert extra['PGPASSWORD'] == 'clipassword'
|
||||
assert environment['PGPASSWORD'] == 'clipassword'
|
||||
|
||||
|
||||
def test_make_extra_environment_without_cli_password_or_configured_password_does_not_set_password():
|
||||
def test_make_environment_without_cli_password_or_configured_password_does_not_set_password():
|
||||
database = {'name': 'foo'}
|
||||
|
||||
extra = module.make_extra_environment(
|
||||
database, restore_connection_params={'username': 'someone'}
|
||||
environment = module.make_environment(
|
||||
database, {}, restore_connection_params={'username': 'someone'}
|
||||
)
|
||||
|
||||
assert 'PGPASSWORD' not in extra
|
||||
assert 'PGPASSWORD' not in environment
|
||||
|
||||
|
||||
def test_make_extra_environment_without_ssl_mode_does_not_set_ssl_mode():
|
||||
def test_make_environment_without_ssl_mode_does_not_set_ssl_mode():
|
||||
database = {'name': 'foo'}
|
||||
|
||||
extra = module.make_extra_environment(database)
|
||||
environment = module.make_environment(database, {})
|
||||
|
||||
assert 'PGSSLMODE' not in extra
|
||||
assert 'PGSSLMODE' not in environment
|
||||
|
||||
|
||||
def test_database_names_to_dump_passes_through_individual_database_name():
|
||||
database = {'name': 'foo'}
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
|
||||
|
||||
|
||||
def test_database_names_to_dump_passes_through_individual_database_name_with_format():
|
||||
database = {'name': 'foo', 'format': 'custom'}
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
|
||||
|
||||
|
||||
def test_database_names_to_dump_passes_through_all_without_format():
|
||||
database = {'name': 'all'}
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('all',)
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('all',)
|
||||
|
||||
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=True) == ()
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=True) == ()
|
||||
|
||||
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').and_return(
|
||||
'foo,test,\nbar,test,"stuff and such"'
|
||||
)
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
@@ -111,7 +112,7 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostnam
|
||||
database = {'name': 'all', 'format': 'custom', 'hostname': 'localhost', 'port': 1234}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'psql',
|
||||
@@ -125,10 +126,10 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostnam
|
||||
'--port',
|
||||
'1234',
|
||||
),
|
||||
extra_environment=object,
|
||||
environment=object,
|
||||
).and_return('foo,test,\nbar,test,"stuff and such"')
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
@@ -138,7 +139,7 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_usernam
|
||||
database = {'name': 'all', 'format': 'custom', 'username': 'postgres'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'psql',
|
||||
@@ -150,10 +151,10 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_usernam
|
||||
'--username',
|
||||
'postgres',
|
||||
),
|
||||
extra_environment=object,
|
||||
environment=object,
|
||||
).and_return('foo,test,\nbar,test,"stuff and such"')
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
@@ -163,13 +164,13 @@ 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)
|
||||
).replace_with(lambda value, config: 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,
|
||||
environment=object,
|
||||
).and_return('foo,test,\nbar,test,"stuff and such"')
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
@@ -179,12 +180,12 @@ def test_database_names_to_dump_with_all_and_format_excludes_particular_database
|
||||
database = {'name': 'all', 'format': 'custom'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').and_return(
|
||||
'foo,test,\ntemplate0,test,blah'
|
||||
)
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
|
||||
|
||||
|
||||
def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
|
||||
@@ -195,7 +196,7 @@ def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'docker',
|
||||
@@ -210,10 +211,10 @@ def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
|
||||
'--csv',
|
||||
'--tuples-only',
|
||||
),
|
||||
extra_environment=object,
|
||||
environment=object,
|
||||
).and_return('foo,text').once()
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
|
||||
|
||||
|
||||
def test_use_streaming_true_for_any_non_directory_format_databases():
|
||||
@@ -237,7 +238,7 @@ def test_use_streaming_false_for_no_databases():
|
||||
def test_dump_data_sources_runs_pg_dump_for_each_database():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
processes = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
@@ -248,7 +249,7 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
@@ -265,7 +266,7 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
|
||||
f'databases/localhost/{name}',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -284,7 +285,7 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
|
||||
|
||||
def test_dump_data_sources_raises_when_no_database_names_to_dump():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(())
|
||||
|
||||
@@ -301,7 +302,7 @@ def test_dump_data_sources_raises_when_no_database_names_to_dump():
|
||||
|
||||
def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(())
|
||||
|
||||
@@ -317,7 +318,7 @@ def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
|
||||
|
||||
def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
@@ -344,7 +345,7 @@ def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
|
||||
|
||||
def test_dump_data_sources_with_dry_run_skips_pg_dump():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
@@ -355,7 +356,7 @@ def test_dump_data_sources_with_dry_run_skips_pg_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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
|
||||
@@ -375,7 +376,7 @@ def test_dump_data_sources_with_dry_run_skips_pg_dump():
|
||||
def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
|
||||
databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
@@ -384,7 +385,7 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -404,7 +405,7 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
|
||||
'databases/database.example.org/foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -421,7 +422,7 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
|
||||
def test_dump_data_sources_runs_pg_dump_with_username_and_password():
|
||||
databases = [{'name': 'foo', 'username': 'postgres', 'password': 'trustsome1'}]
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
flexmock(module).should_receive('make_environment').and_return(
|
||||
{'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
@@ -432,7 +433,7 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -450,7 +451,7 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password():
|
||||
'databases/localhost/foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -467,7 +468,7 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password():
|
||||
def test_dump_data_sources_with_username_injection_attack_gets_escaped():
|
||||
databases = [{'name': 'foo', 'username': 'postgres; naughty-command', 'password': 'trustsome1'}]
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
flexmock(module).should_receive('make_environment').and_return(
|
||||
{'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
@@ -478,7 +479,7 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -496,7 +497,7 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped():
|
||||
'databases/localhost/foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -512,7 +513,7 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped():
|
||||
|
||||
def test_dump_data_sources_runs_pg_dump_with_directory_format():
|
||||
databases = [{'name': 'foo', 'format': 'directory'}]
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
@@ -521,7 +522,7 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_parent_directory_for_dump')
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
|
||||
@@ -538,7 +539,7 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
|
||||
'foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).and_return(flexmock()).once()
|
||||
|
||||
assert (
|
||||
@@ -557,7 +558,7 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
|
||||
def test_dump_data_sources_runs_pg_dump_with_options():
|
||||
databases = [{'name': 'foo', 'options': '--stuff=such'}]
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
@@ -566,7 +567,7 @@ def test_dump_data_sources_runs_pg_dump_with_options():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -583,7 +584,7 @@ def test_dump_data_sources_runs_pg_dump_with_options():
|
||||
'databases/localhost/foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -600,7 +601,7 @@ def test_dump_data_sources_runs_pg_dump_with_options():
|
||||
def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
|
||||
databases = [{'name': 'all'}]
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('all',))
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
@@ -609,13 +610,13 @@ def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
|
||||
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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('pg_dumpall', '--no-password', '--clean', '--if-exists', '>', 'databases/localhost/all'),
|
||||
shell=True,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -632,7 +633,7 @@ def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
|
||||
def test_dump_data_sources_runs_non_default_pg_dump():
|
||||
databases = [{'name': 'foo', 'pg_dump_command': 'special_pg_dump --compress *'}]
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
@@ -641,7 +642,7 @@ def test_dump_data_sources_runs_non_default_pg_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)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -659,7 +660,7 @@ def test_dump_data_sources_runs_non_default_pg_dump():
|
||||
'databases/localhost/foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -679,8 +680,8 @@ def test_restore_data_source_dump_runs_pg_restore():
|
||||
|
||||
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'})
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
@@ -696,7 +697,7 @@ def test_restore_data_source_dump_runs_pg_restore():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -709,7 +710,7 @@ def test_restore_data_source_dump_runs_pg_restore():
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -736,8 +737,8 @@ def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
|
||||
|
||||
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'})
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
@@ -757,7 +758,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -774,7 +775,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -801,8 +802,8 @@ def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_environment').and_return(
|
||||
{'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
@@ -822,7 +823,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -837,7 +838,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -875,8 +876,8 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_environment').and_return(
|
||||
{'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
@@ -900,7 +901,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
|
||||
environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -919,7 +920,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
|
||||
environment={'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -957,8 +958,8 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_environment').and_return(
|
||||
{'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
@@ -982,7 +983,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
|
||||
environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -1001,7 +1002,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
|
||||
environment={'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -1033,8 +1034,8 @@ def test_restore_data_source_dump_runs_pg_restore_with_options():
|
||||
|
||||
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'})
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
@@ -1051,7 +1052,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_options():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -1065,7 +1066,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_options():
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -1090,8 +1091,8 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
|
||||
|
||||
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'})
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
@@ -1103,11 +1104,11 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('psql', '--no-password', '--no-psqlrc', '--quiet', '--command', 'ANALYZE'),
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -1132,8 +1133,8 @@ def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
|
||||
|
||||
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'})
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
@@ -1141,7 +1142,7 @@ def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -1154,7 +1155,7 @@ def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -1186,8 +1187,8 @@ def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
|
||||
|
||||
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'})
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
@@ -1208,7 +1209,7 @@ def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -1226,7 +1227,7 @@ def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -1250,8 +1251,8 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
|
||||
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'})
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
@@ -1277,8 +1278,8 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
|
||||
|
||||
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'})
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_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')
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
@@ -1295,7 +1296,7 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
|
||||
processes=[],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -1308,7 +1309,7 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -1332,8 +1333,8 @@ def test_restore_data_source_dump_with_schemas_restores_schemas():
|
||||
|
||||
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'})
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_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')
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
@@ -1354,7 +1355,7 @@ def test_restore_data_source_dump_with_schemas_restores_schemas():
|
||||
processes=[],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -1367,7 +1368,7 @@ def test_restore_data_source_dump_with_schemas_restores_schemas():
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user