forked from borgmatic-collective/borgmatic
Add CLI flags for every config option and add config options for many action flags (#303).
Reviewed-on: borgmatic-collective/borgmatic#1040
This commit is contained in:
12
NEWS
12
NEWS
@@ -1,6 +1,16 @@
|
||||
2.0.0.dev0
|
||||
* TL;DR: More flexible, completely revamped command hooks. All config options settable on the
|
||||
command-line. Config option defaults for many command-line flags. New "key import" and "recreate"
|
||||
actions. Almost everything is backwards compatible.
|
||||
* #262: Add a "default_actions" option that supports disabling default actions when borgmatic is
|
||||
run without any command-line arguments.
|
||||
* #303: Deprecate the "--override" flag in favor of direct command-line flags for every borgmatic
|
||||
configuration option. See the documentation for more information:
|
||||
https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/#configuration-overrides
|
||||
* #303: Add configuration options that serve as defaults for some (but not all) command-line
|
||||
action flags. For example, each entry in "repositories:" now has an "encryption" option that
|
||||
applies to the "repo-create" action, serving as a default for the "--encryption" flag. See the
|
||||
documentation for more information: https://torsion.org/borgmatic/docs/reference/configuration/
|
||||
* #345: Add a "key import" action to import a repository key from backup.
|
||||
* #422: Add home directory expansion to file-based and KeePassXC credential hooks.
|
||||
* #610: Add a "recreate" action for recreating archives, for instance for retroactively excluding
|
||||
@@ -26,7 +36,7 @@
|
||||
* #1048: Fix a "no such file or directory" error in ZFS, Btrfs, and LVM hooks with nested
|
||||
directories that reside on separate devices/filesystems.
|
||||
* #1050: Fix a failure in the "spot" check when the archive contains a symlink.
|
||||
* #1051: Add configuration filename to "Successfully ran configuration file" log message.
|
||||
* #1051: Add configuration filename to the "Successfully ran configuration file" log message.
|
||||
|
||||
1.9.14
|
||||
* #409: With the PagerDuty monitoring hook, send borgmatic logs to PagerDuty so they show up in the
|
||||
|
||||
@@ -170,7 +170,7 @@ def filter_checks_on_frequency(
|
||||
|
||||
if calendar.day_name[datetime_now().weekday()] not in days:
|
||||
logger.info(
|
||||
f"Skipping {check} check due to day of the week; check only runs on {'/'.join(days)} (use --force to check anyway)"
|
||||
f"Skipping {check} check due to day of the week; check only runs on {'/'.join(day.title() for day in days)} (use --force to check anyway)"
|
||||
)
|
||||
filtered_checks.remove(check)
|
||||
continue
|
||||
@@ -372,7 +372,7 @@ def collect_spot_check_source_paths(
|
||||
borgmatic.borg.create.make_base_create_command(
|
||||
dry_run=True,
|
||||
repository_path=repository['path'],
|
||||
config=config,
|
||||
config=dict(config, list_details=True),
|
||||
patterns=borgmatic.actions.create.process_patterns(
|
||||
borgmatic.actions.create.collect_patterns(config),
|
||||
working_directory,
|
||||
@@ -382,7 +382,6 @@ def collect_spot_check_source_paths(
|
||||
borgmatic_runtime_directory=borgmatic_runtime_directory,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
list_files=True,
|
||||
stream_processes=stream_processes,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -37,9 +37,7 @@ def run_compact(
|
||||
global_arguments,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
progress=compact_arguments.progress,
|
||||
cleanup_commits=compact_arguments.cleanup_commits,
|
||||
threshold=compact_arguments.threshold,
|
||||
)
|
||||
else: # pragma: nocover
|
||||
logger.info('Skipping compact (only available/needed in Borg 1.2+)')
|
||||
|
||||
@@ -119,7 +119,9 @@ def run_bootstrap(bootstrap_arguments, global_arguments, local_borg_version):
|
||||
bootstrap_arguments.repository,
|
||||
archive_name,
|
||||
[config_path.lstrip(os.path.sep) for config_path in manifest_config_paths],
|
||||
config,
|
||||
# Only add progress here and not the extract_archive() call above, because progress
|
||||
# conflicts with extract_to_stdout.
|
||||
dict(config, progress=bootstrap_arguments.progress or False),
|
||||
local_borg_version,
|
||||
global_arguments,
|
||||
local_path=bootstrap_arguments.local_path,
|
||||
@@ -127,5 +129,4 @@ def run_bootstrap(bootstrap_arguments, global_arguments, local_borg_version):
|
||||
extract_to_stdout=False,
|
||||
destination_path=bootstrap_arguments.destination,
|
||||
strip_components=bootstrap_arguments.strip_components,
|
||||
progress=bootstrap_arguments.progress,
|
||||
)
|
||||
|
||||
@@ -289,6 +289,16 @@ def run_create(
|
||||
):
|
||||
return
|
||||
|
||||
if config.get('list_details') and config.get('progress'):
|
||||
raise ValueError(
|
||||
'With the create action, only one of --list/--files/list_details and --progress/progress can be used.'
|
||||
)
|
||||
|
||||
if config.get('list_details') and create_arguments.json:
|
||||
raise ValueError(
|
||||
'With the create action, only one of --list/--files/list_details and --json can be used.'
|
||||
)
|
||||
|
||||
logger.info(f'Creating archive{dry_run_label}')
|
||||
working_directory = borgmatic.config.paths.get_working_directory(config)
|
||||
|
||||
@@ -327,10 +337,7 @@ def run_create(
|
||||
borgmatic_runtime_directory,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
progress=create_arguments.progress,
|
||||
stats=create_arguments.stats,
|
||||
json=create_arguments.json,
|
||||
list_files=create_arguments.list_files,
|
||||
stream_processes=stream_processes,
|
||||
)
|
||||
|
||||
|
||||
@@ -43,6 +43,5 @@ def run_export_tar(
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
tar_filter=export_tar_arguments.tar_filter,
|
||||
list_files=export_tar_arguments.list_files,
|
||||
strip_components=export_tar_arguments.strip_components,
|
||||
)
|
||||
|
||||
@@ -45,5 +45,4 @@ def run_extract(
|
||||
remote_path=remote_path,
|
||||
destination_path=extract_arguments.destination,
|
||||
strip_components=extract_arguments.strip_components,
|
||||
progress=extract_arguments.progress,
|
||||
)
|
||||
|
||||
@@ -24,18 +24,38 @@ def run_repo_create(
|
||||
return
|
||||
|
||||
logger.info('Creating repository')
|
||||
|
||||
encryption_mode = repo_create_arguments.encryption_mode or repository.get('encryption')
|
||||
|
||||
if not encryption_mode:
|
||||
raise ValueError(
|
||||
'With the repo-create action, either the --encryption flag or the repository encryption option is required.'
|
||||
)
|
||||
|
||||
borgmatic.borg.repo_create.create_repository(
|
||||
global_arguments.dry_run,
|
||||
repository['path'],
|
||||
config,
|
||||
local_borg_version,
|
||||
global_arguments,
|
||||
repo_create_arguments.encryption_mode,
|
||||
encryption_mode,
|
||||
repo_create_arguments.source_repository,
|
||||
repo_create_arguments.copy_crypt_key,
|
||||
repo_create_arguments.append_only,
|
||||
repo_create_arguments.storage_quota,
|
||||
repo_create_arguments.make_parent_dirs,
|
||||
(
|
||||
repository.get('append_only')
|
||||
if repo_create_arguments.append_only is None
|
||||
else repo_create_arguments.append_only
|
||||
),
|
||||
(
|
||||
repository.get('storage_quota')
|
||||
if repo_create_arguments.storage_quota is None
|
||||
else repo_create_arguments.storage_quota
|
||||
),
|
||||
(
|
||||
repository.get('make_parent_directories')
|
||||
if repo_create_arguments.make_parent_directories is None
|
||||
else repo_create_arguments.make_parent_directories
|
||||
),
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
)
|
||||
|
||||
@@ -17,7 +17,13 @@ def run_transfer(
|
||||
'''
|
||||
Run the "transfer" action for the given repository.
|
||||
'''
|
||||
if transfer_arguments.archive and config.get('match_archives'):
|
||||
raise ValueError(
|
||||
'With the transfer action, only one of --archive and --match-archives/match_archives can be used.'
|
||||
)
|
||||
|
||||
logger.info('Transferring archives to repository')
|
||||
|
||||
borgmatic.borg.transfer.transfer_archives(
|
||||
global_arguments.dry_run,
|
||||
repository['path'],
|
||||
|
||||
@@ -32,7 +32,7 @@ def make_archive_filter_flags(local_borg_version, config, checks, check_argument
|
||||
if prefix
|
||||
else (
|
||||
flags.make_match_archives_flags(
|
||||
check_arguments.match_archives or config.get('match_archives'),
|
||||
config.get('match_archives'),
|
||||
config.get('archive_name_format'),
|
||||
local_borg_version,
|
||||
)
|
||||
@@ -170,7 +170,7 @@ def check_archives(
|
||||
+ (('--log-json',) if global_arguments.log_json else ())
|
||||
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
||||
+ verbosity_flags
|
||||
+ (('--progress',) if check_arguments.progress else ())
|
||||
+ (('--progress',) if config.get('progress') else ())
|
||||
+ (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
|
||||
+ flags.make_repository_flags(repository_path, local_borg_version)
|
||||
)
|
||||
@@ -180,7 +180,7 @@ def check_archives(
|
||||
# The Borg repair option triggers an interactive prompt, which won't work when output is
|
||||
# captured. And progress messes with the terminal directly.
|
||||
output_file=(
|
||||
DO_NOT_CAPTURE if check_arguments.repair or check_arguments.progress else None
|
||||
DO_NOT_CAPTURE if check_arguments.repair or config.get('progress') else None
|
||||
),
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=working_directory,
|
||||
|
||||
@@ -15,9 +15,7 @@ def compact_segments(
|
||||
global_arguments,
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
progress=False,
|
||||
cleanup_commits=False,
|
||||
threshold=None,
|
||||
):
|
||||
'''
|
||||
Given dry-run flag, a local or remote repository path, a configuration dict, and the local Borg
|
||||
@@ -26,6 +24,7 @@ def compact_segments(
|
||||
umask = config.get('umask', None)
|
||||
lock_wait = config.get('lock_wait', None)
|
||||
extra_borg_options = config.get('extra_borg_options', {}).get('compact', '')
|
||||
threshold = config.get('compact_threshold')
|
||||
|
||||
full_command = (
|
||||
(local_path, 'compact')
|
||||
@@ -33,7 +32,7 @@ def compact_segments(
|
||||
+ (('--umask', str(umask)) if umask else ())
|
||||
+ (('--log-json',) if global_arguments.log_json else ())
|
||||
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
||||
+ (('--progress',) if progress else ())
|
||||
+ (('--progress',) if config.get('progress') else ())
|
||||
+ (('--cleanup-commits',) if cleanup_commits else ())
|
||||
+ (('--threshold', str(threshold)) if threshold else ())
|
||||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||
|
||||
@@ -196,7 +196,7 @@ def check_all_root_patterns_exist(patterns):
|
||||
|
||||
if missing_paths:
|
||||
raise ValueError(
|
||||
f"Source directories / root pattern paths do not exist: {', '.join(missing_paths)}"
|
||||
f"Source directories or root pattern paths do not exist: {', '.join(missing_paths)}"
|
||||
)
|
||||
|
||||
|
||||
@@ -213,9 +213,7 @@ def make_base_create_command(
|
||||
borgmatic_runtime_directory,
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
progress=False,
|
||||
json=False,
|
||||
list_files=False,
|
||||
stream_processes=None,
|
||||
):
|
||||
'''
|
||||
@@ -293,7 +291,7 @@ def make_base_create_command(
|
||||
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
||||
+ (
|
||||
('--list', '--filter', list_filter_flags)
|
||||
if list_files and not json and not progress
|
||||
if config.get('list_details') and not json and not config.get('progress')
|
||||
else ()
|
||||
)
|
||||
+ (('--dry-run',) if dry_run else ())
|
||||
@@ -361,10 +359,7 @@ def create_archive(
|
||||
borgmatic_runtime_directory,
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
progress=False,
|
||||
stats=False,
|
||||
json=False,
|
||||
list_files=False,
|
||||
stream_processes=None,
|
||||
):
|
||||
'''
|
||||
@@ -389,28 +384,26 @@ def create_archive(
|
||||
borgmatic_runtime_directory,
|
||||
local_path,
|
||||
remote_path,
|
||||
progress,
|
||||
json,
|
||||
list_files,
|
||||
stream_processes,
|
||||
)
|
||||
|
||||
if json:
|
||||
output_log_level = None
|
||||
elif list_files or (stats and not dry_run):
|
||||
elif config.get('list_details') or (config.get('statistics') and not dry_run):
|
||||
output_log_level = logging.ANSWER
|
||||
else:
|
||||
output_log_level = logging.INFO
|
||||
|
||||
# The progress output isn't compatible with captured and logged output, as progress messes with
|
||||
# the terminal directly.
|
||||
output_file = DO_NOT_CAPTURE if progress else None
|
||||
output_file = DO_NOT_CAPTURE if config.get('progress') else None
|
||||
|
||||
create_flags += (
|
||||
(('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
|
||||
+ (('--stats',) if stats and not json and not dry_run else ())
|
||||
+ (('--stats',) if config.get('statistics') and not json and not dry_run else ())
|
||||
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
|
||||
+ (('--progress',) if progress else ())
|
||||
+ (('--progress',) if config.get('progress') else ())
|
||||
+ (('--json',) if json else ())
|
||||
)
|
||||
borg_exit_codes = config.get('borg_exit_codes')
|
||||
|
||||
@@ -34,7 +34,7 @@ def make_delete_command(
|
||||
+ borgmatic.borg.flags.make_flags('umask', config.get('umask'))
|
||||
+ borgmatic.borg.flags.make_flags('log-json', global_arguments.log_json)
|
||||
+ borgmatic.borg.flags.make_flags('lock-wait', config.get('lock_wait'))
|
||||
+ borgmatic.borg.flags.make_flags('list', delete_arguments.list_archives)
|
||||
+ borgmatic.borg.flags.make_flags('list', config.get('list_details'))
|
||||
+ (
|
||||
(('--force',) + (('--force',) if delete_arguments.force >= 2 else ()))
|
||||
if delete_arguments.force
|
||||
@@ -48,9 +48,17 @@ def make_delete_command(
|
||||
local_borg_version=local_borg_version,
|
||||
default_archive_name_format='*',
|
||||
)
|
||||
+ (('--stats',) if config.get('statistics') else ())
|
||||
+ borgmatic.borg.flags.make_flags_from_arguments(
|
||||
delete_arguments,
|
||||
excludes=('list_archives', 'force', 'match_archives', 'archive', 'repository'),
|
||||
excludes=(
|
||||
'list_details',
|
||||
'statistics',
|
||||
'force',
|
||||
'match_archives',
|
||||
'archive',
|
||||
'repository',
|
||||
),
|
||||
)
|
||||
+ borgmatic.borg.flags.make_repository_flags(repository['path'], local_borg_version)
|
||||
)
|
||||
@@ -98,7 +106,7 @@ def delete_archives(
|
||||
|
||||
repo_delete_arguments = argparse.Namespace(
|
||||
repository=repository['path'],
|
||||
list_archives=delete_arguments.list_archives,
|
||||
list_details=delete_arguments.list_details,
|
||||
force=delete_arguments.force,
|
||||
cache_only=delete_arguments.cache_only,
|
||||
keep_security_info=delete_arguments.keep_security_info,
|
||||
|
||||
@@ -20,7 +20,6 @@ def export_tar_archive(
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
tar_filter=None,
|
||||
list_files=False,
|
||||
strip_components=None,
|
||||
):
|
||||
'''
|
||||
@@ -43,7 +42,7 @@ def export_tar_archive(
|
||||
+ (('--log-json',) if global_arguments.log_json else ())
|
||||
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
||||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||
+ (('--list',) if list_files else ())
|
||||
+ (('--list',) if config.get('list_details') else ())
|
||||
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
|
||||
+ (('--dry-run',) if dry_run else ())
|
||||
+ (('--tar-filter', tar_filter) if tar_filter else ())
|
||||
@@ -57,7 +56,7 @@ def export_tar_archive(
|
||||
+ (tuple(paths) if paths else ())
|
||||
)
|
||||
|
||||
if list_files:
|
||||
if config.get('list_details'):
|
||||
output_log_level = logging.ANSWER
|
||||
else:
|
||||
output_log_level = logging.INFO
|
||||
|
||||
@@ -77,7 +77,6 @@ def extract_archive(
|
||||
remote_path=None,
|
||||
destination_path=None,
|
||||
strip_components=None,
|
||||
progress=False,
|
||||
extract_to_stdout=False,
|
||||
):
|
||||
'''
|
||||
@@ -92,8 +91,8 @@ def extract_archive(
|
||||
umask = config.get('umask', None)
|
||||
lock_wait = config.get('lock_wait', None)
|
||||
|
||||
if progress and extract_to_stdout:
|
||||
raise ValueError('progress and extract_to_stdout cannot both be set')
|
||||
if config.get('progress') and extract_to_stdout:
|
||||
raise ValueError('progress and extract to stdout cannot both be set')
|
||||
|
||||
if feature.available(feature.Feature.NUMERIC_IDS, local_borg_version):
|
||||
numeric_ids_flags = ('--numeric-ids',) if config.get('numeric_ids') else ()
|
||||
@@ -128,7 +127,7 @@ def extract_archive(
|
||||
+ (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
|
||||
+ (('--dry-run',) if dry_run else ())
|
||||
+ (('--strip-components', str(strip_components)) if strip_components else ())
|
||||
+ (('--progress',) if progress else ())
|
||||
+ (('--progress',) if config.get('progress') else ())
|
||||
+ (('--stdout',) if extract_to_stdout else ())
|
||||
+ flags.make_repository_archive_flags(
|
||||
# Make the repository path absolute so the destination directory used below via changing
|
||||
@@ -148,7 +147,7 @@ def extract_archive(
|
||||
|
||||
# The progress output isn't compatible with captured and logged output, as progress messes with
|
||||
# the terminal directly.
|
||||
if progress:
|
||||
if config.get('progress'):
|
||||
return execute_command(
|
||||
full_command,
|
||||
output_file=DO_NOT_CAPTURE,
|
||||
|
||||
@@ -48,9 +48,7 @@ def make_info_command(
|
||||
if info_arguments.prefix
|
||||
else (
|
||||
flags.make_match_archives_flags(
|
||||
info_arguments.match_archives
|
||||
or info_arguments.archive
|
||||
or config.get('match_archives'),
|
||||
info_arguments.archive or config.get('match_archives'),
|
||||
config.get('archive_name_format'),
|
||||
local_borg_version,
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ def make_prune_flags(config, prune_arguments, local_borg_version):
|
||||
if prefix
|
||||
else (
|
||||
flags.make_match_archives_flags(
|
||||
prune_arguments.match_archives or config.get('match_archives'),
|
||||
config.get('match_archives'),
|
||||
config.get('archive_name_format'),
|
||||
local_borg_version,
|
||||
)
|
||||
@@ -77,7 +77,7 @@ def prune_archives(
|
||||
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
||||
+ (
|
||||
('--stats',)
|
||||
if prune_arguments.stats
|
||||
if config.get('statistics')
|
||||
and not dry_run
|
||||
and not feature.available(feature.Feature.NO_PRUNE_STATS, local_borg_version)
|
||||
else ()
|
||||
@@ -85,16 +85,16 @@ def prune_archives(
|
||||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||
+ flags.make_flags_from_arguments(
|
||||
prune_arguments,
|
||||
excludes=('repository', 'match_archives', 'stats', 'list_archives'),
|
||||
excludes=('repository', 'match_archives', 'statistics', 'list_details'),
|
||||
)
|
||||
+ (('--list',) if prune_arguments.list_archives else ())
|
||||
+ (('--list',) if config.get('list_details') else ())
|
||||
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
|
||||
+ (('--dry-run',) if dry_run else ())
|
||||
+ (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
|
||||
+ flags.make_repository_flags(repository_path, local_borg_version)
|
||||
)
|
||||
|
||||
if prune_arguments.stats or prune_arguments.list_archives:
|
||||
if config.get('statistics') or config.get('list_details'):
|
||||
output_log_level = logging.ANSWER
|
||||
else:
|
||||
output_log_level = logging.INFO
|
||||
|
||||
@@ -23,18 +23,16 @@ def recreate_archive(
|
||||
patterns=None,
|
||||
):
|
||||
'''
|
||||
Given a local or remote repository path, an archive name, a configuration dict,
|
||||
the local Borg version string, an argparse.Namespace of recreate arguments,
|
||||
an argparse.Namespace of global arguments, optional local and remote Borg paths.
|
||||
|
||||
Executes the recreate command with the given arguments.
|
||||
Given a local or remote repository path, an archive name, a configuration dict, the local Borg
|
||||
version string, an argparse.Namespace of recreate arguments, an argparse.Namespace of global
|
||||
arguments, optional local and remote Borg paths, executes the recreate command with the given
|
||||
arguments.
|
||||
'''
|
||||
|
||||
lock_wait = config.get('lock_wait', None)
|
||||
exclude_flags = make_exclude_flags(config)
|
||||
compression = config.get('compression', None)
|
||||
chunker_params = config.get('chunker_params', None)
|
||||
# Available recompress MODES: 'if-different' (default), 'always', 'never'
|
||||
# Available recompress MODES: "if-different", "always", "never" (default)
|
||||
recompress = config.get('recompress', None)
|
||||
|
||||
# Write patterns to a temporary file and use that file with --patterns-from.
|
||||
@@ -56,10 +54,10 @@ def recreate_archive(
|
||||
'--filter',
|
||||
make_list_filter_flags(local_borg_version, global_arguments.dry_run),
|
||||
)
|
||||
if recreate_arguments.list
|
||||
if config.get('list_details')
|
||||
else ()
|
||||
)
|
||||
# Flag --target works only for a single archive
|
||||
# Flag --target works only for a single archive.
|
||||
+ (('--target', recreate_arguments.target) if recreate_arguments.target and archive else ())
|
||||
+ (
|
||||
('--comment', shlex.quote(recreate_arguments.comment))
|
||||
|
||||
@@ -24,7 +24,7 @@ def create_repository(
|
||||
copy_crypt_key=False,
|
||||
append_only=None,
|
||||
storage_quota=None,
|
||||
make_parent_dirs=False,
|
||||
make_parent_directories=False,
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
):
|
||||
@@ -79,7 +79,7 @@ def create_repository(
|
||||
+ (('--copy-crypt-key',) if copy_crypt_key else ())
|
||||
+ (('--append-only',) if append_only else ())
|
||||
+ (('--storage-quota', storage_quota) if storage_quota else ())
|
||||
+ (('--make-parent-dirs',) if make_parent_dirs else ())
|
||||
+ (('--make-parent-dirs',) if make_parent_directories else ())
|
||||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||
+ (('--debug',) if logger.isEnabledFor(logging.DEBUG) else ())
|
||||
+ (('--log-json',) if global_arguments.log_json else ())
|
||||
|
||||
@@ -39,14 +39,14 @@ def make_repo_delete_command(
|
||||
+ borgmatic.borg.flags.make_flags('umask', config.get('umask'))
|
||||
+ borgmatic.borg.flags.make_flags('log-json', global_arguments.log_json)
|
||||
+ borgmatic.borg.flags.make_flags('lock-wait', config.get('lock_wait'))
|
||||
+ borgmatic.borg.flags.make_flags('list', repo_delete_arguments.list_archives)
|
||||
+ borgmatic.borg.flags.make_flags('list', config.get('list_details'))
|
||||
+ (
|
||||
(('--force',) + (('--force',) if repo_delete_arguments.force >= 2 else ()))
|
||||
if repo_delete_arguments.force
|
||||
else ()
|
||||
)
|
||||
+ borgmatic.borg.flags.make_flags_from_arguments(
|
||||
repo_delete_arguments, excludes=('list_archives', 'force', 'repository')
|
||||
repo_delete_arguments, excludes=('list_details', 'force', 'repository')
|
||||
)
|
||||
+ borgmatic.borg.flags.make_repository_flags(repository['path'], local_borg_version)
|
||||
)
|
||||
|
||||
@@ -113,7 +113,7 @@ def make_repo_list_command(
|
||||
if repo_list_arguments.prefix
|
||||
else (
|
||||
flags.make_match_archives_flags(
|
||||
repo_list_arguments.match_archives or config.get('match_archives'),
|
||||
config.get('match_archives'),
|
||||
config.get('archive_name_format'),
|
||||
local_borg_version,
|
||||
)
|
||||
|
||||
@@ -32,17 +32,22 @@ def transfer_archives(
|
||||
+ flags.make_flags('remote-path', remote_path)
|
||||
+ flags.make_flags('umask', config.get('umask'))
|
||||
+ flags.make_flags('log-json', global_arguments.log_json)
|
||||
+ flags.make_flags('lock-wait', config.get('lock_wait', None))
|
||||
+ flags.make_flags('lock-wait', config.get('lock_wait'))
|
||||
+ flags.make_flags('progress', config.get('progress'))
|
||||
+ (
|
||||
flags.make_flags_from_arguments(
|
||||
transfer_arguments,
|
||||
excludes=('repository', 'source_repository', 'archive', 'match_archives'),
|
||||
excludes=(
|
||||
'repository',
|
||||
'source_repository',
|
||||
'archive',
|
||||
'match_archives',
|
||||
'progress',
|
||||
),
|
||||
)
|
||||
or (
|
||||
flags.make_match_archives_flags(
|
||||
transfer_arguments.match_archives
|
||||
or transfer_arguments.archive
|
||||
or config.get('match_archives'),
|
||||
transfer_arguments.archive or config.get('match_archives'),
|
||||
config.get('archive_name_format'),
|
||||
local_borg_version,
|
||||
)
|
||||
@@ -56,7 +61,7 @@ def transfer_archives(
|
||||
return execute_command(
|
||||
full_command,
|
||||
output_log_level=logging.ANSWER,
|
||||
output_file=DO_NOT_CAPTURE if transfer_arguments.progress else None,
|
||||
output_file=DO_NOT_CAPTURE if config.get('progress') else None,
|
||||
environment=environment.make_environment(config),
|
||||
working_directory=borgmatic.config.paths.get_working_directory(config),
|
||||
borg_local_path=local_path,
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import collections
|
||||
import io
|
||||
import itertools
|
||||
import re
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import ruamel.yaml
|
||||
|
||||
import borgmatic.config.schema
|
||||
from borgmatic.config import collect
|
||||
|
||||
ACTION_ALIASES = {
|
||||
@@ -64,9 +69,9 @@ def get_subactions_for_actions(action_parsers):
|
||||
|
||||
def omit_values_colliding_with_action_names(unparsed_arguments, parsed_arguments):
|
||||
'''
|
||||
Given a sequence of string arguments and a dict from action name to parsed argparse.Namespace
|
||||
arguments, return the string arguments with any values omitted that happen to be the same as
|
||||
the name of a borgmatic action.
|
||||
Given unparsed arguments as a sequence of strings and a dict from action name to parsed
|
||||
argparse.Namespace arguments, return the string arguments with any values omitted that happen to
|
||||
be the same as the name of a borgmatic action.
|
||||
|
||||
This prevents, for instance, "check --only extract" from triggering the "extract" action.
|
||||
'''
|
||||
@@ -283,17 +288,270 @@ def parse_arguments_for_actions(unparsed_arguments, action_parsers, global_parse
|
||||
)
|
||||
|
||||
|
||||
def make_parsers():
|
||||
OMITTED_FLAG_NAMES = {'match-archives', 'progress', 'statistics', 'list-details'}
|
||||
|
||||
|
||||
def make_argument_description(schema, flag_name):
|
||||
'''
|
||||
Build a global arguments parser, individual action parsers, and a combined parser containing
|
||||
both. Return them as a tuple. The global parser is useful for parsing just global arguments
|
||||
while ignoring actions, and the combined parser is handy for displaying help that includes
|
||||
everything: global flags, a list of actions, etc.
|
||||
Given a configuration schema dict and a flag name for it, extend the schema's description with
|
||||
an example or additional information as appropriate based on its type. Return the updated
|
||||
description for use in a command-line argument.
|
||||
'''
|
||||
description = schema.get('description')
|
||||
schema_type = schema.get('type')
|
||||
example = schema.get('example')
|
||||
pieces = [description] if description else []
|
||||
|
||||
if '[0]' in flag_name:
|
||||
pieces.append(
|
||||
' To specify a different list element, replace the "[0]" with another array index ("[1]", "[2]", etc.).'
|
||||
)
|
||||
|
||||
if example and schema_type in ('array', 'object'):
|
||||
example_buffer = io.StringIO()
|
||||
yaml = ruamel.yaml.YAML(typ='safe')
|
||||
yaml.default_flow_style = True
|
||||
yaml.dump(example, example_buffer)
|
||||
|
||||
pieces.append(f'Example value: "{example_buffer.getvalue().strip()}"')
|
||||
|
||||
return ' '.join(pieces).replace('%', '%%')
|
||||
|
||||
|
||||
def add_array_element_arguments(arguments_group, unparsed_arguments, flag_name):
|
||||
r'''
|
||||
Given an argparse._ArgumentGroup instance, a sequence of unparsed argument strings, and a dotted
|
||||
flag name, add command-line array element flags that correspond to the given unparsed arguments.
|
||||
|
||||
Here's the background. We want to support flags that can have arbitrary indices like:
|
||||
|
||||
--foo.bar[1].baz
|
||||
|
||||
But argparse doesn't support that natively because the index can be an arbitrary number. We
|
||||
won't let that stop us though, will we?
|
||||
|
||||
If the current flag name has an array component in it (e.g. a name with "[0]"), then make a
|
||||
pattern that would match the flag name regardless of the number that's in it. The idea is that
|
||||
we want to look for unparsed arguments that appear like the flag name, but instead of "[0]" they
|
||||
have, say, "[1]" or "[123]".
|
||||
|
||||
Next, we check each unparsed argument against that pattern. If one of them matches, add an
|
||||
argument flag for it to the argument parser group. Example:
|
||||
|
||||
Let's say flag_name is:
|
||||
|
||||
--foo.bar[0].baz
|
||||
|
||||
... then the regular expression pattern will be:
|
||||
|
||||
^--foo\.bar\[\d+\]\.baz
|
||||
|
||||
... and, if that matches an unparsed argument of:
|
||||
|
||||
--foo.bar[1].baz
|
||||
|
||||
... then an argument flag will get added equal to that unparsed argument. And so the unparsed
|
||||
argument will match it when parsing is performed! In this manner, we're using the actual user
|
||||
CLI input to inform what exact flags we support.
|
||||
'''
|
||||
if '[0]' not in flag_name or not unparsed_arguments or '--help' in unparsed_arguments:
|
||||
return
|
||||
|
||||
pattern = re.compile(fr'^--{flag_name.replace("[0]", r"\[\d+\]").replace(".", r"\.")}$')
|
||||
|
||||
try:
|
||||
# Find an existing list index flag (and its action) corresponding to the given flag name.
|
||||
(argument_action, existing_flag_name) = next(
|
||||
(action, action_flag_name)
|
||||
for action in arguments_group._group_actions
|
||||
for action_flag_name in action.option_strings
|
||||
if pattern.match(action_flag_name)
|
||||
if f'--{flag_name}'.startswith(action_flag_name)
|
||||
)
|
||||
|
||||
# Based on the type of the action (e.g. argparse._StoreTrueAction), look up the corresponding
|
||||
# action registry name (e.g., "store_true") to pass to add_argument(action=...) below.
|
||||
action_registry_name = next(
|
||||
registry_name
|
||||
for registry_name, action_type in arguments_group._registries['action'].items()
|
||||
# Not using isinstance() here because we only want an exact match—no parent classes.
|
||||
if type(argument_action) is action_type
|
||||
)
|
||||
except StopIteration:
|
||||
return
|
||||
|
||||
for unparsed in unparsed_arguments:
|
||||
unparsed_flag_name = unparsed.split('=', 1)[0]
|
||||
destination_name = unparsed_flag_name.lstrip('-').replace('-', '_')
|
||||
|
||||
if not pattern.match(unparsed_flag_name) or unparsed_flag_name == existing_flag_name:
|
||||
continue
|
||||
|
||||
if action_registry_name in ('store_true', 'store_false'):
|
||||
arguments_group.add_argument(
|
||||
unparsed_flag_name,
|
||||
action=action_registry_name,
|
||||
default=argument_action.default,
|
||||
dest=destination_name,
|
||||
required=argument_action.nargs,
|
||||
)
|
||||
else:
|
||||
arguments_group.add_argument(
|
||||
unparsed_flag_name,
|
||||
action=action_registry_name,
|
||||
choices=argument_action.choices,
|
||||
default=argument_action.default,
|
||||
dest=destination_name,
|
||||
nargs=argument_action.nargs,
|
||||
required=argument_action.nargs,
|
||||
type=argument_action.type,
|
||||
)
|
||||
|
||||
|
||||
def add_arguments_from_schema(arguments_group, schema, unparsed_arguments, names=None):
|
||||
'''
|
||||
Given an argparse._ArgumentGroup instance, a configuration schema dict, and a sequence of
|
||||
unparsed argument strings, convert the entire schema into corresponding command-line flags and
|
||||
add them to the arguments group.
|
||||
|
||||
For instance, given a schema of:
|
||||
|
||||
{
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'bar': {'type': 'integer'}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
... the following flag will be added to the arguments group:
|
||||
|
||||
--foo.bar
|
||||
|
||||
If "foo" is instead an array of objects, both of the following will get added:
|
||||
|
||||
--foo
|
||||
--foo[0].bar
|
||||
|
||||
And if names are also passed in, they are considered to be the name components of an option
|
||||
(e.g. "foo" and "bar") and are used to construct a resulting flag.
|
||||
|
||||
Bail if the schema is not a dict.
|
||||
'''
|
||||
if names is None:
|
||||
names = ()
|
||||
|
||||
if not isinstance(schema, dict):
|
||||
return
|
||||
|
||||
schema_type = schema.get('type')
|
||||
|
||||
# If this option has multiple types, just use the first one (that isn't "null").
|
||||
if isinstance(schema_type, list):
|
||||
try:
|
||||
schema_type = next(single_type for single_type in schema_type if single_type != 'null')
|
||||
except StopIteration:
|
||||
raise ValueError(f'Unknown type in configuration schema: {schema_type}')
|
||||
|
||||
# If this is an "object" type, recurse for each child option ("property").
|
||||
if schema_type == 'object':
|
||||
properties = schema.get('properties')
|
||||
|
||||
# If there are child properties, recurse for each one. But if there are no child properties,
|
||||
# fall through so that a flag gets added below for the (empty) object.
|
||||
if properties:
|
||||
for name, child in properties.items():
|
||||
add_arguments_from_schema(
|
||||
arguments_group, child, unparsed_arguments, names + (name,)
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
# If this is an "array" type, recurse for each items type child option. Don't return yet so that
|
||||
# a flag also gets added below for the array itself.
|
||||
if schema_type == 'array':
|
||||
items = schema.get('items', {})
|
||||
properties = borgmatic.config.schema.get_properties(items)
|
||||
|
||||
if properties:
|
||||
for name, child in properties.items():
|
||||
add_arguments_from_schema(
|
||||
arguments_group,
|
||||
child,
|
||||
unparsed_arguments,
|
||||
names[:-1] + (f'{names[-1]}[0]',) + (name,),
|
||||
)
|
||||
# If there aren't any children, then this is an array of scalars. Recurse accordingly.
|
||||
else:
|
||||
add_arguments_from_schema(
|
||||
arguments_group, items, unparsed_arguments, names[:-1] + (f'{names[-1]}[0]',)
|
||||
)
|
||||
|
||||
flag_name = '.'.join(names).replace('_', '-')
|
||||
|
||||
# Certain options already have corresponding flags on individual actions (like "create
|
||||
# --progress"), so don't bother adding them to the global flags.
|
||||
if not flag_name or flag_name in OMITTED_FLAG_NAMES:
|
||||
return
|
||||
|
||||
metavar = names[-1].upper()
|
||||
description = make_argument_description(schema, flag_name)
|
||||
|
||||
# The object=str and array=str given here is to support specifying an object or an array as a
|
||||
# YAML string on the command-line.
|
||||
argument_type = borgmatic.config.schema.parse_type(schema_type, object=str, array=str)
|
||||
|
||||
# As a UX nicety, add separate true and false flags for boolean options.
|
||||
if schema_type == 'boolean':
|
||||
arguments_group.add_argument(
|
||||
f'--{flag_name}',
|
||||
action='store_true',
|
||||
default=None,
|
||||
help=description,
|
||||
)
|
||||
|
||||
if names[-1].startswith('no_'):
|
||||
no_flag_name = '.'.join(names[:-1] + (names[-1][len('no_') :],)).replace('_', '-')
|
||||
else:
|
||||
no_flag_name = '.'.join(names[:-1] + ('no-' + names[-1],)).replace('_', '-')
|
||||
|
||||
arguments_group.add_argument(
|
||||
f'--{no_flag_name}',
|
||||
dest=flag_name.replace('-', '_'),
|
||||
action='store_false',
|
||||
default=None,
|
||||
help=f'Set the --{flag_name} value to false.',
|
||||
)
|
||||
else:
|
||||
arguments_group.add_argument(
|
||||
f'--{flag_name}',
|
||||
type=argument_type,
|
||||
metavar=metavar,
|
||||
help=description,
|
||||
)
|
||||
|
||||
add_array_element_arguments(arguments_group, unparsed_arguments, flag_name)
|
||||
|
||||
|
||||
def make_parsers(schema, unparsed_arguments):
|
||||
'''
|
||||
Given a configuration schema dict and unparsed arguments as a sequence of strings, build a
|
||||
global arguments parser, individual action parsers, and a combined parser containing both.
|
||||
Return them as a tuple. The global parser is useful for parsing just global arguments while
|
||||
ignoring actions, and the combined parser is handy for displaying help that includes everything:
|
||||
global flags, a list of actions, etc.
|
||||
'''
|
||||
config_paths = collect.get_default_config_paths(expand_home=True)
|
||||
unexpanded_config_paths = collect.get_default_config_paths(expand_home=False)
|
||||
|
||||
global_parser = ArgumentParser(add_help=False)
|
||||
# Using allow_abbrev=False here prevents the global parser from erroring about "ambiguous"
|
||||
# options like --encryption. Such options are intended for an action parser rather than the
|
||||
# global parser, and so we don't want to error on them here.
|
||||
global_parser = ArgumentParser(allow_abbrev=False, add_help=False)
|
||||
global_group = global_parser.add_argument_group('global arguments')
|
||||
|
||||
global_group.add_argument(
|
||||
@@ -310,9 +568,6 @@ def make_parsers():
|
||||
action='store_true',
|
||||
help='Go through the motions, but do not actually write to any repositories',
|
||||
)
|
||||
global_group.add_argument(
|
||||
'-nc', '--no-color', dest='no_color', action='store_true', help='Disable colored output'
|
||||
)
|
||||
global_group.add_argument(
|
||||
'-v',
|
||||
'--verbosity',
|
||||
@@ -389,6 +644,7 @@ def make_parsers():
|
||||
action='store_true',
|
||||
help='Display installed version number of borgmatic and exit',
|
||||
)
|
||||
add_arguments_from_schema(global_group, schema, unparsed_arguments)
|
||||
|
||||
global_plus_action_parser = ArgumentParser(
|
||||
description='''
|
||||
@@ -416,7 +672,6 @@ def make_parsers():
|
||||
'--encryption',
|
||||
dest='encryption_mode',
|
||||
help='Borg repository encryption mode',
|
||||
required=True,
|
||||
)
|
||||
repo_create_group.add_argument(
|
||||
'--source-repository',
|
||||
@@ -435,6 +690,7 @@ def make_parsers():
|
||||
)
|
||||
repo_create_group.add_argument(
|
||||
'--append-only',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Create an append-only repository',
|
||||
)
|
||||
@@ -444,6 +700,8 @@ def make_parsers():
|
||||
)
|
||||
repo_create_group.add_argument(
|
||||
'--make-parent-dirs',
|
||||
dest='make_parent_directories',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Create any missing parent directories of the repository directory',
|
||||
)
|
||||
@@ -478,7 +736,7 @@ def make_parsers():
|
||||
)
|
||||
transfer_group.add_argument(
|
||||
'--progress',
|
||||
default=False,
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Display progress as each archive is transferred',
|
||||
)
|
||||
@@ -545,13 +803,17 @@ def make_parsers():
|
||||
)
|
||||
prune_group.add_argument(
|
||||
'--stats',
|
||||
dest='stats',
|
||||
default=False,
|
||||
dest='statistics',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Display statistics of the pruned archive [Borg 1 only]',
|
||||
)
|
||||
prune_group.add_argument(
|
||||
'--list', dest='list_archives', action='store_true', help='List archives kept/pruned'
|
||||
'--list',
|
||||
dest='list_details',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='List archives kept/pruned',
|
||||
)
|
||||
prune_group.add_argument(
|
||||
'--oldest',
|
||||
@@ -589,8 +851,7 @@ def make_parsers():
|
||||
)
|
||||
compact_group.add_argument(
|
||||
'--progress',
|
||||
dest='progress',
|
||||
default=False,
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Display progress as each segment is compacted',
|
||||
)
|
||||
@@ -604,7 +865,7 @@ def make_parsers():
|
||||
compact_group.add_argument(
|
||||
'--threshold',
|
||||
type=int,
|
||||
dest='threshold',
|
||||
dest='compact_threshold',
|
||||
help='Minimum saved space percentage threshold for compacting a segment, defaults to 10',
|
||||
)
|
||||
compact_group.add_argument(
|
||||
@@ -625,20 +886,24 @@ def make_parsers():
|
||||
)
|
||||
create_group.add_argument(
|
||||
'--progress',
|
||||
dest='progress',
|
||||
default=False,
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Display progress for each file as it is backed up',
|
||||
)
|
||||
create_group.add_argument(
|
||||
'--stats',
|
||||
dest='stats',
|
||||
default=False,
|
||||
dest='statistics',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Display statistics of archive',
|
||||
)
|
||||
create_group.add_argument(
|
||||
'--list', '--files', dest='list_files', action='store_true', help='Show per-file details'
|
||||
'--list',
|
||||
'--files',
|
||||
dest='list_details',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Show per-file details',
|
||||
)
|
||||
create_group.add_argument(
|
||||
'--json', dest='json', default=False, action='store_true', help='Output results as JSON'
|
||||
@@ -659,8 +924,7 @@ def make_parsers():
|
||||
)
|
||||
check_group.add_argument(
|
||||
'--progress',
|
||||
dest='progress',
|
||||
default=False,
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Display progress for each file as it is checked',
|
||||
)
|
||||
@@ -717,12 +981,15 @@ def make_parsers():
|
||||
)
|
||||
delete_group.add_argument(
|
||||
'--list',
|
||||
dest='list_archives',
|
||||
dest='list_details',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Show details for the deleted archives',
|
||||
)
|
||||
delete_group.add_argument(
|
||||
'--stats',
|
||||
dest='statistics',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Display statistics for the deleted archives',
|
||||
)
|
||||
@@ -827,8 +1094,7 @@ def make_parsers():
|
||||
)
|
||||
extract_group.add_argument(
|
||||
'--progress',
|
||||
dest='progress',
|
||||
default=False,
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Display progress for each file as it is extracted',
|
||||
)
|
||||
@@ -903,8 +1169,7 @@ def make_parsers():
|
||||
)
|
||||
config_bootstrap_group.add_argument(
|
||||
'--progress',
|
||||
dest='progress',
|
||||
default=False,
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Display progress for each file as it is extracted',
|
||||
)
|
||||
@@ -997,7 +1262,12 @@ def make_parsers():
|
||||
'--tar-filter', help='Name of filter program to pipe data through'
|
||||
)
|
||||
export_tar_group.add_argument(
|
||||
'--list', '--files', dest='list_files', action='store_true', help='Show per-file details'
|
||||
'--list',
|
||||
'--files',
|
||||
dest='list_details',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Show per-file details',
|
||||
)
|
||||
export_tar_group.add_argument(
|
||||
'--strip-components',
|
||||
@@ -1108,7 +1378,8 @@ def make_parsers():
|
||||
)
|
||||
repo_delete_group.add_argument(
|
||||
'--list',
|
||||
dest='list_archives',
|
||||
dest='list_details',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Show details for the archives in the given repository',
|
||||
)
|
||||
@@ -1539,7 +1810,11 @@ def make_parsers():
|
||||
help='Archive name, hash, or series to recreate',
|
||||
)
|
||||
recreate_group.add_argument(
|
||||
'--list', dest='list', action='store_true', help='Show per-file details'
|
||||
'--list',
|
||||
dest='list_details',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Show per-file details',
|
||||
)
|
||||
recreate_group.add_argument(
|
||||
'--target',
|
||||
@@ -1595,15 +1870,18 @@ def make_parsers():
|
||||
return global_parser, action_parsers, global_plus_action_parser
|
||||
|
||||
|
||||
def parse_arguments(*unparsed_arguments):
|
||||
def parse_arguments(schema, *unparsed_arguments):
|
||||
'''
|
||||
Given command-line arguments with which this script was invoked, parse the arguments and return
|
||||
them as a dict mapping from action name (or "global") to an argparse.Namespace instance.
|
||||
Given a configuration schema dict and the command-line arguments with which this script was
|
||||
invoked and unparsed arguments as a sequence of strings, parse the arguments and return them as
|
||||
a dict mapping from action name (or "global") to an argparse.Namespace instance.
|
||||
|
||||
Raise ValueError if the arguments cannot be parsed.
|
||||
Raise SystemExit with an error code of 0 if "--help" was requested.
|
||||
'''
|
||||
global_parser, action_parsers, global_plus_action_parser = make_parsers()
|
||||
global_parser, action_parsers, global_plus_action_parser = make_parsers(
|
||||
schema, unparsed_arguments
|
||||
)
|
||||
arguments, remaining_action_arguments = parse_arguments_for_actions(
|
||||
unparsed_arguments, action_parsers.choices, global_parser
|
||||
)
|
||||
@@ -1631,15 +1909,6 @@ def parse_arguments(*unparsed_arguments):
|
||||
f"Unrecognized argument{'s' if len(unknown_arguments) > 1 else ''}: {' '.join(unknown_arguments)}"
|
||||
)
|
||||
|
||||
if 'create' in arguments and arguments['create'].list_files and arguments['create'].progress:
|
||||
raise ValueError(
|
||||
'With the create action, only one of --list (--files) and --progress flags can be used.'
|
||||
)
|
||||
if 'create' in arguments and arguments['create'].list_files and arguments['create'].json:
|
||||
raise ValueError(
|
||||
'With the create action, only one of --list (--files) and --json flags can be used.'
|
||||
)
|
||||
|
||||
if (
|
||||
('list' in arguments and 'repo-info' in arguments and arguments['list'].json)
|
||||
or ('list' in arguments and 'info' in arguments and arguments['list'].json)
|
||||
@@ -1647,15 +1916,6 @@ def parse_arguments(*unparsed_arguments):
|
||||
):
|
||||
raise ValueError('With the --json flag, multiple actions cannot be used together.')
|
||||
|
||||
if (
|
||||
'transfer' in arguments
|
||||
and arguments['transfer'].archive
|
||||
and arguments['transfer'].match_archives
|
||||
):
|
||||
raise ValueError(
|
||||
'With the transfer action, only one of --archive and --match-archives flags can be used.'
|
||||
)
|
||||
|
||||
if 'list' in arguments and (arguments['list'].prefix and arguments['list'].match_archives):
|
||||
raise ValueError(
|
||||
'With the list action, only one of --prefix or --match-archives flags can be used.'
|
||||
|
||||
@@ -8,6 +8,8 @@ import time
|
||||
from queue import Queue
|
||||
from subprocess import CalledProcessError
|
||||
|
||||
import ruamel.yaml
|
||||
|
||||
import borgmatic.actions.borg
|
||||
import borgmatic.actions.break_lock
|
||||
import borgmatic.actions.change_passphrase
|
||||
@@ -35,6 +37,7 @@ import borgmatic.actions.restore
|
||||
import borgmatic.actions.transfer
|
||||
import borgmatic.commands.completion.bash
|
||||
import borgmatic.commands.completion.fish
|
||||
import borgmatic.config.load
|
||||
import borgmatic.config.paths
|
||||
from borgmatic.borg import umount as borg_umount
|
||||
from borgmatic.borg import version as borg_version
|
||||
@@ -597,14 +600,14 @@ def run_actions(
|
||||
)
|
||||
|
||||
|
||||
def load_configurations(config_filenames, overrides=None, resolve_env=True):
|
||||
def load_configurations(config_filenames, arguments, overrides=None, resolve_env=True):
|
||||
'''
|
||||
Given a sequence of configuration filenames, a sequence of configuration file override strings
|
||||
in the form of "option.suboption=value", and whether to resolve environment variables, load and
|
||||
validate each configuration file. Return the results as a tuple of: dict of configuration
|
||||
filename to corresponding parsed configuration, a sequence of paths for all loaded configuration
|
||||
files (including includes), and a sequence of logging.LogRecord instances containing any parse
|
||||
errors.
|
||||
Given a sequence of configuration filenames, arguments as a dict from action name to
|
||||
argparse.Namespace, a sequence of configuration file override strings in the form of
|
||||
"option.suboption=value", and whether to resolve environment variables, load and validate each
|
||||
configuration file. Return the results as a tuple of: dict of configuration filename to
|
||||
corresponding parsed configuration, a sequence of paths for all loaded configuration files
|
||||
(including includes), and a sequence of logging.LogRecord instances containing any parse errors.
|
||||
|
||||
Log records are returned here instead of being logged directly because logging isn't yet
|
||||
initialized at this point! (Although with the Delayed_logging_handler now in place, maybe this
|
||||
@@ -632,6 +635,7 @@ def load_configurations(config_filenames, overrides=None, resolve_env=True):
|
||||
configs[config_filename], paths, parse_logs = validate.parse_configuration(
|
||||
config_filename,
|
||||
validate.schema_filename(),
|
||||
arguments,
|
||||
overrides,
|
||||
resolve_env,
|
||||
)
|
||||
@@ -970,9 +974,17 @@ def check_and_show_help_on_no_args(configs):
|
||||
def main(extra_summary_logs=[]): # pragma: no cover
|
||||
configure_signals()
|
||||
configure_delayed_logging()
|
||||
schema_filename = validate.schema_filename()
|
||||
|
||||
try:
|
||||
arguments = parse_arguments(*sys.argv[1:])
|
||||
schema = borgmatic.config.load.load_configuration(schema_filename)
|
||||
except (ruamel.yaml.error.YAMLError, RecursionError) as error:
|
||||
configure_logging(logging.CRITICAL)
|
||||
logger.critical(error)
|
||||
exit_with_help_link()
|
||||
|
||||
try:
|
||||
arguments = parse_arguments(schema, *sys.argv[1:])
|
||||
except ValueError as error:
|
||||
configure_logging(logging.CRITICAL)
|
||||
logger.critical(error)
|
||||
@@ -995,10 +1007,10 @@ def main(extra_summary_logs=[]): # pragma: no cover
|
||||
print(borgmatic.commands.completion.fish.fish_completion())
|
||||
sys.exit(0)
|
||||
|
||||
validate = bool('validate' in arguments)
|
||||
config_filenames = tuple(collect.collect_config_filenames(global_arguments.config_paths))
|
||||
configs, config_paths, parse_logs = load_configurations(
|
||||
config_filenames,
|
||||
arguments,
|
||||
global_arguments.overrides,
|
||||
resolve_env=global_arguments.resolve_env and not validate,
|
||||
)
|
||||
@@ -1013,7 +1025,7 @@ def main(extra_summary_logs=[]): # pragma: no cover
|
||||
any_json_flags = any(
|
||||
getattr(sub_arguments, 'json', False) for sub_arguments in arguments.values()
|
||||
)
|
||||
color_enabled = should_do_markup(global_arguments.no_color or any_json_flags, configs)
|
||||
color_enabled = should_do_markup(configs, any_json_flags)
|
||||
|
||||
try:
|
||||
configure_logging(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import borgmatic.commands.arguments
|
||||
import borgmatic.commands.completion.actions
|
||||
import borgmatic.commands.completion.flag
|
||||
import borgmatic.config.validate
|
||||
|
||||
|
||||
def parser_flags(parser):
|
||||
@@ -7,7 +9,12 @@ def parser_flags(parser):
|
||||
Given an argparse.ArgumentParser instance, return its argument flags in a space-separated
|
||||
string.
|
||||
'''
|
||||
return ' '.join(option for action in parser._actions for option in action.option_strings)
|
||||
return ' '.join(
|
||||
flag_variant
|
||||
for action in parser._actions
|
||||
for flag_name in action.option_strings
|
||||
for flag_variant in borgmatic.commands.completion.flag.variants(flag_name)
|
||||
)
|
||||
|
||||
|
||||
def bash_completion():
|
||||
@@ -19,7 +26,10 @@ def bash_completion():
|
||||
unused_global_parser,
|
||||
action_parsers,
|
||||
global_plus_action_parser,
|
||||
) = borgmatic.commands.arguments.make_parsers()
|
||||
) = borgmatic.commands.arguments.make_parsers(
|
||||
schema=borgmatic.config.validate.load_schema(borgmatic.config.validate.schema_filename()),
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
global_flags = parser_flags(global_plus_action_parser)
|
||||
|
||||
# Avert your eyes.
|
||||
|
||||
@@ -4,6 +4,7 @@ from textwrap import dedent
|
||||
|
||||
import borgmatic.commands.arguments
|
||||
import borgmatic.commands.completion.actions
|
||||
import borgmatic.config.validate
|
||||
|
||||
|
||||
def has_file_options(action: Action):
|
||||
@@ -26,9 +27,11 @@ def has_choice_options(action: Action):
|
||||
def has_unknown_required_param_options(action: Action):
|
||||
'''
|
||||
A catch-all for options that take a required parameter, but we don't know what the parameter is.
|
||||
This should be used last. These are actions that take something like a glob, a list of numbers, or a string.
|
||||
This should be used last. These are actions that take something like a glob, a list of numbers,
|
||||
or a string.
|
||||
|
||||
Actions that match this pattern should not show the normal arguments, because those are unlikely to be valid.
|
||||
Actions that match this pattern should not show the normal arguments, because those are unlikely
|
||||
to be valid.
|
||||
'''
|
||||
return (
|
||||
action.required is True
|
||||
@@ -52,9 +55,9 @@ def has_exact_options(action: Action):
|
||||
|
||||
def exact_options_completion(action: Action):
|
||||
'''
|
||||
Given an argparse.Action instance, return a completion invocation that forces file completions, options completion,
|
||||
or just that some value follow the action, if the action takes such an argument and was the last action on the
|
||||
command line prior to the cursor.
|
||||
Given an argparse.Action instance, return a completion invocation that forces file completions,
|
||||
options completion, or just that some value follow the action, if the action takes such an
|
||||
argument and was the last action on the command line prior to the cursor.
|
||||
|
||||
Otherwise, return an empty string.
|
||||
'''
|
||||
@@ -80,8 +83,9 @@ def exact_options_completion(action: Action):
|
||||
|
||||
def dedent_strip_as_tuple(string: str):
|
||||
'''
|
||||
Dedent a string, then strip it to avoid requiring your first line to have content, then return a tuple of the string.
|
||||
Makes it easier to write multiline strings for completions when you join them with a tuple.
|
||||
Dedent a string, then strip it to avoid requiring your first line to have content, then return a
|
||||
tuple of the string. Makes it easier to write multiline strings for completions when you join
|
||||
them with a tuple.
|
||||
'''
|
||||
return (dedent(string).strip('\n'),)
|
||||
|
||||
@@ -95,7 +99,10 @@ def fish_completion():
|
||||
unused_global_parser,
|
||||
action_parsers,
|
||||
global_plus_action_parser,
|
||||
) = borgmatic.commands.arguments.make_parsers()
|
||||
) = borgmatic.commands.arguments.make_parsers(
|
||||
schema=borgmatic.config.validate.load_schema(borgmatic.config.validate.schema_filename()),
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
all_action_parsers = ' '.join(action for action in action_parsers.choices.keys())
|
||||
|
||||
|
||||
13
borgmatic/commands/completion/flag.py
Normal file
13
borgmatic/commands/completion/flag.py
Normal file
@@ -0,0 +1,13 @@
|
||||
def variants(flag_name):
|
||||
'''
|
||||
Given a flag name as a string, yield it and any variations that should be complete-able as well.
|
||||
For instance, for a string like "--foo[0].bar", yield "--foo[0].bar", "--foo[1].bar", ...,
|
||||
"--foo[9].bar".
|
||||
'''
|
||||
if '[0]' in flag_name:
|
||||
for index in range(0, 10):
|
||||
yield flag_name.replace('[0]', f'[{index}]')
|
||||
|
||||
return
|
||||
|
||||
yield flag_name
|
||||
176
borgmatic/config/arguments.py
Normal file
176
borgmatic/config/arguments.py
Normal file
@@ -0,0 +1,176 @@
|
||||
import io
|
||||
import re
|
||||
|
||||
import ruamel.yaml
|
||||
|
||||
import borgmatic.config.schema
|
||||
|
||||
LIST_INDEX_KEY_PATTERN = re.compile(r'^(?P<list_name>[a-zA-z-]+)\[(?P<index>\d+)\]$')
|
||||
|
||||
|
||||
def set_values(config, keys, value):
|
||||
'''
|
||||
Given a configuration dict, a sequence of parsed key strings, and a string value, descend into
|
||||
the configuration hierarchy based on the given keys and set the value into the right place.
|
||||
For example, consider these keys:
|
||||
|
||||
('foo', 'bar', 'baz')
|
||||
|
||||
This looks up "foo" in the given configuration dict. And within that, it looks up "bar". And
|
||||
then within that, it looks up "baz" and sets it to the given value. Another example:
|
||||
|
||||
('mylist[0]', 'foo')
|
||||
|
||||
This looks for the zeroth element of "mylist" in the given configuration. And within that, it
|
||||
looks up "foo" and sets it to the given value.
|
||||
'''
|
||||
if not keys:
|
||||
return
|
||||
|
||||
first_key = keys[0]
|
||||
|
||||
# Support "mylist[0]" list index syntax.
|
||||
match = LIST_INDEX_KEY_PATTERN.match(first_key)
|
||||
|
||||
if match:
|
||||
list_key = match.group('list_name')
|
||||
list_index = int(match.group('index'))
|
||||
|
||||
try:
|
||||
if len(keys) == 1:
|
||||
config[list_key][list_index] = value
|
||||
|
||||
return
|
||||
|
||||
if list_key not in config:
|
||||
config[list_key] = []
|
||||
|
||||
set_values(config[list_key][list_index], keys[1:], value)
|
||||
except (IndexError, KeyError):
|
||||
raise ValueError(f'Argument list index {first_key} is out of range')
|
||||
|
||||
return
|
||||
|
||||
if len(keys) == 1:
|
||||
config[first_key] = value
|
||||
|
||||
return
|
||||
|
||||
if first_key not in config:
|
||||
config[first_key] = {}
|
||||
|
||||
set_values(config[first_key], keys[1:], value)
|
||||
|
||||
|
||||
def type_for_option(schema, option_keys):
|
||||
'''
|
||||
Given a configuration schema dict and a sequence of keys identifying a potentially nested
|
||||
option, e.g. ('extra_borg_options', 'create'), return the schema type of that option as a
|
||||
string.
|
||||
|
||||
Return None if the option or its type cannot be found in the schema.
|
||||
'''
|
||||
option_schema = schema
|
||||
|
||||
for key in option_keys:
|
||||
# Support "name[0]"-style list index syntax.
|
||||
match = LIST_INDEX_KEY_PATTERN.match(key)
|
||||
properties = borgmatic.config.schema.get_properties(option_schema)
|
||||
|
||||
try:
|
||||
if match:
|
||||
option_schema = properties[match.group('list_name')]['items']
|
||||
else:
|
||||
option_schema = properties[key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
try:
|
||||
return option_schema['type']
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
def convert_value_type(value, option_type):
|
||||
'''
|
||||
Given a string value and its schema type as a string, determine its logical type (string,
|
||||
boolean, integer, etc.), and return it converted to that type.
|
||||
|
||||
If the destination option type is a string, then leave the value as-is so that special
|
||||
characters in it don't get interpreted as YAML during conversion.
|
||||
|
||||
And if the source value isn't a string, return it as-is.
|
||||
|
||||
Raise ruamel.yaml.error.YAMLError if there's a parse issue with the YAML.
|
||||
Raise ValueError if the parsed value doesn't match the option type.
|
||||
'''
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
|
||||
if option_type == 'string':
|
||||
return value
|
||||
|
||||
try:
|
||||
parsed_value = ruamel.yaml.YAML(typ='safe').load(io.StringIO(value))
|
||||
except ruamel.yaml.error.YAMLError as error:
|
||||
raise ValueError(f'Argument value "{value}" is invalid: {error.problem}')
|
||||
|
||||
if not isinstance(parsed_value, borgmatic.config.schema.parse_type(option_type)):
|
||||
raise ValueError(f'Argument value "{value}" is not of the expected type: {option_type}')
|
||||
|
||||
return parsed_value
|
||||
|
||||
|
||||
def prepare_arguments_for_config(global_arguments, schema):
|
||||
'''
|
||||
Given global arguments as an argparse.Namespace and a configuration schema dict, parse each
|
||||
argument that corresponds to an option in the schema and return a sequence of tuples (keys,
|
||||
values) for that option, where keys is a sequence of strings. For instance, given the following
|
||||
arguments:
|
||||
|
||||
argparse.Namespace(**{'my_option.sub_option': 'value1', 'other_option': 'value2'})
|
||||
|
||||
... return this:
|
||||
|
||||
(
|
||||
(('my_option', 'sub_option'), 'value1'),
|
||||
(('other_option',), 'value2'),
|
||||
)
|
||||
'''
|
||||
prepared_values = []
|
||||
|
||||
for argument_name, value in global_arguments.__dict__.items():
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
keys = tuple(argument_name.split('.'))
|
||||
option_type = type_for_option(schema, keys)
|
||||
|
||||
# The argument doesn't correspond to any option in the schema, so ignore it. It's
|
||||
# probably a flag that borgmatic has on the command-line but not in configuration.
|
||||
if option_type is None:
|
||||
continue
|
||||
|
||||
prepared_values.append(
|
||||
(
|
||||
keys,
|
||||
convert_value_type(value, option_type),
|
||||
)
|
||||
)
|
||||
|
||||
return tuple(prepared_values)
|
||||
|
||||
|
||||
def apply_arguments_to_config(config, schema, arguments):
|
||||
'''
|
||||
Given a configuration dict, a corresponding configuration schema dict, and arguments as a dict
|
||||
from action name to argparse.Namespace, set those given argument values into their corresponding
|
||||
configuration options in the configuration dict.
|
||||
|
||||
This supports argument flags of the from "--foo.bar.baz" where each dotted component is a nested
|
||||
configuration object. Additionally, flags like "--foo.bar[0].baz" are supported to update a list
|
||||
element in the configuration.
|
||||
'''
|
||||
for action_arguments in arguments.values():
|
||||
for keys, value in prepare_arguments_for_config(action_arguments, schema):
|
||||
set_values(config, keys, value)
|
||||
@@ -1,11 +1,11 @@
|
||||
import collections
|
||||
import io
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
|
||||
import ruamel.yaml
|
||||
|
||||
import borgmatic.config.schema
|
||||
from borgmatic.config import load, normalize
|
||||
|
||||
INDENT = 4
|
||||
@@ -22,25 +22,7 @@ def insert_newline_before_comment(config, field_name):
|
||||
)
|
||||
|
||||
|
||||
def get_properties(schema):
|
||||
'''
|
||||
Given a schema dict, return its properties. But if it's got sub-schemas with multiple different
|
||||
potential properties, returned their merged properties instead (interleaved so the first
|
||||
properties of each sub-schema come first). The idea is that the user should see all possible
|
||||
options even if they're not all possible together.
|
||||
'''
|
||||
if 'oneOf' in schema:
|
||||
return dict(
|
||||
item
|
||||
for item in itertools.chain(
|
||||
*itertools.zip_longest(
|
||||
*[sub_schema['properties'].items() for sub_schema in schema['oneOf']]
|
||||
)
|
||||
)
|
||||
if item is not None
|
||||
)
|
||||
|
||||
return schema['properties']
|
||||
SCALAR_SCHEMA_TYPES = {'string', 'boolean', 'integer', 'number'}
|
||||
|
||||
|
||||
def schema_to_sample_configuration(schema, source_config=None, level=0, parent_is_sequence=False):
|
||||
@@ -54,37 +36,45 @@ def schema_to_sample_configuration(schema, source_config=None, level=0, parent_i
|
||||
schema_type = schema.get('type')
|
||||
example = schema.get('example')
|
||||
|
||||
if example is not None:
|
||||
return example
|
||||
|
||||
if schema_type == 'array' or (isinstance(schema_type, list) and 'array' in schema_type):
|
||||
if borgmatic.config.schema.compare_types(schema_type, {'array'}):
|
||||
config = ruamel.yaml.comments.CommentedSeq(
|
||||
[
|
||||
example
|
||||
if borgmatic.config.schema.compare_types(
|
||||
schema['items'].get('type'), SCALAR_SCHEMA_TYPES
|
||||
)
|
||||
else [
|
||||
schema_to_sample_configuration(
|
||||
schema['items'], source_config, level, parent_is_sequence=True
|
||||
)
|
||||
]
|
||||
)
|
||||
add_comments_to_configuration_sequence(config, schema, indent=(level * INDENT))
|
||||
elif schema_type == 'object' or (isinstance(schema_type, list) and 'object' in schema_type):
|
||||
elif borgmatic.config.schema.compare_types(schema_type, {'object'}):
|
||||
if source_config and isinstance(source_config, list) and isinstance(source_config[0], dict):
|
||||
source_config = dict(collections.ChainMap(*source_config))
|
||||
|
||||
config = ruamel.yaml.comments.CommentedMap(
|
||||
[
|
||||
(
|
||||
field_name,
|
||||
schema_to_sample_configuration(
|
||||
sub_schema, (source_config or {}).get(field_name, {}), level + 1
|
||||
),
|
||||
)
|
||||
for field_name, sub_schema in get_properties(schema).items()
|
||||
]
|
||||
config = (
|
||||
ruamel.yaml.comments.CommentedMap(
|
||||
[
|
||||
(
|
||||
field_name,
|
||||
schema_to_sample_configuration(
|
||||
sub_schema, (source_config or {}).get(field_name, {}), level + 1
|
||||
),
|
||||
)
|
||||
for field_name, sub_schema in borgmatic.config.schema.get_properties(
|
||||
schema
|
||||
).items()
|
||||
]
|
||||
)
|
||||
or example
|
||||
)
|
||||
indent = (level * INDENT) + (SEQUENCE_INDENT if parent_is_sequence else 0)
|
||||
add_comments_to_configuration_object(
|
||||
config, schema, source_config, indent=indent, skip_first=parent_is_sequence
|
||||
)
|
||||
elif borgmatic.config.schema.compare_types(schema_type, SCALAR_SCHEMA_TYPES, match=all):
|
||||
return example
|
||||
else:
|
||||
raise ValueError(f'Schema at level {level} is unsupported: {schema}')
|
||||
|
||||
@@ -189,7 +179,7 @@ def add_comments_to_configuration_sequence(config, schema, indent=0):
|
||||
return
|
||||
|
||||
for field_name in config[0].keys():
|
||||
field_schema = get_properties(schema['items']).get(field_name, {})
|
||||
field_schema = borgmatic.config.schema.get_properties(schema['items']).get(field_name, {})
|
||||
description = field_schema.get('description')
|
||||
|
||||
# No description to use? Skip it.
|
||||
@@ -223,7 +213,7 @@ def add_comments_to_configuration_object(
|
||||
if skip_first and index == 0:
|
||||
continue
|
||||
|
||||
field_schema = get_properties(schema).get(field_name, {})
|
||||
field_schema = borgmatic.config.schema.get_properties(schema).get(field_name, {})
|
||||
description = field_schema.get('description', '').strip()
|
||||
|
||||
# If this isn't a default key, add an indicator to the comment flagging it to be commented
|
||||
|
||||
@@ -326,7 +326,11 @@ def normalize(config_filename, config):
|
||||
config['repositories'] = []
|
||||
|
||||
for repository_dict in repositories:
|
||||
repository_path = repository_dict['path']
|
||||
repository_path = repository_dict.get('path')
|
||||
|
||||
if repository_path is None:
|
||||
continue
|
||||
|
||||
if '~' in repository_path:
|
||||
logs.append(
|
||||
logging.makeLogRecord(
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import io
|
||||
import logging
|
||||
|
||||
import ruamel.yaml
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def set_values(config, keys, value):
|
||||
'''
|
||||
@@ -134,6 +137,11 @@ def apply_overrides(config, schema, raw_overrides):
|
||||
'''
|
||||
overrides = parse_overrides(raw_overrides, schema)
|
||||
|
||||
if overrides:
|
||||
logger.warning(
|
||||
"The --override flag is deprecated and will be removed from a future release. Instead, use a command-line flag corresponding to the configuration option you'd like to set."
|
||||
)
|
||||
|
||||
for keys, value in overrides:
|
||||
set_values(config, keys, value)
|
||||
set_values(config, strip_section_names(keys), value)
|
||||
|
||||
72
borgmatic/config/schema.py
Normal file
72
borgmatic/config/schema.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import decimal
|
||||
import itertools
|
||||
|
||||
|
||||
def get_properties(schema):
|
||||
'''
|
||||
Given a schema dict, return its properties. But if it's got sub-schemas with multiple different
|
||||
potential properties, return their merged properties instead (interleaved so the first
|
||||
properties of each sub-schema come first). The idea is that the user should see all possible
|
||||
options even if they're not all possible together.
|
||||
'''
|
||||
if 'oneOf' in schema:
|
||||
return dict(
|
||||
item
|
||||
for item in itertools.chain(
|
||||
*itertools.zip_longest(
|
||||
*[sub_schema['properties'].items() for sub_schema in schema['oneOf']]
|
||||
)
|
||||
)
|
||||
if item is not None
|
||||
)
|
||||
|
||||
return schema.get('properties', {})
|
||||
|
||||
|
||||
SCHEMA_TYPE_TO_PYTHON_TYPE = {
|
||||
'array': list,
|
||||
'boolean': bool,
|
||||
'integer': int,
|
||||
'number': decimal.Decimal,
|
||||
'object': dict,
|
||||
'string': str,
|
||||
}
|
||||
|
||||
|
||||
def parse_type(schema_type, **overrides):
|
||||
'''
|
||||
Given a schema type as a string, return the corresponding Python type.
|
||||
|
||||
If any overrides are given in the from of a schema type string to a Python type, then override
|
||||
the default type mapping with them.
|
||||
|
||||
Raise ValueError if the schema type is unknown.
|
||||
'''
|
||||
try:
|
||||
return dict(
|
||||
SCHEMA_TYPE_TO_PYTHON_TYPE,
|
||||
**overrides,
|
||||
)[schema_type]
|
||||
except KeyError:
|
||||
raise ValueError(f'Unknown type in configuration schema: {schema_type}')
|
||||
|
||||
|
||||
def compare_types(schema_type, target_types, match=any):
|
||||
'''
|
||||
Given a schema type as a string or a list of strings (representing multiple types) and a set of
|
||||
target type strings, return whether every schema type is in the set of target types.
|
||||
|
||||
If the schema type is a list of strings, use the given match function (such as any or all) to
|
||||
compare elements. For instance, if match is given as all, then every element of the schema_type
|
||||
list must be in the target types.
|
||||
'''
|
||||
if isinstance(schema_type, list):
|
||||
if match(element_schema_type in target_types for element_schema_type in schema_type):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
if schema_type in target_types:
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -33,13 +33,47 @@ properties:
|
||||
type: object
|
||||
required:
|
||||
- path
|
||||
additionalProperties: false
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
example: ssh://user@backupserver/./{fqdn}
|
||||
description: The local path or Borg URL of the repository.
|
||||
example: ssh://user@backupserver/./sourcehostname.borg
|
||||
label:
|
||||
type: string
|
||||
description: |
|
||||
An optional label for the repository, used in logging
|
||||
and to make selecting the repository easier on the
|
||||
command-line.
|
||||
example: backupserver
|
||||
encryption:
|
||||
type: string
|
||||
description: |
|
||||
The encryption mode with which to create the repository,
|
||||
only used for the repo-create action. To see the
|
||||
available encryption modes, run "borg init --help" with
|
||||
Borg 1 or "borg repo-create --help" with Borg 2.
|
||||
example: repokey-blake2
|
||||
append_only:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether the repository should be created append-only,
|
||||
only used for the repo-create action. Defaults to false.
|
||||
example: true
|
||||
storage_quota:
|
||||
type: string
|
||||
description: |
|
||||
The storage quota with which to create the repository,
|
||||
only used for the repo-create action. Defaults to no
|
||||
quota.
|
||||
example: 5G
|
||||
make_parent_directories:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether any missing parent directories of the repository
|
||||
path should be created, only used for the repo-create
|
||||
action. Defaults to false.
|
||||
example: true
|
||||
description: |
|
||||
A required list of local or remote repositories with paths and
|
||||
optional labels (which can be used with the --repository flag to
|
||||
@@ -48,8 +82,7 @@ properties:
|
||||
output of "borg help placeholders" for details. See ssh_command for
|
||||
SSH options like identity file or port. If systemd service is used,
|
||||
then add local repository paths in the systemd service file to the
|
||||
ReadWritePaths list. Prior to borgmatic 1.7.10, repositories was a
|
||||
list of plain path strings.
|
||||
ReadWritePaths list.
|
||||
example:
|
||||
- path: ssh://user@backupserver/./sourcehostname.borg
|
||||
label: backupserver
|
||||
@@ -99,13 +132,13 @@ properties:
|
||||
used when backing up special devices such as /dev/zero. Defaults to
|
||||
false. But when a database hook is used, the setting here is ignored
|
||||
and read_special is considered true.
|
||||
example: false
|
||||
example: true
|
||||
flags:
|
||||
type: boolean
|
||||
description: |
|
||||
Record filesystem flags (e.g. NODUMP, IMMUTABLE) in archive.
|
||||
Defaults to true.
|
||||
example: true
|
||||
example: false
|
||||
files_cache:
|
||||
type: string
|
||||
description: |
|
||||
@@ -442,19 +475,19 @@ properties:
|
||||
type: boolean
|
||||
description: |
|
||||
Bypass Borg error about a repository that has been moved. Defaults
|
||||
to not bypassing.
|
||||
to false.
|
||||
example: true
|
||||
unknown_unencrypted_repo_access_is_ok:
|
||||
type: boolean
|
||||
description: |
|
||||
Bypass Borg error about a previously unknown unencrypted repository.
|
||||
Defaults to not bypassing.
|
||||
Defaults to false.
|
||||
example: true
|
||||
check_i_know_what_i_am_doing:
|
||||
type: boolean
|
||||
description: |
|
||||
Bypass Borg confirmation about check with repair option. Defaults to
|
||||
an interactive prompt from Borg.
|
||||
false and an interactive prompt from Borg.
|
||||
example: true
|
||||
extra_borg_options:
|
||||
type: object
|
||||
@@ -534,6 +567,12 @@ properties:
|
||||
not specified, borgmatic defaults to matching archives based on the
|
||||
archive_name_format (see above).
|
||||
example: sourcehostname
|
||||
compact_threshold:
|
||||
type: integer
|
||||
description: |
|
||||
Minimum saved space percentage threshold for compacting a segment,
|
||||
defaults to 10.
|
||||
example: 20
|
||||
checks:
|
||||
type: array
|
||||
items:
|
||||
@@ -749,6 +788,10 @@ properties:
|
||||
List of one or more consistency checks to run on a periodic basis
|
||||
(if "frequency" is set) or every time borgmatic runs checks (if
|
||||
"frequency" is omitted).
|
||||
example:
|
||||
- name: archives
|
||||
frequency: 2 weeks
|
||||
- name: repository
|
||||
check_repositories:
|
||||
type: array
|
||||
items:
|
||||
@@ -770,9 +813,29 @@ properties:
|
||||
color:
|
||||
type: boolean
|
||||
description: |
|
||||
Apply color to console output. Can be overridden with --no-color
|
||||
command-line flag. Defaults to true.
|
||||
Apply color to console output. Defaults to true.
|
||||
example: false
|
||||
progress:
|
||||
type: boolean
|
||||
description: |
|
||||
Display progress as each file or archive is processed when running
|
||||
supported actions. Corresponds to the "--progress" flag on those
|
||||
actions. Defaults to false.
|
||||
example: true
|
||||
statistics:
|
||||
type: boolean
|
||||
description: |
|
||||
Display statistics for an archive when running supported actions.
|
||||
Corresponds to the "--stats" flag on those actions. Defaults to
|
||||
false.
|
||||
example: true
|
||||
list_details:
|
||||
type: boolean
|
||||
description: |
|
||||
Display details for each file or archive as it is processed when
|
||||
running supported actions. Corresponds to the "--list" flag on those
|
||||
actions. Defaults to false.
|
||||
example: true
|
||||
skip_actions:
|
||||
type: array
|
||||
items:
|
||||
@@ -1099,8 +1162,13 @@ properties:
|
||||
List of one or more command hooks to execute, triggered at
|
||||
particular points during borgmatic's execution. For each command
|
||||
hook, specify one of "before" or "after", not both.
|
||||
example:
|
||||
- before: action
|
||||
when: [create]
|
||||
run: [echo Backing up.]
|
||||
bootstrap:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
store_config_files:
|
||||
type: boolean
|
||||
@@ -1313,6 +1381,9 @@ properties:
|
||||
https://www.postgresql.org/docs/current/app-pgdump.html and
|
||||
https://www.postgresql.org/docs/current/libpq-ssl.html for
|
||||
details.
|
||||
example:
|
||||
- name: users
|
||||
hostname: database.example.org
|
||||
mariadb_databases:
|
||||
type: array
|
||||
items:
|
||||
@@ -1458,6 +1529,9 @@ properties:
|
||||
added to your source directories at runtime and streamed directly
|
||||
to Borg. Requires mariadb-dump/mariadb commands. See
|
||||
https://mariadb.com/kb/en/library/mysqldump/ for details.
|
||||
example:
|
||||
- name: users
|
||||
hostname: database.example.org
|
||||
mysql_databases:
|
||||
type: array
|
||||
items:
|
||||
@@ -1603,6 +1677,9 @@ properties:
|
||||
to Borg. Requires mysqldump/mysql commands. See
|
||||
https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html for
|
||||
details.
|
||||
example:
|
||||
- name: users
|
||||
hostname: database.example.org
|
||||
sqlite_databases:
|
||||
type: array
|
||||
items:
|
||||
@@ -1650,6 +1727,15 @@ properties:
|
||||
sqlite3 version (e.g., one inside a running container).
|
||||
Defaults to "sqlite3".
|
||||
example: docker exec sqlite_container sqlite3
|
||||
description: |
|
||||
List of one or more SQLite databases to dump before creating a
|
||||
backup, run once per configuration file. The database dumps are
|
||||
added to your source directories at runtime and streamed directly to
|
||||
Borg. Requires the sqlite3 command. See https://sqlite.org/cli.html
|
||||
for details.
|
||||
example:
|
||||
- name: users
|
||||
path: /var/lib/db.sqlite
|
||||
mongodb_databases:
|
||||
type: array
|
||||
items:
|
||||
@@ -1771,6 +1857,9 @@ properties:
|
||||
to Borg. Requires mongodump/mongorestore commands. See
|
||||
https://docs.mongodb.com/database-tools/mongodump/ and
|
||||
https://docs.mongodb.com/database-tools/mongorestore/ for details.
|
||||
example:
|
||||
- name: users
|
||||
hostname: database.example.org
|
||||
ntfy:
|
||||
type: object
|
||||
required: ['topic']
|
||||
@@ -1807,6 +1896,7 @@ properties:
|
||||
example: tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2
|
||||
start:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
@@ -1830,6 +1920,7 @@ properties:
|
||||
example: incoming_envelope
|
||||
finish:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
@@ -1853,6 +1944,7 @@ properties:
|
||||
example: incoming_envelope
|
||||
fail:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
@@ -1911,6 +2003,7 @@ properties:
|
||||
example: hwRwoWsXMBWwgrSecfa9EfPey55WSN
|
||||
start:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
@@ -1950,8 +2043,8 @@ properties:
|
||||
type: boolean
|
||||
description: |
|
||||
Set to True to enable HTML parsing of the message.
|
||||
Set to False for plain text.
|
||||
example: True
|
||||
Set to false for plain text.
|
||||
example: true
|
||||
sound:
|
||||
type: string
|
||||
description: |
|
||||
@@ -1986,6 +2079,7 @@ properties:
|
||||
example: Pushover Link
|
||||
finish:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
@@ -2025,8 +2119,8 @@ properties:
|
||||
type: boolean
|
||||
description: |
|
||||
Set to True to enable HTML parsing of the message.
|
||||
Set to False for plain text.
|
||||
example: True
|
||||
Set to false for plain text.
|
||||
example: true
|
||||
sound:
|
||||
type: string
|
||||
description: |
|
||||
@@ -2061,6 +2155,7 @@ properties:
|
||||
example: Pushover Link
|
||||
fail:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
@@ -2100,8 +2195,8 @@ properties:
|
||||
type: boolean
|
||||
description: |
|
||||
Set to True to enable HTML parsing of the message.
|
||||
Set to False for plain text.
|
||||
example: True
|
||||
Set to false for plain text.
|
||||
example: true
|
||||
sound:
|
||||
type: string
|
||||
description: |
|
||||
@@ -2200,6 +2295,7 @@ properties:
|
||||
example: fakekey
|
||||
start:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
value:
|
||||
type: ["integer", "string"]
|
||||
@@ -2208,6 +2304,7 @@ properties:
|
||||
example: STARTED
|
||||
finish:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
value:
|
||||
type: ["integer", "string"]
|
||||
@@ -2216,6 +2313,7 @@ properties:
|
||||
example: FINISH
|
||||
fail:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
value:
|
||||
type: ["integer", "string"]
|
||||
@@ -2247,15 +2345,20 @@ properties:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- url
|
||||
- label
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
description: URL of this Apprise service.
|
||||
example: "gotify://hostname/token"
|
||||
label:
|
||||
type: string
|
||||
description: |
|
||||
Label used in borgmatic logs for this Apprise
|
||||
service.
|
||||
example: gotify
|
||||
description: |
|
||||
A list of Apprise services to publish to with URLs and
|
||||
@@ -2270,7 +2373,7 @@ properties:
|
||||
send_logs:
|
||||
type: boolean
|
||||
description: |
|
||||
Send borgmatic logs to Apprise services as part the
|
||||
Send borgmatic logs to Apprise services as part of the
|
||||
"finish", "fail", and "log" states. Defaults to true.
|
||||
example: false
|
||||
logs_size_limit:
|
||||
@@ -2283,6 +2386,7 @@ properties:
|
||||
start:
|
||||
type: object
|
||||
required: ['body']
|
||||
additionalProperties: false
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
@@ -2298,6 +2402,7 @@ properties:
|
||||
finish:
|
||||
type: object
|
||||
required: ['body']
|
||||
additionalProperties: false
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
@@ -2313,6 +2418,7 @@ properties:
|
||||
fail:
|
||||
type: object
|
||||
required: ['body']
|
||||
additionalProperties: false
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
@@ -2328,6 +2434,7 @@ properties:
|
||||
log:
|
||||
type: object
|
||||
required: ['body']
|
||||
additionalProperties: false
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
@@ -2381,7 +2488,7 @@ properties:
|
||||
send_logs:
|
||||
type: boolean
|
||||
description: |
|
||||
Send borgmatic logs to Healthchecks as part the "finish",
|
||||
Send borgmatic logs to Healthchecks as part of the "finish",
|
||||
"fail", and "log" states. Defaults to true.
|
||||
example: false
|
||||
ping_body_limit:
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
import jsonschema
|
||||
import ruamel.yaml
|
||||
|
||||
import borgmatic.config
|
||||
import borgmatic.config.arguments
|
||||
from borgmatic.config import constants, environment, load, normalize, override
|
||||
|
||||
|
||||
@@ -21,6 +21,18 @@ def schema_filename():
|
||||
return schema_path
|
||||
|
||||
|
||||
def load_schema(schema_path): # pragma: no cover
|
||||
'''
|
||||
Given a schema filename path, load the schema and return it as a dict.
|
||||
|
||||
Raise Validation_error if the schema could not be parsed.
|
||||
'''
|
||||
try:
|
||||
return load.load_configuration(schema_path)
|
||||
except (ruamel.yaml.error.YAMLError, RecursionError) as error:
|
||||
raise Validation_error(schema_path, (str(error),))
|
||||
|
||||
|
||||
def format_json_error_path_element(path_element):
|
||||
'''
|
||||
Given a path element into a JSON data structure, format it for display as a string.
|
||||
@@ -84,13 +96,17 @@ def apply_logical_validation(config_filename, parsed_configuration):
|
||||
)
|
||||
|
||||
|
||||
def parse_configuration(config_filename, schema_filename, overrides=None, resolve_env=True):
|
||||
def parse_configuration(
|
||||
config_filename, schema_filename, arguments, overrides=None, resolve_env=True
|
||||
):
|
||||
'''
|
||||
Given the path to a config filename in YAML format, the path to a schema filename in a YAML
|
||||
rendition of JSON Schema format, a sequence of configuration file override strings in the form
|
||||
of "option.suboption=value", and whether to resolve environment variables, return the parsed
|
||||
configuration as a data structure of nested dicts and lists corresponding to the schema. Example
|
||||
return value:
|
||||
rendition of JSON Schema format, arguments as dict from action name to argparse.Namespace, a
|
||||
sequence of configuration file override strings in the form of "option.suboption=value", and
|
||||
whether to resolve environment variables, return the parsed configuration as a data structure of
|
||||
nested dicts and lists corresponding to the schema. Example return value.
|
||||
|
||||
Example return value:
|
||||
|
||||
{
|
||||
'source_directories': ['/home', '/etc'],
|
||||
@@ -113,6 +129,7 @@ def parse_configuration(config_filename, schema_filename, overrides=None, resolv
|
||||
except (ruamel.yaml.error.YAMLError, RecursionError) as error:
|
||||
raise Validation_error(config_filename, (str(error),))
|
||||
|
||||
borgmatic.config.arguments.apply_arguments_to_config(config, schema, arguments)
|
||||
override.apply_overrides(config, schema, overrides)
|
||||
constants.apply_constants(config, config.get('constants') if config else {})
|
||||
|
||||
|
||||
@@ -29,12 +29,13 @@ def interactive_console():
|
||||
return sys.stderr.isatty() and os.environ.get('TERM') != 'dumb'
|
||||
|
||||
|
||||
def should_do_markup(no_color, configs):
|
||||
def should_do_markup(configs, json_enabled):
|
||||
'''
|
||||
Given the value of the command-line no-color argument, and a dict of configuration filename to
|
||||
corresponding parsed configuration, determine if we should enable color marking up.
|
||||
Given a dict of configuration filename to corresponding parsed configuration (which already have
|
||||
any command-line overrides applied) and whether json is enabled, determine if we should enable
|
||||
color marking up.
|
||||
'''
|
||||
if no_color:
|
||||
if json_enabled:
|
||||
return False
|
||||
|
||||
if any(config.get('color', True) is False for config in configs.values()):
|
||||
|
||||
@@ -17,8 +17,8 @@ points as it runs.
|
||||
feature](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/)
|
||||
instead.)
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 2.0.0 (not yet
|
||||
released)</span> Command hooks are now configured via a list of `commands:` in
|
||||
<span class="minilink minilink-addedin">New in version 2.0.0 (**not yet
|
||||
released**)</span> Command hooks are now configured via a list of `commands:` in
|
||||
your borgmatic configuration file. For example:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -482,16 +482,89 @@ applications, but then set the repository for each application at runtime. Or
|
||||
you might want to try a variant of an option for testing purposes without
|
||||
actually touching your configuration file.
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 2.0.0</span>
|
||||
Whatever the reason, you can override borgmatic configuration options at the
|
||||
command-line via the `--override` flag. Here's an example:
|
||||
command-line, as there's a command-line flag corresponding to every
|
||||
configuration option (with its underscores converted to dashes).
|
||||
|
||||
For instance, to override the `compression` configuration option, use the
|
||||
corresponding `--compression` flag on the command-line:
|
||||
|
||||
```bash
|
||||
borgmatic create --compression zstd
|
||||
```
|
||||
|
||||
What this does is load your given configuration files and for each one, disregard
|
||||
the configured value for the `compression` option and use the value given on the
|
||||
command-line instead—but just for the duration of the borgmatic run.
|
||||
|
||||
You can override nested configuration options too by separating such option
|
||||
names with a period. For instance:
|
||||
|
||||
```bash
|
||||
borgmatic create --bootstrap.store-config-files false
|
||||
```
|
||||
|
||||
You can even set complex option data structures by using inline YAML syntax. For
|
||||
example, set the `repositories` option with a YAML list of key/value pairs:
|
||||
|
||||
```bash
|
||||
borgmatic create --repositories "[{path: /mnt/backup, label: local}]"
|
||||
```
|
||||
|
||||
If your override value contains characters like colons or spaces, then you'll
|
||||
need to use quotes for it to parse correctly.
|
||||
|
||||
You can also set individual nested options within existing list elements:
|
||||
|
||||
```bash
|
||||
borgmatic create --repositories[0].path /mnt/backup
|
||||
```
|
||||
|
||||
This updates the `path` option for the first repository in `repositories`.
|
||||
Change the `[0]` index as needed to address different list elements. And note
|
||||
that this only works for elements already set in configuration; you can't append
|
||||
new list elements from the command-line.
|
||||
|
||||
See the [command-line reference
|
||||
documentation](https://torsion.org/borgmatic/docs/reference/command-line/) for
|
||||
the full set of available arguments, including examples of each for the complex
|
||||
values.
|
||||
|
||||
There are a handful of configuration options that don't have corresponding
|
||||
command-line flags at the global scope, but instead have flags within individual
|
||||
borgmatic actions. For instance, the `list_details` option can be overridden by
|
||||
the `--list` flag that's only present on particular actions. Similarly with
|
||||
`progress` and `--progress`, `statistics` and `--stats`, and `match_archives`
|
||||
and `--match-archives`.
|
||||
|
||||
Also note that if you want to pass a command-line flag itself as a value to one
|
||||
of these override flags, that may not work. For instance, specifying
|
||||
`--extra-borg-options.create --no-cache-sync` results in an error, because
|
||||
`--no-cache-sync` gets interpreted as a borgmatic option (which in this case
|
||||
doesn't exist) rather than a Borg option.
|
||||
|
||||
An alternate to command-line overrides is passing in your values via
|
||||
[environment
|
||||
variables](https://torsion.org/borgmatic/docs/how-to/provide-your-passwords/).
|
||||
|
||||
|
||||
### Deprecated overrides
|
||||
|
||||
<span class="minilink minilink-addedin">Prior to version 2.0.0</span>
|
||||
Configuration overrides were performed with an `--override` flag. You can still
|
||||
use `--override` with borgmatic 2.0.0+, but it's deprecated in favor of the new
|
||||
command-line flags described above.
|
||||
|
||||
Here's an example of `--override`:
|
||||
|
||||
```bash
|
||||
borgmatic create --override remote_path=/usr/local/bin/borg1
|
||||
```
|
||||
|
||||
What this does is load your configuration files and for each one, disregard
|
||||
the configured value for the `remote_path` option and use the value of
|
||||
`/usr/local/bin/borg1` instead.
|
||||
What this does is load your given configuration files and for each one, disregard
|
||||
the configured value for the `remote_path` option and use the value given on the
|
||||
command-line instead—but just for the duration of the borgmatic run.
|
||||
|
||||
You can even override nested values or multiple values at once. For instance:
|
||||
|
||||
@@ -540,10 +613,6 @@ reference](https://torsion.org/borgmatic/docs/reference/configuration/) for
|
||||
which options are list types. (YAML list values look like `- this` with an
|
||||
indentation and a leading dash.)
|
||||
|
||||
An alternate to command-line overrides is passing in your values via
|
||||
[environment
|
||||
variables](https://torsion.org/borgmatic/docs/how-to/provide-your-passwords/).
|
||||
|
||||
|
||||
## Constant interpolation
|
||||
|
||||
|
||||
55
tests/end-to-end/test_config_flag.py
Normal file
55
tests/end-to-end/test_config_flag.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
def generate_configuration(config_path):
|
||||
'''
|
||||
Generate borgmatic configuration into a file at the config path, and update the defaults so as
|
||||
to work for testing (including injecting the given repository path and tacking on an encryption
|
||||
passphrase). But don't actually set the repository path, as that's done on the command-line
|
||||
below.
|
||||
'''
|
||||
subprocess.check_call(f'borgmatic config generate --destination {config_path}'.split(' '))
|
||||
config = (
|
||||
open(config_path)
|
||||
.read()
|
||||
.replace('- ssh://user@backupserver/./{fqdn}', '') # noqa: FS003
|
||||
.replace('- /var/local/backups/local.borg', '')
|
||||
.replace('- /home/user/path with spaces', '')
|
||||
.replace('- /home', f'- {config_path}')
|
||||
.replace('- /etc', '')
|
||||
.replace('- /var/log/syslog*', '')
|
||||
+ 'encryption_passphrase: "test"'
|
||||
)
|
||||
config_file = open(config_path, 'w')
|
||||
config_file.write(config)
|
||||
config_file.close()
|
||||
|
||||
|
||||
def test_config_flags_do_not_error():
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
repository_path = os.path.join(temporary_directory, 'test.borg')
|
||||
|
||||
original_working_directory = os.getcwd()
|
||||
|
||||
try:
|
||||
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||
generate_configuration(config_path)
|
||||
|
||||
subprocess.check_call(
|
||||
shlex.split(
|
||||
f'borgmatic -v 2 --config {config_path} --repositories "[{{path: {repository_path}, label: repo}}]" repo-create --encryption repokey'
|
||||
)
|
||||
)
|
||||
|
||||
subprocess.check_call(
|
||||
shlex.split(
|
||||
f'borgmatic create --config {config_path} --repositories[0].path "{repository_path}"'
|
||||
)
|
||||
)
|
||||
finally:
|
||||
os.chdir(original_working_directory)
|
||||
shutil.rmtree(temporary_directory)
|
||||
@@ -53,7 +53,7 @@ def fuzz_argument(arguments, argument_name):
|
||||
|
||||
def test_transfer_archives_command_does_not_duplicate_flags_or_raise():
|
||||
arguments = borgmatic.commands.arguments.parse_arguments(
|
||||
'transfer', '--source-repository', 'foo'
|
||||
{}, 'transfer', '--source-repository', 'foo'
|
||||
)['transfer']
|
||||
flexmock(borgmatic.borg.transfer).should_receive('execute_command').replace_with(
|
||||
assert_command_does_not_duplicate_flags
|
||||
@@ -74,7 +74,7 @@ def test_transfer_archives_command_does_not_duplicate_flags_or_raise():
|
||||
|
||||
|
||||
def test_prune_archives_command_does_not_duplicate_flags_or_raise():
|
||||
arguments = borgmatic.commands.arguments.parse_arguments('prune')['prune']
|
||||
arguments = borgmatic.commands.arguments.parse_arguments({}, 'prune')['prune']
|
||||
flexmock(borgmatic.borg.prune).should_receive('execute_command').replace_with(
|
||||
assert_command_does_not_duplicate_flags
|
||||
)
|
||||
@@ -94,7 +94,7 @@ def test_prune_archives_command_does_not_duplicate_flags_or_raise():
|
||||
|
||||
|
||||
def test_mount_archive_command_does_not_duplicate_flags_or_raise():
|
||||
arguments = borgmatic.commands.arguments.parse_arguments('mount', '--mount-point', 'tmp')[
|
||||
arguments = borgmatic.commands.arguments.parse_arguments({}, 'mount', '--mount-point', 'tmp')[
|
||||
'mount'
|
||||
]
|
||||
flexmock(borgmatic.borg.mount).should_receive('execute_command').replace_with(
|
||||
@@ -116,7 +116,7 @@ def test_mount_archive_command_does_not_duplicate_flags_or_raise():
|
||||
|
||||
|
||||
def test_make_list_command_does_not_duplicate_flags_or_raise():
|
||||
arguments = borgmatic.commands.arguments.parse_arguments('list')['list']
|
||||
arguments = borgmatic.commands.arguments.parse_arguments({}, 'list')['list']
|
||||
|
||||
for argument_name in dir(arguments):
|
||||
if argument_name.startswith('_'):
|
||||
@@ -134,7 +134,7 @@ def test_make_list_command_does_not_duplicate_flags_or_raise():
|
||||
|
||||
|
||||
def test_make_repo_list_command_does_not_duplicate_flags_or_raise():
|
||||
arguments = borgmatic.commands.arguments.parse_arguments('repo-list')['repo-list']
|
||||
arguments = borgmatic.commands.arguments.parse_arguments({}, 'repo-list')['repo-list']
|
||||
|
||||
for argument_name in dir(arguments):
|
||||
if argument_name.startswith('_'):
|
||||
@@ -152,7 +152,7 @@ def test_make_repo_list_command_does_not_duplicate_flags_or_raise():
|
||||
|
||||
|
||||
def test_display_archives_info_command_does_not_duplicate_flags_or_raise():
|
||||
arguments = borgmatic.commands.arguments.parse_arguments('info')['info']
|
||||
arguments = borgmatic.commands.arguments.parse_arguments({}, 'info')['info']
|
||||
flexmock(borgmatic.borg.info).should_receive('execute_command_and_capture_output').replace_with(
|
||||
assert_command_does_not_duplicate_flags
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import borgmatic.commands.arguments
|
||||
import borgmatic.config.validate
|
||||
from borgmatic.commands.completion import actions as module
|
||||
|
||||
|
||||
@@ -7,7 +8,10 @@ def test_available_actions_uses_only_subactions_for_action_with_subactions():
|
||||
unused_global_parser,
|
||||
action_parsers,
|
||||
unused_combined_parser,
|
||||
) = borgmatic.commands.arguments.make_parsers()
|
||||
) = borgmatic.commands.arguments.make_parsers(
|
||||
schema=borgmatic.config.validate.load_schema(borgmatic.config.validate.schema_filename()),
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
actions = module.available_actions(action_parsers, 'config')
|
||||
|
||||
@@ -20,7 +24,10 @@ def test_available_actions_omits_subactions_for_action_without_subactions():
|
||||
unused_global_parser,
|
||||
action_parsers,
|
||||
unused_combined_parser,
|
||||
) = borgmatic.commands.arguments.make_parsers()
|
||||
) = borgmatic.commands.arguments.make_parsers(
|
||||
schema=borgmatic.config.validate.load_schema(borgmatic.config.validate.schema_filename()),
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
actions = module.available_actions(action_parsers, 'list')
|
||||
|
||||
|
||||
@@ -4,11 +4,144 @@ from flexmock import flexmock
|
||||
from borgmatic.commands import arguments as module
|
||||
|
||||
|
||||
def test_make_argument_description_with_object_adds_example():
|
||||
assert (
|
||||
module.make_argument_description(
|
||||
schema={
|
||||
'description': 'Thing.',
|
||||
'type': 'object',
|
||||
'example': {'bar': 'baz'},
|
||||
},
|
||||
flag_name='flag',
|
||||
)
|
||||
# Apparently different versions of ruamel.yaml serialize this
|
||||
# differently.
|
||||
in ('Thing. Example value: "bar: baz"' 'Thing. Example value: "{bar: baz}"')
|
||||
)
|
||||
|
||||
|
||||
def test_make_argument_description_with_array_adds_example():
|
||||
assert (
|
||||
module.make_argument_description(
|
||||
schema={
|
||||
'description': 'Thing.',
|
||||
'type': 'array',
|
||||
'example': [1, '- foo', {'bar': 'baz'}],
|
||||
},
|
||||
flag_name='flag',
|
||||
)
|
||||
# Apparently different versions of ruamel.yaml serialize this
|
||||
# differently.
|
||||
in (
|
||||
'Thing. Example value: "[1, \'- foo\', bar: baz]"'
|
||||
'Thing. Example value: "[1, \'- foo\', {bar: baz}]"'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_add_array_element_arguments_adds_arguments_for_array_index_flags():
|
||||
parser = module.ArgumentParser(allow_abbrev=False, add_help=False)
|
||||
arguments_group = parser.add_argument_group('arguments')
|
||||
arguments_group.add_argument(
|
||||
'--foo[0].val',
|
||||
action='store_true',
|
||||
dest='--foo[0].val',
|
||||
)
|
||||
|
||||
flexmock(arguments_group).should_receive('add_argument').with_args(
|
||||
'--foo[25].val',
|
||||
action='store_true',
|
||||
default=False,
|
||||
dest='foo[25].val',
|
||||
required=object,
|
||||
).once()
|
||||
|
||||
module.add_array_element_arguments(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=('--foo[25].val', 'fooval', '--bar[1].val', 'barval'),
|
||||
flag_name='foo[0].val',
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_with_nested_object_adds_flag_for_each_option():
|
||||
parser = module.ArgumentParser(allow_abbrev=False, add_help=False)
|
||||
arguments_group = parser.add_argument_group('arguments')
|
||||
flexmock(arguments_group).should_receive('add_argument').with_args(
|
||||
'--foo.bar',
|
||||
type=int,
|
||||
metavar='BAR',
|
||||
help='help 1',
|
||||
).once()
|
||||
flexmock(arguments_group).should_receive('add_argument').with_args(
|
||||
'--foo.baz',
|
||||
type=str,
|
||||
metavar='BAZ',
|
||||
help='help 2',
|
||||
).once()
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'bar': {'type': 'integer', 'description': 'help 1'},
|
||||
'baz': {'type': 'string', 'description': 'help 2'},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_with_array_and_nested_object_adds_multiple_flags():
|
||||
parser = module.ArgumentParser(allow_abbrev=False, add_help=False)
|
||||
arguments_group = parser.add_argument_group('arguments')
|
||||
flexmock(arguments_group).should_receive('add_argument').with_args(
|
||||
'--foo[0].bar',
|
||||
type=int,
|
||||
metavar='BAR',
|
||||
help=object,
|
||||
).once()
|
||||
flexmock(arguments_group).should_receive('add_argument').with_args(
|
||||
'--foo',
|
||||
type=str,
|
||||
metavar='FOO',
|
||||
help='help 2',
|
||||
).once()
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'bar': {
|
||||
'type': 'integer',
|
||||
'description': 'help 1',
|
||||
}
|
||||
},
|
||||
},
|
||||
'description': 'help 2',
|
||||
}
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
|
||||
def test_parse_arguments_with_no_arguments_uses_defaults():
|
||||
config_paths = ['default']
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths)
|
||||
|
||||
arguments = module.parse_arguments()
|
||||
arguments = module.parse_arguments({})
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == config_paths
|
||||
@@ -21,7 +154,7 @@ def test_parse_arguments_with_no_arguments_uses_defaults():
|
||||
def test_parse_arguments_with_multiple_config_flags_parses_as_list():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('--config', 'myconfig', '--config', 'otherconfig')
|
||||
arguments = module.parse_arguments({}, '--config', 'myconfig', '--config', 'otherconfig')
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == ['myconfig', 'otherconfig']
|
||||
@@ -34,7 +167,7 @@ def test_parse_arguments_with_multiple_config_flags_parses_as_list():
|
||||
def test_parse_arguments_with_action_after_config_path_omits_action():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('--config', 'myconfig', 'list', '--json')
|
||||
arguments = module.parse_arguments({}, '--config', 'myconfig', 'list', '--json')
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == ['myconfig']
|
||||
@@ -45,7 +178,9 @@ def test_parse_arguments_with_action_after_config_path_omits_action():
|
||||
def test_parse_arguments_with_action_after_config_path_omits_aliased_action():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('--config', 'myconfig', 'init', '--encryption', 'repokey')
|
||||
arguments = module.parse_arguments(
|
||||
{}, '--config', 'myconfig', 'init', '--encryption', 'repokey'
|
||||
)
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == ['myconfig']
|
||||
@@ -56,7 +191,7 @@ def test_parse_arguments_with_action_after_config_path_omits_aliased_action():
|
||||
def test_parse_arguments_with_action_and_positional_arguments_after_config_path_omits_action_and_arguments():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('--config', 'myconfig', 'borg', 'key', 'export')
|
||||
arguments = module.parse_arguments({}, '--config', 'myconfig', 'borg', 'key', 'export')
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == ['myconfig']
|
||||
@@ -68,7 +203,7 @@ def test_parse_arguments_with_verbosity_overrides_default():
|
||||
config_paths = ['default']
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths)
|
||||
|
||||
arguments = module.parse_arguments('--verbosity', '1')
|
||||
arguments = module.parse_arguments({}, '--verbosity', '1')
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == config_paths
|
||||
@@ -82,7 +217,7 @@ def test_parse_arguments_with_syslog_verbosity_overrides_default():
|
||||
config_paths = ['default']
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths)
|
||||
|
||||
arguments = module.parse_arguments('--syslog-verbosity', '2')
|
||||
arguments = module.parse_arguments({}, '--syslog-verbosity', '2')
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == config_paths
|
||||
@@ -96,7 +231,7 @@ def test_parse_arguments_with_log_file_verbosity_overrides_default():
|
||||
config_paths = ['default']
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths)
|
||||
|
||||
arguments = module.parse_arguments('--log-file-verbosity', '-1')
|
||||
arguments = module.parse_arguments({}, '--log-file-verbosity', '-1')
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == config_paths
|
||||
@@ -109,7 +244,7 @@ def test_parse_arguments_with_log_file_verbosity_overrides_default():
|
||||
def test_parse_arguments_with_single_override_parses():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('--override', 'foo.bar=baz')
|
||||
arguments = module.parse_arguments({}, '--override', 'foo.bar=baz')
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.overrides == ['foo.bar=baz']
|
||||
@@ -119,7 +254,7 @@ def test_parse_arguments_with_multiple_overrides_flags_parses():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments(
|
||||
'--override', 'foo.bar=baz', '--override', 'foo.quux=7', '--override', 'this.that=8'
|
||||
{}, '--override', 'foo.bar=baz', '--override', 'foo.quux=7', '--override', 'this.that=8'
|
||||
)
|
||||
|
||||
global_arguments = arguments['global']
|
||||
@@ -127,7 +262,7 @@ def test_parse_arguments_with_multiple_overrides_flags_parses():
|
||||
|
||||
|
||||
def test_parse_arguments_with_list_json_overrides_default():
|
||||
arguments = module.parse_arguments('list', '--json')
|
||||
arguments = module.parse_arguments({}, 'list', '--json')
|
||||
|
||||
assert 'list' in arguments
|
||||
assert arguments['list'].json is True
|
||||
@@ -136,7 +271,7 @@ def test_parse_arguments_with_list_json_overrides_default():
|
||||
def test_parse_arguments_with_no_actions_defaults_to_all_actions_enabled():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments()
|
||||
arguments = module.parse_arguments({})
|
||||
|
||||
assert 'prune' in arguments
|
||||
assert 'create' in arguments
|
||||
@@ -146,14 +281,14 @@ def test_parse_arguments_with_no_actions_defaults_to_all_actions_enabled():
|
||||
def test_parse_arguments_with_no_actions_passes_argument_to_relevant_actions():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('--stats', '--list')
|
||||
arguments = module.parse_arguments({}, '--stats', '--list')
|
||||
|
||||
assert 'prune' in arguments
|
||||
assert arguments['prune'].stats
|
||||
assert arguments['prune'].list_archives
|
||||
assert arguments['prune'].statistics
|
||||
assert arguments['prune'].list_details
|
||||
assert 'create' in arguments
|
||||
assert arguments['create'].stats
|
||||
assert arguments['create'].list_files
|
||||
assert arguments['create'].statistics
|
||||
assert arguments['create'].list_details
|
||||
assert 'check' in arguments
|
||||
|
||||
|
||||
@@ -161,7 +296,7 @@ def test_parse_arguments_with_help_and_no_actions_shows_global_help(capsys):
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit) as exit:
|
||||
module.parse_arguments('--help')
|
||||
module.parse_arguments({}, '--help')
|
||||
|
||||
assert exit.value.code == 0
|
||||
captured = capsys.readouterr()
|
||||
@@ -173,7 +308,7 @@ def test_parse_arguments_with_help_and_action_shows_action_help(capsys):
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit) as exit:
|
||||
module.parse_arguments('create', '--help')
|
||||
module.parse_arguments({}, 'create', '--help')
|
||||
|
||||
assert exit.value.code == 0
|
||||
captured = capsys.readouterr()
|
||||
@@ -185,7 +320,7 @@ def test_parse_arguments_with_help_and_action_shows_action_help(capsys):
|
||||
def test_parse_arguments_with_action_before_global_options_parses_options():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('prune', '--verbosity', '2')
|
||||
arguments = module.parse_arguments({}, 'prune', '--verbosity', '2')
|
||||
|
||||
assert 'prune' in arguments
|
||||
assert arguments['global'].verbosity == 2
|
||||
@@ -194,7 +329,7 @@ def test_parse_arguments_with_action_before_global_options_parses_options():
|
||||
def test_parse_arguments_with_global_options_before_action_parses_options():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('--verbosity', '2', 'prune')
|
||||
arguments = module.parse_arguments({}, '--verbosity', '2', 'prune')
|
||||
|
||||
assert 'prune' in arguments
|
||||
assert arguments['global'].verbosity == 2
|
||||
@@ -203,7 +338,7 @@ def test_parse_arguments_with_global_options_before_action_parses_options():
|
||||
def test_parse_arguments_with_prune_action_leaves_other_actions_disabled():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('prune')
|
||||
arguments = module.parse_arguments({}, 'prune')
|
||||
|
||||
assert 'prune' in arguments
|
||||
assert 'create' not in arguments
|
||||
@@ -213,7 +348,7 @@ def test_parse_arguments_with_prune_action_leaves_other_actions_disabled():
|
||||
def test_parse_arguments_with_multiple_actions_leaves_other_action_disabled():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('create', 'check')
|
||||
arguments = module.parse_arguments({}, 'create', 'check')
|
||||
|
||||
assert 'prune' not in arguments
|
||||
assert 'create' in arguments
|
||||
@@ -224,60 +359,53 @@ def test_parse_arguments_disallows_invalid_argument():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--posix-me-harder')
|
||||
module.parse_arguments({}, '--posix-me-harder')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_encryption_mode_without_init():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--encryption', 'repokey')
|
||||
module.parse_arguments({}, '--config', 'myconfig', '--encryption', 'repokey')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_encryption_mode_with_init():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--config', 'myconfig', 'init', '--encryption', 'repokey')
|
||||
|
||||
|
||||
def test_parse_arguments_requires_encryption_mode_with_init():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', 'init')
|
||||
module.parse_arguments({}, '--config', 'myconfig', 'init', '--encryption', 'repokey')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_append_only_without_init():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--append-only')
|
||||
module.parse_arguments({}, '--config', 'myconfig', '--append-only')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_storage_quota_without_init():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--storage-quota', '5G')
|
||||
module.parse_arguments({}, '--config', 'myconfig', '--storage-quota', '5G')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_init_and_prune():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--config', 'myconfig', 'init', '--encryption', 'repokey', 'prune')
|
||||
module.parse_arguments({}, '--config', 'myconfig', 'init', '--encryption', 'repokey', 'prune')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_init_and_create():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--config', 'myconfig', 'init', '--encryption', 'repokey', 'create')
|
||||
module.parse_arguments({}, '--config', 'myconfig', 'init', '--encryption', 'repokey', 'create')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_repository_with_extract():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments(
|
||||
'--config', 'myconfig', 'extract', '--repository', 'test.borg', '--archive', 'test'
|
||||
{}, '--config', 'myconfig', 'extract', '--repository', 'test.borg', '--archive', 'test'
|
||||
)
|
||||
|
||||
|
||||
@@ -285,6 +413,7 @@ def test_parse_arguments_allows_repository_with_mount():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments(
|
||||
{},
|
||||
'--config',
|
||||
'myconfig',
|
||||
'mount',
|
||||
@@ -300,276 +429,247 @@ def test_parse_arguments_allows_repository_with_mount():
|
||||
def test_parse_arguments_allows_repository_with_list():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--config', 'myconfig', 'list', '--repository', 'test.borg')
|
||||
module.parse_arguments({}, '--config', 'myconfig', 'list', '--repository', 'test.borg')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_archive_unless_action_consumes_it():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--archive', 'test')
|
||||
module.parse_arguments({}, '--config', 'myconfig', '--archive', 'test')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_paths_unless_action_consumes_it():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--path', 'test')
|
||||
module.parse_arguments({}, '--config', 'myconfig', '--path', 'test')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_other_actions_with_config_bootstrap():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('config', 'bootstrap', '--repository', 'test.borg', 'list')
|
||||
module.parse_arguments({}, 'config', 'bootstrap', '--repository', 'test.borg', 'list')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_archive_with_extract():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--config', 'myconfig', 'extract', '--archive', 'test')
|
||||
module.parse_arguments({}, '--config', 'myconfig', 'extract', '--archive', 'test')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_archive_with_mount():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments(
|
||||
'--config', 'myconfig', 'mount', '--archive', 'test', '--mount-point', '/mnt'
|
||||
{}, '--config', 'myconfig', 'mount', '--archive', 'test', '--mount-point', '/mnt'
|
||||
)
|
||||
|
||||
|
||||
def test_parse_arguments_allows_archive_with_restore():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--config', 'myconfig', 'restore', '--archive', 'test')
|
||||
module.parse_arguments({}, '--config', 'myconfig', 'restore', '--archive', 'test')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_archive_with_list():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--config', 'myconfig', 'list', '--archive', 'test')
|
||||
module.parse_arguments({}, '--config', 'myconfig', 'list', '--archive', 'test')
|
||||
|
||||
|
||||
def test_parse_arguments_requires_archive_with_extract():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', 'extract')
|
||||
module.parse_arguments({}, '--config', 'myconfig', 'extract')
|
||||
|
||||
|
||||
def test_parse_arguments_requires_archive_with_restore():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', 'restore')
|
||||
module.parse_arguments({}, '--config', 'myconfig', 'restore')
|
||||
|
||||
|
||||
def test_parse_arguments_requires_mount_point_with_mount():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', 'mount', '--archive', 'test')
|
||||
module.parse_arguments({}, '--config', 'myconfig', 'mount', '--archive', 'test')
|
||||
|
||||
|
||||
def test_parse_arguments_requires_mount_point_with_umount():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', 'umount')
|
||||
module.parse_arguments({}, '--config', 'myconfig', 'umount')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_progress_before_create():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--progress', 'create', 'list')
|
||||
module.parse_arguments({}, '--progress', 'create', 'list')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_progress_after_create():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('create', '--progress', 'list')
|
||||
module.parse_arguments({}, 'create', '--progress', 'list')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_progress_and_extract():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--progress', 'extract', '--archive', 'test', 'list')
|
||||
module.parse_arguments({}, '--progress', 'extract', '--archive', 'test', 'list')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_progress_without_create():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--progress', 'list')
|
||||
module.parse_arguments({}, '--progress', 'list')
|
||||
|
||||
|
||||
def test_parse_arguments_with_stats_and_create_flags_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--stats', 'create', 'list')
|
||||
module.parse_arguments({}, '--stats', 'create', 'list')
|
||||
|
||||
|
||||
def test_parse_arguments_with_stats_and_prune_flags_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--stats', 'prune', 'list')
|
||||
module.parse_arguments({}, '--stats', 'prune', 'list')
|
||||
|
||||
|
||||
def test_parse_arguments_with_stats_flag_but_no_create_or_prune_flag_raises_value_error():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--stats', 'list')
|
||||
module.parse_arguments({}, '--stats', 'list')
|
||||
|
||||
|
||||
def test_parse_arguments_with_list_and_create_flags_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--list', 'create')
|
||||
module.parse_arguments({}, '--list', 'create')
|
||||
|
||||
|
||||
def test_parse_arguments_with_list_and_prune_flags_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--list', 'prune')
|
||||
module.parse_arguments({}, '--list', 'prune')
|
||||
|
||||
|
||||
def test_parse_arguments_with_list_flag_but_no_relevant_action_raises_value_error():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--list', 'repo-create')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_list_with_progress_for_create_action():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('create', '--list', '--progress')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_list_with_json_for_create_action():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('create', '--list', '--json')
|
||||
module.parse_arguments({}, '--list', 'repo-create')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_json_with_list_or_info():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('list', '--json')
|
||||
module.parse_arguments('info', '--json')
|
||||
module.parse_arguments({}, 'list', '--json')
|
||||
module.parse_arguments({}, 'info', '--json')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_json_with_both_list_and_info():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('list', 'info', '--json')
|
||||
module.parse_arguments({}, 'list', 'info', '--json')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_json_with_both_list_and_repo_info():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('list', 'repo-info', '--json')
|
||||
module.parse_arguments({}, 'list', 'repo-info', '--json')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_json_with_both_repo_info_and_info():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('repo-info', 'info', '--json')
|
||||
|
||||
|
||||
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):
|
||||
module.parse_arguments(
|
||||
'transfer',
|
||||
'--source-repository',
|
||||
'source.borg',
|
||||
'--archive',
|
||||
'foo',
|
||||
'--match-archives',
|
||||
'sh:*bar',
|
||||
)
|
||||
module.parse_arguments({}, 'repo-info', 'info', '--json')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_list_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('list', '--prefix', 'foo', '--match-archives', 'sh:*bar')
|
||||
module.parse_arguments({}, 'list', '--prefix', 'foo', '--match-archives', 'sh:*bar')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_repo_list_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('repo-list', '--prefix', 'foo', '--match-archives', 'sh:*bar')
|
||||
module.parse_arguments({}, 'repo-list', '--prefix', 'foo', '--match-archives', 'sh:*bar')
|
||||
|
||||
|
||||
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', '--match-archives', 'sh:*bar')
|
||||
module.parse_arguments({}, 'info', '--archive', 'foo', '--match-archives', 'sh:*bar')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_info_with_both_archive_and_prefix():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('info', '--archive', 'foo', '--prefix', 'bar')
|
||||
module.parse_arguments({}, 'info', '--archive', 'foo', '--prefix', 'bar')
|
||||
|
||||
|
||||
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', '--match-archives', 'sh:*bar')
|
||||
module.parse_arguments({}, 'info', '--prefix', 'foo', '--match-archives', 'sh:*bar')
|
||||
|
||||
|
||||
def test_parse_arguments_check_only_extract_does_not_raise_extract_subparser_error():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('check', '--only', 'extract')
|
||||
module.parse_arguments({}, 'check', '--only', 'extract')
|
||||
|
||||
|
||||
def test_parse_arguments_extract_archive_check_does_not_raise_check_subparser_error():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('extract', '--archive', 'check')
|
||||
module.parse_arguments({}, 'extract', '--archive', 'check')
|
||||
|
||||
|
||||
def test_parse_arguments_extract_with_check_only_extract_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('extract', '--archive', 'name', 'check', '--only', 'extract')
|
||||
module.parse_arguments({}, 'extract', '--archive', 'name', 'check', '--only', 'extract')
|
||||
|
||||
|
||||
def test_parse_arguments_bootstrap_without_config_errors():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('bootstrap')
|
||||
module.parse_arguments({}, 'bootstrap')
|
||||
|
||||
|
||||
def test_parse_arguments_config_with_no_subaction_errors():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('config')
|
||||
module.parse_arguments({}, 'config')
|
||||
|
||||
|
||||
def test_parse_arguments_config_with_help_shows_config_help(capsys):
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit) as exit:
|
||||
module.parse_arguments('config', '--help')
|
||||
module.parse_arguments({}, 'config', '--help')
|
||||
|
||||
assert exit.value.code == 0
|
||||
captured = capsys.readouterr()
|
||||
@@ -582,7 +682,7 @@ def test_parse_arguments_config_with_subaction_but_missing_flags_errors():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit) as exit:
|
||||
module.parse_arguments('config', 'bootstrap')
|
||||
module.parse_arguments({}, 'config', 'bootstrap')
|
||||
|
||||
assert exit.value.code == 2
|
||||
|
||||
@@ -591,7 +691,7 @@ def test_parse_arguments_config_with_subaction_and_help_shows_subaction_help(cap
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit) as exit:
|
||||
module.parse_arguments('config', 'bootstrap', '--help')
|
||||
module.parse_arguments({}, 'config', 'bootstrap', '--help')
|
||||
|
||||
assert exit.value.code == 0
|
||||
captured = capsys.readouterr()
|
||||
@@ -601,26 +701,30 @@ def test_parse_arguments_config_with_subaction_and_help_shows_subaction_help(cap
|
||||
def test_parse_arguments_config_with_subaction_and_required_flags_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('config', 'bootstrap', '--repository', 'repo.borg')
|
||||
module.parse_arguments({}, 'config', 'bootstrap', '--repository', 'repo.borg')
|
||||
|
||||
|
||||
def test_parse_arguments_config_with_subaction_and_global_flags_at_start_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--verbosity', '1', 'config', 'bootstrap', '--repository', 'repo.borg')
|
||||
module.parse_arguments(
|
||||
{}, '--verbosity', '1', 'config', 'bootstrap', '--repository', 'repo.borg'
|
||||
)
|
||||
|
||||
|
||||
def test_parse_arguments_config_with_subaction_and_global_flags_at_end_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('config', 'bootstrap', '--repository', 'repo.borg', '--verbosity', '1')
|
||||
module.parse_arguments(
|
||||
{}, 'config', 'bootstrap', '--repository', 'repo.borg', '--verbosity', '1'
|
||||
)
|
||||
|
||||
|
||||
def test_parse_arguments_config_with_subaction_and_explicit_config_file_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments(
|
||||
'config', 'bootstrap', '--repository', 'repo.borg', '--config', 'test.yaml'
|
||||
{}, 'config', 'bootstrap', '--repository', 'repo.borg', '--config', 'test.yaml'
|
||||
)
|
||||
|
||||
|
||||
@@ -628,10 +732,23 @@ def test_parse_arguments_with_borg_action_and_dry_run_raises():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--dry-run', 'borg', 'list')
|
||||
module.parse_arguments({}, '--dry-run', 'borg', 'list')
|
||||
|
||||
|
||||
def test_parse_arguments_with_borg_action_and_no_dry_run_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('borg', 'list')
|
||||
module.parse_arguments({}, 'borg', 'list')
|
||||
|
||||
|
||||
def test_parse_arguments_with_argument_from_schema_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments(
|
||||
{
|
||||
'type': 'object',
|
||||
'properties': {'foo': {'type': 'object', 'properties': {'bar': {'type': 'integer'}}}},
|
||||
},
|
||||
'--foo.bar',
|
||||
'3',
|
||||
)
|
||||
|
||||
34
tests/integration/config/test_arguments.py
Normal file
34
tests/integration/config/test_arguments.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import pytest
|
||||
|
||||
from borgmatic.config import arguments as module
|
||||
|
||||
|
||||
def test_convert_value_type_passes_through_non_string_value():
|
||||
assert module.convert_value_type([1, 2], 'array') == [1, 2]
|
||||
|
||||
|
||||
def test_convert_value_type_passes_through_string_option_type():
|
||||
assert module.convert_value_type('foo', 'string') == 'foo'
|
||||
|
||||
|
||||
def test_convert_value_type_parses_array_option_type():
|
||||
assert module.convert_value_type('[foo, bar]', 'array') == ['foo', 'bar']
|
||||
|
||||
|
||||
def test_convert_value_type_with_array_option_type_and_no_array_raises():
|
||||
with pytest.raises(ValueError):
|
||||
module.convert_value_type('{foo, bar}', 'array')
|
||||
|
||||
|
||||
def test_convert_value_type_parses_object_option_type():
|
||||
assert module.convert_value_type('{foo: bar}', 'object') == {'foo': 'bar'}
|
||||
|
||||
|
||||
def test_convert_value_type_with_invalid_value_raises():
|
||||
with pytest.raises(ValueError):
|
||||
module.convert_value_type('{foo, bar', 'object')
|
||||
|
||||
|
||||
def test_convert_value_type_with_unknown_option_type_raises():
|
||||
with pytest.raises(ValueError):
|
||||
module.convert_value_type('{foo, bar}', 'thingy')
|
||||
@@ -21,9 +21,9 @@ def test_schema_to_sample_configuration_comments_out_non_default_options():
|
||||
'type': 'object',
|
||||
'properties': dict(
|
||||
[
|
||||
('field1', {'example': 'Example 1'}),
|
||||
('field2', {'example': 'Example 2'}),
|
||||
('source_directories', {'example': 'Example 3'}),
|
||||
('field1', {'type': 'string', 'example': 'Example 1'}),
|
||||
('field2', {'type': 'string', 'example': 'Example 2'}),
|
||||
('source_directories', {'type': 'string', 'example': 'Example 3'}),
|
||||
]
|
||||
),
|
||||
}
|
||||
@@ -47,9 +47,9 @@ def test_schema_to_sample_configuration_comments_out_non_source_config_options()
|
||||
'type': 'object',
|
||||
'properties': dict(
|
||||
[
|
||||
('field1', {'example': 'Example 1'}),
|
||||
('field2', {'example': 'Example 2'}),
|
||||
('field3', {'example': 'Example 3'}),
|
||||
('field1', {'type': 'string', 'example': 'Example 1'}),
|
||||
('field2', {'type': 'string', 'example': 'Example 2'}),
|
||||
('field3', {'type': 'string', 'example': 'Example 3'}),
|
||||
]
|
||||
),
|
||||
}
|
||||
@@ -76,9 +76,9 @@ def test_schema_to_sample_configuration_comments_out_non_default_options_in_sequ
|
||||
'type': 'object',
|
||||
'properties': dict(
|
||||
[
|
||||
('field1', {'example': 'Example 1'}),
|
||||
('field2', {'example': 'Example 2'}),
|
||||
('source_directories', {'example': 'Example 3'}),
|
||||
('field1', {'type': 'string', 'example': 'Example 1'}),
|
||||
('field2', {'type': 'string', 'example': 'Example 2'}),
|
||||
('source_directories', {'type': 'string', 'example': 'Example 3'}),
|
||||
]
|
||||
),
|
||||
},
|
||||
@@ -105,9 +105,9 @@ def test_schema_to_sample_configuration_comments_out_non_source_config_options_i
|
||||
'type': 'object',
|
||||
'properties': dict(
|
||||
[
|
||||
('field1', {'example': 'Example 1'}),
|
||||
('field2', {'example': 'Example 2'}),
|
||||
('field3', {'example': 'Example 3'}),
|
||||
('field1', {'type': 'string', 'example': 'Example 1'}),
|
||||
('field2', {'type': 'string', 'example': 'Example 2'}),
|
||||
('field3', {'type': 'string', 'example': 'Example 3'}),
|
||||
]
|
||||
),
|
||||
},
|
||||
|
||||
@@ -58,7 +58,9 @@ def test_parse_configuration_transforms_file_into_mapping():
|
||||
'''
|
||||
)
|
||||
|
||||
config, config_paths, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
||||
config, config_paths, logs = module.parse_configuration(
|
||||
'/tmp/config.yaml', '/tmp/schema.yaml', arguments={'global': flexmock()}
|
||||
)
|
||||
|
||||
assert config == {
|
||||
'source_directories': ['/home', '/etc'],
|
||||
@@ -86,7 +88,9 @@ def test_parse_configuration_passes_through_quoted_punctuation():
|
||||
'''
|
||||
)
|
||||
|
||||
config, config_paths, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
||||
config, config_paths, logs = module.parse_configuration(
|
||||
'/tmp/config.yaml', '/tmp/schema.yaml', arguments={'global': flexmock()}
|
||||
)
|
||||
|
||||
assert config == {
|
||||
'source_directories': [f'/home/{string.punctuation}'],
|
||||
@@ -119,7 +123,9 @@ def test_parse_configuration_with_schema_lacking_examples_does_not_raise():
|
||||
''',
|
||||
)
|
||||
|
||||
module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
||||
module.parse_configuration(
|
||||
'/tmp/config.yaml', '/tmp/schema.yaml', arguments={'global': flexmock()}
|
||||
)
|
||||
|
||||
|
||||
def test_parse_configuration_inlines_include_inside_deprecated_section():
|
||||
@@ -145,7 +151,9 @@ def test_parse_configuration_inlines_include_inside_deprecated_section():
|
||||
include_file.name = 'include.yaml'
|
||||
builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
|
||||
|
||||
config, config_paths, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
||||
config, config_paths, logs = module.parse_configuration(
|
||||
'/tmp/config.yaml', '/tmp/schema.yaml', arguments={'global': flexmock()}
|
||||
)
|
||||
|
||||
assert config == {
|
||||
'source_directories': ['/home'],
|
||||
@@ -181,7 +189,9 @@ def test_parse_configuration_merges_include():
|
||||
include_file.name = 'include.yaml'
|
||||
builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
|
||||
|
||||
config, config_paths, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
||||
config, config_paths, logs = module.parse_configuration(
|
||||
'/tmp/config.yaml', '/tmp/schema.yaml', arguments={'global': flexmock()}
|
||||
)
|
||||
|
||||
assert config == {
|
||||
'source_directories': ['/home'],
|
||||
@@ -196,7 +206,9 @@ def test_parse_configuration_merges_include():
|
||||
|
||||
def test_parse_configuration_raises_for_missing_config_file():
|
||||
with pytest.raises(FileNotFoundError):
|
||||
module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
||||
module.parse_configuration(
|
||||
'/tmp/config.yaml', '/tmp/schema.yaml', arguments={'global': flexmock()}
|
||||
)
|
||||
|
||||
|
||||
def test_parse_configuration_raises_for_missing_schema_file():
|
||||
@@ -208,14 +220,18 @@ def test_parse_configuration_raises_for_missing_schema_file():
|
||||
builtins.should_receive('open').with_args('/tmp/schema.yaml').and_raise(FileNotFoundError)
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
||||
module.parse_configuration(
|
||||
'/tmp/config.yaml', '/tmp/schema.yaml', arguments={'global': flexmock()}
|
||||
)
|
||||
|
||||
|
||||
def test_parse_configuration_raises_for_syntax_error():
|
||||
mock_config_and_schema('foo:\nbar')
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
||||
module.parse_configuration(
|
||||
'/tmp/config.yaml', '/tmp/schema.yaml', arguments={'global': flexmock()}
|
||||
)
|
||||
|
||||
|
||||
def test_parse_configuration_raises_for_validation_error():
|
||||
@@ -228,7 +244,9 @@ def test_parse_configuration_raises_for_validation_error():
|
||||
)
|
||||
|
||||
with pytest.raises(module.Validation_error):
|
||||
module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
||||
module.parse_configuration(
|
||||
'/tmp/config.yaml', '/tmp/schema.yaml', arguments={'global': flexmock()}
|
||||
)
|
||||
|
||||
|
||||
def test_parse_configuration_applies_overrides():
|
||||
@@ -245,7 +263,10 @@ def test_parse_configuration_applies_overrides():
|
||||
)
|
||||
|
||||
config, config_paths, logs = module.parse_configuration(
|
||||
'/tmp/config.yaml', '/tmp/schema.yaml', overrides=['local_path=borg2']
|
||||
'/tmp/config.yaml',
|
||||
'/tmp/schema.yaml',
|
||||
arguments={'global': flexmock()},
|
||||
overrides=['local_path=borg2'],
|
||||
)
|
||||
|
||||
assert config == {
|
||||
@@ -273,7 +294,9 @@ def test_parse_configuration_applies_normalization_after_environment_variable_in
|
||||
)
|
||||
flexmock(os).should_receive('getenv').replace_with(lambda variable_name, default: default)
|
||||
|
||||
config, config_paths, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
||||
config, config_paths, logs = module.parse_configuration(
|
||||
'/tmp/config.yaml', '/tmp/schema.yaml', arguments={'global': flexmock()}
|
||||
)
|
||||
|
||||
assert config == {
|
||||
'source_directories': ['/home'],
|
||||
|
||||
@@ -105,7 +105,7 @@ def test_get_config_paths_translates_ssh_command_argument_to_config():
|
||||
flexmock(module.borgmatic.config.paths).should_receive(
|
||||
'get_borgmatic_source_directory'
|
||||
).and_return('/source')
|
||||
config = flexmock()
|
||||
config = {}
|
||||
flexmock(module).should_receive('make_bootstrap_config').and_return(config)
|
||||
bootstrap_arguments = flexmock(
|
||||
repository='repo',
|
||||
@@ -267,11 +267,11 @@ def test_run_bootstrap_does_not_raise():
|
||||
archive='archive',
|
||||
destination='dest',
|
||||
strip_components=1,
|
||||
progress=False,
|
||||
user_runtime_directory='/borgmatic',
|
||||
ssh_command=None,
|
||||
local_path='borg7',
|
||||
remote_path='borg8',
|
||||
progress=None,
|
||||
)
|
||||
global_arguments = flexmock(
|
||||
dry_run=False,
|
||||
@@ -299,7 +299,7 @@ def test_run_bootstrap_does_not_raise():
|
||||
|
||||
|
||||
def test_run_bootstrap_translates_ssh_command_argument_to_config():
|
||||
config = flexmock()
|
||||
config = {}
|
||||
flexmock(module).should_receive('make_bootstrap_config').and_return(config)
|
||||
flexmock(module).should_receive('get_config_paths').and_return(['/borgmatic/config.yaml'])
|
||||
bootstrap_arguments = flexmock(
|
||||
@@ -307,11 +307,11 @@ def test_run_bootstrap_translates_ssh_command_argument_to_config():
|
||||
archive='archive',
|
||||
destination='dest',
|
||||
strip_components=1,
|
||||
progress=False,
|
||||
user_runtime_directory='/borgmatic',
|
||||
ssh_command='ssh -i key',
|
||||
local_path='borg7',
|
||||
remote_path='borg8',
|
||||
progress=None,
|
||||
)
|
||||
global_arguments = flexmock(
|
||||
dry_run=False,
|
||||
@@ -333,13 +333,12 @@ def test_run_bootstrap_translates_ssh_command_argument_to_config():
|
||||
'repo',
|
||||
'archive',
|
||||
object,
|
||||
config,
|
||||
{'progress': False},
|
||||
object,
|
||||
object,
|
||||
extract_to_stdout=False,
|
||||
destination_path='dest',
|
||||
strip_components=1,
|
||||
progress=False,
|
||||
local_path='borg7',
|
||||
remote_path='borg8',
|
||||
).and_return(extract_process).once()
|
||||
|
||||
@@ -577,7 +577,6 @@ def test_collect_spot_check_source_paths_parses_borg_output():
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
local_path=object,
|
||||
remote_path=object,
|
||||
list_files=True,
|
||||
stream_processes=True,
|
||||
).and_return((('borg', 'create'), ('repo::archive',), flexmock()))
|
||||
flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
|
||||
@@ -625,7 +624,6 @@ def test_collect_spot_check_source_paths_passes_through_stream_processes_false()
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
local_path=object,
|
||||
remote_path=object,
|
||||
list_files=True,
|
||||
stream_processes=False,
|
||||
).and_return((('borg', 'create'), ('repo::archive',), flexmock()))
|
||||
flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
|
||||
@@ -673,7 +671,6 @@ def test_collect_spot_check_source_paths_without_working_directory_parses_borg_o
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
local_path=object,
|
||||
remote_path=object,
|
||||
list_files=True,
|
||||
stream_processes=True,
|
||||
).and_return((('borg', 'create'), ('repo::archive',), flexmock()))
|
||||
flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
|
||||
@@ -721,7 +718,6 @@ def test_collect_spot_check_source_paths_skips_directories():
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
local_path=object,
|
||||
remote_path=object,
|
||||
list_files=True,
|
||||
stream_processes=True,
|
||||
).and_return((('borg', 'create'), ('repo::archive',), flexmock()))
|
||||
flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
|
||||
@@ -860,14 +856,13 @@ def test_collect_spot_check_source_paths_uses_working_directory():
|
||||
flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
|
||||
dry_run=True,
|
||||
repository_path='repo',
|
||||
config=object,
|
||||
config={'working_directory': '/working/dir', 'list_details': True},
|
||||
patterns=[Pattern('foo'), Pattern('bar')],
|
||||
local_borg_version=object,
|
||||
global_arguments=object,
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
local_path=object,
|
||||
remote_path=object,
|
||||
list_files=True,
|
||||
stream_processes=True,
|
||||
).and_return((('borg', 'create'), ('repo::archive',), flexmock()))
|
||||
flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
|
||||
|
||||
@@ -9,7 +9,10 @@ def test_compact_actions_calls_hooks_for_configured_repository():
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
|
||||
flexmock(module.borgmatic.borg.compact).should_receive('compact_segments').once()
|
||||
compact_arguments = flexmock(
|
||||
repository=None, progress=flexmock(), cleanup_commits=flexmock(), threshold=flexmock()
|
||||
repository=None,
|
||||
progress=flexmock(),
|
||||
cleanup_commits=flexmock(),
|
||||
compact_threshold=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
@@ -34,7 +37,10 @@ def test_compact_runs_with_selected_repository():
|
||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||
flexmock(module.borgmatic.borg.compact).should_receive('compact_segments').once()
|
||||
compact_arguments = flexmock(
|
||||
repository=flexmock(), progress=flexmock(), cleanup_commits=flexmock(), threshold=flexmock()
|
||||
repository=flexmock(),
|
||||
progress=flexmock(),
|
||||
cleanup_commits=flexmock(),
|
||||
compact_threshold=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
@@ -59,7 +65,10 @@ def test_compact_bails_if_repository_does_not_match():
|
||||
).once().and_return(False)
|
||||
flexmock(module.borgmatic.borg.compact).should_receive('compact_segments').never()
|
||||
compact_arguments = flexmock(
|
||||
repository=flexmock(), progress=flexmock(), cleanup_commits=flexmock(), threshold=flexmock()
|
||||
repository=flexmock(),
|
||||
progress=flexmock(),
|
||||
cleanup_commits=flexmock(),
|
||||
compact_threshold=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
|
||||
@@ -443,9 +443,9 @@ def test_run_create_executes_and_calls_hooks_for_configured_repository():
|
||||
create_arguments = flexmock(
|
||||
repository=None,
|
||||
progress=flexmock(),
|
||||
stats=flexmock(),
|
||||
statistics=flexmock(),
|
||||
json=False,
|
||||
list_files=flexmock(),
|
||||
list_details=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
@@ -484,9 +484,9 @@ def test_run_create_runs_with_selected_repository():
|
||||
create_arguments = flexmock(
|
||||
repository=flexmock(),
|
||||
progress=flexmock(),
|
||||
stats=flexmock(),
|
||||
statistics=flexmock(),
|
||||
json=False,
|
||||
list_files=flexmock(),
|
||||
list_details=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
@@ -516,9 +516,9 @@ def test_run_create_bails_if_repository_does_not_match():
|
||||
create_arguments = flexmock(
|
||||
repository=flexmock(),
|
||||
progress=flexmock(),
|
||||
stats=flexmock(),
|
||||
statistics=flexmock(),
|
||||
json=False,
|
||||
list_files=flexmock(),
|
||||
list_details=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
@@ -538,6 +538,72 @@ def test_run_create_bails_if_repository_does_not_match():
|
||||
)
|
||||
|
||||
|
||||
def test_run_create_with_both_list_and_json_errors():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive(
|
||||
'repositories_match'
|
||||
).once().and_return(True)
|
||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').never()
|
||||
flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
|
||||
create_arguments = flexmock(
|
||||
repository=flexmock(),
|
||||
progress=flexmock(),
|
||||
statistics=flexmock(),
|
||||
json=True,
|
||||
list_details=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
list(
|
||||
module.run_create(
|
||||
config_filename='test.yaml',
|
||||
repository={'path': 'repo'},
|
||||
config={'list_details': True},
|
||||
config_paths=['/tmp/test.yaml'],
|
||||
local_borg_version=None,
|
||||
create_arguments=create_arguments,
|
||||
global_arguments=global_arguments,
|
||||
dry_run_label='',
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_run_create_with_both_list_and_progress_errors():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive(
|
||||
'repositories_match'
|
||||
).once().and_return(True)
|
||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').never()
|
||||
flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
|
||||
create_arguments = flexmock(
|
||||
repository=flexmock(),
|
||||
progress=flexmock(),
|
||||
statistics=flexmock(),
|
||||
json=False,
|
||||
list_details=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
list(
|
||||
module.run_create(
|
||||
config_filename='test.yaml',
|
||||
repository={'path': 'repo'},
|
||||
config={'list_details': True, 'progress': True},
|
||||
config_paths=['/tmp/test.yaml'],
|
||||
local_borg_version=None,
|
||||
create_arguments=create_arguments,
|
||||
global_arguments=global_arguments,
|
||||
dry_run_label='',
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_run_create_produces_json():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive(
|
||||
@@ -561,9 +627,9 @@ def test_run_create_produces_json():
|
||||
create_arguments = flexmock(
|
||||
repository=flexmock(),
|
||||
progress=flexmock(),
|
||||
stats=flexmock(),
|
||||
statistics=flexmock(),
|
||||
json=True,
|
||||
list_files=flexmock(),
|
||||
list_details=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ def test_run_export_tar_does_not_raise():
|
||||
paths=flexmock(),
|
||||
destination=flexmock(),
|
||||
tar_filter=flexmock(),
|
||||
list_files=flexmock(),
|
||||
list_details=flexmock(),
|
||||
strip_components=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
@@ -27,3 +27,81 @@ def test_run_export_tar_does_not_raise():
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
|
||||
def test_run_export_tar_favors_flags_over_config():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||
flexmock(module.borgmatic.borg.export_tar).should_receive('export_tar_archive').with_args(
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
local_path=object,
|
||||
remote_path=object,
|
||||
tar_filter=object,
|
||||
strip_components=object,
|
||||
).once()
|
||||
export_tar_arguments = flexmock(
|
||||
repository=flexmock(),
|
||||
archive=flexmock(),
|
||||
paths=flexmock(),
|
||||
destination=flexmock(),
|
||||
tar_filter=flexmock(),
|
||||
list_details=False,
|
||||
strip_components=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
module.run_export_tar(
|
||||
repository={'path': 'repo'},
|
||||
config={'list_details': True},
|
||||
local_borg_version=None,
|
||||
export_tar_arguments=export_tar_arguments,
|
||||
global_arguments=global_arguments,
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
|
||||
def test_run_export_tar_defaults_to_config():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||
flexmock(module.borgmatic.borg.export_tar).should_receive('export_tar_archive').with_args(
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
local_path=object,
|
||||
remote_path=object,
|
||||
tar_filter=object,
|
||||
strip_components=object,
|
||||
).once()
|
||||
export_tar_arguments = flexmock(
|
||||
repository=flexmock(),
|
||||
archive=flexmock(),
|
||||
paths=flexmock(),
|
||||
destination=flexmock(),
|
||||
tar_filter=flexmock(),
|
||||
list_details=None,
|
||||
strip_components=flexmock(),
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
module.run_export_tar(
|
||||
repository={'path': 'repo'},
|
||||
config={'list_details': True},
|
||||
local_borg_version=None,
|
||||
export_tar_arguments=export_tar_arguments,
|
||||
global_arguments=global_arguments,
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
@@ -27,3 +27,79 @@ def test_run_extract_calls_hooks():
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
|
||||
def test_run_extract_favors_flags_over_config():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||
flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').with_args(
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
local_path=object,
|
||||
remote_path=object,
|
||||
destination_path=object,
|
||||
strip_components=object,
|
||||
).once()
|
||||
extract_arguments = flexmock(
|
||||
paths=flexmock(),
|
||||
progress=False,
|
||||
destination=flexmock(),
|
||||
strip_components=flexmock(),
|
||||
archive=flexmock(),
|
||||
repository='repo',
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
module.run_extract(
|
||||
config_filename='test.yaml',
|
||||
repository={'path': 'repo'},
|
||||
config={'repositories': ['repo'], 'progress': True},
|
||||
local_borg_version=None,
|
||||
extract_arguments=extract_arguments,
|
||||
global_arguments=global_arguments,
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
|
||||
def test_run_extract_defaults_to_config():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||
flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').with_args(
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
local_path=object,
|
||||
remote_path=object,
|
||||
destination_path=object,
|
||||
strip_components=object,
|
||||
).once()
|
||||
extract_arguments = flexmock(
|
||||
paths=flexmock(),
|
||||
progress=None,
|
||||
destination=flexmock(),
|
||||
strip_components=flexmock(),
|
||||
archive=flexmock(),
|
||||
repository='repo',
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
module.run_extract(
|
||||
config_filename='test.yaml',
|
||||
repository={'path': 'repo'},
|
||||
config={'repositories': ['repo'], 'progress': True},
|
||||
local_borg_version=None,
|
||||
extract_arguments=extract_arguments,
|
||||
global_arguments=global_arguments,
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ def test_run_prune_calls_hooks_for_configured_repository():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
|
||||
flexmock(module.borgmatic.borg.prune).should_receive('prune_archives').once()
|
||||
prune_arguments = flexmock(repository=None, stats=flexmock(), list_archives=flexmock())
|
||||
prune_arguments = flexmock(repository=None, statistics=flexmock(), list_details=flexmock())
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
module.run_prune(
|
||||
@@ -29,7 +29,9 @@ def test_run_prune_runs_with_selected_repository():
|
||||
'repositories_match'
|
||||
).once().and_return(True)
|
||||
flexmock(module.borgmatic.borg.prune).should_receive('prune_archives').once()
|
||||
prune_arguments = flexmock(repository=flexmock(), stats=flexmock(), list_archives=flexmock())
|
||||
prune_arguments = flexmock(
|
||||
repository=flexmock(), statistics=flexmock(), list_details=flexmock()
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
module.run_prune(
|
||||
@@ -51,7 +53,9 @@ def test_run_prune_bails_if_repository_does_not_match():
|
||||
'repositories_match'
|
||||
).once().and_return(False)
|
||||
flexmock(module.borgmatic.borg.prune).should_receive('prune_archives').never()
|
||||
prune_arguments = flexmock(repository=flexmock(), stats=flexmock(), list_archives=flexmock())
|
||||
prune_arguments = flexmock(
|
||||
repository=flexmock(), statistics=flexmock(), list_details=flexmock()
|
||||
)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
module.run_prune(
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.actions import repo_create as module
|
||||
|
||||
|
||||
def test_run_repo_create_does_not_raise():
|
||||
def test_run_repo_create_with_encryption_mode_argument_does_not_raise():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||
flexmock(module.borgmatic.borg.repo_create).should_receive('create_repository')
|
||||
@@ -14,7 +15,7 @@ def test_run_repo_create_does_not_raise():
|
||||
copy_crypt_key=flexmock(),
|
||||
append_only=flexmock(),
|
||||
storage_quota=flexmock(),
|
||||
make_parent_dirs=flexmock(),
|
||||
make_parent_directories=flexmock(),
|
||||
)
|
||||
|
||||
module.run_repo_create(
|
||||
@@ -28,6 +29,57 @@ def test_run_repo_create_does_not_raise():
|
||||
)
|
||||
|
||||
|
||||
def test_run_repo_create_with_encryption_mode_option_does_not_raise():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||
flexmock(module.borgmatic.borg.repo_create).should_receive('create_repository')
|
||||
arguments = flexmock(
|
||||
encryption_mode=None,
|
||||
source_repository=flexmock(),
|
||||
repository=flexmock(),
|
||||
copy_crypt_key=flexmock(),
|
||||
append_only=flexmock(),
|
||||
storage_quota=flexmock(),
|
||||
make_parent_directories=flexmock(),
|
||||
)
|
||||
|
||||
module.run_repo_create(
|
||||
repository={'path': 'repo', 'encryption': flexmock()},
|
||||
config={},
|
||||
local_borg_version=None,
|
||||
repo_create_arguments=arguments,
|
||||
global_arguments=flexmock(dry_run=False),
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
|
||||
def test_run_repo_create_without_encryption_mode_raises():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||
flexmock(module.borgmatic.borg.repo_create).should_receive('create_repository')
|
||||
arguments = flexmock(
|
||||
encryption_mode=None,
|
||||
source_repository=flexmock(),
|
||||
repository=flexmock(),
|
||||
copy_crypt_key=flexmock(),
|
||||
append_only=flexmock(),
|
||||
storage_quota=flexmock(),
|
||||
make_parent_directories=flexmock(),
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.run_repo_create(
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version=None,
|
||||
repo_create_arguments=arguments,
|
||||
global_arguments=flexmock(dry_run=False),
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
|
||||
def test_run_repo_create_bails_if_repository_does_not_match():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(
|
||||
@@ -41,7 +93,7 @@ def test_run_repo_create_bails_if_repository_does_not_match():
|
||||
copy_crypt_key=flexmock(),
|
||||
append_only=flexmock(),
|
||||
storage_quota=flexmock(),
|
||||
make_parent_dirs=flexmock(),
|
||||
make_parent_directories=flexmock(),
|
||||
)
|
||||
|
||||
module.run_repo_create(
|
||||
@@ -53,3 +105,91 @@ def test_run_repo_create_bails_if_repository_does_not_match():
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
|
||||
def test_run_repo_create_favors_flags_over_config():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||
flexmock(module.borgmatic.borg.repo_create).should_receive('create_repository').with_args(
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
append_only=False,
|
||||
storage_quota=0,
|
||||
make_parent_directories=False,
|
||||
local_path=object,
|
||||
remote_path=object,
|
||||
).once()
|
||||
arguments = flexmock(
|
||||
encryption_mode=flexmock(),
|
||||
source_repository=flexmock(),
|
||||
repository=flexmock(),
|
||||
copy_crypt_key=flexmock(),
|
||||
append_only=False,
|
||||
storage_quota=0,
|
||||
make_parent_directories=False,
|
||||
)
|
||||
|
||||
module.run_repo_create(
|
||||
repository={
|
||||
'path': 'repo',
|
||||
'append_only': True,
|
||||
'storage_quota': '10G',
|
||||
'make_parent_directories': True,
|
||||
},
|
||||
config={},
|
||||
local_borg_version=None,
|
||||
repo_create_arguments=arguments,
|
||||
global_arguments=flexmock(dry_run=False),
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
|
||||
def test_run_repo_create_defaults_to_config():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||
flexmock(module.borgmatic.borg.repo_create).should_receive('create_repository').with_args(
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
object,
|
||||
append_only=True,
|
||||
storage_quota='10G',
|
||||
make_parent_directories=True,
|
||||
local_path=object,
|
||||
remote_path=object,
|
||||
).once()
|
||||
arguments = flexmock(
|
||||
encryption_mode=flexmock(),
|
||||
source_repository=flexmock(),
|
||||
repository=flexmock(),
|
||||
copy_crypt_key=flexmock(),
|
||||
append_only=None,
|
||||
storage_quota=None,
|
||||
make_parent_directories=None,
|
||||
)
|
||||
|
||||
module.run_repo_create(
|
||||
repository={
|
||||
'path': 'repo',
|
||||
'append_only': True,
|
||||
'storage_quota': '10G',
|
||||
'make_parent_directories': True,
|
||||
},
|
||||
config={},
|
||||
local_borg_version=None,
|
||||
repo_create_arguments=arguments,
|
||||
global_arguments=flexmock(dry_run=False),
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.actions import transfer as module
|
||||
@@ -6,7 +7,7 @@ from borgmatic.actions import transfer as module
|
||||
def test_run_transfer_does_not_raise():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.borg.transfer).should_receive('transfer_archives')
|
||||
transfer_arguments = flexmock()
|
||||
transfer_arguments = flexmock(archive=None)
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
module.run_transfer(
|
||||
@@ -18,3 +19,21 @@ def test_run_transfer_does_not_raise():
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
|
||||
def test_run_transfer_with_archive_and_match_archives_raises():
|
||||
flexmock(module.logger).answer = lambda message: None
|
||||
flexmock(module.borgmatic.borg.transfer).should_receive('transfer_archives')
|
||||
transfer_arguments = flexmock(archive='foo')
|
||||
global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.run_transfer(
|
||||
repository={'path': 'repo'},
|
||||
config={'match_archives': 'foo*'},
|
||||
local_borg_version=None,
|
||||
transfer_arguments=transfer_arguments,
|
||||
global_arguments=global_arguments,
|
||||
local_path=None,
|
||||
remote_path=None,
|
||||
)
|
||||
|
||||
@@ -155,22 +155,6 @@ def test_make_archive_filter_flags_with_data_check_and_prefix_includes_match_arc
|
||||
assert flags == ('--match-archives', 'sh:foo-*')
|
||||
|
||||
|
||||
def test_make_archive_filter_flags_prefers_check_arguments_match_archives_to_config_match_archives():
|
||||
flexmock(module.feature).should_receive('available').and_return(True)
|
||||
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
|
||||
'baz-*', None, '1.2.3'
|
||||
).and_return(('--match-archives', 'sh:baz-*'))
|
||||
|
||||
flags = module.make_archive_filter_flags(
|
||||
'1.2.3',
|
||||
{'match_archives': 'bar-{now}', 'prefix': ''}, # noqa: FS003
|
||||
('archives',),
|
||||
check_arguments=flexmock(match_archives='baz-*'),
|
||||
)
|
||||
|
||||
assert flags == ('--match-archives', 'sh:baz-*')
|
||||
|
||||
|
||||
def test_make_archive_filter_flags_with_archives_check_and_empty_prefix_uses_archive_name_format_instead():
|
||||
flexmock(module.feature).should_receive('available').and_return(True)
|
||||
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
|
||||
@@ -332,7 +316,7 @@ def test_get_repository_id_with_missing_json_keys_raises():
|
||||
|
||||
|
||||
def test_check_archives_with_progress_passes_through_to_borg():
|
||||
config = {}
|
||||
config = {'progress': True}
|
||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||
{'repository'}, ()
|
||||
).and_return(())
|
||||
@@ -353,7 +337,7 @@ def test_check_archives_with_progress_passes_through_to_borg():
|
||||
config=config,
|
||||
local_borg_version='1.2.3',
|
||||
check_arguments=flexmock(
|
||||
progress=True,
|
||||
progress=None,
|
||||
repair=None,
|
||||
only_checks=None,
|
||||
force=None,
|
||||
|
||||
@@ -27,7 +27,7 @@ def insert_execute_command_mock(
|
||||
COMPACT_COMMAND = ('borg', 'compact')
|
||||
|
||||
|
||||
def test_compact_segments_calls_borg_with_parameters():
|
||||
def test_compact_segments_calls_borg_with_flags():
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('repo',), logging.INFO)
|
||||
|
||||
@@ -40,7 +40,7 @@ def test_compact_segments_calls_borg_with_parameters():
|
||||
)
|
||||
|
||||
|
||||
def test_compact_segments_with_log_info_calls_borg_with_info_parameter():
|
||||
def test_compact_segments_with_log_info_calls_borg_with_info_flag():
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--info', 'repo'), logging.INFO)
|
||||
insert_logging_mock(logging.INFO)
|
||||
@@ -54,7 +54,7 @@ def test_compact_segments_with_log_info_calls_borg_with_info_parameter():
|
||||
)
|
||||
|
||||
|
||||
def test_compact_segments_with_log_debug_calls_borg_with_debug_parameter():
|
||||
def test_compact_segments_with_log_debug_calls_borg_with_debug_flag():
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--debug', '--show-rc', 'repo'), logging.INFO)
|
||||
insert_logging_mock(logging.DEBUG)
|
||||
@@ -110,7 +110,7 @@ def test_compact_segments_with_exit_codes_calls_borg_using_them():
|
||||
)
|
||||
|
||||
|
||||
def test_compact_segments_with_remote_path_calls_borg_with_remote_path_parameters():
|
||||
def test_compact_segments_with_remote_path_calls_borg_with_remote_path_flags():
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--remote-path', 'borg1', 'repo'), logging.INFO)
|
||||
|
||||
@@ -124,21 +124,20 @@ def test_compact_segments_with_remote_path_calls_borg_with_remote_path_parameter
|
||||
)
|
||||
|
||||
|
||||
def test_compact_segments_with_progress_calls_borg_with_progress_parameter():
|
||||
def test_compact_segments_with_progress_calls_borg_with_progress_flag():
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--progress', 'repo'), logging.INFO)
|
||||
|
||||
module.compact_segments(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={},
|
||||
config={'progress': True},
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
progress=True,
|
||||
)
|
||||
|
||||
|
||||
def test_compact_segments_with_cleanup_commits_calls_borg_with_cleanup_commits_parameter():
|
||||
def test_compact_segments_with_cleanup_commits_calls_borg_with_cleanup_commits_flag():
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--cleanup-commits', 'repo'), logging.INFO)
|
||||
|
||||
@@ -152,21 +151,20 @@ def test_compact_segments_with_cleanup_commits_calls_borg_with_cleanup_commits_p
|
||||
)
|
||||
|
||||
|
||||
def test_compact_segments_with_threshold_calls_borg_with_threshold_parameter():
|
||||
def test_compact_segments_with_threshold_calls_borg_with_threshold_flag():
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--threshold', '20', 'repo'), logging.INFO)
|
||||
|
||||
module.compact_segments(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={},
|
||||
config={'compact_threshold': 20},
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
threshold=20,
|
||||
)
|
||||
|
||||
|
||||
def test_compact_segments_with_umask_calls_borg_with_umask_parameters():
|
||||
def test_compact_segments_with_umask_calls_borg_with_umask_flags():
|
||||
config = {'umask': '077'}
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--umask', '077', 'repo'), logging.INFO)
|
||||
@@ -180,7 +178,7 @@ def test_compact_segments_with_umask_calls_borg_with_umask_parameters():
|
||||
)
|
||||
|
||||
|
||||
def test_compact_segments_with_log_json_calls_borg_with_log_json_parameters():
|
||||
def test_compact_segments_with_log_json_calls_borg_with_log_json_flags():
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--log-json', 'repo'), logging.INFO)
|
||||
|
||||
@@ -193,7 +191,7 @@ def test_compact_segments_with_log_json_calls_borg_with_log_json_parameters():
|
||||
)
|
||||
|
||||
|
||||
def test_compact_segments_with_lock_wait_calls_borg_with_lock_wait_parameters():
|
||||
def test_compact_segments_with_lock_wait_calls_borg_with_lock_wait_flags():
|
||||
config = {'lock_wait': 5}
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||
insert_execute_command_mock(COMPACT_COMMAND + ('--lock-wait', '5', 'repo'), logging.INFO)
|
||||
|
||||
@@ -631,12 +631,12 @@ def test_make_base_create_command_includes_list_flags_in_borg_command():
|
||||
config={
|
||||
'source_directories': ['foo', 'bar'],
|
||||
'repositories': ['repo'],
|
||||
'list_details': True,
|
||||
},
|
||||
patterns=[Pattern('foo'), Pattern('bar')],
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
list_files=True,
|
||||
)
|
||||
|
||||
assert create_flags == ('borg', 'create', '--list', '--filter', 'FOO')
|
||||
@@ -962,7 +962,7 @@ def test_make_base_create_command_with_non_existent_directory_and_source_directo
|
||||
)
|
||||
|
||||
|
||||
def test_create_archive_calls_borg_with_parameters():
|
||||
def test_create_archive_calls_borg_with_flags():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module).should_receive('make_base_create_command').and_return(
|
||||
@@ -1029,7 +1029,7 @@ def test_create_archive_calls_borg_with_environment():
|
||||
)
|
||||
|
||||
|
||||
def test_create_archive_with_log_info_calls_borg_with_info_parameter():
|
||||
def test_create_archive_with_log_info_calls_borg_with_info_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module).should_receive('make_base_create_command').and_return(
|
||||
@@ -1096,7 +1096,7 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
|
||||
)
|
||||
|
||||
|
||||
def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
|
||||
def test_create_archive_with_log_debug_calls_borg_with_debug_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module).should_receive('make_base_create_command').and_return(
|
||||
@@ -1196,7 +1196,6 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats():
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
borgmatic_runtime_directory='/borgmatic/run',
|
||||
stats=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -1271,7 +1270,7 @@ def test_create_archive_with_exit_codes_calls_borg_using_them():
|
||||
)
|
||||
|
||||
|
||||
def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_answer_output_log_level():
|
||||
def test_create_archive_with_stats_calls_borg_with_stats_flag_and_answer_output_log_level():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module).should_receive('make_base_create_command').and_return(
|
||||
@@ -1296,12 +1295,12 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_answer_ou
|
||||
'source_directories': ['foo', 'bar'],
|
||||
'repositories': ['repo'],
|
||||
'exclude_patterns': None,
|
||||
'statistics': True,
|
||||
},
|
||||
patterns=[Pattern('foo'), Pattern('bar')],
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
borgmatic_runtime_directory='/borgmatic/run',
|
||||
stats=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -1334,16 +1333,16 @@ def test_create_archive_with_files_calls_borg_with_answer_output_log_level():
|
||||
'source_directories': ['foo', 'bar'],
|
||||
'repositories': ['repo'],
|
||||
'exclude_patterns': None,
|
||||
'list_details': True,
|
||||
},
|
||||
patterns=[Pattern('foo'), Pattern('bar')],
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
borgmatic_runtime_directory='/borgmatic/run',
|
||||
list_files=True,
|
||||
)
|
||||
|
||||
|
||||
def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_parameter_and_no_list():
|
||||
def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_flag_and_no_list():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module).should_receive('make_base_create_command').and_return(
|
||||
@@ -1369,16 +1368,16 @@ def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_para
|
||||
'source_directories': ['foo', 'bar'],
|
||||
'repositories': ['repo'],
|
||||
'exclude_patterns': None,
|
||||
'progress': True,
|
||||
},
|
||||
patterns=[Pattern('foo'), Pattern('bar')],
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
borgmatic_runtime_directory='/borgmatic/run',
|
||||
progress=True,
|
||||
)
|
||||
|
||||
|
||||
def test_create_archive_with_progress_calls_borg_with_progress_parameter():
|
||||
def test_create_archive_with_progress_calls_borg_with_progress_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module).should_receive('make_base_create_command').and_return(
|
||||
@@ -1403,16 +1402,16 @@ def test_create_archive_with_progress_calls_borg_with_progress_parameter():
|
||||
'source_directories': ['foo', 'bar'],
|
||||
'repositories': ['repo'],
|
||||
'exclude_patterns': None,
|
||||
'progress': True,
|
||||
},
|
||||
patterns=[Pattern('foo'), Pattern('bar')],
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
borgmatic_runtime_directory='/borgmatic/run',
|
||||
progress=True,
|
||||
)
|
||||
|
||||
|
||||
def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progress_parameter():
|
||||
def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progress_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
processes = flexmock()
|
||||
@@ -1459,12 +1458,12 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
|
||||
'source_directories': ['foo', 'bar'],
|
||||
'repositories': ['repo'],
|
||||
'exclude_patterns': None,
|
||||
'progress': True,
|
||||
},
|
||||
patterns=[Pattern('foo'), Pattern('bar')],
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
borgmatic_runtime_directory='/borgmatic/run',
|
||||
progress=True,
|
||||
stream_processes=processes,
|
||||
)
|
||||
|
||||
@@ -1532,7 +1531,6 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_flag():
|
||||
global_arguments=flexmock(log_json=False),
|
||||
borgmatic_runtime_directory='/borgmatic/run',
|
||||
json=True,
|
||||
stats=True,
|
||||
)
|
||||
|
||||
assert json_output == '[]'
|
||||
|
||||
@@ -21,7 +21,7 @@ def test_make_delete_command_includes_log_info():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(list_archives=False, force=0, match_archives=None, archive=None),
|
||||
delete_arguments=flexmock(list_details=False, force=0, match_archives=None, archive=None),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -43,7 +43,7 @@ def test_make_delete_command_includes_log_debug():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(list_archives=False, force=0, match_archives=None, archive=None),
|
||||
delete_arguments=flexmock(list_details=False, force=0, match_archives=None, archive=None),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -67,7 +67,7 @@ def test_make_delete_command_includes_dry_run():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(list_archives=False, force=0, match_archives=None, archive=None),
|
||||
delete_arguments=flexmock(list_details=False, force=0, match_archives=None, archive=None),
|
||||
global_arguments=flexmock(dry_run=True, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -91,7 +91,7 @@ def test_make_delete_command_includes_remote_path():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(list_archives=False, force=0, match_archives=None, archive=None),
|
||||
delete_arguments=flexmock(list_details=False, force=0, match_archives=None, archive=None),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path='borg1',
|
||||
@@ -114,7 +114,7 @@ def test_make_delete_command_includes_umask():
|
||||
repository={'path': 'repo'},
|
||||
config={'umask': '077'},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(list_archives=False, force=0, match_archives=None, archive=None),
|
||||
delete_arguments=flexmock(list_details=False, force=0, match_archives=None, archive=None),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -138,7 +138,7 @@ def test_make_delete_command_includes_log_json():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(list_archives=False, force=0, match_archives=None, archive=None),
|
||||
delete_arguments=flexmock(list_details=False, force=0, match_archives=None, archive=None),
|
||||
global_arguments=flexmock(dry_run=False, log_json=True),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -162,7 +162,7 @@ def test_make_delete_command_includes_lock_wait():
|
||||
repository={'path': 'repo'},
|
||||
config={'lock_wait': 5},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(list_archives=False, force=0, match_archives=None, archive=None),
|
||||
delete_arguments=flexmock(list_details=False, force=0, match_archives=None, archive=None),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -171,7 +171,7 @@ def test_make_delete_command_includes_lock_wait():
|
||||
assert command == ('borg', 'delete', '--lock-wait', '5', 'repo')
|
||||
|
||||
|
||||
def test_make_delete_command_includes_list():
|
||||
def test_make_delete_command_with_list_config_calls_borg_with_list_flag():
|
||||
flexmock(module.borgmatic.borg.flags).should_receive('make_flags').and_return(())
|
||||
flexmock(module.borgmatic.borg.flags).should_receive('make_flags').with_args(
|
||||
'list', True
|
||||
@@ -184,9 +184,9 @@ def test_make_delete_command_includes_list():
|
||||
|
||||
command = module.make_delete_command(
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
config={'list_details': True},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(list_archives=True, force=0, match_archives=None, archive=None),
|
||||
delete_arguments=flexmock(list_details=None, force=0, match_archives=None, archive=None),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -207,7 +207,7 @@ def test_make_delete_command_includes_force():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(list_archives=False, force=1, match_archives=None, archive=None),
|
||||
delete_arguments=flexmock(list_details=False, force=1, match_archives=None, archive=None),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -228,7 +228,7 @@ def test_make_delete_command_includes_force_twice():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(list_archives=False, force=2, match_archives=None, archive=None),
|
||||
delete_arguments=flexmock(list_details=False, force=2, match_archives=None, archive=None),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -252,7 +252,7 @@ def test_make_delete_command_includes_archive():
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(
|
||||
list_archives=False, force=0, match_archives=None, archive='archive'
|
||||
list_details=False, force=0, match_archives=None, archive='archive'
|
||||
),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
@@ -277,7 +277,7 @@ def test_make_delete_command_includes_match_archives():
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
delete_arguments=flexmock(
|
||||
list_archives=False, force=0, match_archives='sh:foo*', archive='archive'
|
||||
list_details=False, force=0, match_archives='sh:foo*', archive='archive'
|
||||
),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
@@ -287,8 +287,12 @@ def test_make_delete_command_includes_match_archives():
|
||||
assert command == ('borg', 'delete', '--match-archives', 'sh:foo*', 'repo')
|
||||
|
||||
|
||||
LOGGING_ANSWER = flexmock()
|
||||
|
||||
|
||||
def test_delete_archives_with_archive_calls_borg_delete():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = LOGGING_ANSWER
|
||||
flexmock(module.borgmatic.borg.repo_delete).should_receive('delete_repository').never()
|
||||
flexmock(module).should_receive('make_delete_command').and_return(flexmock())
|
||||
flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
|
||||
@@ -308,6 +312,7 @@ def test_delete_archives_with_archive_calls_borg_delete():
|
||||
|
||||
def test_delete_archives_with_match_archives_calls_borg_delete():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = LOGGING_ANSWER
|
||||
flexmock(module.borgmatic.borg.repo_delete).should_receive('delete_repository').never()
|
||||
flexmock(module).should_receive('make_delete_command').and_return(flexmock())
|
||||
flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
|
||||
@@ -328,6 +333,7 @@ def test_delete_archives_with_match_archives_calls_borg_delete():
|
||||
@pytest.mark.parametrize('argument_name', module.ARCHIVE_RELATED_ARGUMENT_NAMES[2:])
|
||||
def test_delete_archives_with_archive_related_argument_calls_borg_delete(argument_name):
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = LOGGING_ANSWER
|
||||
flexmock(module.borgmatic.borg.repo_delete).should_receive('delete_repository').never()
|
||||
flexmock(module).should_receive('make_delete_command').and_return(flexmock())
|
||||
flexmock(module.borgmatic.borg.environment).should_receive('make_environment').and_return(
|
||||
@@ -347,6 +353,7 @@ def test_delete_archives_with_archive_related_argument_calls_borg_delete(argumen
|
||||
|
||||
def test_delete_archives_without_archive_related_argument_calls_borg_repo_delete():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = LOGGING_ANSWER
|
||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||
flexmock(module.borgmatic.borg.repo_delete).should_receive('delete_repository').once()
|
||||
flexmock(module).should_receive('make_delete_command').never()
|
||||
@@ -359,7 +366,7 @@ def test_delete_archives_without_archive_related_argument_calls_borg_repo_delete
|
||||
config={},
|
||||
local_borg_version=flexmock(),
|
||||
delete_arguments=flexmock(
|
||||
list_archives=True, force=False, cache_only=False, keep_security_info=False
|
||||
list_details=True, force=False, cache_only=False, keep_security_info=False
|
||||
),
|
||||
global_arguments=flexmock(),
|
||||
)
|
||||
@@ -367,6 +374,7 @@ def test_delete_archives_without_archive_related_argument_calls_borg_repo_delete
|
||||
|
||||
def test_delete_archives_calls_borg_delete_with_working_directory():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = LOGGING_ANSWER
|
||||
flexmock(module.borgmatic.borg.repo_delete).should_receive('delete_repository').never()
|
||||
command = flexmock()
|
||||
flexmock(module).should_receive('make_delete_command').and_return(command)
|
||||
|
||||
@@ -144,7 +144,7 @@ def test_export_tar_archive_calls_borg_with_umask_flags():
|
||||
)
|
||||
|
||||
|
||||
def test_export_tar_archive_calls_borg_with_log_json_parameter():
|
||||
def test_export_tar_archive_calls_borg_with_log_json_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||
@@ -186,7 +186,7 @@ def test_export_tar_archive_calls_borg_with_lock_wait_flags():
|
||||
)
|
||||
|
||||
|
||||
def test_export_tar_archive_with_log_info_calls_borg_with_info_parameter():
|
||||
def test_export_tar_archive_with_log_info_calls_borg_with_info_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||
@@ -230,7 +230,7 @@ def test_export_tar_archive_with_log_debug_calls_borg_with_debug_flags():
|
||||
)
|
||||
|
||||
|
||||
def test_export_tar_archive_calls_borg_with_dry_run_parameter():
|
||||
def test_export_tar_archive_calls_borg_with_dry_run_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||
@@ -273,7 +273,7 @@ def test_export_tar_archive_calls_borg_with_tar_filter_flags():
|
||||
)
|
||||
|
||||
|
||||
def test_export_tar_archive_calls_borg_with_list_parameter():
|
||||
def test_export_tar_archive_calls_borg_with_list_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||
@@ -290,14 +290,13 @@ def test_export_tar_archive_calls_borg_with_list_parameter():
|
||||
archive='archive',
|
||||
paths=None,
|
||||
destination_path='test.tar',
|
||||
config={},
|
||||
config={'list_details': True},
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
list_files=True,
|
||||
)
|
||||
|
||||
|
||||
def test_export_tar_archive_calls_borg_with_strip_components_parameter():
|
||||
def test_export_tar_archive_calls_borg_with_strip_components_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||
@@ -320,7 +319,7 @@ def test_export_tar_archive_calls_borg_with_strip_components_parameter():
|
||||
)
|
||||
|
||||
|
||||
def test_export_tar_archive_skips_abspath_for_remote_repository_parameter():
|
||||
def test_export_tar_archive_skips_abspath_for_remote_repository_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||
|
||||
@@ -580,7 +580,7 @@ def test_extract_archive_with_strip_components_all_and_no_paths_raises():
|
||||
)
|
||||
|
||||
|
||||
def test_extract_archive_calls_borg_with_progress_parameter():
|
||||
def test_extract_archive_calls_borg_with_progress_flag():
|
||||
flexmock(module.os.path).should_receive('abspath').and_return('repo')
|
||||
flexmock(module.environment).should_receive('make_environment')
|
||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
@@ -606,10 +606,9 @@ def test_extract_archive_calls_borg_with_progress_parameter():
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
paths=None,
|
||||
config={},
|
||||
config={'progress': True},
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
progress=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -622,10 +621,9 @@ def test_extract_archive_with_progress_and_extract_to_stdout_raises():
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
paths=None,
|
||||
config={},
|
||||
config={'progress': True},
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
progress=True,
|
||||
extract_to_stdout=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ def test_make_info_command_with_match_archives_flag_passes_through_to_command():
|
||||
|
||||
command = module.make_info_command(
|
||||
repository_path='repo',
|
||||
config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
|
||||
config={'archive_name_format': 'bar-{now}', 'match_archives': 'sh:foo-*'}, # noqa: FS003
|
||||
local_borg_version='2.3.4',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
info_arguments=flexmock(archive=None, json=False, prefix=None, match_archives='sh:foo-*'),
|
||||
|
||||
@@ -135,32 +135,6 @@ def test_make_prune_flags_without_prefix_uses_archive_name_format_instead():
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_make_prune_flags_without_prefix_uses_match_archives_flag_instead_of_option():
|
||||
config = {
|
||||
'archive_name_format': 'bar-{now}', # noqa: FS003
|
||||
'match_archives': 'foo*',
|
||||
'keep_daily': 1,
|
||||
'prefix': None,
|
||||
}
|
||||
flexmock(module.feature).should_receive('available').and_return(True)
|
||||
flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
|
||||
'baz*', 'bar-{now}', '1.2.3' # noqa: FS003
|
||||
).and_return(('--match-archives', 'sh:bar-*')).once()
|
||||
|
||||
result = module.make_prune_flags(
|
||||
config, flexmock(match_archives='baz*'), local_borg_version='1.2.3'
|
||||
)
|
||||
|
||||
expected = (
|
||||
'--keep-daily',
|
||||
'1',
|
||||
'--match-archives',
|
||||
'sh:bar-*', # noqa: FS003
|
||||
)
|
||||
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_make_prune_flags_without_prefix_uses_match_archives_option():
|
||||
config = {
|
||||
'archive_name_format': 'bar-{now}', # noqa: FS003
|
||||
@@ -215,7 +189,7 @@ def test_prune_archives_calls_borg_with_flags():
|
||||
).and_return(False)
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('repo',), logging.INFO)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
@@ -237,7 +211,7 @@ def test_prune_archives_with_log_info_calls_borg_with_info_flag():
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('--info', 'repo'), logging.INFO)
|
||||
insert_logging_mock(logging.INFO)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
repository_path='repo',
|
||||
config={},
|
||||
@@ -259,7 +233,7 @@ def test_prune_archives_with_log_debug_calls_borg_with_debug_flag():
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('--debug', '--show-rc', 'repo'), logging.INFO)
|
||||
insert_logging_mock(logging.DEBUG)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
repository_path='repo',
|
||||
config={},
|
||||
@@ -280,7 +254,7 @@ def test_prune_archives_with_dry_run_calls_borg_with_dry_run_flag():
|
||||
).and_return(False)
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('--dry-run', 'repo'), logging.INFO)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
repository_path='repo',
|
||||
config={},
|
||||
@@ -301,7 +275,7 @@ def test_prune_archives_with_local_path_calls_borg_via_local_path():
|
||||
).and_return(False)
|
||||
insert_execute_command_mock(('borg1',) + PRUNE_COMMAND[1:] + ('repo',), logging.INFO)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
@@ -328,7 +302,7 @@ def test_prune_archives_with_exit_codes_calls_borg_using_them():
|
||||
borg_exit_codes=borg_exit_codes,
|
||||
)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
@@ -349,7 +323,7 @@ def test_prune_archives_with_remote_path_calls_borg_with_remote_path_flags():
|
||||
).and_return(False)
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('--remote-path', 'borg1', 'repo'), logging.INFO)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
@@ -361,7 +335,7 @@ def test_prune_archives_with_remote_path_calls_borg_with_remote_path_flags():
|
||||
)
|
||||
|
||||
|
||||
def test_prune_archives_with_stats_calls_borg_with_stats_flag_and_answer_output_log_level():
|
||||
def test_prune_archives_with_stats_config_calls_borg_with_stats_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
|
||||
@@ -371,18 +345,18 @@ def test_prune_archives_with_stats_calls_borg_with_stats_flag_and_answer_output_
|
||||
).and_return(False)
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('--stats', 'repo'), module.borgmatic.logger.ANSWER)
|
||||
|
||||
prune_arguments = flexmock(stats=True, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=None, list_details=False)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={},
|
||||
config={'statistics': True},
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
prune_arguments=prune_arguments,
|
||||
)
|
||||
|
||||
|
||||
def test_prune_archives_with_files_calls_borg_with_list_flag_and_answer_output_log_level():
|
||||
def test_prune_archives_with_list_config_calls_borg_with_list_flag():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
|
||||
@@ -392,11 +366,11 @@ def test_prune_archives_with_files_calls_borg_with_list_flag_and_answer_output_l
|
||||
).and_return(False)
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('--list', 'repo'), module.borgmatic.logger.ANSWER)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=True)
|
||||
prune_arguments = flexmock(statistics=False, list_details=None)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={},
|
||||
config={'list_details': True},
|
||||
local_borg_version='1.2.3',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
prune_arguments=prune_arguments,
|
||||
@@ -414,7 +388,7 @@ def test_prune_archives_with_umask_calls_borg_with_umask_flags():
|
||||
).and_return(False)
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('--umask', '077', 'repo'), logging.INFO)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
@@ -435,7 +409,7 @@ def test_prune_archives_with_log_json_calls_borg_with_log_json_flag():
|
||||
).and_return(False)
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('--log-json', 'repo'), logging.INFO)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
@@ -457,7 +431,7 @@ def test_prune_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
|
||||
).and_return(False)
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('--lock-wait', '5', 'repo'), logging.INFO)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
@@ -478,7 +452,7 @@ def test_prune_archives_with_extra_borg_options_calls_borg_with_extra_options():
|
||||
).and_return(False)
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('--extra', '--options', 'repo'), logging.INFO)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
@@ -546,7 +520,7 @@ def test_prune_archives_with_date_based_matching_calls_borg_with_date_based_flag
|
||||
)
|
||||
|
||||
prune_arguments = flexmock(
|
||||
stats=False, list_archives=False, newer='1d', newest='1y', older='1m', oldest='1w'
|
||||
statistics=False, list_details=False, newer='1d', newest='1y', older='1m', oldest='1w'
|
||||
)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
@@ -570,7 +544,7 @@ def test_prune_archives_calls_borg_with_working_directory():
|
||||
PRUNE_COMMAND + ('repo',), logging.INFO, working_directory='/working/dir'
|
||||
)
|
||||
|
||||
prune_arguments = flexmock(stats=False, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=False, list_details=False)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
@@ -581,7 +555,7 @@ def test_prune_archives_calls_borg_with_working_directory():
|
||||
)
|
||||
|
||||
|
||||
def test_prune_archives_calls_borg_with_flags_and_when_feature_available():
|
||||
def test_prune_archives_calls_borg_without_stats_when_feature_is_not_available():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
|
||||
@@ -591,11 +565,11 @@ def test_prune_archives_calls_borg_with_flags_and_when_feature_available():
|
||||
).and_return(True)
|
||||
insert_execute_command_mock(PRUNE_COMMAND + ('repo',), logging.ANSWER)
|
||||
|
||||
prune_arguments = flexmock(stats=True, list_archives=False)
|
||||
prune_arguments = flexmock(statistics=True, list_details=False)
|
||||
module.prune_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={},
|
||||
config={'statistics': True},
|
||||
local_borg_version='2.0.0b10',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
prune_arguments=prune_arguments,
|
||||
|
||||
@@ -267,7 +267,7 @@ def test_recreate_with_log_json():
|
||||
)
|
||||
|
||||
|
||||
def test_recreate_with_list_filter_flags():
|
||||
def test_recreate_with_list_config_calls_borg_with_list_flag():
|
||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||
@@ -288,10 +288,10 @@ def test_recreate_with_list_filter_flags():
|
||||
module.recreate_archive(
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
config={},
|
||||
config={'list_details': True},
|
||||
local_borg_version='1.2.3',
|
||||
recreate_arguments=flexmock(
|
||||
list=True,
|
||||
list=None,
|
||||
target=None,
|
||||
comment=None,
|
||||
timestamp=None,
|
||||
|
||||
@@ -228,7 +228,29 @@ def test_create_repository_with_append_only_calls_borg_with_append_only_flag():
|
||||
module.create_repository(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={},
|
||||
config={'append_only': True},
|
||||
local_borg_version='2.3.4',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
encryption_mode='repokey',
|
||||
append_only=True,
|
||||
)
|
||||
|
||||
|
||||
def test_create_repository_with_append_only_config_calls_borg_with_append_only_flag():
|
||||
insert_repo_info_command_not_found_mock()
|
||||
insert_repo_create_command_mock(REPO_CREATE_COMMAND + ('--append-only', '--repo', 'repo'))
|
||||
flexmock(module.feature).should_receive('available').and_return(True)
|
||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(
|
||||
(
|
||||
'--repo',
|
||||
'repo',
|
||||
)
|
||||
)
|
||||
|
||||
module.create_repository(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={'append_only': True},
|
||||
local_borg_version='2.3.4',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
encryption_mode='repokey',
|
||||
@@ -252,7 +274,7 @@ def test_create_repository_with_storage_quota_calls_borg_with_storage_quota_flag
|
||||
module.create_repository(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={},
|
||||
config={'storage_quota': '5G'},
|
||||
local_borg_version='2.3.4',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
encryption_mode='repokey',
|
||||
@@ -274,11 +296,11 @@ def test_create_repository_with_make_parent_dirs_calls_borg_with_make_parent_dir
|
||||
module.create_repository(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={},
|
||||
config={'make_parent_directories': True},
|
||||
local_borg_version='2.3.4',
|
||||
global_arguments=flexmock(log_json=False),
|
||||
encryption_mode='repokey',
|
||||
make_parent_dirs=True,
|
||||
make_parent_directories=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ def test_make_repo_delete_command_with_feature_available_runs_borg_repo_delete()
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=False, force=0),
|
||||
repo_delete_arguments=flexmock(list_details=False, force=0),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -40,7 +40,7 @@ def test_make_repo_delete_command_without_feature_available_runs_borg_delete():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=False, force=0),
|
||||
repo_delete_arguments=flexmock(list_details=False, force=0),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -62,7 +62,7 @@ def test_make_repo_delete_command_includes_log_info():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=False, force=0),
|
||||
repo_delete_arguments=flexmock(list_details=False, force=0),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -84,7 +84,7 @@ def test_make_repo_delete_command_includes_log_debug():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=False, force=0),
|
||||
repo_delete_arguments=flexmock(list_details=False, force=0),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -108,7 +108,7 @@ def test_make_repo_delete_command_includes_dry_run():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=False, force=0),
|
||||
repo_delete_arguments=flexmock(list_details=False, force=0),
|
||||
global_arguments=flexmock(dry_run=True, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -132,7 +132,7 @@ def test_make_repo_delete_command_includes_remote_path():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=False, force=0),
|
||||
repo_delete_arguments=flexmock(list_details=False, force=0),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path='borg1',
|
||||
@@ -155,7 +155,7 @@ def test_make_repo_delete_command_includes_umask():
|
||||
repository={'path': 'repo'},
|
||||
config={'umask': '077'},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=False, force=0),
|
||||
repo_delete_arguments=flexmock(list_details=False, force=0),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -179,7 +179,7 @@ def test_make_repo_delete_command_includes_log_json():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=False, force=0),
|
||||
repo_delete_arguments=flexmock(list_details=False, force=0),
|
||||
global_arguments=flexmock(dry_run=False, log_json=True),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -203,7 +203,7 @@ def test_make_repo_delete_command_includes_lock_wait():
|
||||
repository={'path': 'repo'},
|
||||
config={'lock_wait': 5},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=False, force=0),
|
||||
repo_delete_arguments=flexmock(list_details=False, force=0),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -225,9 +225,9 @@ def test_make_repo_delete_command_includes_list():
|
||||
|
||||
command = module.make_repo_delete_command(
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
config={'list_details': True},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=True, force=0),
|
||||
repo_delete_arguments=flexmock(list_details=True, force=0),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -248,7 +248,7 @@ def test_make_repo_delete_command_includes_force():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=False, force=1),
|
||||
repo_delete_arguments=flexmock(list_details=False, force=1),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
@@ -269,7 +269,7 @@ def test_make_repo_delete_command_includes_force_twice():
|
||||
repository={'path': 'repo'},
|
||||
config={},
|
||||
local_borg_version='1.2.3',
|
||||
repo_delete_arguments=flexmock(list_archives=False, force=2),
|
||||
repo_delete_arguments=flexmock(list_details=False, force=2),
|
||||
global_arguments=flexmock(dry_run=False, log_json=False),
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
|
||||
@@ -664,7 +664,7 @@ def test_make_repo_list_command_with_match_archives_calls_borg_with_match_archiv
|
||||
|
||||
command = module.make_repo_list_command(
|
||||
repository_path='repo',
|
||||
config={},
|
||||
config={'match_archives': 'foo-*'},
|
||||
local_borg_version='1.2.3',
|
||||
repo_list_arguments=flexmock(
|
||||
archive=None,
|
||||
|
||||
@@ -193,7 +193,7 @@ def test_transfer_archives_with_match_archives_calls_borg_with_match_archives_fl
|
||||
module.transfer_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={'archive_name_format': 'bar-{now}'}, # noqa: FS003
|
||||
config={'archive_name_format': 'bar-{now}', 'match_archives': 'sh:foo*'}, # noqa: FS003
|
||||
local_borg_version='2.3.4',
|
||||
transfer_arguments=flexmock(
|
||||
archive=None, progress=None, match_archives='sh:foo*', source_repository=None
|
||||
@@ -436,12 +436,15 @@ def test_transfer_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
|
||||
)
|
||||
|
||||
|
||||
def test_transfer_archives_with_progress_calls_borg_with_progress_flag():
|
||||
def test_transfer_archives_with_progress_calls_borg_with_progress_flags():
|
||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
||||
flexmock(module.flags).should_receive('make_flags').and_return(())
|
||||
flexmock(module.flags).should_receive('make_flags').with_args('progress', True).and_return(
|
||||
('--progress',)
|
||||
)
|
||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||
flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(('--progress',))
|
||||
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.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||
@@ -458,10 +461,10 @@ def test_transfer_archives_with_progress_calls_borg_with_progress_flag():
|
||||
module.transfer_archives(
|
||||
dry_run=False,
|
||||
repository_path='repo',
|
||||
config={},
|
||||
config={'progress': True},
|
||||
local_borg_version='2.3.4',
|
||||
transfer_arguments=flexmock(
|
||||
archive=None, progress=True, match_archives=None, source_repository=None
|
||||
archive=None, progress=None, match_archives=None, source_repository=None
|
||||
),
|
||||
global_arguments=flexmock(log_json=False),
|
||||
)
|
||||
|
||||
20
tests/unit/commands/completion/test_flag.py
Normal file
20
tests/unit/commands/completion/test_flag.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from borgmatic.commands.completion import flag as module
|
||||
|
||||
|
||||
def test_variants_passes_through_non_list_index_flag_name():
|
||||
assert tuple(module.variants('foo')) == ('foo',)
|
||||
|
||||
|
||||
def test_variants_broadcasts_list_index_flag_name_with_a_range_of_indices():
|
||||
assert tuple(module.variants('foo[0].bar')) == (
|
||||
'foo[0].bar',
|
||||
'foo[1].bar',
|
||||
'foo[2].bar',
|
||||
'foo[3].bar',
|
||||
'foo[4].bar',
|
||||
'foo[5].bar',
|
||||
'foo[6].bar',
|
||||
'foo[7].bar',
|
||||
'foo[8].bar',
|
||||
'foo[9].bar',
|
||||
)
|
||||
@@ -575,3 +575,755 @@ def test_parse_arguments_for_actions_raises_error_when_no_action_is_specified():
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments_for_actions(('config',), action_parsers, global_parser)
|
||||
|
||||
|
||||
def test_make_argument_description_with_object_adds_example():
|
||||
buffer = flexmock()
|
||||
buffer.should_receive('getvalue').and_return('{foo: example}')
|
||||
flexmock(module.io).should_receive('StringIO').and_return(buffer)
|
||||
yaml = flexmock()
|
||||
yaml.should_receive('dump')
|
||||
flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml)
|
||||
|
||||
assert (
|
||||
module.make_argument_description(
|
||||
schema={
|
||||
'description': 'Thing.',
|
||||
'type': 'object',
|
||||
'example': {'foo': 'example'},
|
||||
},
|
||||
flag_name='flag',
|
||||
)
|
||||
== 'Thing. Example value: "{foo: example}"'
|
||||
)
|
||||
|
||||
|
||||
def test_make_argument_description_without_description_and_with_object_sets_example():
|
||||
buffer = flexmock()
|
||||
buffer.should_receive('getvalue').and_return('{foo: example}')
|
||||
flexmock(module.io).should_receive('StringIO').and_return(buffer)
|
||||
yaml = flexmock()
|
||||
yaml.should_receive('dump')
|
||||
flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml)
|
||||
|
||||
assert (
|
||||
module.make_argument_description(
|
||||
schema={
|
||||
'type': 'object',
|
||||
'example': {'foo': 'example'},
|
||||
},
|
||||
flag_name='flag',
|
||||
)
|
||||
== 'Example value: "{foo: example}"'
|
||||
)
|
||||
|
||||
|
||||
def test_make_argument_description_with_object_skips_missing_example():
|
||||
flexmock(module.ruamel.yaml).should_receive('YAML').never()
|
||||
|
||||
assert (
|
||||
module.make_argument_description(
|
||||
schema={
|
||||
'description': 'Thing.',
|
||||
'type': 'object',
|
||||
},
|
||||
flag_name='flag',
|
||||
)
|
||||
== 'Thing.'
|
||||
)
|
||||
|
||||
|
||||
def test_make_argument_description_with_array_adds_example():
|
||||
buffer = flexmock()
|
||||
buffer.should_receive('getvalue').and_return('[example]')
|
||||
flexmock(module.io).should_receive('StringIO').and_return(buffer)
|
||||
yaml = flexmock()
|
||||
yaml.should_receive('dump')
|
||||
flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml)
|
||||
|
||||
assert (
|
||||
module.make_argument_description(
|
||||
schema={
|
||||
'description': 'Thing.',
|
||||
'type': 'array',
|
||||
'example': ['example'],
|
||||
},
|
||||
flag_name='flag',
|
||||
)
|
||||
== 'Thing. Example value: "[example]"'
|
||||
)
|
||||
|
||||
|
||||
def test_make_argument_description_without_description_and_with_array_sets_example():
|
||||
buffer = flexmock()
|
||||
buffer.should_receive('getvalue').and_return('[example]')
|
||||
flexmock(module.io).should_receive('StringIO').and_return(buffer)
|
||||
yaml = flexmock()
|
||||
yaml.should_receive('dump')
|
||||
flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml)
|
||||
|
||||
assert (
|
||||
module.make_argument_description(
|
||||
schema={
|
||||
'type': 'array',
|
||||
'example': ['example'],
|
||||
},
|
||||
flag_name='flag',
|
||||
)
|
||||
== 'Example value: "[example]"'
|
||||
)
|
||||
|
||||
|
||||
def test_make_argument_description_with_array_skips_missing_example():
|
||||
flexmock(module.ruamel.yaml).should_receive('YAML').never()
|
||||
|
||||
assert (
|
||||
module.make_argument_description(
|
||||
schema={
|
||||
'description': 'Thing.',
|
||||
'type': 'array',
|
||||
},
|
||||
flag_name='flag',
|
||||
)
|
||||
== 'Thing.'
|
||||
)
|
||||
|
||||
|
||||
def test_make_argument_description_with_array_index_in_flag_name_adds_to_description():
|
||||
assert 'list element' in module.make_argument_description(
|
||||
schema={
|
||||
'description': 'Thing.',
|
||||
'type': 'something',
|
||||
},
|
||||
flag_name='flag[0]',
|
||||
)
|
||||
|
||||
|
||||
def test_make_argument_description_without_description_and_with_array_index_in_flag_name_sets_description():
|
||||
assert 'list element' in module.make_argument_description(
|
||||
schema={
|
||||
'type': 'something',
|
||||
},
|
||||
flag_name='flag[0]',
|
||||
)
|
||||
|
||||
|
||||
def test_make_argument_description_escapes_percent_character():
|
||||
assert (
|
||||
module.make_argument_description(
|
||||
schema={
|
||||
'description': '% Thing.',
|
||||
'type': 'something',
|
||||
},
|
||||
flag_name='flag',
|
||||
)
|
||||
== '%% Thing.'
|
||||
)
|
||||
|
||||
|
||||
def test_add_array_element_arguments_without_array_index_bails():
|
||||
arguments_group = flexmock()
|
||||
arguments_group.should_receive('add_argument').never()
|
||||
|
||||
module.add_array_element_arguments(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=(),
|
||||
flag_name='foo',
|
||||
)
|
||||
|
||||
|
||||
def test_add_array_element_arguments_with_help_flag_bails():
|
||||
arguments_group = flexmock()
|
||||
arguments_group.should_receive('add_argument').never()
|
||||
|
||||
module.add_array_element_arguments(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=('--foo', '--help', '--bar'),
|
||||
flag_name='foo[0]',
|
||||
)
|
||||
|
||||
|
||||
def test_add_array_element_arguments_without_any_flags_bails():
|
||||
arguments_group = flexmock()
|
||||
arguments_group.should_receive('add_argument').never()
|
||||
|
||||
module.add_array_element_arguments(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=(),
|
||||
flag_name='foo[0]',
|
||||
)
|
||||
|
||||
|
||||
# Use this instead of a flexmock because it's not easy to check the type() of a flexmock instance.
|
||||
Group_action = collections.namedtuple(
|
||||
'Group_action',
|
||||
(
|
||||
'option_strings',
|
||||
'choices',
|
||||
'default',
|
||||
'nargs',
|
||||
'required',
|
||||
'type',
|
||||
),
|
||||
defaults=(
|
||||
flexmock(),
|
||||
flexmock(),
|
||||
flexmock(),
|
||||
flexmock(),
|
||||
flexmock(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_add_array_element_arguments_without_array_index_flags_bails():
|
||||
arguments_group = flexmock(
|
||||
_group_actions=(
|
||||
Group_action(
|
||||
option_strings=('--foo[0].val',),
|
||||
),
|
||||
),
|
||||
_registries={'action': {'store_stuff': Group_action}},
|
||||
)
|
||||
arguments_group.should_receive('add_argument').never()
|
||||
|
||||
module.add_array_element_arguments(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=('--foo', '--bar'),
|
||||
flag_name='foo[0].val',
|
||||
)
|
||||
|
||||
|
||||
def test_add_array_element_arguments_with_non_matching_array_index_flags_bails():
|
||||
arguments_group = flexmock(
|
||||
_group_actions=(
|
||||
Group_action(
|
||||
option_strings=('--foo[0].val',),
|
||||
),
|
||||
),
|
||||
_registries={'action': {'store_stuff': Group_action}},
|
||||
)
|
||||
arguments_group.should_receive('add_argument').never()
|
||||
|
||||
module.add_array_element_arguments(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=('--foo', '--bar[25].val', 'barval'),
|
||||
flag_name='foo[0].val',
|
||||
)
|
||||
|
||||
|
||||
def test_add_array_element_arguments_with_identical_array_index_flag_bails():
|
||||
arguments_group = flexmock(
|
||||
_group_actions=(
|
||||
Group_action(
|
||||
option_strings=('--foo[0].val',),
|
||||
),
|
||||
),
|
||||
_registries={'action': {'store_stuff': Group_action}},
|
||||
)
|
||||
arguments_group.should_receive('add_argument').never()
|
||||
|
||||
module.add_array_element_arguments(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=('--foo[0].val', 'fooval', '--bar'),
|
||||
flag_name='foo[0].val',
|
||||
)
|
||||
|
||||
|
||||
def test_add_array_element_arguments_without_action_type_in_registry_bails():
|
||||
arguments_group = flexmock(
|
||||
_group_actions=(
|
||||
Group_action(
|
||||
option_strings=('--foo[0].val',),
|
||||
choices=flexmock(),
|
||||
default=flexmock(),
|
||||
nargs=flexmock(),
|
||||
required=flexmock(),
|
||||
type=flexmock(),
|
||||
),
|
||||
),
|
||||
_registries={'action': {'store_stuff': bool}},
|
||||
)
|
||||
arguments_group.should_receive('add_argument').never()
|
||||
|
||||
module.add_array_element_arguments(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=('--foo[25].val', 'fooval', '--bar[1].val', 'barval'),
|
||||
flag_name='foo[0].val',
|
||||
)
|
||||
|
||||
|
||||
def test_add_array_element_arguments_adds_arguments_for_array_index_flags():
|
||||
arguments_group = flexmock(
|
||||
_group_actions=(
|
||||
Group_action(
|
||||
option_strings=('--foo[0].val',),
|
||||
choices=flexmock(),
|
||||
default=flexmock(),
|
||||
nargs=flexmock(),
|
||||
required=flexmock(),
|
||||
type=flexmock(),
|
||||
),
|
||||
),
|
||||
_registries={'action': {'store_stuff': Group_action}},
|
||||
)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo[25].val',
|
||||
action='store_stuff',
|
||||
choices=object,
|
||||
default=object,
|
||||
dest='foo[25].val',
|
||||
nargs=object,
|
||||
required=object,
|
||||
type=object,
|
||||
).once()
|
||||
|
||||
module.add_array_element_arguments(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=('--foo[25].val', 'fooval', '--bar[1].val', 'barval'),
|
||||
flag_name='foo[0].val',
|
||||
)
|
||||
|
||||
|
||||
def test_add_array_element_arguments_adds_arguments_for_array_index_flags_with_equals_sign():
|
||||
arguments_group = flexmock(
|
||||
_group_actions=(
|
||||
Group_action(
|
||||
option_strings=('--foo[0].val',),
|
||||
choices=flexmock(),
|
||||
default=flexmock(),
|
||||
nargs=flexmock(),
|
||||
required=flexmock(),
|
||||
type=flexmock(),
|
||||
),
|
||||
),
|
||||
_registries={'action': {'store_stuff': Group_action}},
|
||||
)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo[25].val',
|
||||
action='store_stuff',
|
||||
choices=object,
|
||||
default=object,
|
||||
dest='foo[25].val',
|
||||
nargs=object,
|
||||
required=object,
|
||||
type=object,
|
||||
).once()
|
||||
|
||||
module.add_array_element_arguments(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=('--foo[25].val=fooval', '--bar[1].val=barval'),
|
||||
flag_name='foo[0].val',
|
||||
)
|
||||
|
||||
|
||||
def test_add_array_element_arguments_adds_arguments_for_array_index_flags_with_dashes():
|
||||
arguments_group = flexmock(
|
||||
_group_actions=(
|
||||
Group_action(
|
||||
option_strings=('--foo[0].val-and-stuff',),
|
||||
choices=flexmock(),
|
||||
default=flexmock(),
|
||||
nargs=flexmock(),
|
||||
required=flexmock(),
|
||||
type=flexmock(),
|
||||
),
|
||||
),
|
||||
_registries={'action': {'store_stuff': Group_action}},
|
||||
)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo[25].val-and-stuff',
|
||||
action='store_stuff',
|
||||
choices=object,
|
||||
default=object,
|
||||
dest='foo[25].val_and_stuff',
|
||||
nargs=object,
|
||||
required=object,
|
||||
type=object,
|
||||
).once()
|
||||
|
||||
module.add_array_element_arguments(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=('--foo[25].val-and-stuff', 'fooval', '--bar[1].val', 'barval'),
|
||||
flag_name='foo[0].val-and-stuff',
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_with_non_dict_schema_bails():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').never()
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').never()
|
||||
arguments_group.should_receive('add_argument').never()
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group, schema='foo', unparsed_arguments=()
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_with_nested_object_adds_flag_for_each_option():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').and_return('help 1').and_return(
|
||||
'help 2'
|
||||
)
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(
|
||||
int
|
||||
).and_return(str)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo.bar',
|
||||
type=int,
|
||||
metavar='BAR',
|
||||
help='help 1',
|
||||
).once()
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo.baz',
|
||||
type=str,
|
||||
metavar='BAZ',
|
||||
help='help 2',
|
||||
).once()
|
||||
flexmock(module).should_receive('add_array_element_arguments')
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'bar': {'type': 'integer'},
|
||||
'baz': {'type': 'str'},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_uses_first_non_null_type_from_multi_type_object():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').and_return('help 1')
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(int)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo.bar',
|
||||
type=int,
|
||||
metavar='BAR',
|
||||
help='help 1',
|
||||
).once()
|
||||
flexmock(module).should_receive('add_array_element_arguments')
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {
|
||||
'type': ['null', 'object', 'boolean'],
|
||||
'properties': {
|
||||
'bar': {'type': 'integer'},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_with_empty_multi_type_raises():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').and_return('help 1')
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(int)
|
||||
arguments_group.should_receive('add_argument').never()
|
||||
flexmock(module).should_receive('add_array_element_arguments').never()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {
|
||||
'type': [],
|
||||
'properties': {
|
||||
'bar': {'type': 'integer'},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_with_propertyless_option_adds_flag():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').and_return('help')
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo',
|
||||
type=str,
|
||||
metavar='FOO',
|
||||
help='help',
|
||||
).once()
|
||||
flexmock(module).should_receive('add_array_element_arguments')
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {
|
||||
'type': 'object',
|
||||
}
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_with_array_of_scalars_adds_multiple_flags():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').and_return('help')
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').with_args(
|
||||
'integer', object=str, array=str
|
||||
).and_return(int)
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').with_args(
|
||||
'array', object=str, array=str
|
||||
).and_return(str)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo[0]',
|
||||
type=int,
|
||||
metavar='FOO[0]',
|
||||
help='help',
|
||||
).once()
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo',
|
||||
type=str,
|
||||
metavar='FOO',
|
||||
help='help',
|
||||
).once()
|
||||
flexmock(module).should_receive('add_array_element_arguments')
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'integer',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_with_array_of_objects_adds_multiple_flags():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').and_return('help 1').and_return(
|
||||
'help 2'
|
||||
)
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(
|
||||
int
|
||||
).and_return(str)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo[0].bar',
|
||||
type=int,
|
||||
metavar='BAR',
|
||||
help='help 1',
|
||||
).once()
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo',
|
||||
type=str,
|
||||
metavar='FOO',
|
||||
help='help 2',
|
||||
).once()
|
||||
flexmock(module).should_receive('add_array_element_arguments')
|
||||
flexmock(module).should_receive('add_array_element_arguments').with_args(
|
||||
arguments_group=arguments_group,
|
||||
unparsed_arguments=(),
|
||||
flag_name='foo[0].bar',
|
||||
).once()
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'bar': {
|
||||
'type': 'integer',
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_with_boolean_adds_two_valueless_flags():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').and_return('help')
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo',
|
||||
action='store_true',
|
||||
default=None,
|
||||
help='help',
|
||||
).once()
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--no-foo',
|
||||
dest='foo',
|
||||
action='store_false',
|
||||
default=None,
|
||||
help=object,
|
||||
).once()
|
||||
flexmock(module).should_receive('add_array_element_arguments')
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {
|
||||
'type': 'boolean',
|
||||
}
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_with_nested_boolean_adds_two_valueless_flags():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').and_return('help')
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo.bar.baz-quux',
|
||||
action='store_true',
|
||||
default=None,
|
||||
help='help',
|
||||
).once()
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo.bar.no-baz-quux',
|
||||
dest='foo.bar.baz_quux',
|
||||
action='store_false',
|
||||
default=None,
|
||||
help=object,
|
||||
).once()
|
||||
flexmock(module).should_receive('add_array_element_arguments')
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'baz_quux': {
|
||||
'type': 'boolean',
|
||||
}
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
names=('foo', 'bar'),
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_with_boolean_with_name_prefixed_with_no_adds_two_valueless_flags_and_removes_the_no_for_one():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').and_return('help')
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--no-foo',
|
||||
action='store_true',
|
||||
default=None,
|
||||
help='help',
|
||||
).once()
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo',
|
||||
dest='no_foo',
|
||||
action='store_false',
|
||||
default=None,
|
||||
help=object,
|
||||
).once()
|
||||
flexmock(module).should_receive('add_array_element_arguments')
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'no_foo': {
|
||||
'type': 'boolean',
|
||||
}
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_skips_omitted_flag_name():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').and_return('help')
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--match-archives',
|
||||
type=object,
|
||||
metavar=object,
|
||||
help=object,
|
||||
).never()
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo',
|
||||
type=str,
|
||||
metavar='FOO',
|
||||
help='help',
|
||||
).once()
|
||||
flexmock(module).should_receive('add_array_element_arguments')
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'match_archives': {
|
||||
'type': 'string',
|
||||
},
|
||||
'foo': {
|
||||
'type': 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
|
||||
def test_add_arguments_from_schema_rewrites_option_name_to_flag_name():
|
||||
arguments_group = flexmock()
|
||||
flexmock(module).should_receive('make_argument_description').and_return('help')
|
||||
flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
|
||||
arguments_group.should_receive('add_argument').with_args(
|
||||
'--foo-and-stuff',
|
||||
type=str,
|
||||
metavar='FOO_AND_STUFF',
|
||||
help='help',
|
||||
).once()
|
||||
flexmock(module).should_receive('add_array_element_arguments')
|
||||
|
||||
module.add_arguments_from_schema(
|
||||
arguments_group=arguments_group,
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo_and_stuff': {
|
||||
'type': 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
unparsed_arguments=(),
|
||||
)
|
||||
|
||||
@@ -1578,6 +1578,7 @@ def test_load_configurations_collects_parsed_configurations_and_logs(resolve_env
|
||||
configs, config_paths, logs = tuple(
|
||||
module.load_configurations(
|
||||
('test.yaml', 'other.yaml'),
|
||||
arguments=flexmock(),
|
||||
resolve_env=resolve_env,
|
||||
)
|
||||
)
|
||||
@@ -1590,7 +1591,9 @@ def test_load_configurations_collects_parsed_configurations_and_logs(resolve_env
|
||||
def test_load_configurations_logs_warning_for_permission_error():
|
||||
flexmock(module.validate).should_receive('parse_configuration').and_raise(PermissionError)
|
||||
|
||||
configs, config_paths, logs = tuple(module.load_configurations(('test.yaml',)))
|
||||
configs, config_paths, logs = tuple(
|
||||
module.load_configurations(('test.yaml',), arguments=flexmock())
|
||||
)
|
||||
|
||||
assert configs == {}
|
||||
assert config_paths == []
|
||||
@@ -1600,7 +1603,9 @@ def test_load_configurations_logs_warning_for_permission_error():
|
||||
def test_load_configurations_logs_critical_for_parse_error():
|
||||
flexmock(module.validate).should_receive('parse_configuration').and_raise(ValueError)
|
||||
|
||||
configs, config_paths, logs = tuple(module.load_configurations(('test.yaml',)))
|
||||
configs, config_paths, logs = tuple(
|
||||
module.load_configurations(('test.yaml',), arguments=flexmock())
|
||||
)
|
||||
|
||||
assert configs == {}
|
||||
assert config_paths == []
|
||||
|
||||
234
tests/unit/config/test_arguments.py
Normal file
234
tests/unit/config/test_arguments.py
Normal file
@@ -0,0 +1,234 @@
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.config import arguments as module
|
||||
|
||||
|
||||
def test_set_values_without_keys_bails():
|
||||
config = {'option': 'value'}
|
||||
module.set_values(config=config, keys=(), value=5)
|
||||
|
||||
assert config == {'option': 'value'}
|
||||
|
||||
|
||||
def test_set_values_with_keys_adds_them_to_config():
|
||||
config = {'option': 'value'}
|
||||
|
||||
module.set_values(config=config, keys=('foo', 'bar', 'baz'), value=5)
|
||||
|
||||
assert config == {'option': 'value', 'foo': {'bar': {'baz': 5}}}
|
||||
|
||||
|
||||
def test_set_values_with_one_existing_key_adds_others_to_config():
|
||||
config = {'foo': {'other': 'value'}}
|
||||
|
||||
module.set_values(config=config, keys=('foo', 'bar', 'baz'), value=5)
|
||||
|
||||
assert config == {'foo': {'other': 'value', 'bar': {'baz': 5}}}
|
||||
|
||||
|
||||
def test_set_values_with_two_existing_keys_adds_others_to_config():
|
||||
config = {'foo': {'bar': {'other': 'value'}}}
|
||||
|
||||
module.set_values(config=config, keys=('foo', 'bar', 'baz'), value=5)
|
||||
|
||||
assert config == {'foo': {'bar': {'other': 'value', 'baz': 5}}}
|
||||
|
||||
|
||||
def test_set_values_with_list_index_key_adds_it_to_config():
|
||||
config = {'foo': {'bar': [{'option': 'value'}, {'other': 'thing'}]}}
|
||||
|
||||
module.set_values(config=config, keys=('foo', 'bar[1]', 'baz'), value=5)
|
||||
|
||||
assert config == {'foo': {'bar': [{'option': 'value'}, {'other': 'thing', 'baz': 5}]}}
|
||||
|
||||
|
||||
def test_set_values_with_list_index_key_out_of_range_raises():
|
||||
config = {'foo': {'bar': [{'option': 'value'}]}}
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.set_values(config=config, keys=('foo', 'bar[1]', 'baz'), value=5)
|
||||
|
||||
|
||||
def test_set_values_with_final_list_index_key_out_of_range_raises():
|
||||
config = {'foo': {'bar': [{'option': 'value'}]}}
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.set_values(config=config, keys=('foo', 'bar[1]'), value=5)
|
||||
|
||||
|
||||
def test_set_values_with_list_index_key_missing_list_and_out_of_range_raises():
|
||||
config = {'other': 'value'}
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.set_values(config=config, keys=('foo', 'bar[1]', 'baz'), value=5)
|
||||
|
||||
|
||||
def test_set_values_with_final_list_index_key_adds_it_to_config():
|
||||
config = {'foo': {'bar': [1, 2]}}
|
||||
|
||||
module.set_values(config=config, keys=('foo', 'bar[1]'), value=5)
|
||||
|
||||
assert config == {'foo': {'bar': [1, 5]}}
|
||||
|
||||
|
||||
def test_type_for_option_with_option_finds_type():
|
||||
flexmock(module.borgmatic.config.schema).should_receive('get_properties').replace_with(
|
||||
lambda sub_schema: sub_schema['properties']
|
||||
)
|
||||
|
||||
assert (
|
||||
module.type_for_option(
|
||||
schema={'type': 'object', 'properties': {'foo': {'type': 'integer'}}},
|
||||
option_keys=('foo',),
|
||||
)
|
||||
== 'integer'
|
||||
)
|
||||
|
||||
|
||||
def test_type_for_option_with_nested_option_finds_type():
|
||||
flexmock(module.borgmatic.config.schema).should_receive('get_properties').replace_with(
|
||||
lambda sub_schema: sub_schema['properties']
|
||||
)
|
||||
|
||||
assert (
|
||||
module.type_for_option(
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {'type': 'object', 'properties': {'bar': {'type': 'boolean'}}}
|
||||
},
|
||||
},
|
||||
option_keys=('foo', 'bar'),
|
||||
)
|
||||
== 'boolean'
|
||||
)
|
||||
|
||||
|
||||
def test_type_for_option_with_missing_nested_option_finds_nothing():
|
||||
flexmock(module.borgmatic.config.schema).should_receive('get_properties').replace_with(
|
||||
lambda sub_schema: sub_schema['properties']
|
||||
)
|
||||
|
||||
assert (
|
||||
module.type_for_option(
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': {'type': 'object', 'properties': {'other': {'type': 'integer'}}}
|
||||
},
|
||||
},
|
||||
option_keys=('foo', 'bar'),
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_type_for_option_with_typeless_nested_option_finds_nothing():
|
||||
flexmock(module.borgmatic.config.schema).should_receive('get_properties').replace_with(
|
||||
lambda sub_schema: sub_schema['properties']
|
||||
)
|
||||
|
||||
assert (
|
||||
module.type_for_option(
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {'foo': {'type': 'object', 'properties': {'bar': {'example': 5}}}},
|
||||
},
|
||||
option_keys=('foo', 'bar'),
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_type_for_option_with_list_index_option_finds_type():
|
||||
flexmock(module.borgmatic.config.schema).should_receive('get_properties').replace_with(
|
||||
lambda sub_schema: sub_schema['properties']
|
||||
)
|
||||
|
||||
assert (
|
||||
module.type_for_option(
|
||||
schema={
|
||||
'type': 'object',
|
||||
'properties': {'foo': {'type': 'array', 'items': {'type': 'integer'}}},
|
||||
},
|
||||
option_keys=('foo[0]',),
|
||||
)
|
||||
== 'integer'
|
||||
)
|
||||
|
||||
|
||||
def test_type_for_option_with_nested_list_index_option_finds_type():
|
||||