Compare commits

...
30 Commits
Author SHA1 Message Date
Dan Helfman e2002b5488 Bump version for release. 2022-10-12 10:59:54 -07:00
Dan Helfman c9742e1d04 Code formatting. 2022-10-12 10:52:32 -07:00
Dan Helfman 906da838ef Add missing break-lock action command-line help (#357). 2022-10-12 10:48:10 -07:00
Dan Helfman d7f1c10c8c To prevent Borg hangs, unconditionally delete stale named pipes before dumping databases (#360). 2022-10-12 10:26:09 -07:00
Dan Helfman e8e4d17168 Clean up changelog for the current dev release. 2022-10-06 22:06:03 -07:00
Dan Helfman a31ce337e9 Skip auto-exclusion of special files when user explicitly sets read_special to true (#587). 2022-10-06 11:07:43 -07:00
Dan Helfman 902730df46 Update sample systemd file to allow system idle (#589). 2022-10-05 10:20:25 -07:00
Dan Helfman c969c822ee Do not inhibit idle in borgmatic.service (#589).
Reviewed-on: https://projects.torsion.org/borgmatic-collective/borgmatic/pulls/589
2022-10-05 17:14:19 +00:00
Dan Helfman c31702d092 Fix for potential data loss with "patterns_from". Also, display excluded files (#590). 2022-10-04 22:57:18 -07:00
Dan Helfman ba8fbe7a44 Add "break-lock" action for removing any repository and cache locks leftover from Borg aborting (#357). 2022-10-04 13:42:18 -07:00
Dan Helfman 2774c2e4c0 Add support for Borg 2's "--match-archives" flag (replaces "--glob-archives") (#591). 2022-10-03 22:50:37 -07:00
Dan Helfman ae036aebd7 When the "read_special" option is true or database hooks are enabled, auto-exclude special files for a "create" action to prevent Borg from hanging (#587). 2022-10-03 12:58:13 -07:00
LaserEyess 2e9f70d496 Do not inhibit idle in borgmatic.service
When backing up a machine with a monitor using logind to control idle
timeout and things like DPMS, borgmatic can block the screen from
turning on/off with systemd-inhibit. This is because by default
systemd-inhibit will block "idle:sleep:shutdown". Borgmatic does not
need to care about idle, only about suspend and shutdown. So, add an
explicit `--what` flag for what borgmatic should inhibit.

For more information see systemd-inhibit(1).
2022-10-01 09:33:38 -04:00
Dan Helfman 90be5b84b1 Fix changelog development version. 2022-09-20 14:02:48 -07:00
Dan Helfman 80e95f20a3 Add "borgmatic borg" documentation note about interactive prompts. 2022-09-20 14:01:47 -07:00
Dan Helfman ac7c7d4036 Warn when ignoring a configured "read_special" value of false, as true is needed when database hooks are enabled (#587). 2022-09-20 13:52:13 -07:00
Dan Helfman 858b0b9fbe Note version of borgmatic needed for "borgmatic borg" action (#586). 2022-09-13 09:05:18 -07:00
Dan Helfman 9cc043f60e Update "find" command in documentation to work on BSDs and not just Linux (#583). 2022-09-11 20:02:30 -07:00
Dan Helfman 276a27d485 Bump version for release. 2022-09-08 10:29:44 -07:00
Dan Helfman 679bb839d7 Fix hang when database hooks are enabled and "patterns" contains a parent directory of "~/.borgmatic" (#582). 2022-09-08 10:16:42 -07:00
Dan Helfman 9e64d847ef Fix regression in which "borgmatic info --archive ..." showed repository info instead of archive info with Borg 1 (#577). 2022-08-30 20:42:42 -07:00
Dan Helfman 61fb275896 Fix duplicate-appearing log entries for "list" action. 2022-08-30 20:29:26 -07:00
Dan Helfman ca0c79c93c Fix duplicate bind path in sample systemd service. 2022-08-28 14:49:23 -07:00
Dan Helfman 87c97b7568 Fixed spurious, intermittent test failures related to command execution and logging. 2022-08-28 09:06:06 -07:00
Dan Helfman 80b8c25bba Update docs about "source_directories" being optional. 2022-08-25 13:24:26 -07:00
Dan Helfman d1837cd1d3 Bump version for release. 2022-08-25 11:58:06 -07:00
Dan Helfman c46f2b8508 Fix conflict between "patterns" and "source_directories" (#574), make "source_directories" optional (#542). 2022-08-25 11:55:34 -07:00
Dan Helfman a274c0dbf7 In generate-borgmatic-config, indicate that the example options are exhaustive. 2022-08-24 09:53:54 -07:00
Dan Helfman ef7e95e22a Fix end-to-end tests. 2022-08-21 23:29:13 -07:00
Dan Helfman 3be99de5b1 Update "repositories" examples in configuration to use ssh:// style syntax. 2022-08-21 22:40:31 -07:00
44 changed files with 1222 additions and 437 deletions
+39
View File
@@ -1,3 +1,42 @@
1.7.3
* #357: Add "break-lock" action for removing any repository and cache locks leftover from Borg
aborting.
* #360: To prevent Borg hangs, unconditionally delete stale named pipes before dumping databases.
* #587: When database hooks are enabled, auto-exclude special files from a "create" action to
prevent Borg from hanging. You can override/prevent this behavior by explicitly setting the
"read_special" option to true.
* #587: Warn when ignoring a configured "read_special" value of false, as true is needed when
database hooks are enabled.
* #589: Update sample systemd service file to allow system "idle" (e.g. a video monitor turning
off) while borgmatic is running.
* #590: Fix for potential data loss (data not getting backed up) when the "patterns_from" option
was used with "source_directories" (or the "~/.borgmatic" path existed, which got injected into
"source_directories" implicitly). The fix is for borgmatic to convert "source_directories" into
patterns whenever "patterns_from" is used, working around a Borg bug:
https://github.com/borgbackup/borg/issues/6994
* #590: In "borgmatic create --list" output, display which files get excluded from the backup due
to patterns or excludes.
* #591: Add support for Borg 2's "--match-archives" flag. This replaces "--glob-archives", which
borgmatic now treats as an alias for "--match-archives". But note that the two flags have
slightly different syntax. See the Borg 2 changelog for more information:
https://borgbackup.readthedocs.io/en/2.0.0b3/changes.html#version-2-0-0b3-2022-10-02
* Fix for "borgmatic --archive latest" not finding the latest archive when a verbosity is set.
1.7.2
* #577: Fix regression in which "borgmatic info --archive ..." showed repository info instead of
archive info with Borg 1.
* #582: Fix hang when database hooks are enabled and "patterns" contains a parent directory of
"~/.borgmatic".
1.7.1
* #542: Make the "source_directories" option optional. This is useful for "check"-only setups or
using "patterns" exclusively.
* #574: Fix for potential data loss (data not getting backed up) when the "patterns" option was
used with "source_directories" (or the "~/.borgmatic" path existed, which got injected into
"source_directories" implicitly). The fix is for borgmatic to convert "source_directories" into
patterns whenever "patterns" is used, working around a Borg bug:
https://github.com/borgbackup/borg/issues/6994
1.7.0
* #463: Add "before_actions" and "after_actions" command hooks that run before/after all the
actions for each repository. These new hooks are a good place to run per-repository steps like
+31
View File
@@ -0,0 +1,31 @@
import logging
from borgmatic.borg import environment, flags
from borgmatic.execute import execute_command
logger = logging.getLogger(__name__)
def break_lock(
repository, storage_config, local_borg_version, local_path='borg', remote_path=None,
):
'''
Given a local or remote repository path, a storage configuration dict, the local Borg version,
and optional local and remote Borg paths, break any repository and cache locks leftover from Borg
aborting.
'''
umask = storage_config.get('umask', None)
lock_wait = storage_config.get('lock_wait', None)
full_command = (
(local_path, 'break-lock')
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--umask', str(umask)) if umask else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
+ flags.make_repository_flags(repository, local_borg_version)
)
borg_environment = environment.make_environment(storage_config)
execute_command(full_command, borg_local_path=local_path, extra_environment=borg_environment)
+12 -8
View File
@@ -5,7 +5,7 @@ import logging
import os
import pathlib
from borgmatic.borg import environment, extract, flags, rinfo, state
from borgmatic.borg import environment, extract, feature, flags, rinfo, state
from borgmatic.execute import DO_NOT_CAPTURE, execute_command
DEFAULT_CHECKS = (
@@ -146,9 +146,10 @@ def filter_checks_on_frequency(
return tuple(filtered_checks)
def make_check_flags(checks, check_last=None, prefix=None):
def make_check_flags(local_borg_version, checks, check_last=None, prefix=None):
'''
Given a parsed sequence of checks, transform it into tuple of command-line flags.
Given the local Borg version and a parsed sequence of checks, transform the checks into tuple of
command-line flags.
For example, given parsed checks of:
@@ -163,14 +164,17 @@ def make_check_flags(checks, check_last=None, prefix=None):
Additionally, if a check_last value is given and "archives" is in checks, then include a
"--last" flag. And if a prefix value is given and "archives" is in checks, then include a
"--glob-archives" flag.
"--match-archives" flag.
'''
if 'archives' in checks:
last_flags = ('--last', str(check_last)) if check_last else ()
glob_archives_flags = ('--glob-archives', f'{prefix}*') if prefix else ()
if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
match_archives_flags = ('--match-archives', f'sh:{prefix}*') if prefix else ()
else:
match_archives_flags = ('--glob-archives', f'{prefix}*') if prefix else ()
else:
last_flags = ()
glob_archives_flags = ()
match_archives_flags = ()
if check_last:
logger.info('Ignoring check_last option, as "archives" is not in consistency checks')
if prefix:
@@ -184,7 +188,7 @@ def make_check_flags(checks, check_last=None, prefix=None):
else:
data_flags = ()
common_flags = last_flags + glob_archives_flags + data_flags
common_flags = last_flags + match_archives_flags + data_flags
if {'repository', 'archives'}.issubset(set(checks)):
return common_flags
@@ -298,7 +302,7 @@ def check_archives(
full_command = (
(local_path, 'check')
+ (('--repair',) if repair else ())
+ make_check_flags(checks, check_last, prefix)
+ make_check_flags(local_borg_version, checks, check_last, prefix)
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ verbosity_flags
+156 -29
View File
@@ -3,6 +3,7 @@ import itertools
import logging
import os
import pathlib
import stat
import tempfile
from borgmatic.borg import environment, feature, flags, state
@@ -59,7 +60,7 @@ def map_directories_to_devices(directories):
}
def deduplicate_directories(directory_devices):
def deduplicate_directories(directory_devices, additional_directory_devices):
'''
Given a map from directory to the identifier for the device on which that directory resides,
return the directories as a sorted tuple with all duplicate child directories removed. For
@@ -74,22 +75,28 @@ def deduplicate_directories(directory_devices):
there are cases where Borg coming across the same file twice will result in duplicate reads and
even hangs, e.g. when a database hook is using a named pipe for streaming database dumps to
Borg.
If any additional directory devices are given, also deduplicate against them, but don't include
them in the returned directories.
'''
deduplicated = set()
directories = sorted(directory_devices.keys())
additional_directories = sorted(additional_directory_devices.keys())
all_devices = {**directory_devices, **additional_directory_devices}
for directory in directories:
deduplicated.add(directory)
parents = pathlib.PurePath(directory).parents
# If another directory in the given list is a parent of current directory (even n levels
# up) and both are on the same filesystem, then the current directory is a duplicate.
for other_directory in directories:
# If another directory in the given list (or the additional list) is a parent of current
# directory (even n levels up) and both are on the same filesystem, then the current
# directory is a duplicate.
for other_directory in directories + additional_directories:
for parent in parents:
if (
pathlib.PurePath(other_directory) == parent
and directory_devices[directory] is not None
and directory_devices[other_directory] == directory_devices[directory]
and all_devices[directory] is not None
and all_devices[other_directory] == all_devices[directory]
):
if directory in deduplicated:
deduplicated.remove(directory)
@@ -98,16 +105,24 @@ def deduplicate_directories(directory_devices):
return tuple(sorted(deduplicated))
def write_pattern_file(patterns=None):
def write_pattern_file(patterns=None, sources=None, pattern_file=None):
'''
Given a sequence of patterns, write them to a named temporary file and return it. Return None
if no patterns are provided.
Given a sequence of patterns and an optional sequence of source directories, write them to a
named temporary file (with the source directories as additional roots) and return the file.
If an optional open pattern file is given, overwrite it instead of making a new temporary file.
Return None if no patterns are provided.
'''
if not patterns:
if not patterns and not sources:
return None
pattern_file = tempfile.NamedTemporaryFile('w')
pattern_file.write('\n'.join(patterns))
if pattern_file is None:
pattern_file = tempfile.NamedTemporaryFile('w')
else:
pattern_file.seek(0)
pattern_file.write(
'\n'.join(tuple(patterns or ()) + tuple(f'R {source}' for source in (sources or [])))
)
pattern_file.flush()
return pattern_file
@@ -178,7 +193,7 @@ def make_exclude_flags(location_config, exclude_filename=None):
DEFAULT_ARCHIVE_NAME_FORMAT = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'
def borgmatic_source_directories(borgmatic_source_directory):
def collect_borgmatic_source_directories(borgmatic_source_directory):
'''
Return a list of borgmatic-specific source directories used for state like database backups.
'''
@@ -192,6 +207,75 @@ def borgmatic_source_directories(borgmatic_source_directory):
)
ROOT_PATTERN_PREFIX = 'R '
def pattern_root_directories(patterns=None):
'''
Given a sequence of patterns, parse out and return just the root directories.
'''
if not patterns:
return []
return [
pattern.split(ROOT_PATTERN_PREFIX, maxsplit=1)[1]
for pattern in patterns
if pattern.startswith(ROOT_PATTERN_PREFIX)
]
def special_file(path):
'''
Return whether the given path is a special file (character device, block device, or named pipe
/ FIFO).
'''
mode = os.stat(path).st_mode
return stat.S_ISCHR(mode) or stat.S_ISBLK(mode) or stat.S_ISFIFO(mode)
def any_parent_directories(path, candidate_parents):
'''
Return whether any of the given candidate parent directories are an actual parent of the given
path. This includes grandparents, etc.
'''
for parent in candidate_parents:
if pathlib.PurePosixPath(parent) in pathlib.PurePath(path).parents:
return True
return False
def collect_special_file_paths(
create_command, local_path, working_directory, borg_environment, skip_directories
):
'''
Given a Borg create command as a tuple, a local Borg path, a working directory, and a dict of
environment variables to pass to Borg, and a sequence of parent directories to skip, collect the
paths for any special files (character devices, block devices, and named pipes / FIFOs) that
Borg would encounter during a create. These are all paths that could cause Borg to hang if its
--read-special flag is used.
'''
paths_output = execute_command(
create_command + ('--dry-run', '--list'),
output_log_level=None,
borg_local_path=local_path,
working_directory=working_directory,
extra_environment=borg_environment,
)
paths = tuple(
path_line.split(' ', 1)[1]
for path_line in paths_output.split('\n')
if path_line and path_line.startswith('- ')
)
return tuple(
path
for path in paths
if special_file(path) and not any_parent_directories(path, skip_directories)
)
def create_archive(
dry_run,
repository,
@@ -213,20 +297,32 @@ def create_archive(
If a sequence of stream processes is given (instances of subprocess.Popen), then execute the
create command while also triggering the given processes to produce output.
'''
borgmatic_source_directories = expand_directories(
collect_borgmatic_source_directories(location_config.get('borgmatic_source_directory'))
)
sources = deduplicate_directories(
map_directories_to_devices(
expand_directories(
location_config['source_directories']
+ borgmatic_source_directories(location_config.get('borgmatic_source_directory'))
tuple(location_config.get('source_directories', ())) + borgmatic_source_directories
)
)
),
additional_directory_devices=map_directories_to_devices(
expand_directories(pattern_root_directories(location_config.get('patterns')))
),
)
ensure_files_readable(location_config.get('patterns_from'), location_config.get('exclude_from'))
try:
working_directory = os.path.expanduser(location_config.get('working_directory'))
except TypeError:
working_directory = None
pattern_file = write_pattern_file(location_config.get('patterns'))
pattern_file = (
write_pattern_file(location_config.get('patterns'), sources)
if location_config.get('patterns') or location_config.get('patterns_from')
else None
)
exclude_file = write_pattern_file(
expand_home_directories(location_config.get('exclude_patterns'))
)
@@ -264,9 +360,12 @@ def create_archive(
('--remote-ratelimit', str(upload_rate_limit)) if upload_rate_limit else ()
)
ensure_files_readable(location_config.get('patterns_from'), location_config.get('exclude_from'))
if stream_processes and location_config.get('read_special') is False:
logger.warning(
f'{repository}: Ignoring configured "read_special" value of false, as true is needed for database hooks.'
)
full_command = (
create_command = (
tuple(local_path.split(' '))
+ ('create',)
+ make_pattern_flags(location_config, pattern_file.name if pattern_file else None)
@@ -284,22 +383,17 @@ def create_archive(
+ atime_flags
+ (('--noctime',) if location_config.get('ctime') is False else ())
+ (('--nobirthtime',) if location_config.get('birthtime') is False else ())
+ (('--read-special',) if (location_config.get('read_special') or stream_processes) else ())
+ (('--read-special',) if location_config.get('read_special') or stream_processes else ())
+ noflags_flags
+ (('--files-cache', files_cache) if files_cache else ())
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--umask', str(umask)) if umask else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ (('--list', '--filter', 'AME-') if list_files and not json and not progress else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
+ (('--stats',) if stats and not json and not dry_run else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
+ (('--list', '--filter', 'AMEx-') if list_files and not json and not progress else ())
+ (('--dry-run',) if dry_run else ())
+ (('--progress',) if progress else ())
+ (('--json',) if json else ())
+ (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
+ flags.make_repository_archive_flags(repository, archive_name_format, local_borg_version)
+ sources
+ (sources if not pattern_file else ())
)
if json:
@@ -315,9 +409,42 @@ def create_archive(
borg_environment = environment.make_environment(storage_config)
# If database hooks are enabled (as indicated by streaming processes), exclude files that might
# cause Borg to hang. But skip this if the user has explicitly set the "read_special" to True.
if stream_processes and not location_config.get('read_special'):
logger.debug(f'{repository}: Collecting special file paths')
special_file_paths = collect_special_file_paths(
create_command,
local_path,
working_directory,
borg_environment,
skip_directories=borgmatic_source_directories,
)
logger.warning(
f'{repository}: Excluding special files to prevent Borg from hanging: {", ".join(special_file_paths)}'
)
exclude_file = write_pattern_file(
expand_home_directories(
tuple(location_config.get('exclude_patterns') or ()) + special_file_paths
),
pattern_file=exclude_file,
)
if exclude_file:
create_command += make_exclude_flags(location_config, exclude_file.name)
create_command += (
(('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
+ (('--stats',) if stats and not json and not dry_run else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
+ (('--progress',) if progress else ())
+ (('--json',) if json else ())
)
if stream_processes:
return execute_command_with_processes(
full_command,
create_command,
stream_processes,
output_log_level,
output_file,
@@ -327,7 +454,7 @@ def create_archive(
)
return execute_command(
full_command,
create_command,
output_log_level,
output_file,
borg_local_path=local_path,
+2
View File
@@ -13,6 +13,7 @@ class Feature(Enum):
RCREATE = 7
RLIST = 8
RINFO = 9
MATCH_ARCHIVES = 10
FEATURE_TO_MINIMUM_BORG_VERSION = {
@@ -25,6 +26,7 @@ FEATURE_TO_MINIMUM_BORG_VERSION = {
Feature.RCREATE: parse_version('2.0.0a2'), # borg rcreate
Feature.RLIST: parse_version('2.0.0a2'), # borg rlist
Feature.RINFO: parse_version('2.0.0a2'), # borg rinfo
Feature.MATCH_ARCHIVES: parse_version('2.0.0b3'), # borg --match-archives
}
+9 -9
View File
@@ -36,22 +36,22 @@ def display_archives_info(
+ flags.make_flags('remote-path', remote_path)
+ flags.make_flags('lock-wait', lock_wait)
+ (
flags.make_flags('glob-archives', f'{info_arguments.prefix}*')
(
flags.make_flags('match-archives', f'sh:{info_arguments.prefix}*')
if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version)
else flags.make_flags('glob-archives', f'{info_arguments.prefix}*')
)
if info_arguments.prefix
else ()
)
+ flags.make_flags_from_arguments(
info_arguments, excludes=('repository', 'archive', 'prefix')
)
+ flags.make_repository_flags(repository, local_borg_version)
+ (
flags.make_repository_flags(repository, local_borg_version)
+ (
flags.make_flags('glob-archives', info_arguments.archive)
if feature.available(
feature.Feature.SEPARATE_REPOSITORY_ARCHIVE, local_borg_version
)
else ()
)
flags.make_flags('match-archives', info_arguments.archive)
if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version)
else flags.make_flags('glob-archives', info_arguments.archive)
)
)
+3 -3
View File
@@ -9,7 +9,7 @@ from borgmatic.execute import execute_command
logger = logging.getLogger(__name__)
ARCHIVE_FILTER_FLAGS_MOVED_TO_RLIST = ('prefix', 'glob_archives', 'sort_by', 'first', 'last')
ARCHIVE_FILTER_FLAGS_MOVED_TO_RLIST = ('prefix', 'match_archives', 'sort_by', 'first', 'last')
MAKE_FLAGS_EXCLUDES = (
'repository',
'archive',
@@ -111,7 +111,7 @@ def list_archive(
format=list_arguments.format,
json=list_arguments.json,
prefix=list_arguments.prefix,
glob_archives=list_arguments.glob_archives,
match_archives=list_arguments.match_archives,
sort_by=list_arguments.sort_by,
first=list_arguments.first,
last=list_arguments.last,
@@ -143,7 +143,7 @@ def list_archive(
format=None,
json=None,
prefix=list_arguments.prefix,
glob_archives=list_arguments.glob_archives,
match_archives=list_arguments.match_archives,
sort_by=list_arguments.sort_by,
first=list_arguments.first,
last=list_arguments.last,
+5 -1
View File
@@ -39,7 +39,11 @@ def mount_archive(
+ (
(
flags.make_repository_flags(repository, local_borg_version)
+ ('--glob-archives', archive)
+ (
('--match-archives', archive)
if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version)
else ('--glob-archives', archive)
)
)
if feature.available(feature.Feature.SEPARATE_REPOSITORY_ARCHIVE, local_borg_version)
else (
+12 -4
View File
@@ -1,12 +1,12 @@
import logging
from borgmatic.borg import environment, flags
from borgmatic.borg import environment, feature, flags
from borgmatic.execute import execute_command
logger = logging.getLogger(__name__)
def make_prune_flags(retention_config):
def make_prune_flags(retention_config, local_borg_version):
'''
Given a retention config dict mapping from option name to value, tranform it into an iterable of
command-line name-value flag pairs.
@@ -24,8 +24,12 @@ def make_prune_flags(retention_config):
'''
config = retention_config.copy()
prefix = config.pop('prefix', '{hostname}-')
if prefix:
config['glob_archives'] = f'{prefix}*'
if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version):
config['match_archives'] = f'sh:{prefix}*'
else:
config['glob_archives'] = f'{prefix}*'
return (
('--' + option_name.replace('_', '-'), str(value)) for option_name, value in config.items()
@@ -54,7 +58,11 @@ def prune_archives(
full_command = (
(local_path, 'prune')
+ tuple(element for pair in make_prune_flags(retention_config) for element in pair)
+ tuple(
element
for pair in make_prune_flags(retention_config, local_borg_version)
for element in pair
)
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--umask', str(umask)) if umask else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+5 -3
View File
@@ -26,8 +26,6 @@ def resolve_archive_name(
local_path,
'rlist' if feature.available(feature.Feature.RLIST, local_borg_version) else 'list',
)
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
+ flags.make_flags('remote-path', remote_path)
+ flags.make_flags('lock-wait', lock_wait)
+ flags.make_flags('last', 1)
@@ -87,7 +85,11 @@ def make_rlist_command(
+ flags.make_flags('remote-path', remote_path)
+ flags.make_flags('lock-wait', lock_wait)
+ (
flags.make_flags('glob-archives', f'{rlist_arguments.prefix}*')
(
flags.make_flags('match-archives', f'sh:{rlist_arguments.prefix}*')
if feature.available(feature.Feature.MATCH_ARCHIVES, local_borg_version)
else flags.make_flags('glob-archives', f'{rlist_arguments.prefix}*')
)
if rlist_arguments.prefix
else ()
)
+5 -3
View File
@@ -25,12 +25,14 @@ def transfer_archives(
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
+ flags.make_flags('remote-path', remote_path)
+ flags.make_flags('lock-wait', storage_config.get('lock_wait', None))
+ flags.make_flags(
'glob-archives', transfer_arguments.glob_archives or transfer_arguments.archive
+ (
flags.make_flags(
'match-archives', transfer_arguments.match_archives or transfer_arguments.archive
)
)
+ flags.make_flags_from_arguments(
transfer_arguments,
excludes=('repository', 'source_repository', 'archive', 'glob_archives'),
excludes=('repository', 'source_repository', 'archive', 'match_archives'),
)
+ flags.make_repository_flags(repository, local_borg_version)
+ flags.make_flags('other-repo', transfer_arguments.source_repository)
+37 -10
View File
@@ -19,6 +19,7 @@ SUBPARSER_ALIASES = {
'rinfo': [],
'info': ['-i'],
'transfer': [],
'break-lock': [],
'borg': [],
}
@@ -293,9 +294,10 @@ def make_parsers():
)
transfer_group.add_argument(
'-a',
'--match-archives',
'--glob-archives',
metavar='GLOB',
help='Only transfer archives with names matching this glob',
metavar='PATTERN',
help='Only transfer archives with names matching this pattern',
)
transfer_group.add_argument(
'--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
@@ -627,7 +629,11 @@ def make_parsers():
'-P', '--prefix', help='Only list archive names starting with this prefix'
)
rlist_group.add_argument(
'-a', '--glob-archives', metavar='GLOB', help='Only list archive names matching this glob'
'-a',
'--match-archives',
'--glob-archives',
metavar='PATTERN',
help='Only list archive names matching this pattern',
)
rlist_group.add_argument(
'--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
@@ -678,7 +684,11 @@ def make_parsers():
'-P', '--prefix', help='Only list archive names starting with this prefix'
)
list_group.add_argument(
'-a', '--glob-archives', metavar='GLOB', help='Only list archive names matching this glob'
'-a',
'--match-archives',
'--glob-archives',
metavar='PATTERN',
help='Only list archive names matching this pattern',
)
list_group.add_argument(
'--successful',
@@ -747,9 +757,10 @@ def make_parsers():
)
info_group.add_argument(
'-a',
'--match-archives',
'--glob-archives',
metavar='GLOB',
help='Only show info for archive names matching this glob',
metavar='PATTERN',
help='Only show info for archive names matching this pattern',
)
info_group.add_argument(
'--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
@@ -764,6 +775,22 @@ def make_parsers():
)
info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
break_lock_parser = subparsers.add_parser(
'break-lock',
aliases=SUBPARSER_ALIASES['break-lock'],
help='Break the repository and cache locks left behind by Borg aborting',
description='Break Borg repository and cache locks left behind by Borg aborting',
add_help=False,
)
break_lock_group = break_lock_parser.add_argument_group('break-lock arguments')
break_lock_group.add_argument(
'--repository',
help='Path of repository to break the lock for, defaults to the configured repository if there is only one',
)
break_lock_group.add_argument(
'-h', '--help', action='help', help='Show this help message and exit'
)
borg_parser = subparsers.add_parser(
'borg',
aliases=SUBPARSER_ALIASES['borg'],
@@ -816,7 +843,7 @@ def parse_arguments(*unparsed_arguments):
if (
'transfer' in arguments
and arguments['transfer'].archive
and arguments['transfer'].glob_archives
and arguments['transfer'].match_archives
):
raise ValueError(
'With the transfer action, only one of --archive and --glob-archives flags can be used.'
@@ -824,11 +851,11 @@ def parse_arguments(*unparsed_arguments):
if 'info' in arguments and (
(arguments['info'].archive and arguments['info'].prefix)
or (arguments['info'].archive and arguments['info'].glob_archives)
or (arguments['info'].prefix and arguments['info'].glob_archives)
or (arguments['info'].archive and arguments['info'].match_archives)
or (arguments['info'].prefix and arguments['info'].match_archives)
):
raise ValueError(
'With the info action, only one of --archive, --prefix, or --glob-archives flags can be used.'
'With the info action, only one of --archive, --prefix, or --match-archives flags can be used.'
)
return arguments
+19 -6
View File
@@ -13,6 +13,7 @@ import pkg_resources
import borgmatic.commands.completion
from borgmatic.borg import borg as borg_borg
from borgmatic.borg import break_lock as borg_break_lock
from borgmatic.borg import check as borg_check
from borgmatic.borg import compact as borg_compact
from borgmatic.borg import create as borg_create
@@ -359,7 +360,7 @@ def run_actions(
**hook_context,
)
logger.info('{}: Creating archive{}'.format(repository, dry_run_label))
dispatch.call_hooks(
dispatch.call_hooks_even_if_unconfigured(
'remove_database_dumps',
hooks,
repository,
@@ -394,7 +395,7 @@ def run_actions(
if json_output: # pragma: nocover
yield json.loads(json_output)
dispatch.call_hooks(
dispatch.call_hooks_even_if_unconfigured(
'remove_database_dumps',
hooks,
config_filename,
@@ -555,7 +556,7 @@ def run_actions(
repository, arguments['restore'].archive
)
)
dispatch.call_hooks(
dispatch.call_hooks_even_if_unconfigured(
'remove_database_dumps',
hooks,
repository,
@@ -625,7 +626,7 @@ def run_actions(
extract_process,
)
dispatch.call_hooks(
dispatch.call_hooks_even_if_unconfigured(
'remove_database_dumps',
hooks,
repository,
@@ -669,8 +670,8 @@ def run_actions(
if not list_arguments.json: # pragma: nocover
if list_arguments.find_paths:
logger.warning('{}: Searching archives'.format(repository))
else:
logger.warning('{}: Listing archive'.format(repository))
elif not list_arguments.archive:
logger.warning('{}: Listing archives'.format(repository))
list_arguments.archive = borg_rlist.resolve_archive_name(
repository,
list_arguments.archive,
@@ -731,6 +732,18 @@ def run_actions(
)
if json_output: # pragma: nocover
yield json.loads(json_output)
if 'break-lock' in arguments:
if arguments['break-lock'].repository is None or validate.repositories_match(
repository, arguments['break-lock'].repository
):
logger.warning(f'{repository}: Breaking repository and cache locks')
borg_break_lock.break_lock(
repository,
storage,
local_borg_version,
local_path=local_path,
remote_path=remote_path,
)
if 'borg' in arguments:
if arguments['borg'].repository is None or validate.repositories_match(
repository, arguments['borg'].repository
+2 -2
View File
@@ -60,8 +60,8 @@ def main(): # pragma: no cover
' diff --unified {} {}'.format(args.source_filename, args.destination_filename)
)
print()
print('Please edit the file to suit your needs. The values are representative.')
print('All fields are optional except where indicated.')
print('This includes all available configuration options with example values. The few')
print('required options are indicated. Please edit the file to suit your needs.')
print()
print('If you ever need help: https://torsion.org/borgmatic/#issues')
except (ValueError, OSError) as error:
+2 -2
View File
@@ -70,8 +70,8 @@ def parse_overrides(raw_overrides):
def apply_overrides(config, raw_overrides):
'''
Given a sequence of configuration file override strings in the form of "section.option=value"
and a configuration dict, parse each override and set it the configuration dict.
Given a configuration dict and a sequence of configuration file override strings in the form of
"section.option=value", parse each override and set it the configuration dict.
'''
overrides = parse_overrides(raw_overrides)
+7 -6
View File
@@ -11,7 +11,6 @@ properties:
https://borgbackup.readthedocs.io/en/stable/usage/create.html
for details.
required:
- source_directories
- repositories
additionalProperties: false
properties:
@@ -20,8 +19,8 @@ properties:
items:
type: string
description: |
List of source directories to backup (required). Globs and
tildes are expanded. Do not backslash spaces in path names.
List of source directories to backup. Globs and tildes are
expanded. Do not backslash spaces in path names.
example:
- /home
- /etc
@@ -40,8 +39,9 @@ properties:
is used, then add local repository paths in the systemd
service file to the ReadWritePaths list.
example:
- user@backupserver:sourcehostname.borg
- "user@backupserver:{fqdn}"
- ssh://user@backupserver/./sourcehostname.borg
- ssh://user@backupserver/./{fqdn}
- /var/local/backups/local.borg
working_directory:
type: string
description: |
@@ -122,7 +122,8 @@ properties:
backups. Globs are expanded. (Tildes are not.) See the
output of "borg help patterns" for more details. Quote any
value if it contains leading punctuation, so it parses
correctly.
correctly. Note that only one of "patterns" and
"source_directories" may be used.
example:
- 'R /'
- '- /home/*/.cache'
+1 -1
View File
@@ -72,7 +72,7 @@ def apply_logical_validation(config_filename, parsed_configuration):
raise Validation_error(
config_filename,
(
'Unknown repository in the consistency section\'s check_repositories: {}'.format(
'Unknown repository in the "consistency" section\'s "check_repositories": {}'.format(
repository
),
),
+5 -4
View File
@@ -69,6 +69,7 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path):
}
output_buffers = list(process_for_output_buffer.keys())
captured_outputs = collections.defaultdict(list)
still_running = True
# Log output for each process until they all exit.
while True:
@@ -108,6 +109,9 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path):
else:
logger.log(output_log_level, line)
if not still_running:
break
still_running = False
for process in processes:
@@ -137,9 +141,6 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path):
exit_code, command_for_process(process), '\n'.join(last_lines)
)
if not still_running:
break
if captured_outputs:
return {
process: '\n'.join(output_lines) for process, output_lines in captured_outputs.items()
@@ -196,7 +197,7 @@ def execute_command(
if output_log_level is None:
output = subprocess.check_output(
command, shell=shell, env=environment, cwd=working_directory
command, stderr=subprocess.STDOUT, shell=shell, env=environment, cwd=working_directory
)
return output.decode() if output is not None else None
+20 -9
View File
@@ -29,19 +29,14 @@ def call_hook(function_name, hooks, log_prefix, hook_name, *args, **kwargs):
'''
Given the hooks configuration dict and a prefix to use in log entries, call the requested
function of the Python module corresponding to the given hook name. Supply that call with the
configuration for this hook, the log prefix, and any given args and kwargs. Return any return
value.
If the hook name is not present in the hooks configuration, then bail without calling anything.
configuration for this hook (if any), the log prefix, and any given args and kwargs. Return any
return value.
Raise ValueError if the hook name is unknown.
Raise AttributeError if the function name is not found in the module.
Raise anything else that the called function raises.
'''
config = hooks.get(hook_name)
if not config:
logger.debug('{}: No {} hook configured.'.format(log_prefix, hook_name))
return
config = hooks.get(hook_name, {})
try:
module = HOOK_NAME_TO_MODULE[hook_name]
@@ -59,7 +54,7 @@ def call_hooks(function_name, hooks, log_prefix, hook_names, *args, **kwargs):
configuration for that hook, the log prefix, and any given args and kwargs. Collect any return
values into a dict from hook name to return value.
If the hook name is not present in the hooks configuration, then don't call the function for it,
If the hook name is not present in the hooks configuration, then don't call the function for it
and omit it from the return values.
Raise ValueError if the hook name is unknown.
@@ -71,3 +66,19 @@ def call_hooks(function_name, hooks, log_prefix, hook_names, *args, **kwargs):
for hook_name in hook_names
if hooks.get(hook_name)
}
def call_hooks_even_if_unconfigured(function_name, hooks, log_prefix, hook_names, *args, **kwargs):
'''
Given the hooks configuration dict and a prefix to use in log entries, call the requested
function of the Python module corresponding to each given hook name. Supply each call with the
configuration for that hook, the log prefix, and any given args and kwargs. Collect any return
values into a dict from hook name to return value.
Raise AttributeError if the function name is not found in the module.
Raise anything else that a called function raises. An error stops calls to subsequent functions.
'''
return {
hook_name: call_hook(function_name, hooks, log_prefix, hook_name, *args, **kwargs)
for hook_name in hook_names
}
+1 -1
View File
@@ -55,7 +55,7 @@ def remove_database_dumps(dump_path, database_type_name, log_prefix, dry_run):
'''
dry_run_label = ' (dry run; not actually removing anything)' if dry_run else ''
logger.info(
logger.debug(
'{}: Removing {} database dumps{}'.format(log_prefix, database_type_name, dry_run_label)
)
+1 -1
View File
@@ -4,7 +4,7 @@ COPY . /app
RUN apk add --no-cache py3-pip py3-ruamel.yaml py3-ruamel.yaml.clib
RUN pip install --no-cache /app && generate-borgmatic-config && chmod +r /etc/borgmatic/config.yaml
RUN borgmatic --help > /command-line.txt \
&& for action in rcreate transfer prune compact create check extract export-tar mount umount restore rlist list rinfo info borg; do \
&& for action in rcreate transfer prune compact create check extract export-tar mount umount restore rlist list rinfo info break-lock borg; do \
echo -e "\n--------------------------------------------------------------------------------\n" >> /command-line.txt \
&& borgmatic "$action" --help >> /command-line.txt; done
+16 -3
View File
@@ -92,7 +92,8 @@ Note that you may need to use a `username` of the `postgres` superuser for
this to work with PostgreSQL.
If you would like to backup databases only and not source directories, you can
specify an empty `source_directories` value (as it is a mandatory field):
specify an empty `source_directories` value (as it is a mandatory field prior
to borgmatic 1.7.1):
```yaml
location:
@@ -102,6 +103,9 @@ hooks:
- name: all
```
<span class="minilink minilink-addedin">New in version 1.7.1</span> You can
omit `source_directories` entirely.
### External passwords
If you don't want to keep your database passwords in your borgmatic
@@ -211,8 +215,13 @@ databases that share the exact same name on different hosts.
setting to support dump and restore streaming, you'll need to ensure that any
special files are excluded from backups (named pipes, block devices,
character devices, and sockets) to prevent hanging. Try a command like
`find /your/source/path -type c,b,p,s` to find such files. Common directories
to exclude are `/dev` and `/run`, but that may not be exhaustive.
`find /your/source/path -type b -or -type c -or -type p -or -type s` to find
such files. Common directories to exclude are `/dev` and `/run`, but that may
not be exhaustive. <span class="minilink minilink-addedin">New in version
1.7.3</span> When database hooks are enabled, borgmatic automatically excludes
special files that may cause Borg to hang, so you no longer need to manually
exclude them. You can override/prevent this behavior by explicitly setting
`read_special` to true.
### Manual restoration
@@ -268,3 +277,7 @@ Alternatively, if excluding special files is too onerous, you can create two
separate borgmatic configuration files—one for your source files and a
separate one for backing up databases. That way, the database `read_special`
option will not be active when backing up special files.
<span class="minilink minilink-addedin">New in version 1.7.3</span> See
Limitations above about borgmatic's automatic exclusion of special files to
prevent Borg hangs.
+1 -1
View File
@@ -84,7 +84,7 @@ be a [Borg
pattern](https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-patterns).
To limit the archives searched, use the standard `list` parameters for
filtering archives such as `--last`, `--archive`, `--glob-archives`, etc. For
filtering archives such as `--last`, `--archive`, `--match-archives`, etc. For
example, to search only the last five archives:
```bash
+5 -2
View File
@@ -30,8 +30,8 @@ based on your borgmatic configuration files or command-line arguments:
### borg action
The way you run Borg with borgmatic is via the `borg` action. Here's a simple
example:
<span class="minilink minilink-addedin">New in version 1.5.15</span> The way
you run Borg with borgmatic is via the `borg` action. Here's a simple example:
```bash
borgmatic borg break-lock
@@ -87,6 +87,9 @@ borgmatic's `borg` action is not without limitations:
borgmatic action. In this case, only the Borg command is run.
* Unlike normal borgmatic actions that support JSON, the `borg` action will
not disable certain borgmatic logs to avoid interfering with JSON output.
* Unlike other borgmatic actions, the `borg` action captures (and logs) all
output, so interactive prompts or flags like `--progress` will not work as
expected.
In general, this `borgmatic borg` feature should be considered an escape
valve—a feature of second resort. In the long run, it's preferable to wrap
+2 -2
View File
@@ -41,7 +41,7 @@ ProtectSystem=full
# ReadOnlyPaths=-/var/lib/my_backup_source
# This will mount a tmpfs on top of /root and pass through needed paths
# ProtectHome=tmpfs
# BindPaths=-/root/.cache/borg -/root/.cache/borg -/root/.borgmatic
# BindPaths=-/root/.cache/borg -/root/.config/borg -/root/.borgmatic
# May interfere with running external programs within borgmatic hooks.
CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_NET_RAW
@@ -61,4 +61,4 @@ LogRateLimitIntervalSec=0
# Delay start to prevent backups running during boot. Note that systemd-inhibit requires dbus and
# dbus-user-session to be installed.
ExecStartPre=sleep 1m
ExecStart=systemd-inhibit --who="borgmatic" --why="Prevent interrupting scheduled backup" /root/.local/bin/borgmatic --verbosity -1 --syslog-verbosity 1
ExecStart=systemd-inhibit --who="borgmatic" --what="sleep:shutdown" --why="Prevent interrupting scheduled backup" /root/.local/bin/borgmatic --verbosity -1 --syslog-verbosity 1
+1
View File
@@ -53,6 +53,7 @@ for sub_command in prune create check list info; do
| grep -v '^--first' \
| grep -v '^--format' \
| grep -v '^--glob-archives' \
| grep -v '^--match-archives' \
| grep -v '^--last' \
| grep -v '^--format' \
| grep -v '^--patterns-from' \
+1 -1
View File
@@ -1,6 +1,6 @@
from setuptools import find_packages, setup
VERSION = '1.7.0'
VERSION = '1.7.3'
setup(
+3 -2
View File
@@ -18,8 +18,9 @@ def generate_configuration(config_path, repository_path):
config = (
open(config_path)
.read()
.replace('user@backupserver:sourcehostname.borg', repository_path)
.replace('- user@backupserver:{fqdn}', '')
.replace('ssh://user@backupserver/./sourcehostname.borg', repository_path)
.replace('- ssh://user@backupserver/./{fqdn}', '')
.replace('- /var/local/backups/local.borg', '')
.replace('- /home/user/path with spaces', '')
.replace('- /home', '- {}'.format(config_path))
.replace('- /etc', '')
+21 -11
View File
@@ -9,20 +9,24 @@ import pytest
def write_configuration(
config_path, repository_path, borgmatic_source_directory, postgresql_dump_format='custom'
source_directory,
config_path,
repository_path,
borgmatic_source_directory,
postgresql_dump_format='custom',
):
'''
Write out borgmatic configuration into a file at the config path. Set the options so as to work
for testing. This includes injecting the given repository path, borgmatic source directory for
storing database dumps, dump format (for PostgreSQL), and encryption passphrase.
'''
config = '''
config = f'''
location:
source_directories:
- {}
- {source_directory}
repositories:
- {}
borgmatic_source_directory: {}
- {repository_path}
borgmatic_source_directory: {borgmatic_source_directory}
storage:
encryption_passphrase: "test"
@@ -33,7 +37,7 @@ hooks:
hostname: postgresql
username: postgres
password: test
format: {}
format: {postgresql_dump_format}
- name: all
hostname: postgresql
username: postgres
@@ -57,9 +61,7 @@ hooks:
hostname: mongodb
username: root
password: test
'''.format(
config_path, repository_path, borgmatic_source_directory, postgresql_dump_format
)
'''
with open(config_path, 'w') as config_file:
config_file.write(config)
@@ -71,11 +73,16 @@ def test_database_dump_and_restore():
repository_path = os.path.join(temporary_directory, 'test.borg')
borgmatic_source_directory = os.path.join(temporary_directory, '.borgmatic')
# Write out a special file to ensure that it gets properly excluded and Borg doesn't hang on it.
os.mkfifo(os.path.join(temporary_directory, 'special_file'))
original_working_directory = os.getcwd()
try:
config_path = os.path.join(temporary_directory, 'test.yaml')
write_configuration(config_path, repository_path, borgmatic_source_directory)
write_configuration(
temporary_directory, config_path, repository_path, borgmatic_source_directory
)
subprocess.check_call(
['borgmatic', '-v', '2', '--config', config_path, 'init', '--encryption', 'repokey']
@@ -114,6 +121,7 @@ def test_database_dump_and_restore_with_directory_format():
try:
config_path = os.path.join(temporary_directory, 'test.yaml')
write_configuration(
temporary_directory,
config_path,
repository_path,
borgmatic_source_directory,
@@ -146,7 +154,9 @@ def test_database_dump_with_error_causes_borgmatic_to_exit():
try:
config_path = os.path.join(temporary_directory, 'test.yaml')
write_configuration(config_path, repository_path, borgmatic_source_directory)
write_configuration(
temporary_directory, config_path, repository_path, borgmatic_source_directory
)
subprocess.check_call(
['borgmatic', '-v', '2', '--config', config_path, 'init', '--encryption', 'repokey']
+3 -5
View File
@@ -16,8 +16,9 @@ def generate_configuration(config_path, repository_path):
config = (
open(config_path)
.read()
.replace('user@backupserver:sourcehostname.borg', repository_path)
.replace('- user@backupserver:{fqdn}', '')
.replace('ssh://user@backupserver/./sourcehostname.borg', repository_path)
.replace('- ssh://user@backupserver/./{fqdn}', '')
.replace('- /var/local/backups/local.borg', '')
.replace('- /home/user/path with spaces', '')
.replace('- /home', '- {}'.format(config_path))
.replace('- /etc', '')
@@ -32,11 +33,8 @@ def generate_configuration(config_path, repository_path):
def test_override_get_normalized():
temporary_directory = tempfile.mkdtemp()
repository_path = os.path.join(temporary_directory, 'test.borg')
extract_path = os.path.join(temporary_directory, 'extract')
original_working_directory = os.getcwd()
os.mkdir(extract_path)
os.chdir(extract_path)
try:
config_path = os.path.join(temporary_directory, 'test.yaml')
+7 -7
View File
@@ -450,7 +450,7 @@ def test_parse_arguments_disallows_json_with_both_rinfo_and_info():
module.parse_arguments('rinfo', 'info', '--json')
def test_parse_arguments_disallows_transfer_with_both_archive_and_glob_archives():
def test_parse_arguments_disallows_transfer_with_both_archive_and_match_archives():
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
with pytest.raises(ValueError):
@@ -460,16 +460,16 @@ def test_parse_arguments_disallows_transfer_with_both_archive_and_glob_archives(
'source.borg',
'--archive',
'foo',
'--glob-archives',
'*bar',
'--match-archives',
'sh:*bar',
)
def test_parse_arguments_disallows_info_with_both_archive_and_glob_archives():
def test_parse_arguments_disallows_info_with_both_archive_and_match_archives():
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
with pytest.raises(ValueError):
module.parse_arguments('info', '--archive', 'foo', '--glob-archives', '*bar')
module.parse_arguments('info', '--archive', 'foo', '--match-archives', 'sh:*bar')
def test_parse_arguments_disallows_info_with_both_archive_and_prefix():
@@ -479,11 +479,11 @@ def test_parse_arguments_disallows_info_with_both_archive_and_prefix():
module.parse_arguments('info', '--archive', 'foo', '--prefix', 'bar')
def test_parse_arguments_disallows_info_with_both_prefix_and_glob_archives():
def test_parse_arguments_disallows_info_with_both_prefix_and_match_archives():
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
with pytest.raises(ValueError):
module.parse_arguments('info', '--prefix', 'foo', '--glob-archives', '*bar')
module.parse_arguments('info', '--prefix', 'foo', '--match-archives', 'sh:*bar')
def test_parse_arguments_check_only_extract_does_not_raise_extract_subparser_error():
+1 -1
View File
@@ -278,7 +278,7 @@ def test_log_outputs_with_unfinished_process_re_polls():
flexmock(module).should_receive('exit_code_indicates_error').and_return(False)
process = subprocess.Popen(['true'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
flexmock(process).should_receive('poll').and_return(None).and_return(0).twice()
flexmock(process).should_receive('poll').and_return(None).and_return(0).times(3)
flexmock(module).should_receive('output_buffer_for_process').and_return(process.stdout)
module.log_outputs(
+70
View File
@@ -0,0 +1,70 @@
import logging
from flexmock import flexmock
from borgmatic.borg import break_lock as module
from ..test_verbosity import insert_logging_mock
def insert_execute_command_mock(command):
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
command, borg_local_path='borg', extra_environment=None,
).once()
def test_break_lock_calls_borg_with_required_flags():
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'break-lock', 'repo'))
module.break_lock(
repository='repo', storage_config={}, local_borg_version='1.2.3',
)
def test_break_lock_calls_borg_with_remote_path_flags():
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'break-lock', '--remote-path', 'borg1', 'repo'))
module.break_lock(
repository='repo', storage_config={}, local_borg_version='1.2.3', remote_path='borg1',
)
def test_break_lock_calls_borg_with_umask_flags():
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'break-lock', '--umask', '0770', 'repo'))
module.break_lock(
repository='repo', storage_config={'umask': '0770'}, local_borg_version='1.2.3',
)
def test_break_lock_calls_borg_with_lock_wait_flags():
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'break-lock', '--lock-wait', '5', 'repo'))
module.break_lock(
repository='repo', storage_config={'lock_wait': '5'}, local_borg_version='1.2.3',
)
def test_break_lock_with_log_info_calls_borg_with_info_parameter():
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'break-lock', '--info', 'repo'))
insert_logging_mock(logging.INFO)
module.break_lock(
repository='repo', storage_config={}, local_borg_version='1.2.3',
)
def test_break_lock_with_log_debug_calls_borg_with_debug_flags():
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'break-lock', '--debug', '--show-rc', 'repo'))
insert_logging_mock(logging.DEBUG)
module.break_lock(
repository='repo', storage_config={}, local_borg_version='1.2.3',
)
+70 -28
View File
@@ -188,95 +188,137 @@ def test_filter_checks_on_frequency_restains_check_with_unelapsed_frequency_and_
def test_make_check_flags_with_repository_check_returns_flag():
flags = module.make_check_flags(('repository',))
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags('1.2.3', ('repository',))
assert flags == ('--repository-only',)
def test_make_check_flags_with_archives_check_returns_flag():
flags = module.make_check_flags(('archives',))
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags('1.2.3', ('archives',))
assert flags == ('--archives-only',)
def test_make_check_flags_with_data_check_returns_flag_and_implies_archives():
flags = module.make_check_flags(('data',))
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags('1.2.3', ('data',))
assert flags == ('--archives-only', '--verify-data',)
def test_make_check_flags_with_extract_omits_extract_flag():
flags = module.make_check_flags(('extract',))
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags('1.2.3', ('extract',))
assert flags == ()
def test_make_check_flags_with_repository_and_data_checks_does_not_return_repository_only():
flags = module.make_check_flags(('repository', 'data',))
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags('1.2.3', ('repository', 'data',))
assert flags == ('--verify-data',)
def test_make_check_flags_with_default_checks_and_default_prefix_returns_default_flags():
flags = module.make_check_flags(('repository', 'archives'), prefix=module.DEFAULT_PREFIX)
flexmock(module.feature).should_receive('available').and_return(True)
assert flags == ('--glob-archives', f'{module.DEFAULT_PREFIX}*')
flags = module.make_check_flags(
'1.2.3', ('repository', 'archives'), prefix=module.DEFAULT_PREFIX
)
assert flags == ('--match-archives', f'sh:{module.DEFAULT_PREFIX}*')
def test_make_check_flags_with_all_checks_and_default_prefix_returns_default_flags():
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags(
('repository', 'archives', 'extract'), prefix=module.DEFAULT_PREFIX
'1.2.3', ('repository', 'archives', 'extract'), prefix=module.DEFAULT_PREFIX
)
assert flags == ('--match-archives', f'sh:{module.DEFAULT_PREFIX}*')
def test_make_check_flags_with_all_checks_and_default_prefix_without_borg_features_returns_glob_archives_flags():
flexmock(module.feature).should_receive('available').and_return(False)
flags = module.make_check_flags(
'1.2.3', ('repository', 'archives', 'extract'), prefix=module.DEFAULT_PREFIX
)
assert flags == ('--glob-archives', f'{module.DEFAULT_PREFIX}*')
def test_make_check_flags_with_archives_check_and_last_includes_last_flag():
flags = module.make_check_flags(('archives',), check_last=3)
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags('1.2.3', ('archives',), check_last=3)
assert flags == ('--archives-only', '--last', '3')
def test_make_check_flags_with_repository_check_and_last_omits_last_flag():
flags = module.make_check_flags(('repository',), check_last=3)
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags('1.2.3', ('repository',), check_last=3)
assert flags == ('--repository-only',)
def test_make_check_flags_with_default_checks_and_last_includes_last_flag():
flags = module.make_check_flags(('repository', 'archives'), check_last=3)
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags('1.2.3', ('repository', 'archives'), check_last=3)
assert flags == ('--last', '3')
def test_make_check_flags_with_archives_check_and_prefix_includes_glob_archives_flag():
flags = module.make_check_flags(('archives',), prefix='foo-')
def test_make_check_flags_with_archives_check_and_prefix_includes_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
assert flags == ('--archives-only', '--glob-archives', 'foo-*')
flags = module.make_check_flags('1.2.3', ('archives',), prefix='foo-')
assert flags == ('--archives-only', '--match-archives', 'sh:foo-*')
def test_make_check_flags_with_archives_check_and_empty_prefix_omits_glob_archives_flag():
flags = module.make_check_flags(('archives',), prefix='')
def test_make_check_flags_with_archives_check_and_empty_prefix_omits_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags('1.2.3', ('archives',), prefix='')
assert flags == ('--archives-only',)
def test_make_check_flags_with_archives_check_and_none_prefix_omits_glob_archives_flag():
flags = module.make_check_flags(('archives',), prefix=None)
def test_make_check_flags_with_archives_check_and_none_prefix_omits_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags('1.2.3', ('archives',), prefix=None)
assert flags == ('--archives-only',)
def test_make_check_flags_with_repository_check_and_prefix_omits_glob_archives_flag():
flags = module.make_check_flags(('repository',), prefix='foo-')
def test_make_check_flags_with_repository_check_and_prefix_omits_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
flags = module.make_check_flags('1.2.3', ('repository',), prefix='foo-')
assert flags == ('--repository-only',)
def test_make_check_flags_with_default_checks_and_prefix_includes_glob_archives_flag():
flags = module.make_check_flags(('repository', 'archives'), prefix='foo-')
def test_make_check_flags_with_default_checks_and_prefix_includes_match_archives_flag():
flexmock(module.feature).should_receive('available').and_return(True)
assert flags == ('--glob-archives', 'foo-*')
flags = module.make_check_flags('1.2.3', ('repository', 'archives'), prefix='foo-')
assert flags == ('--match-archives', 'sh:foo-*')
def test_read_check_time_does_not_raise():
@@ -369,7 +411,7 @@ def test_check_archives_calls_borg_with_parameters(checks):
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
checks, check_last, module.DEFAULT_PREFIX
'1.2.3', checks, check_last, module.DEFAULT_PREFIX
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', 'repo'))
@@ -523,7 +565,7 @@ def test_check_archives_with_local_path_calls_borg_via_local_path():
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
checks, check_last, module.DEFAULT_PREFIX
'1.2.3', checks, check_last, module.DEFAULT_PREFIX
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg1', 'check', 'repo'))
@@ -550,7 +592,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters(
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
checks, check_last, module.DEFAULT_PREFIX
'1.2.3', checks, check_last, module.DEFAULT_PREFIX
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', '--remote-path', 'borg1', 'repo'))
@@ -577,7 +619,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
checks, check_last, module.DEFAULT_PREFIX
'1.2.3', checks, check_last, module.DEFAULT_PREFIX
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', '--lock-wait', '5', 'repo'))
@@ -604,7 +646,7 @@ def test_check_archives_with_retention_prefix():
'{"repository": {"id": "repo"}}'
)
flexmock(module).should_receive('make_check_flags').with_args(
checks, check_last, prefix
'1.2.3', checks, check_last, prefix
).and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg', 'check', 'repo'))
File diff suppressed because it is too large Load Diff
+9 -63
View File
@@ -12,7 +12,6 @@ def test_display_archives_info_calls_borg_with_parameters():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--repo', 'repo'),
@@ -29,32 +28,10 @@ def test_display_archives_info_calls_borg_with_parameters():
)
def test_display_archives_info_without_borg_features_calls_borg_without_repo_flag():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
flexmock(module.feature).should_receive('available').and_return(False)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', 'repo'),
output_log_level=logging.WARNING,
borg_local_path='borg',
extra_environment=None,
)
module.display_archives_info(
repository='repo',
storage_config={},
local_borg_version='2.3.4',
info_arguments=flexmock(archive=None, json=False, prefix=None),
)
def test_display_archives_info_with_log_info_calls_borg_with_info_parameter():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--info', '--repo', 'repo'),
@@ -75,7 +52,6 @@ def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_outpu
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--json', '--repo', 'repo'),
@@ -99,7 +75,6 @@ def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--debug', '--show-rc', '--repo', 'repo'),
@@ -121,7 +96,6 @@ def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_outp
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--json', '--repo', 'repo'),
@@ -145,7 +119,6 @@ def test_display_archives_info_with_json_calls_borg_with_json_parameter():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--json',))
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--json', '--repo', 'repo'),
@@ -164,38 +137,16 @@ def test_display_archives_info_with_json_calls_borg_with_json_parameter():
assert json_output == '[]'
def test_display_archives_info_with_archive_calls_borg_with_glob_archives_parameter():
def test_display_archives_info_with_archive_calls_borg_with_match_archives_parameter():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags').with_args(
'glob-archives', 'archive'
).and_return(('--glob-archives', 'archive'))
'match-archives', 'archive'
).and_return(('--match-archives', 'archive'))
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--repo', 'repo', '--glob-archives', 'archive'),
output_log_level=logging.WARNING,
borg_local_path='borg',
extra_environment=None,
)
module.display_archives_info(
repository='repo',
storage_config={},
local_borg_version='2.3.4',
info_arguments=flexmock(archive='archive', json=False, prefix=None),
)
def test_display_archives_info_with_archive_and_without_borg_features_calls_borg_with_repo_archive_parameter():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo::archive',))
flexmock(module.feature).should_receive('available').and_return(False)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', 'repo::archive'),
('borg', 'info', '--repo', 'repo', '--match-archives', 'archive'),
output_log_level=logging.WARNING,
borg_local_path='borg',
extra_environment=None,
@@ -212,7 +163,6 @@ def test_display_archives_info_with_archive_and_without_borg_features_calls_borg
def test_display_archives_info_with_local_path_calls_borg_via_local_path():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
@@ -238,7 +188,6 @@ def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_para
).and_return(('--remote-path', 'borg1'))
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--remote-path', 'borg1', '--repo', 'repo'),
@@ -264,7 +213,6 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
storage_config = {'lock_wait': 5}
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--lock-wait', '5', '--repo', 'repo'),
@@ -281,17 +229,16 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete
)
def test_display_archives_info_with_prefix_calls_borg_with_glob_archives_parameters():
def test_display_archives_info_with_prefix_calls_borg_with_match_archives_parameters():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags').with_args(
'glob-archives', 'foo*'
).and_return(('--glob-archives', 'foo*'))
'match-archives', 'sh:foo*'
).and_return(('--match-archives', 'sh:foo*'))
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', '--glob-archives', 'foo*', '--repo', 'repo'),
('borg', 'info', '--match-archives', 'sh:foo*', '--repo', 'repo'),
output_log_level=logging.WARNING,
borg_local_path='borg',
extra_environment=None,
@@ -305,7 +252,7 @@ def test_display_archives_info_with_prefix_calls_borg_with_glob_archives_paramet
)
@pytest.mark.parametrize('argument_name', ('glob_archives', 'sort_by', 'first', 'last'))
@pytest.mark.parametrize('argument_name', ('match_archives', 'sort_by', 'first', 'last'))
def test_display_archives_info_passes_through_arguments_to_borg(argument_name):
flag_name = f"--{argument_name.replace('_', ' ')}"
flexmock(module.flags).should_receive('make_flags').and_return(())
@@ -313,7 +260,6 @@ def test_display_archives_info_passes_through_arguments_to_borg(argument_name):
(flag_name, 'value')
)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', flag_name, 'value', '--repo', 'repo'),
+11 -11
View File
@@ -192,7 +192,7 @@ def test_make_list_command_includes_short():
'argument_name',
(
'prefix',
'glob_archives',
'match_archives',
'sort_by',
'first',
'last',
@@ -260,7 +260,7 @@ def test_list_archive_calls_borg_with_parameters():
json=False,
find_paths=None,
prefix=None,
glob_archives=None,
match_archives=None,
sort_by=None,
first=None,
last=None,
@@ -313,7 +313,7 @@ def test_list_archive_calls_borg_with_local_path():
json=False,
find_paths=None,
prefix=None,
glob_archives=None,
match_archives=None,
sort_by=None,
first=None,
last=None,
@@ -353,7 +353,7 @@ def test_list_archive_calls_borg_multiple_times_with_find_paths():
json=False,
find_paths=['foo.txt'],
prefix=None,
glob_archives=None,
match_archives=None,
sort_by=None,
first=None,
last=None,
@@ -400,7 +400,7 @@ def test_list_archive_calls_borg_with_archive():
json=False,
find_paths=None,
prefix=None,
glob_archives=None,
match_archives=None,
sort_by=None,
first=None,
last=None,
@@ -439,7 +439,7 @@ def test_list_archive_without_archive_delegates_to_list_repository():
format=None,
json=None,
prefix=None,
glob_archives=None,
match_archives=None,
sort_by=None,
first=None,
last=None,
@@ -466,7 +466,7 @@ def test_list_archive_with_borg_features_without_archive_delegates_to_list_repos
format=None,
json=None,
prefix=None,
glob_archives=None,
match_archives=None,
sort_by=None,
first=None,
last=None,
@@ -487,12 +487,12 @@ def test_list_archive_with_borg_features_without_archive_delegates_to_list_repos
@pytest.mark.parametrize(
'archive_filter_flag', ('prefix', 'glob_archives', 'sort_by', 'first', 'last',),
'archive_filter_flag', ('prefix', 'match_archives', 'sort_by', 'first', 'last',),
)
def test_list_archive_with_archive_ignores_archive_filter_flag(archive_filter_flag,):
default_filter_flags = {
'prefix': None,
'glob_archives': None,
'match_archives': None,
'sort_by': None,
'first': None,
'last': None,
@@ -532,14 +532,14 @@ def test_list_archive_with_archive_ignores_archive_filter_flag(archive_filter_fl
@pytest.mark.parametrize(
'archive_filter_flag', ('prefix', 'glob_archives', 'sort_by', 'first', 'last',),
'archive_filter_flag', ('prefix', 'match_archives', 'sort_by', 'first', 'last',),
)
def test_list_archive_with_find_paths_allows_archive_filter_flag_but_only_passes_it_to_rlist(
archive_filter_flag,
):
default_filter_flags = {
'prefix': None,
'glob_archives': None,
'match_archives': None,
'sort_by': None,
'first': None,
'last': None,
+2 -2
View File
@@ -31,11 +31,11 @@ def test_mount_archive_calls_borg_with_required_flags():
)
def test_mount_archive_with_borg_features_calls_borg_with_repository_and_glob_archives_flags():
def test_mount_archive_with_borg_features_calls_borg_with_repository_and_match_archives_flags():
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo',))
insert_execute_command_mock(
('borg', 'mount', '--repo', 'repo', '--glob-archives', 'archive', '/mnt')
('borg', 'mount', '--repo', 'repo', '--match-archives', 'archive', '/mnt')
)
module.mount_archive(
+46 -70
View File
@@ -23,16 +23,29 @@ BASE_PRUNE_FLAGS = (('--keep-daily', '1'), ('--keep-weekly', '2'), ('--keep-mont
def test_make_prune_flags_returns_flags_from_config_plus_default_prefix_glob():
retention_config = OrderedDict((('keep_daily', 1), ('keep_weekly', 2), ('keep_monthly', 3)))
flexmock(module.feature).should_receive('available').and_return(True)
result = module.make_prune_flags(retention_config)
result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
assert tuple(result) == BASE_PRUNE_FLAGS + (('--glob-archives', '{hostname}-*'),)
assert tuple(result) == BASE_PRUNE_FLAGS + (('--match-archives', 'sh:{hostname}-*'),)
def test_make_prune_flags_accepts_prefix_with_placeholders():
retention_config = OrderedDict((('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}')))
flexmock(module.feature).should_receive('available').and_return(True)
result = module.make_prune_flags(retention_config)
result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
expected = (('--keep-daily', '1'), ('--match-archives', 'sh:Documents_{hostname}-{now}*'))
assert tuple(result) == expected
def test_make_prune_flags_with_prefix_without_borg_features_uses_glob_archives():
retention_config = OrderedDict((('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}')))
flexmock(module.feature).should_receive('available').and_return(False)
result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
expected = (('--keep-daily', '1'), ('--glob-archives', 'Documents_{hostname}-{now}*'))
@@ -41,8 +54,9 @@ def test_make_prune_flags_accepts_prefix_with_placeholders():
def test_make_prune_flags_treats_empty_prefix_as_no_prefix():
retention_config = OrderedDict((('keep_daily', 1), ('prefix', '')))
flexmock(module.feature).should_receive('available').and_return(True)
result = module.make_prune_flags(retention_config)
result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
expected = (('--keep-daily', '1'),)
@@ -51,8 +65,9 @@ def test_make_prune_flags_treats_empty_prefix_as_no_prefix():
def test_make_prune_flags_treats_none_prefix_as_no_prefix():
retention_config = OrderedDict((('keep_daily', 1), ('prefix', None)))
flexmock(module.feature).should_receive('available').and_return(True)
result = module.make_prune_flags(retention_config)
result = module.make_prune_flags(retention_config, local_borg_version='1.2.3')
expected = (('--keep-daily', '1'),)
@@ -63,10 +78,7 @@ PRUNE_COMMAND = ('borg', 'prune', '--keep-daily', '1', '--keep-weekly', '2', '--
def test_prune_archives_calls_borg_with_parameters():
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(PRUNE_COMMAND + ('repo',), logging.INFO)
@@ -74,16 +86,13 @@ def test_prune_archives_calls_borg_with_parameters():
dry_run=False,
repository='repo',
storage_config={},
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
)
def test_prune_archives_with_log_info_calls_borg_with_info_parameter():
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(PRUNE_COMMAND + ('--info', 'repo'), logging.INFO)
insert_logging_mock(logging.INFO)
@@ -92,16 +101,13 @@ def test_prune_archives_with_log_info_calls_borg_with_info_parameter():
repository='repo',
storage_config={},
dry_run=False,
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
)
def test_prune_archives_with_log_debug_calls_borg_with_debug_parameter():
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(PRUNE_COMMAND + ('--debug', '--show-rc', 'repo'), logging.INFO)
insert_logging_mock(logging.DEBUG)
@@ -110,16 +116,13 @@ def test_prune_archives_with_log_debug_calls_borg_with_debug_parameter():
repository='repo',
storage_config={},
dry_run=False,
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
)
def test_prune_archives_with_dry_run_calls_borg_with_dry_run_parameter():
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(PRUNE_COMMAND + ('--dry-run', 'repo'), logging.INFO)
@@ -127,16 +130,13 @@ def test_prune_archives_with_dry_run_calls_borg_with_dry_run_parameter():
repository='repo',
storage_config={},
dry_run=True,
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
)
def test_prune_archives_with_local_path_calls_borg_via_local_path():
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(('borg1',) + PRUNE_COMMAND[1:] + ('repo',), logging.INFO)
@@ -144,17 +144,14 @@ def test_prune_archives_with_local_path_calls_borg_via_local_path():
dry_run=False,
repository='repo',
storage_config={},
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
local_path='borg1',
)
def test_prune_archives_with_remote_path_calls_borg_with_remote_path_parameters():
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(PRUNE_COMMAND + ('--remote-path', 'borg1', 'repo'), logging.INFO)
@@ -162,17 +159,14 @@ def test_prune_archives_with_remote_path_calls_borg_with_remote_path_parameters(
dry_run=False,
repository='repo',
storage_config={},
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
remote_path='borg1',
)
def test_prune_archives_with_stats_calls_borg_with_stats_parameter_and_warning_output_log_level():
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(PRUNE_COMMAND + ('--stats', 'repo'), logging.WARNING)
@@ -180,17 +174,14 @@ def test_prune_archives_with_stats_calls_borg_with_stats_parameter_and_warning_o
dry_run=False,
repository='repo',
storage_config={},
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
stats=True,
)
def test_prune_archives_with_stats_and_log_info_calls_borg_with_stats_parameter_and_info_output_log_level():
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_logging_mock(logging.INFO)
insert_execute_command_mock(PRUNE_COMMAND + ('--stats', '--info', 'repo'), logging.INFO)
@@ -199,17 +190,14 @@ def test_prune_archives_with_stats_and_log_info_calls_borg_with_stats_parameter_
dry_run=False,
repository='repo',
storage_config={},
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
stats=True,
)
def test_prune_archives_with_files_calls_borg_with_list_parameter_and_warning_output_log_level():
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(PRUNE_COMMAND + ('--list', 'repo'), logging.WARNING)
@@ -217,17 +205,14 @@ def test_prune_archives_with_files_calls_borg_with_list_parameter_and_warning_ou
dry_run=False,
repository='repo',
storage_config={},
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
list_archives=True,
)
def test_prune_archives_with_files_and_log_info_calls_borg_with_list_parameter_and_info_output_log_level():
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_logging_mock(logging.INFO)
insert_execute_command_mock(PRUNE_COMMAND + ('--info', '--list', 'repo'), logging.INFO)
@@ -236,7 +221,7 @@ def test_prune_archives_with_files_and_log_info_calls_borg_with_list_parameter_a
dry_run=False,
repository='repo',
storage_config={},
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
list_archives=True,
)
@@ -244,10 +229,7 @@ def test_prune_archives_with_files_and_log_info_calls_borg_with_list_parameter_a
def test_prune_archives_with_umask_calls_borg_with_umask_parameters():
storage_config = {'umask': '077'}
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(PRUNE_COMMAND + ('--umask', '077', 'repo'), logging.INFO)
@@ -255,17 +237,14 @@ def test_prune_archives_with_umask_calls_borg_with_umask_parameters():
dry_run=False,
repository='repo',
storage_config=storage_config,
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
)
def test_prune_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
storage_config = {'lock_wait': 5}
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(PRUNE_COMMAND + ('--lock-wait', '5', 'repo'), logging.INFO)
@@ -273,16 +252,13 @@ def test_prune_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
dry_run=False,
repository='repo',
storage_config=storage_config,
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
)
def test_prune_archives_with_extra_borg_options_calls_borg_with_extra_options():
retention_config = flexmock()
flexmock(module).should_receive('make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
insert_execute_command_mock(PRUNE_COMMAND + ('--extra', '--options', 'repo'), logging.INFO)
@@ -290,6 +266,6 @@ def test_prune_archives_with_extra_borg_options_calls_borg_with_extra_options():
dry_run=False,
repository='repo',
storage_config={'extra_borg_options': {'prune': '--extra --options'}},
retention_config=retention_config,
retention_config=flexmock(),
local_borg_version='1.2.3',
)
+8 -8
View File
@@ -41,11 +41,11 @@ def test_resolve_archive_name_calls_borg_with_parameters():
)
def test_resolve_archive_name_with_log_info_calls_borg_with_info_parameter():
def test_resolve_archive_name_with_log_info_calls_borg_without_info_parameter():
expected_archive = 'archive-name'
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'list', '--info') + BORG_LIST_LATEST_ARGUMENTS,
('borg', 'list') + BORG_LIST_LATEST_ARGUMENTS,
output_log_level=None,
borg_local_path='borg',
extra_environment=None,
@@ -58,11 +58,11 @@ def test_resolve_archive_name_with_log_info_calls_borg_with_info_parameter():
)
def test_resolve_archive_name_with_log_debug_calls_borg_with_debug_parameter():
def test_resolve_archive_name_with_log_debug_calls_borg_without_debug_parameter():
expected_archive = 'archive-name'
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'list', '--debug', '--show-rc') + BORG_LIST_LATEST_ARGUMENTS,
('borg', 'list') + BORG_LIST_LATEST_ARGUMENTS,
output_log_level=None,
borg_local_path='borg',
extra_environment=None,
@@ -273,9 +273,9 @@ def test_make_rlist_command_includes_remote_path():
assert command == ('borg', 'list', '--remote-path', 'borg2', 'repo')
def test_make_rlist_command_transforms_prefix_into_glob_archives():
def test_make_rlist_command_transforms_prefix_into_match_archives():
flexmock(module.flags).should_receive('make_flags').and_return(()).and_return(()).and_return(
('--glob-archives', 'foo*')
('--match-archives', 'sh:foo*')
)
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
@@ -287,7 +287,7 @@ def test_make_rlist_command_transforms_prefix_into_glob_archives():
rlist_arguments=flexmock(archive=None, paths=None, json=False, prefix='foo'),
)
assert command == ('borg', 'list', '--glob-archives', 'foo*', 'repo')
assert command == ('borg', 'list', '--match-archives', 'sh:foo*', 'repo')
def test_make_rlist_command_includes_short():
@@ -308,7 +308,7 @@ def test_make_rlist_command_includes_short():
@pytest.mark.parametrize(
'argument_name',
(
'glob_archives',
'match_archives',
'sort_by',
'first',
'last',
+19 -19
View File
@@ -25,7 +25,7 @@ def test_transfer_archives_calls_borg_with_flags():
repository='repo',
storage_config={},
local_borg_version='2.3.4',
transfer_arguments=flexmock(archive=None, glob_archives=None, source_repository=None),
transfer_arguments=flexmock(archive=None, match_archives=None, source_repository=None),
)
@@ -49,7 +49,7 @@ def test_transfer_archives_with_dry_run_calls_borg_with_dry_run_flag():
repository='repo',
storage_config={},
local_borg_version='2.3.4',
transfer_arguments=flexmock(archive=None, glob_archives=None, source_repository=None),
transfer_arguments=flexmock(archive=None, match_archives=None, source_repository=None),
)
@@ -70,7 +70,7 @@ def test_transfer_archives_with_log_info_calls_borg_with_info_flag():
repository='repo',
storage_config={},
local_borg_version='2.3.4',
transfer_arguments=flexmock(archive=None, glob_archives=None, source_repository=None),
transfer_arguments=flexmock(archive=None, match_archives=None, source_repository=None),
)
@@ -92,20 +92,20 @@ def test_transfer_archives_with_log_debug_calls_borg_with_debug_flag():
repository='repo',
storage_config={},
local_borg_version='2.3.4',
transfer_arguments=flexmock(archive=None, glob_archives=None, source_repository=None),
transfer_arguments=flexmock(archive=None, match_archives=None, source_repository=None),
)
def test_transfer_archives_with_archive_calls_borg_with_glob_archives_flag():
def test_transfer_archives_with_archive_calls_borg_with_match_archives_flag():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags').with_args(
'glob-archives', 'archive'
).and_return(('--glob-archives', 'archive'))
'match-archives', 'archive'
).and_return(('--match-archives', 'archive'))
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'transfer', '--glob-archives', 'archive', '--repo', 'repo'),
('borg', 'transfer', '--match-archives', 'archive', '--repo', 'repo'),
output_log_level=logging.WARNING,
borg_local_path='borg',
extra_environment=None,
@@ -116,20 +116,20 @@ def test_transfer_archives_with_archive_calls_borg_with_glob_archives_flag():
repository='repo',
storage_config={},
local_borg_version='2.3.4',
transfer_arguments=flexmock(archive='archive', glob_archives=None, source_repository=None),
transfer_arguments=flexmock(archive='archive', match_archives=None, source_repository=None),
)
def test_transfer_archives_with_glob_archives_calls_borg_with_glob_archives_flag():
def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_flag():
flexmock(module.flags).should_receive('make_flags').and_return(())
flexmock(module.flags).should_receive('make_flags').with_args(
'glob-archives', 'foo*'
).and_return(('--glob-archives', 'foo*'))
'match-archives', 'sh:foo*'
).and_return(('--match-archives', 'sh:foo*'))
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(())
flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'transfer', '--glob-archives', 'foo*', '--repo', 'repo'),
('borg', 'transfer', '--match-archives', 'sh:foo*', '--repo', 'repo'),
output_log_level=logging.WARNING,
borg_local_path='borg',
extra_environment=None,
@@ -140,7 +140,7 @@ def test_transfer_archives_with_glob_archives_calls_borg_with_glob_archives_flag
repository='repo',
storage_config={},
local_borg_version='2.3.4',
transfer_arguments=flexmock(archive=None, glob_archives='foo*', source_repository=None),
transfer_arguments=flexmock(archive=None, match_archives='sh:foo*', source_repository=None),
)
@@ -161,7 +161,7 @@ def test_transfer_archives_with_local_path_calls_borg_via_local_path():
repository='repo',
storage_config={},
local_borg_version='2.3.4',
transfer_arguments=flexmock(archive=None, glob_archives=None, source_repository=None),
transfer_arguments=flexmock(archive=None, match_archives=None, source_repository=None),
local_path='borg2',
)
@@ -186,7 +186,7 @@ def test_transfer_archives_with_remote_path_calls_borg_with_remote_path_flags():
repository='repo',
storage_config={},
local_borg_version='2.3.4',
transfer_arguments=flexmock(archive=None, glob_archives=None, source_repository=None),
transfer_arguments=flexmock(archive=None, match_archives=None, source_repository=None),
remote_path='borg2',
)
@@ -212,7 +212,7 @@ def test_transfer_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
repository='repo',
storage_config=storage_config,
local_borg_version='2.3.4',
transfer_arguments=flexmock(archive=None, glob_archives=None, source_repository=None),
transfer_arguments=flexmock(archive=None, match_archives=None, source_repository=None),
)
@@ -238,7 +238,7 @@ def test_transfer_archives_passes_through_arguments_to_borg(argument_name):
storage_config={},
local_borg_version='2.3.4',
transfer_arguments=flexmock(
archive=None, glob_archives=None, source_repository=None, **{argument_name: 'value'}
archive=None, match_archives=None, source_repository=None, **{argument_name: 'value'}
),
)
@@ -263,5 +263,5 @@ def test_transfer_archives_with_source_repository_calls_borg_with_other_repo_fla
repository='repo',
storage_config={},
local_borg_version='2.3.4',
transfer_arguments=flexmock(archive=None, glob_archives=None, source_repository='other'),
transfer_arguments=flexmock(archive=None, match_archives=None, source_repository='other'),
)
+27 -1
View File
@@ -455,7 +455,8 @@ def test_run_actions_executes_and_calls_hooks_for_create_action():
flexmock(module.command).should_receive('execute_hook').times(
4
) # Before/after extract and before/after actions.
flexmock(module.dispatch).should_receive('call_hooks').and_return({}).times(3)
flexmock(module.dispatch).should_receive('call_hooks').and_return({})
flexmock(module.dispatch).should_receive('call_hooks_even_if_unconfigured').and_return({})
arguments = {
'global': flexmock(monitoring_verbosity=1, dry_run=False),
'create': flexmock(
@@ -712,6 +713,31 @@ def test_run_actions_does_not_raise_for_info_action():
)
def test_run_actions_does_not_raise_for_break_lock_action():
flexmock(module.validate).should_receive('repositories_match').and_return(True)
flexmock(module.borg_break_lock).should_receive('break_lock')
arguments = {
'global': flexmock(monitoring_verbosity=1, dry_run=False),
'break-lock': flexmock(repository=flexmock()),
}
list(
module.run_actions(
arguments=arguments,
config_filename='test.yaml',
location={'repositories': ['repo']},
storage={},
retention={},
consistency={},
hooks={},
local_path=None,
remote_path=None,
local_borg_version=None,
repository_path='repo',
)
)
def test_run_actions_does_not_raise_for_borg_action():
flexmock(module.validate).should_receive('repositories_match').and_return(True)
flexmock(module.borg_rlist).should_receive('resolve_archive_name').and_return(flexmock())
+36 -3
View File
@@ -27,13 +27,18 @@ def test_call_hook_invokes_module_function_with_arguments_and_returns_value():
assert return_value == expected_return_value
def test_call_hook_without_hook_config_skips_call():
def test_call_hook_without_hook_config_invokes_module_function_with_arguments_and_returns_value():
hooks = {'other_hook': flexmock()}
expected_return_value = flexmock()
test_module = sys.modules[__name__]
flexmock(module).HOOK_NAME_TO_MODULE = {'super_hook': test_module}
flexmock(test_module).should_receive('hook_function').never()
flexmock(test_module).should_receive('hook_function').with_args(
{}, 'prefix', 55, value=66
).and_return(expected_return_value).once()
module.call_hook('hook_function', hooks, 'prefix', 'super_hook', 55, value=66)
return_value = module.call_hook('hook_function', hooks, 'prefix', 'super_hook', 55, value=66)
assert return_value == expected_return_value
def test_call_hook_without_corresponding_module_raises():
@@ -76,3 +81,31 @@ def test_call_hooks_calls_skips_return_values_for_null_hooks():
return_values = module.call_hooks('do_stuff', hooks, 'prefix', ('super_hook', 'other_hook'), 55)
assert return_values == expected_return_values
def test_call_hooks_even_if_unconfigured_calls_each_hook_and_collects_return_values():
hooks = {'super_hook': flexmock(), 'other_hook': flexmock()}
expected_return_values = {'super_hook': flexmock(), 'other_hook': flexmock()}
flexmock(module).should_receive('call_hook').and_return(
expected_return_values['super_hook']
).and_return(expected_return_values['other_hook'])
return_values = module.call_hooks_even_if_unconfigured(
'do_stuff', hooks, 'prefix', ('super_hook', 'other_hook'), 55
)
assert return_values == expected_return_values
def test_call_hooks_even_if_unconfigured_calls_each_hook_configured_or_not_and_collects_return_values():
hooks = {'other_hook': flexmock()}
expected_return_values = {'super_hook': flexmock(), 'other_hook': flexmock()}
flexmock(module).should_receive('call_hook').and_return(
expected_return_values['super_hook']
).and_return(expected_return_values['other_hook'])
return_values = module.call_hooks_even_if_unconfigured(
'do_stuff', hooks, 'prefix', ('super_hook', 'other_hook'), 55
)
assert return_values == expected_return_values
+8 -4
View File
@@ -218,7 +218,7 @@ def test_execute_command_captures_output():
expected_output = '[]'
flexmock(module.os, environ={'a': 'b'})
flexmock(module.subprocess).should_receive('check_output').with_args(
full_command, shell=False, env=None, cwd=None
full_command, stderr=module.subprocess.STDOUT, shell=False, env=None, cwd=None
).and_return(flexmock(decode=lambda: expected_output)).once()
output = module.execute_command(full_command, output_log_level=None)
@@ -231,7 +231,7 @@ def test_execute_command_captures_output_with_shell():
expected_output = '[]'
flexmock(module.os, environ={'a': 'b'})
flexmock(module.subprocess).should_receive('check_output').with_args(
'foo bar', shell=True, env=None, cwd=None
'foo bar', stderr=module.subprocess.STDOUT, shell=True, env=None, cwd=None
).and_return(flexmock(decode=lambda: expected_output)).once()
output = module.execute_command(full_command, output_log_level=None, shell=True)
@@ -244,7 +244,11 @@ def test_execute_command_captures_output_with_extra_environment():
expected_output = '[]'
flexmock(module.os, environ={'a': 'b'})
flexmock(module.subprocess).should_receive('check_output').with_args(
full_command, shell=False, env={'a': 'b', 'c': 'd'}, cwd=None
full_command,
stderr=module.subprocess.STDOUT,
shell=False,
env={'a': 'b', 'c': 'd'},
cwd=None,
).and_return(flexmock(decode=lambda: expected_output)).once()
output = module.execute_command(
@@ -259,7 +263,7 @@ def test_execute_command_captures_output_with_working_directory():
expected_output = '[]'
flexmock(module.os, environ={'a': 'b'})
flexmock(module.subprocess).should_receive('check_output').with_args(
full_command, shell=False, env=None, cwd='/working'
full_command, stderr=module.subprocess.STDOUT, shell=False, env=None, cwd='/working'
).and_return(flexmock(decode=lambda: expected_output)).once()
output = module.execute_command(