mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-22 18:13:02 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2002b5488 | ||
|
|
c9742e1d04 | ||
|
|
906da838ef | ||
|
|
d7f1c10c8c | ||
|
|
e8e4d17168 | ||
|
|
a31ce337e9 | ||
|
|
902730df46 | ||
|
|
c969c822ee | ||
|
|
c31702d092 | ||
|
|
ba8fbe7a44 | ||
|
|
2774c2e4c0 | ||
|
|
ae036aebd7 | ||
|
|
2e9f70d496 | ||
|
|
90be5b84b1 | ||
|
|
80e95f20a3 | ||
|
|
ac7c7d4036 | ||
|
|
858b0b9fbe | ||
|
|
9cc043f60e | ||
|
|
276a27d485 | ||
|
|
679bb839d7 | ||
|
|
9e64d847ef | ||
|
|
61fb275896 | ||
|
|
ca0c79c93c | ||
|
|
87c97b7568 | ||
|
|
80b8c25bba | ||
|
|
d1837cd1d3 | ||
|
|
c46f2b8508 | ||
|
|
a274c0dbf7 | ||
|
|
ef7e95e22a | ||
|
|
3be99de5b1 |
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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 ())
|
||||
|
||||
@@ -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 ()
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
VERSION = '1.7.0'
|
||||
VERSION = '1.7.3'
|
||||
|
||||
|
||||
setup(
|
||||
|
||||
@@ -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', '')
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
@@ -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'))
|
||||
|
||||
+481
-91
File diff suppressed because it is too large
Load Diff
@@ -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'),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user