mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-23 02:23:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd192a6909 | ||
|
|
7185146481 | ||
|
|
c15e6c5fe5 | ||
|
|
79c2b9df06 | ||
|
|
acd6772148 | ||
|
|
cd91dbd4f7 | ||
|
|
8fc4efff88 | ||
|
|
4bf3e906a1 | ||
|
|
0ca43ef67a | ||
|
|
603e055a39 | ||
|
|
75c04611dc | ||
|
|
881dc9b01e | ||
|
|
8c72e909a7 |
@@ -0,0 +1,2 @@
|
||||
.git
|
||||
.tox
|
||||
@@ -1,3 +1,7 @@
|
||||
1.3.9
|
||||
* #195: Switch to command-line actions as more traditional sub-commands, e.g. "borgmatic create",
|
||||
"borgmatic prune", etc. However, the classic dashed options like "--create" still work!
|
||||
|
||||
1.3.8
|
||||
* #191: Disable console color via "color" option in borgmatic configuration output section.
|
||||
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
import collections
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from borgmatic.config import collect
|
||||
|
||||
SUBPARSER_ALIASES = {
|
||||
'init': ['--init', '-I'],
|
||||
'prune': ['--prune', '-p'],
|
||||
'create': ['--create', '-C'],
|
||||
'check': ['--check', '-k'],
|
||||
'extract': ['--extract', '-x'],
|
||||
'list': ['--list', '-l'],
|
||||
'info': ['--info', '-i'],
|
||||
}
|
||||
|
||||
|
||||
def parse_subparser_arguments(unparsed_arguments, top_level_parser, subparsers):
|
||||
'''
|
||||
Given a sequence of arguments, a top-level parser (containing subparsers), and a subparsers
|
||||
object as returned by argparse.ArgumentParser().add_subparsers(), ask each subparser to parse
|
||||
its own arguments and the top-level parser to parse any remaining arguments.
|
||||
|
||||
Return the result as a dict mapping from subparser name (or "global") to a parsed namespace of
|
||||
arguments.
|
||||
'''
|
||||
arguments = collections.OrderedDict()
|
||||
remaining_arguments = list(unparsed_arguments)
|
||||
alias_to_subparser_name = {
|
||||
alias: subparser_name
|
||||
for subparser_name, aliases in SUBPARSER_ALIASES.items()
|
||||
for alias in aliases
|
||||
}
|
||||
|
||||
# Give each requested action's subparser a shot at parsing all arguments.
|
||||
for subparser_name, subparser in subparsers.choices.items():
|
||||
if subparser_name not in unparsed_arguments:
|
||||
continue
|
||||
|
||||
remaining_arguments.remove(subparser_name)
|
||||
canonical_name = alias_to_subparser_name.get(subparser_name, subparser_name)
|
||||
|
||||
parsed, remaining = subparser.parse_known_args(unparsed_arguments)
|
||||
arguments[canonical_name] = parsed
|
||||
|
||||
# If no actions are explicitly requested, assume defaults: prune, create, and check.
|
||||
if not arguments and '--help' not in unparsed_arguments and '-h' not in unparsed_arguments:
|
||||
for subparser_name in ('prune', 'create', 'check'):
|
||||
subparser = subparsers.choices[subparser_name]
|
||||
parsed, remaining = subparser.parse_known_args(unparsed_arguments)
|
||||
arguments[subparser_name] = parsed
|
||||
|
||||
# Then ask each subparser, one by one, to greedily consume arguments. Any arguments that remain
|
||||
# are global arguments.
|
||||
for subparser_name in arguments.keys():
|
||||
subparser = subparsers.choices[subparser_name]
|
||||
parsed, remaining_arguments = subparser.parse_known_args(remaining_arguments)
|
||||
|
||||
arguments['global'] = top_level_parser.parse_args(remaining_arguments)
|
||||
|
||||
return arguments
|
||||
|
||||
|
||||
def parse_arguments(*unparsed_arguments):
|
||||
'''
|
||||
Given command-line arguments with which this script was invoked, parse the arguments and return
|
||||
them as a dict mapping from subparser name (or "global") to an argparse.Namespace instance.
|
||||
'''
|
||||
config_paths = collect.get_default_config_paths()
|
||||
|
||||
global_parser = ArgumentParser(add_help=False)
|
||||
global_group = global_parser.add_argument_group('global arguments')
|
||||
|
||||
global_group.add_argument(
|
||||
'-c',
|
||||
'--config',
|
||||
nargs='*',
|
||||
dest='config_paths',
|
||||
default=config_paths,
|
||||
help='Configuration filenames or directories, defaults to: {}'.format(
|
||||
' '.join(config_paths)
|
||||
),
|
||||
)
|
||||
global_group.add_argument(
|
||||
'--excludes',
|
||||
dest='excludes_filename',
|
||||
help='Deprecated in favor of exclude_patterns within configuration',
|
||||
)
|
||||
global_group.add_argument(
|
||||
'-n',
|
||||
'--dry-run',
|
||||
dest='dry_run',
|
||||
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',
|
||||
type=int,
|
||||
choices=range(0, 3),
|
||||
default=0,
|
||||
help='Display verbose progress to the console (from none to lots: 0, 1, or 2)',
|
||||
)
|
||||
global_group.add_argument(
|
||||
'--syslog-verbosity',
|
||||
type=int,
|
||||
choices=range(0, 3),
|
||||
default=0,
|
||||
help='Display verbose progress to syslog (from none to lots: 0, 1, or 2)',
|
||||
)
|
||||
global_group.add_argument(
|
||||
'--version',
|
||||
dest='version',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Display installed version number of borgmatic and exit',
|
||||
)
|
||||
|
||||
top_level_parser = ArgumentParser(
|
||||
description='''
|
||||
A simple wrapper script for the Borg backup software that creates and prunes backups.
|
||||
If none of the action options are given, then borgmatic defaults to: prune, create, and
|
||||
check archives.
|
||||
''',
|
||||
parents=[global_parser],
|
||||
)
|
||||
|
||||
subparsers = top_level_parser.add_subparsers(
|
||||
title='actions',
|
||||
metavar='',
|
||||
help='Specify zero or more actions. Defaults to prune, create, and check. Use --help with action for details:',
|
||||
)
|
||||
init_parser = subparsers.add_parser(
|
||||
'init',
|
||||
aliases=SUBPARSER_ALIASES['init'],
|
||||
help='Initialize an empty Borg repository',
|
||||
description='Initialize an empty Borg repository',
|
||||
add_help=False,
|
||||
)
|
||||
init_group = init_parser.add_argument_group('init arguments')
|
||||
init_group.add_argument(
|
||||
'-e',
|
||||
'--encryption',
|
||||
dest='encryption_mode',
|
||||
help='Borg repository encryption mode',
|
||||
required=True,
|
||||
)
|
||||
init_group.add_argument(
|
||||
'--append-only',
|
||||
dest='append_only',
|
||||
action='store_true',
|
||||
help='Create an append-only repository',
|
||||
)
|
||||
init_group.add_argument(
|
||||
'--storage-quota',
|
||||
dest='storage_quota',
|
||||
help='Create a repository with a fixed storage quota',
|
||||
)
|
||||
init_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
|
||||
|
||||
prune_parser = subparsers.add_parser(
|
||||
'prune',
|
||||
aliases=SUBPARSER_ALIASES['prune'],
|
||||
help='Prune archives according to the retention policy',
|
||||
description='Prune archives according to the retention policy',
|
||||
add_help=False,
|
||||
)
|
||||
prune_group = prune_parser.add_argument_group('prune arguments')
|
||||
prune_group.add_argument(
|
||||
'--stats',
|
||||
dest='stats',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Display statistics of archive',
|
||||
)
|
||||
prune_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
|
||||
|
||||
create_parser = subparsers.add_parser(
|
||||
'create',
|
||||
aliases=SUBPARSER_ALIASES['create'],
|
||||
help='Create archives (actually perform backups)',
|
||||
description='Create archives (actually perform backups)',
|
||||
add_help=False,
|
||||
)
|
||||
create_group = create_parser.add_argument_group('create arguments')
|
||||
create_group.add_argument(
|
||||
'--progress',
|
||||
dest='progress',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Display progress for each file as it is processed',
|
||||
)
|
||||
create_group.add_argument(
|
||||
'--stats',
|
||||
dest='stats',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Display statistics of archive',
|
||||
)
|
||||
create_group.add_argument(
|
||||
'--json', dest='json', default=False, action='store_true', help='Output results as JSON'
|
||||
)
|
||||
create_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
|
||||
|
||||
check_parser = subparsers.add_parser(
|
||||
'check',
|
||||
aliases=SUBPARSER_ALIASES['check'],
|
||||
help='Check archives for consistency',
|
||||
description='Check archives for consistency',
|
||||
add_help=False,
|
||||
)
|
||||
check_group = check_parser.add_argument_group('check arguments')
|
||||
check_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
|
||||
|
||||
extract_parser = subparsers.add_parser(
|
||||
'extract',
|
||||
aliases=SUBPARSER_ALIASES['extract'],
|
||||
help='Extract a named archive to the current directory',
|
||||
description='Extract a named archive to the current directory',
|
||||
add_help=False,
|
||||
)
|
||||
extract_group = extract_parser.add_argument_group('extract arguments')
|
||||
extract_group.add_argument(
|
||||
'--repository',
|
||||
help='Path of repository to use, defaults to the configured repository if there is only one',
|
||||
)
|
||||
extract_group.add_argument('--archive', help='Name of archive to operate on', required=True)
|
||||
extract_group.add_argument(
|
||||
'--restore-path',
|
||||
nargs='+',
|
||||
dest='restore_paths',
|
||||
help='Paths to restore from archive, defaults to the entire archive',
|
||||
)
|
||||
extract_group.add_argument(
|
||||
'--progress',
|
||||
dest='progress',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Display progress for each file as it is processed',
|
||||
)
|
||||
extract_group.add_argument(
|
||||
'-h', '--help', action='help', help='Show this help message and exit'
|
||||
)
|
||||
|
||||
list_parser = subparsers.add_parser(
|
||||
'list',
|
||||
aliases=SUBPARSER_ALIASES['list'],
|
||||
help='List archives',
|
||||
description='List archives',
|
||||
add_help=False,
|
||||
)
|
||||
list_group = list_parser.add_argument_group('list arguments')
|
||||
list_group.add_argument(
|
||||
'--repository',
|
||||
help='Path of repository to use, defaults to the configured repository if there is only one',
|
||||
)
|
||||
list_group.add_argument('--archive', help='Name of archive to operate on')
|
||||
list_group.add_argument(
|
||||
'--json', dest='json', default=False, action='store_true', help='Output results as JSON'
|
||||
)
|
||||
list_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
|
||||
|
||||
info_parser = subparsers.add_parser(
|
||||
'info',
|
||||
aliases=SUBPARSER_ALIASES['info'],
|
||||
help='Display summary information on archives',
|
||||
description='Display summary information on archives',
|
||||
add_help=False,
|
||||
)
|
||||
info_group = info_parser.add_argument_group('info arguments')
|
||||
info_group.add_argument(
|
||||
'--json', dest='json', default=False, action='store_true', help='Output results as JSON'
|
||||
)
|
||||
info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
|
||||
|
||||
arguments = parse_subparser_arguments(unparsed_arguments, top_level_parser, subparsers)
|
||||
|
||||
if arguments['global'].excludes_filename:
|
||||
raise ValueError(
|
||||
'The --excludes option has been replaced with exclude_patterns in configuration'
|
||||
)
|
||||
|
||||
if 'init' in arguments and arguments['global'].dry_run:
|
||||
raise ValueError('The init action cannot be used with the --dry-run option')
|
||||
|
||||
if (
|
||||
'list' in arguments
|
||||
and 'info' in arguments
|
||||
and arguments['list'].json
|
||||
and arguments['info'].json
|
||||
):
|
||||
raise ValueError('With the --json option, list and info actions cannot be used together')
|
||||
|
||||
return arguments
|
||||
+83
-284
@@ -3,7 +3,6 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from subprocess import CalledProcessError
|
||||
|
||||
import colorama
|
||||
@@ -18,6 +17,7 @@ from borgmatic.borg import info as borg_info
|
||||
from borgmatic.borg import init as borg_init
|
||||
from borgmatic.borg import list as borg_list
|
||||
from borgmatic.borg import prune as borg_prune
|
||||
from borgmatic.commands.arguments import parse_arguments
|
||||
from borgmatic.config import checks, collect, convert, validate
|
||||
from borgmatic.logger import configure_logging, should_do_markup
|
||||
from borgmatic.signals import configure_signals
|
||||
@@ -28,243 +28,10 @@ logger = logging.getLogger(__name__)
|
||||
LEGACY_CONFIG_PATH = '/etc/borgmatic/config'
|
||||
|
||||
|
||||
def parse_arguments(*arguments):
|
||||
def run_configuration(config_filename, config, arguments): # pragma: no cover
|
||||
'''
|
||||
Given command-line arguments with which this script was invoked, parse the arguments and return
|
||||
them as an argparse.ArgumentParser instance.
|
||||
'''
|
||||
config_paths = collect.get_default_config_paths()
|
||||
|
||||
parser = ArgumentParser(
|
||||
description='''
|
||||
A simple wrapper script for the Borg backup software that creates and prunes backups.
|
||||
If none of the action options are given, then borgmatic defaults to: prune, create, and
|
||||
check archives.
|
||||
''',
|
||||
add_help=False,
|
||||
)
|
||||
|
||||
actions_group = parser.add_argument_group('actions')
|
||||
actions_group.add_argument(
|
||||
'-I', '--init', dest='init', action='store_true', help='Initialize an empty Borg repository'
|
||||
)
|
||||
actions_group.add_argument(
|
||||
'-p',
|
||||
'--prune',
|
||||
dest='prune',
|
||||
action='store_true',
|
||||
help='Prune archives according to the retention policy',
|
||||
)
|
||||
actions_group.add_argument(
|
||||
'-C',
|
||||
'--create',
|
||||
dest='create',
|
||||
action='store_true',
|
||||
help='Create archives (actually perform backups)',
|
||||
)
|
||||
actions_group.add_argument(
|
||||
'-k', '--check', dest='check', action='store_true', help='Check archives for consistency'
|
||||
)
|
||||
|
||||
actions_group.add_argument(
|
||||
'-x',
|
||||
'--extract',
|
||||
dest='extract',
|
||||
action='store_true',
|
||||
help='Extract a named archive to the current directory',
|
||||
)
|
||||
actions_group.add_argument(
|
||||
'-l', '--list', dest='list', action='store_true', help='List archives'
|
||||
)
|
||||
actions_group.add_argument(
|
||||
'-i',
|
||||
'--info',
|
||||
dest='info',
|
||||
action='store_true',
|
||||
help='Display summary information on archives',
|
||||
)
|
||||
|
||||
init_group = parser.add_argument_group('options for --init')
|
||||
init_group.add_argument(
|
||||
'-e', '--encryption', dest='encryption_mode', help='Borg repository encryption mode'
|
||||
)
|
||||
init_group.add_argument(
|
||||
'--append-only',
|
||||
dest='append_only',
|
||||
action='store_true',
|
||||
help='Create an append-only repository',
|
||||
)
|
||||
init_group.add_argument(
|
||||
'--storage-quota',
|
||||
dest='storage_quota',
|
||||
help='Create a repository with a fixed storage quota',
|
||||
)
|
||||
|
||||
prune_group = parser.add_argument_group('options for --prune')
|
||||
stats_argument = prune_group.add_argument(
|
||||
'--stats',
|
||||
dest='stats',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Display statistics of archive',
|
||||
)
|
||||
|
||||
create_group = parser.add_argument_group('options for --create')
|
||||
progress_argument = create_group.add_argument(
|
||||
'--progress',
|
||||
dest='progress',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Display progress for each file as it is processed',
|
||||
)
|
||||
create_group._group_actions.append(stats_argument)
|
||||
json_argument = create_group.add_argument(
|
||||
'--json', dest='json', default=False, action='store_true', help='Output results as JSON'
|
||||
)
|
||||
|
||||
extract_group = parser.add_argument_group('options for --extract')
|
||||
repository_argument = extract_group.add_argument(
|
||||
'--repository',
|
||||
help='Path of repository to use, defaults to the configured repository if there is only one',
|
||||
)
|
||||
archive_argument = extract_group.add_argument('--archive', help='Name of archive to operate on')
|
||||
extract_group.add_argument(
|
||||
'--restore-path',
|
||||
nargs='+',
|
||||
dest='restore_paths',
|
||||
help='Paths to restore from archive, defaults to the entire archive',
|
||||
)
|
||||
extract_group._group_actions.append(progress_argument)
|
||||
|
||||
list_group = parser.add_argument_group('options for --list')
|
||||
list_group._group_actions.append(repository_argument)
|
||||
list_group._group_actions.append(archive_argument)
|
||||
list_group._group_actions.append(json_argument)
|
||||
|
||||
info_group = parser.add_argument_group('options for --info')
|
||||
info_group._group_actions.append(json_argument)
|
||||
|
||||
common_group = parser.add_argument_group('common options')
|
||||
common_group.add_argument(
|
||||
'-c',
|
||||
'--config',
|
||||
nargs='+',
|
||||
dest='config_paths',
|
||||
default=config_paths,
|
||||
help='Configuration filenames or directories, defaults to: {}'.format(
|
||||
' '.join(config_paths)
|
||||
),
|
||||
)
|
||||
common_group.add_argument(
|
||||
'--excludes',
|
||||
dest='excludes_filename',
|
||||
help='Deprecated in favor of exclude_patterns within configuration',
|
||||
)
|
||||
common_group.add_argument(
|
||||
'-n',
|
||||
'--dry-run',
|
||||
dest='dry_run',
|
||||
action='store_true',
|
||||
help='Go through the motions, but do not actually write to any repositories',
|
||||
)
|
||||
common_group.add_argument(
|
||||
'-nc', '--no-color', dest='no_color', action='store_true', help='Disable colored output'
|
||||
)
|
||||
common_group.add_argument(
|
||||
'-v',
|
||||
'--verbosity',
|
||||
type=int,
|
||||
choices=range(0, 3),
|
||||
default=0,
|
||||
help='Display verbose progress to the console (from none to lots: 0, 1, or 2)',
|
||||
)
|
||||
common_group.add_argument(
|
||||
'--syslog-verbosity',
|
||||
type=int,
|
||||
choices=range(0, 3),
|
||||
default=0,
|
||||
help='Display verbose progress to syslog (from none to lots: 0, 1, or 2)',
|
||||
)
|
||||
common_group.add_argument(
|
||||
'--version',
|
||||
dest='version',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Display installed version number of borgmatic and exit',
|
||||
)
|
||||
common_group.add_argument('--help', action='help', help='Show this help information and exit')
|
||||
|
||||
args = parser.parse_args(arguments)
|
||||
|
||||
if args.excludes_filename:
|
||||
raise ValueError(
|
||||
'The --excludes option has been replaced with exclude_patterns in configuration'
|
||||
)
|
||||
|
||||
if (args.encryption_mode or args.append_only or args.storage_quota) and not args.init:
|
||||
raise ValueError(
|
||||
'The --encryption, --append-only, and --storage-quota options can only be used with the --init option'
|
||||
)
|
||||
|
||||
if args.init and args.dry_run:
|
||||
raise ValueError('The --init option cannot be used with the --dry-run option')
|
||||
if args.init and not args.encryption_mode:
|
||||
raise ValueError('The --encryption option is required with the --init option')
|
||||
|
||||
if not args.extract:
|
||||
if not args.list:
|
||||
if args.repository:
|
||||
raise ValueError(
|
||||
'The --repository option can only be used with the --extract and --list options'
|
||||
)
|
||||
if args.archive:
|
||||
raise ValueError(
|
||||
'The --archive option can only be used with the --extract and --list options'
|
||||
)
|
||||
if args.restore_paths:
|
||||
raise ValueError('The --restore-path option can only be used with the --extract option')
|
||||
if args.extract and not args.archive:
|
||||
raise ValueError('The --archive option is required with the --extract option')
|
||||
|
||||
if args.progress and not (args.create or args.extract):
|
||||
raise ValueError(
|
||||
'The --progress option can only be used with the --create and --extract options'
|
||||
)
|
||||
|
||||
if args.json and not (args.create or args.list or args.info):
|
||||
raise ValueError(
|
||||
'The --json option can only be used with the --create, --list, or --info options'
|
||||
)
|
||||
|
||||
if args.json and args.list and args.info:
|
||||
raise ValueError(
|
||||
'With the --json option, options --list and --info cannot be used together'
|
||||
)
|
||||
|
||||
# If any of the action flags are explicitly requested, leave them as-is. Otherwise, assume
|
||||
# defaults: Mutate the given arguments to enable the default actions.
|
||||
if (
|
||||
not args.init
|
||||
and not args.prune
|
||||
and not args.create
|
||||
and not args.check
|
||||
and not args.extract
|
||||
and not args.list
|
||||
and not args.info
|
||||
):
|
||||
args.prune = True
|
||||
args.create = True
|
||||
args.check = True
|
||||
|
||||
if args.stats and not (args.create or args.prune):
|
||||
raise ValueError('The --stats option can only be used when creating or pruning archives')
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def run_configuration(config_filename, config, args): # pragma: no cover
|
||||
'''
|
||||
Given a config filename and the corresponding parsed config dict, execute its defined pruning,
|
||||
Given a config filename, the corresponding parsed config dict, and command-line arguments as a
|
||||
dict from subparser name to a namespace of parsed arguments, execute its defined pruning,
|
||||
backups, consistency checks, and/or other actions.
|
||||
|
||||
Yield JSON output strings from executing any actions that produce JSON.
|
||||
@@ -273,24 +40,25 @@ def run_configuration(config_filename, config, args): # pragma: no cover
|
||||
config.get(section_name, {})
|
||||
for section_name in ('location', 'storage', 'retention', 'consistency', 'hooks')
|
||||
)
|
||||
global_arguments = arguments['global']
|
||||
|
||||
try:
|
||||
local_path = location.get('local_path', 'borg')
|
||||
remote_path = location.get('remote_path')
|
||||
borg_environment.initialize(storage)
|
||||
|
||||
if args.create:
|
||||
if 'create' in arguments:
|
||||
hook.execute_hook(
|
||||
hooks.get('before_backup'),
|
||||
hooks.get('umask'),
|
||||
config_filename,
|
||||
'pre-backup',
|
||||
args.dry_run,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
|
||||
for repository_path in location['repositories']:
|
||||
yield from run_actions(
|
||||
args=args,
|
||||
arguments=arguments,
|
||||
location=location,
|
||||
storage=storage,
|
||||
retention=retention,
|
||||
@@ -300,23 +68,35 @@ def run_configuration(config_filename, config, args): # pragma: no cover
|
||||
repository_path=repository_path,
|
||||
)
|
||||
|
||||
if args.create:
|
||||
if 'create' in arguments:
|
||||
hook.execute_hook(
|
||||
hooks.get('after_backup'),
|
||||
hooks.get('umask'),
|
||||
config_filename,
|
||||
'post-backup',
|
||||
args.dry_run,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
except (OSError, CalledProcessError):
|
||||
hook.execute_hook(
|
||||
hooks.get('on_error'), hooks.get('umask'), config_filename, 'on-error', args.dry_run
|
||||
hooks.get('on_error'),
|
||||
hooks.get('umask'),
|
||||
config_filename,
|
||||
'on-error',
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
def run_actions(
|
||||
*, args, location, storage, retention, consistency, local_path, remote_path, repository_path
|
||||
*,
|
||||
arguments,
|
||||
location,
|
||||
storage,
|
||||
retention,
|
||||
consistency,
|
||||
local_path,
|
||||
remote_path,
|
||||
repository_path
|
||||
): # pragma: no cover
|
||||
'''
|
||||
Given parsed command-line arguments as an argparse.ArgumentParser instance, several different
|
||||
@@ -326,79 +106,86 @@ def run_actions(
|
||||
Yield JSON output strings from executing any actions that produce JSON.
|
||||
'''
|
||||
repository = os.path.expanduser(repository_path)
|
||||
dry_run_label = ' (dry run; not making any changes)' if args.dry_run else ''
|
||||
if args.init:
|
||||
global_arguments = arguments['global']
|
||||
dry_run_label = ' (dry run; not making any changes)' if global_arguments.dry_run else ''
|
||||
if 'init' in arguments:
|
||||
logger.info('{}: Initializing repository'.format(repository))
|
||||
borg_init.initialize_repository(
|
||||
repository,
|
||||
args.encryption_mode,
|
||||
args.append_only,
|
||||
args.storage_quota,
|
||||
arguments['init'].encryption_mode,
|
||||
arguments['init'].append_only,
|
||||
arguments['init'].storage_quota,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
)
|
||||
if args.prune:
|
||||
if 'prune' in arguments:
|
||||
logger.info('{}: Pruning archives{}'.format(repository, dry_run_label))
|
||||
borg_prune.prune_archives(
|
||||
args.dry_run,
|
||||
global_arguments.dry_run,
|
||||
repository,
|
||||
storage,
|
||||
retention,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
stats=args.stats,
|
||||
stats=arguments['prune'].stats,
|
||||
)
|
||||
if args.create:
|
||||
if 'create' in arguments:
|
||||
logger.info('{}: Creating archive{}'.format(repository, dry_run_label))
|
||||
json_output = borg_create.create_archive(
|
||||
args.dry_run,
|
||||
global_arguments.dry_run,
|
||||
repository,
|
||||
location,
|
||||
storage,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
progress=args.progress,
|
||||
stats=args.stats,
|
||||
json=args.json,
|
||||
progress=arguments['create'].progress,
|
||||
stats=arguments['create'].stats,
|
||||
json=arguments['create'].json,
|
||||
)
|
||||
if json_output:
|
||||
yield json.loads(json_output)
|
||||
if args.check and checks.repository_enabled_for_checks(repository, consistency):
|
||||
if 'check' in arguments and checks.repository_enabled_for_checks(repository, consistency):
|
||||
logger.info('{}: Running consistency checks'.format(repository))
|
||||
borg_check.check_archives(
|
||||
repository, storage, consistency, local_path=local_path, remote_path=remote_path
|
||||
)
|
||||
if args.extract:
|
||||
if args.repository is None or repository == args.repository:
|
||||
logger.info('{}: Extracting archive {}'.format(repository, args.archive))
|
||||
if 'extract' in arguments:
|
||||
if arguments['extract'].repository is None or repository == arguments['extract'].repository:
|
||||
logger.info(
|
||||
'{}: Extracting archive {}'.format(repository, arguments['extract'].archive)
|
||||
)
|
||||
borg_extract.extract_archive(
|
||||
args.dry_run,
|
||||
global_arguments.dry_run,
|
||||
repository,
|
||||
args.archive,
|
||||
args.restore_paths,
|
||||
arguments['extract'].archive,
|
||||
arguments['extract'].restore_paths,
|
||||
location,
|
||||
storage,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
progress=args.progress,
|
||||
progress=arguments['extract'].progress,
|
||||
)
|
||||
if args.list:
|
||||
if args.repository is None or repository == args.repository:
|
||||
if 'list' in arguments:
|
||||
if arguments['list'].repository is None or repository == arguments['list'].repository:
|
||||
logger.info('{}: Listing archives'.format(repository))
|
||||
json_output = borg_list.list_archives(
|
||||
repository,
|
||||
storage,
|
||||
args.archive,
|
||||
arguments['list'].archive,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
json=args.json,
|
||||
json=arguments['list'].json,
|
||||
)
|
||||
if json_output:
|
||||
yield json.loads(json_output)
|
||||
if args.info:
|
||||
if 'info' in arguments:
|
||||
logger.info('{}: Displaying summary info for archives'.format(repository))
|
||||
json_output = borg_info.display_archives_info(
|
||||
repository, storage, local_path=local_path, remote_path=remote_path, json=args.json
|
||||
repository,
|
||||
storage,
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
json=arguments['info'].json,
|
||||
)
|
||||
if json_output:
|
||||
yield json.loads(json_output)
|
||||
@@ -439,19 +226,27 @@ def load_configurations(config_filenames):
|
||||
return (configs, logs)
|
||||
|
||||
|
||||
def collect_configuration_run_summary_logs(configs, args):
|
||||
def collect_configuration_run_summary_logs(configs, arguments):
|
||||
'''
|
||||
Given a dict of configuration filename to corresponding parsed configuration, and parsed
|
||||
command-line arguments as an argparse.ArgumentParser instance, run each configuration file and
|
||||
yield a series of logging.LogRecord instances containing summary information about each run.
|
||||
command-line arguments as a dict from subparser name to a parsed namespace of arguments, run
|
||||
each configuration file and yield a series of logging.LogRecord instances containing summary
|
||||
information about each run.
|
||||
|
||||
As a side effect of running through these configuration files, output their JSON results, if
|
||||
any, to stdout.
|
||||
'''
|
||||
# Run cross-file validation checks.
|
||||
if args.extract or (args.list and args.archive):
|
||||
if 'extract' in arguments:
|
||||
repository = arguments['extract'].repository
|
||||
elif 'list' in arguments and arguments['list'].archive:
|
||||
repository = arguments['list'].repository
|
||||
else:
|
||||
repository = None
|
||||
|
||||
if repository:
|
||||
try:
|
||||
validate.guard_configuration_contains_repository(args.repository, configs)
|
||||
validate.guard_configuration_contains_repository(repository, configs)
|
||||
except ValueError as error:
|
||||
yield logging.makeLogRecord(
|
||||
dict(levelno=logging.CRITICAL, levelname='CRITICAL', msg=error)
|
||||
@@ -462,7 +257,7 @@ def collect_configuration_run_summary_logs(configs, args):
|
||||
json_results = []
|
||||
for config_filename, config in configs.items():
|
||||
try:
|
||||
json_results.extend(list(run_configuration(config_filename, config, args)))
|
||||
json_results.extend(list(run_configuration(config_filename, config, arguments)))
|
||||
yield logging.makeLogRecord(
|
||||
dict(
|
||||
levelno=logging.INFO,
|
||||
@@ -490,7 +285,9 @@ def collect_configuration_run_summary_logs(configs, args):
|
||||
dict(
|
||||
levelno=logging.CRITICAL,
|
||||
levelname='CRITICAL',
|
||||
msg='{}: No configuration files found'.format(' '.join(args.config_paths)),
|
||||
msg='{}: No configuration files found'.format(
|
||||
' '.join(arguments['global'].config_paths)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -508,7 +305,7 @@ def main(): # pragma: no cover
|
||||
configure_signals()
|
||||
|
||||
try:
|
||||
args = parse_arguments(*sys.argv[1:])
|
||||
arguments = parse_arguments(*sys.argv[1:])
|
||||
except ValueError as error:
|
||||
configure_logging(logging.CRITICAL)
|
||||
logger.critical(error)
|
||||
@@ -520,22 +317,24 @@ def main(): # pragma: no cover
|
||||
logger.critical('Error parsing arguments: {}'.format(' '.join(sys.argv)))
|
||||
exit_with_help_link()
|
||||
|
||||
if args.version:
|
||||
global_arguments = arguments['global']
|
||||
if global_arguments.version:
|
||||
print(pkg_resources.require('borgmatic')[0].version)
|
||||
sys.exit(0)
|
||||
|
||||
config_filenames = tuple(collect.collect_config_filenames(args.config_paths))
|
||||
config_filenames = tuple(collect.collect_config_filenames(global_arguments.config_paths))
|
||||
configs, parse_logs = load_configurations(config_filenames)
|
||||
|
||||
colorama.init(autoreset=True, strip=not should_do_markup(args.no_color, configs))
|
||||
colorama.init(autoreset=True, strip=not should_do_markup(global_arguments.no_color, configs))
|
||||
configure_logging(
|
||||
verbosity_to_log_level(args.verbosity), verbosity_to_log_level(args.syslog_verbosity)
|
||||
verbosity_to_log_level(global_arguments.verbosity),
|
||||
verbosity_to_log_level(global_arguments.syslog_verbosity),
|
||||
)
|
||||
|
||||
logger.debug('Ensuring legacy configuration is upgraded')
|
||||
convert.guard_configuration_upgraded(LEGACY_CONFIG_PATH, config_filenames)
|
||||
|
||||
summary_logs = list(collect_configuration_run_summary_logs(configs, args))
|
||||
summary_logs = list(collect_configuration_run_summary_logs(configs, arguments))
|
||||
|
||||
logger.info('')
|
||||
logger.info('summary:')
|
||||
|
||||
+4
-1
@@ -2,7 +2,10 @@ FROM python:3.7.3-alpine3.9 as borgmatic
|
||||
|
||||
COPY . /app
|
||||
RUN pip install --no-cache /app && generate-borgmatic-config && chmod +r /etc/borgmatic/config.yaml
|
||||
RUN borgmatic --help > /command-line.txt
|
||||
RUN borgmatic --help > /command-line.txt \
|
||||
&& for action in init prune create check extract list info; do \
|
||||
echo -e "\n--------------------------------------------------------------------------------\n" >> /command-line.txt \
|
||||
&& borgmatic "$action" --help >> /command-line.txt; done
|
||||
|
||||
FROM node:11.15.0-alpine as html
|
||||
|
||||
|
||||
@@ -17,15 +17,15 @@ run borgmatic's pruning, creating, or checking actions separately. For
|
||||
instance, the the following optional flags are available:
|
||||
|
||||
```bash
|
||||
borgmatic --prune
|
||||
borgmatic --create
|
||||
borgmatic --check
|
||||
borgmatic prune
|
||||
borgmatic create
|
||||
borgmatic check
|
||||
```
|
||||
|
||||
You can run with only one of these flags provided, or you can mix and match
|
||||
any number of them in a single borgmatic run. This supports approaches like
|
||||
making backups with `--create` on a frequent schedule, while only running
|
||||
expensive consistency checks with `--check` on a much less frequent basis from
|
||||
making backups with `create` on a frequent schedule, while only running
|
||||
expensive consistency checks with `check` on a much less frequent basis from
|
||||
a separate cron job.
|
||||
|
||||
### Consistency check configuration
|
||||
|
||||
@@ -29,8 +29,8 @@ functionality:
|
||||
|
||||
|
||||
```bash
|
||||
borgmatic --list
|
||||
borgmatic --info
|
||||
borgmatic list
|
||||
borgmatic info
|
||||
```
|
||||
|
||||
## Logging
|
||||
@@ -71,8 +71,8 @@ already has this rate limit disabled.
|
||||
## Scripting borgmatic
|
||||
|
||||
To consume the output of borgmatic in other software, you can include an
|
||||
optional `--json` flag with `--create`, `--list`, or `--info` to get the
|
||||
output formatted as JSON.
|
||||
optional `--json` flag with `create`, `list`, or `info` to get the output
|
||||
formatted as JSON.
|
||||
|
||||
Note that when you specify the `--json` flag, Borg's other non-JSON output is
|
||||
suppressed so as not to interfere with the captured JSON. Also note that JSON
|
||||
|
||||
@@ -5,10 +5,10 @@ title: How to restore a backup
|
||||
|
||||
When the worst happens—or you want to test your backups—the first step is
|
||||
to figure out which archive to restore. A good way to do that is to use the
|
||||
`--list` action:
|
||||
`list` action:
|
||||
|
||||
```bash
|
||||
borgmatic --list
|
||||
borgmatic list
|
||||
```
|
||||
|
||||
That should yield output looking something like:
|
||||
@@ -22,7 +22,7 @@ Assuming that you want to restore the archive with the most up-to-date files
|
||||
and therefore the latest timestamp, run a command like:
|
||||
|
||||
```bash
|
||||
borgmatic --extract --archive host-2019-01-02T04:06:07.080910
|
||||
borgmatic extract --archive host-2019-01-02T04:06:07.080910
|
||||
```
|
||||
|
||||
The `--archive` value is the name of the archive to restore. This extracts the
|
||||
@@ -33,13 +33,13 @@ in the right place before running the command.
|
||||
## Repository selection
|
||||
|
||||
If you have a single repository in your borgmatic configuration file(s), no
|
||||
problem: the `--extract` action figures out which repository to use.
|
||||
problem: the `extract` action figures out which repository to use.
|
||||
|
||||
But if you have multiple repositories configured, then you'll need to specify
|
||||
the repository path containing the archive to extract. Here's an example:
|
||||
|
||||
```bash
|
||||
borgmatic --extract --repository repo.borg --archive host-2019-...
|
||||
borgmatic extract --repository repo.borg --archive host-2019-...
|
||||
```
|
||||
|
||||
## Restore particular files
|
||||
@@ -49,7 +49,7 @@ everything from an archive. To do that, tack on one or more `--restore-path`
|
||||
values. For instance:
|
||||
|
||||
```bash
|
||||
borgmatic --extract --archive host-2019-... --restore-path /path/1 /path/2
|
||||
borgmatic extract --archive host-2019-... --restore-path /path/1 /path/2
|
||||
```
|
||||
|
||||
Like a whole-archive restore, this also restores into the current directory.
|
||||
|
||||
@@ -121,7 +121,7 @@ this step if you already have a Borg repository.) To create a repository, run
|
||||
a command like the following:
|
||||
|
||||
```bash
|
||||
borgmatic --init --encryption repokey
|
||||
borgmatic init --encryption repokey
|
||||
```
|
||||
|
||||
This uses the borgmatic configuration file you created above to determine
|
||||
|
||||
@@ -3,7 +3,8 @@ title: borgmatic command-line reference
|
||||
---
|
||||
## borgmatic options
|
||||
|
||||
Here are all of the available borgmatic command-line options:
|
||||
Here are all of the available borgmatic command-line options. This includes the separate options for
|
||||
each action sub-command:
|
||||
|
||||
```
|
||||
{% include borgmatic/command-line.txt %}
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
set -e
|
||||
|
||||
docker build --tag borgmatic-docs --file docs/Dockerfile .
|
||||
echo
|
||||
echo "You can view dev docs at http://localhost:8080"
|
||||
echo "Note that links within these docs will go to the online docs, so you will need to fiddle with URLs manually to stay in the dev docs."
|
||||
echo
|
||||
docker run --interactive --tty --publish 8080:80 --rm borgmatic-docs
|
||||
|
||||
@@ -18,7 +18,7 @@ mv temp.yaml.uncommented temp.yaml
|
||||
for sub_command in prune create check list info; do
|
||||
echo "********** borg $sub_command **********"
|
||||
|
||||
for line in $(borgmatic --config temp.yaml --$sub_command -v 2 2>&1 | grep "borg\w* $sub_command") ; do
|
||||
for line in $(borgmatic --config temp.yaml $sub_command -v 2 2>&1 | grep "borg\w* $sub_command") ; do
|
||||
echo "$line" | grep '^-' >> borgmatic_borg_flags
|
||||
done
|
||||
sort borgmatic_borg_flags > borgmatic_borg_flags.sorted
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
VERSION = '1.3.8'
|
||||
VERSION = '1.3.9'
|
||||
|
||||
|
||||
setup(
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.commands import arguments as module
|
||||
|
||||
|
||||
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()
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == config_paths
|
||||
assert global_arguments.excludes_filename is None
|
||||
assert global_arguments.verbosity == 0
|
||||
assert global_arguments.syslog_verbosity == 0
|
||||
|
||||
|
||||
def test_parse_arguments_with_multiple_config_paths_parses_as_list():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('--config', 'myconfig', 'otherconfig')
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == ['myconfig', 'otherconfig']
|
||||
assert global_arguments.verbosity == 0
|
||||
assert global_arguments.syslog_verbosity == 0
|
||||
|
||||
|
||||
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')
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == config_paths
|
||||
assert global_arguments.excludes_filename is None
|
||||
assert global_arguments.verbosity == 1
|
||||
assert global_arguments.syslog_verbosity == 0
|
||||
|
||||
|
||||
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')
|
||||
|
||||
global_arguments = arguments['global']
|
||||
assert global_arguments.config_paths == config_paths
|
||||
assert global_arguments.excludes_filename is None
|
||||
assert global_arguments.verbosity == 0
|
||||
assert global_arguments.syslog_verbosity == 2
|
||||
|
||||
|
||||
def test_parse_arguments_with_list_json_overrides_default():
|
||||
arguments = module.parse_arguments('list', '--json')
|
||||
|
||||
assert 'list' in arguments
|
||||
assert arguments['list'].json is True
|
||||
|
||||
|
||||
def test_parse_arguments_with_dashed_list_json_overrides_default():
|
||||
arguments = module.parse_arguments('--list', '--json')
|
||||
|
||||
assert 'list' in arguments
|
||||
assert arguments['list'].json is True
|
||||
|
||||
|
||||
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()
|
||||
|
||||
assert 'prune' in arguments
|
||||
assert 'create' in arguments
|
||||
assert 'check' in arguments
|
||||
|
||||
|
||||
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')
|
||||
|
||||
assert exit.value.code == 0
|
||||
captured = capsys.readouterr()
|
||||
assert 'global arguments:' in captured.out
|
||||
assert 'actions:' in captured.out
|
||||
|
||||
|
||||
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')
|
||||
|
||||
assert exit.value.code == 0
|
||||
captured = capsys.readouterr()
|
||||
assert 'global arguments:' not in captured.out
|
||||
assert 'actions:' not in captured.out
|
||||
assert 'create arguments:' in captured.out
|
||||
|
||||
|
||||
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')
|
||||
|
||||
assert 'prune' in arguments
|
||||
assert 'create' not in arguments
|
||||
assert 'check' not in arguments
|
||||
|
||||
|
||||
def test_parse_arguments_with_dashed_prune_action_leaves_other_actions_disabled():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('--prune')
|
||||
|
||||
assert 'prune' in arguments
|
||||
assert 'create' not in arguments
|
||||
assert 'check' not in arguments
|
||||
|
||||
|
||||
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')
|
||||
|
||||
assert 'prune' not in arguments
|
||||
assert 'create' in arguments
|
||||
assert 'check' in arguments
|
||||
|
||||
|
||||
def test_parse_arguments_with_multiple_dashed_actions_leaves_other_action_disabled():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
arguments = module.parse_arguments('--create', '--check')
|
||||
|
||||
assert 'prune' not in arguments
|
||||
assert 'create' in arguments
|
||||
assert 'check' in arguments
|
||||
|
||||
|
||||
def test_parse_arguments_with_invalid_arguments_exits():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--posix-me-harder')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_deprecated_excludes_option():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--excludes', 'myexcludes')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_encryption_mode_without_init():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
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_allows_encryption_mode_with_dashed_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')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_append_only_without_init():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
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(SystemExit):
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_init_and_dry_run():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments(
|
||||
'--config', 'myconfig', 'init', '--encryption', 'repokey', '--dry-run'
|
||||
)
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_repository_without_extract_or_list():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', '--repository', 'test.borg')
|
||||
|
||||
|
||||
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'
|
||||
)
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_archive_without_extract_or_list():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', '--archive', 'test')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_restore_paths_without_extract():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--config', 'myconfig', '--restore-path', 'test')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_archive_with_dashed_extract():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--config', 'myconfig', '--extract', '--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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_progress_without_create():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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(SystemExit):
|
||||
module.parse_arguments('--stats', 'list')
|
||||
|
||||
|
||||
def test_parse_arguments_with_just_stats_flag_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--stats')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_json_with_dashed_info():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
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')
|
||||
@@ -1,281 +1,10 @@
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.commands import borgmatic as module
|
||||
|
||||
|
||||
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)
|
||||
|
||||
parser = module.parse_arguments()
|
||||
|
||||
assert parser.config_paths == config_paths
|
||||
assert parser.excludes_filename is None
|
||||
assert parser.verbosity == 0
|
||||
assert parser.syslog_verbosity == 0
|
||||
assert parser.json is False
|
||||
|
||||
|
||||
def test_parse_arguments_with_multiple_config_paths_parses_as_list():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
parser = module.parse_arguments('--config', 'myconfig', 'otherconfig')
|
||||
|
||||
assert parser.config_paths == ['myconfig', 'otherconfig']
|
||||
assert parser.verbosity == 0
|
||||
assert parser.syslog_verbosity == 0
|
||||
|
||||
|
||||
def test_parse_arguments_with_verbosity_overrides_default():
|
||||
config_paths = ['default']
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths)
|
||||
|
||||
parser = module.parse_arguments('--verbosity', '1')
|
||||
|
||||
assert parser.config_paths == config_paths
|
||||
assert parser.excludes_filename is None
|
||||
assert parser.verbosity == 1
|
||||
assert parser.syslog_verbosity == 0
|
||||
|
||||
|
||||
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)
|
||||
|
||||
parser = module.parse_arguments('--syslog-verbosity', '2')
|
||||
|
||||
assert parser.config_paths == config_paths
|
||||
assert parser.excludes_filename is None
|
||||
assert parser.verbosity == 0
|
||||
assert parser.syslog_verbosity == 2
|
||||
|
||||
|
||||
def test_parse_arguments_with_json_overrides_default():
|
||||
parser = module.parse_arguments('--list', '--json')
|
||||
assert parser.json is True
|
||||
|
||||
|
||||
def test_parse_arguments_with_no_actions_defaults_to_all_actions_enabled():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
parser = module.parse_arguments()
|
||||
|
||||
assert parser.prune is True
|
||||
assert parser.create is True
|
||||
assert parser.check is True
|
||||
|
||||
|
||||
def test_parse_arguments_with_prune_action_leaves_other_actions_disabled():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
parser = module.parse_arguments('--prune')
|
||||
|
||||
assert parser.prune is True
|
||||
assert parser.create is False
|
||||
assert parser.check is False
|
||||
|
||||
|
||||
def test_parse_arguments_with_multiple_actions_leaves_other_action_disabled():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
parser = module.parse_arguments('--create', '--check')
|
||||
|
||||
assert parser.prune is False
|
||||
assert parser.create is True
|
||||
assert parser.check is True
|
||||
|
||||
|
||||
def test_parse_arguments_with_invalid_arguments_exits():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--posix-me-harder')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_deprecated_excludes_option():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--excludes', 'myexcludes')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--init')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_init_and_dry_run():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments(
|
||||
'--config', 'myconfig', '--init', '--encryption', 'repokey', '--dry-run'
|
||||
)
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_repository_without_extract_or_list():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--repository', 'test.borg')
|
||||
|
||||
|
||||
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'
|
||||
)
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_archive_without_extract_or_list():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--archive', 'test')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_restore_paths_without_extract():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--restore-path', 'test')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_parse_arguments_requires_archive_with_extract():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--config', 'myconfig', '--extract')
|
||||
|
||||
|
||||
def test_parse_arguments_allows_progress_and_create():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--progress', '--create', '--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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_parse_arguments_with_just_stats_flag_does_not_raise():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
module.parse_arguments('--stats')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_parse_arguments_disallows_json_without_list_or_info():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.parse_arguments('--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')
|
||||
|
||||
|
||||
def test_borgmatic_version_matches_news_version():
|
||||
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
|
||||
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.commands import arguments as module
|
||||
|
||||
|
||||
def test_parse_subparser_arguments_consumes_subparser_arguments_before_subparser_name():
|
||||
global_namespace = flexmock()
|
||||
action_namespace = flexmock(foo=True)
|
||||
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
|
||||
subparsers = flexmock(
|
||||
choices={
|
||||
'action': flexmock(parse_known_args=lambda arguments: (action_namespace, [])),
|
||||
'other': flexmock(),
|
||||
}
|
||||
)
|
||||
|
||||
arguments = module.parse_subparser_arguments(
|
||||
('--foo', 'true', 'action'), top_level_parser, subparsers
|
||||
)
|
||||
|
||||
assert arguments == {'action': action_namespace, 'global': global_namespace}
|
||||
|
||||
|
||||
def test_parse_subparser_arguments_consumes_subparser_arguments_after_subparser_name():
|
||||
global_namespace = flexmock()
|
||||
action_namespace = flexmock(foo=True)
|
||||
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
|
||||
subparsers = flexmock(
|
||||
choices={
|
||||
'action': flexmock(parse_known_args=lambda arguments: (action_namespace, [])),
|
||||
'other': flexmock(),
|
||||
}
|
||||
)
|
||||
|
||||
arguments = module.parse_subparser_arguments(
|
||||
('action', '--foo', 'true'), top_level_parser, subparsers
|
||||
)
|
||||
|
||||
assert arguments == {'action': action_namespace, 'global': global_namespace}
|
||||
|
||||
|
||||
def test_parse_subparser_arguments_consumes_global_arguments_before_subparser_name():
|
||||
global_namespace = flexmock(verbosity='lots')
|
||||
action_namespace = flexmock()
|
||||
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
|
||||
subparsers = flexmock(
|
||||
choices={
|
||||
'action': flexmock(
|
||||
parse_known_args=lambda arguments: (action_namespace, ['--verbosity', 'lots'])
|
||||
),
|
||||
'other': flexmock(),
|
||||
}
|
||||
)
|
||||
|
||||
arguments = module.parse_subparser_arguments(
|
||||
('--verbosity', 'lots', 'action'), top_level_parser, subparsers
|
||||
)
|
||||
|
||||
assert arguments == {'action': action_namespace, 'global': global_namespace}
|
||||
|
||||
|
||||
def test_parse_subparser_arguments_consumes_global_arguments_after_subparser_name():
|
||||
global_namespace = flexmock(verbosity='lots')
|
||||
action_namespace = flexmock()
|
||||
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
|
||||
subparsers = flexmock(
|
||||
choices={
|
||||
'action': flexmock(
|
||||
parse_known_args=lambda arguments: (action_namespace, ['--verbosity', 'lots'])
|
||||
),
|
||||
'other': flexmock(),
|
||||
}
|
||||
)
|
||||
|
||||
arguments = module.parse_subparser_arguments(
|
||||
('action', '--verbosity', 'lots'), top_level_parser, subparsers
|
||||
)
|
||||
|
||||
assert arguments == {'action': action_namespace, 'global': global_namespace}
|
||||
|
||||
|
||||
def test_parse_subparser_arguments_consumes_subparser_arguments_with_alias():
|
||||
global_namespace = flexmock()
|
||||
action_namespace = flexmock(foo=True)
|
||||
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
|
||||
action_subparser = flexmock(parse_known_args=lambda arguments: (action_namespace, []))
|
||||
subparsers = flexmock(
|
||||
choices={
|
||||
'action': action_subparser,
|
||||
'-a': action_subparser,
|
||||
'other': flexmock(),
|
||||
'-o': flexmock(),
|
||||
}
|
||||
)
|
||||
flexmock(module).SUBPARSER_ALIASES = {'action': ['-a'], 'other': ['-o']}
|
||||
|
||||
arguments = module.parse_subparser_arguments(
|
||||
('-a', '--foo', 'true'), top_level_parser, subparsers
|
||||
)
|
||||
|
||||
assert arguments == {'action': action_namespace, 'global': global_namespace}
|
||||
|
||||
|
||||
def test_parse_subparser_arguments_consumes_multiple_subparser_arguments():
|
||||
global_namespace = flexmock()
|
||||
action_namespace = flexmock(foo=True)
|
||||
other_namespace = flexmock(bar=3)
|
||||
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
|
||||
subparsers = flexmock(
|
||||
choices={
|
||||
'action': flexmock(
|
||||
parse_known_args=lambda arguments: (action_namespace, ['--bar', '3'])
|
||||
),
|
||||
'other': flexmock(parse_known_args=lambda arguments: (other_namespace, [])),
|
||||
}
|
||||
)
|
||||
|
||||
arguments = module.parse_subparser_arguments(
|
||||
('action', '--foo', 'true', 'other', '--bar', '3'), top_level_parser, subparsers
|
||||
)
|
||||
|
||||
assert arguments == {
|
||||
'action': action_namespace,
|
||||
'other': other_namespace,
|
||||
'global': global_namespace,
|
||||
}
|
||||
|
||||
|
||||
def test_parse_subparser_arguments_applies_default_subparsers():
|
||||
global_namespace = flexmock()
|
||||
prune_namespace = flexmock()
|
||||
create_namespace = flexmock(progress=True)
|
||||
check_namespace = flexmock()
|
||||
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
|
||||
subparsers = flexmock(
|
||||
choices={
|
||||
'prune': flexmock(parse_known_args=lambda arguments: (prune_namespace, ['--progress'])),
|
||||
'create': flexmock(parse_known_args=lambda arguments: (create_namespace, [])),
|
||||
'check': flexmock(parse_known_args=lambda arguments: (check_namespace, [])),
|
||||
'other': flexmock(),
|
||||
}
|
||||
)
|
||||
|
||||
arguments = module.parse_subparser_arguments(('--progress'), top_level_parser, subparsers)
|
||||
|
||||
assert arguments == {
|
||||
'prune': prune_namespace,
|
||||
'create': create_namespace,
|
||||
'check': check_namespace,
|
||||
'global': global_namespace,
|
||||
}
|
||||
|
||||
|
||||
def test_parse_subparser_arguments_with_help_does_not_apply_default_subparsers():
|
||||
global_namespace = flexmock(verbosity='lots')
|
||||
action_namespace = flexmock()
|
||||
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
|
||||
subparsers = flexmock(
|
||||
choices={
|
||||
'action': flexmock(
|
||||
parse_known_args=lambda arguments: (action_namespace, ['--verbosity', 'lots'])
|
||||
),
|
||||
'other': flexmock(),
|
||||
}
|
||||
)
|
||||
|
||||
arguments = module.parse_subparser_arguments(
|
||||
('--verbosity', 'lots', '--help'), top_level_parser, subparsers
|
||||
)
|
||||
|
||||
assert arguments == {'global': global_namespace}
|
||||
@@ -27,9 +27,11 @@ def test_load_configurations_logs_critical_for_parse_error():
|
||||
|
||||
def test_collect_configuration_run_summary_logs_info_for_success():
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
args = flexmock(extract=False, list=False)
|
||||
arguments = {}
|
||||
|
||||
logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, args=args))
|
||||
logs = tuple(
|
||||
module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||
)
|
||||
|
||||
assert all(log for log in logs if log.levelno == module.logging.INFO)
|
||||
|
||||
@@ -37,9 +39,11 @@ def test_collect_configuration_run_summary_logs_info_for_success():
|
||||
def test_collect_configuration_run_summary_logs_info_for_success_with_extract():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
args = flexmock(extract=True, list=False, repository='repo')
|
||||
arguments = {'extract': flexmock(repository='repo')}
|
||||
|
||||
logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, args=args))
|
||||
logs = tuple(
|
||||
module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||
)
|
||||
|
||||
assert all(log for log in logs if log.levelno == module.logging.INFO)
|
||||
|
||||
@@ -48,9 +52,11 @@ def test_collect_configuration_run_summary_logs_critical_for_extract_with_reposi
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository').and_raise(
|
||||
ValueError
|
||||
)
|
||||
args = flexmock(extract=True, list=False, repository='repo')
|
||||
arguments = {'extract': flexmock(repository='repo')}
|
||||
|
||||
logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, args=args))
|
||||
logs = tuple(
|
||||
module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||
)
|
||||
|
||||
assert any(log for log in logs if log.levelno == module.logging.CRITICAL)
|
||||
|
||||
@@ -59,18 +65,22 @@ def test_collect_configuration_run_summary_logs_critical_for_list_with_archive_a
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository').and_raise(
|
||||
ValueError
|
||||
)
|
||||
args = flexmock(extract=False, list=True, repository='repo', archive='test')
|
||||
arguments = {'list': flexmock(repository='repo', archive='test')}
|
||||
|
||||
logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, args=args))
|
||||
logs = tuple(
|
||||
module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||
)
|
||||
|
||||
assert any(log for log in logs if log.levelno == module.logging.CRITICAL)
|
||||
|
||||
|
||||
def test_collect_configuration_run_summary_logs_info_for_success_with_list():
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
args = flexmock(extract=False, list=True, repository='repo', archive=None)
|
||||
arguments = {'list': flexmock(repository='repo', archive=None)}
|
||||
|
||||
logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, args=args))
|
||||
logs = tuple(
|
||||
module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||
)
|
||||
|
||||
assert all(log for log in logs if log.levelno == module.logging.INFO)
|
||||
|
||||
@@ -78,9 +88,11 @@ def test_collect_configuration_run_summary_logs_info_for_success_with_list():
|
||||
def test_collect_configuration_run_summary_logs_critical_for_run_error():
|
||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||
flexmock(module).should_receive('run_configuration').and_raise(ValueError)
|
||||
args = flexmock(extract=False, list=False)
|
||||
arguments = {}
|
||||
|
||||
logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, args=args))
|
||||
logs = tuple(
|
||||
module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||
)
|
||||
|
||||
assert any(log for log in logs if log.levelno == module.logging.CRITICAL)
|
||||
|
||||
@@ -90,19 +102,19 @@ def test_collect_configuration_run_summary_logs_outputs_merged_json_results():
|
||||
['baz']
|
||||
)
|
||||
flexmock(module.sys.stdout).should_receive('write').with_args('["foo", "bar", "baz"]').once()
|
||||
args = flexmock(extract=False, list=False)
|
||||
arguments = {}
|
||||
|
||||
tuple(
|
||||
module.collect_configuration_run_summary_logs(
|
||||
{'test.yaml': {}, 'test2.yaml': {}}, args=args
|
||||
{'test.yaml': {}, 'test2.yaml': {}}, arguments=arguments
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_collect_configuration_run_summary_logs_critical_for_missing_configs():
|
||||
flexmock(module).should_receive('run_configuration').and_return([])
|
||||
args = flexmock(extract=False, list=False, config_paths=[])
|
||||
arguments = {'global': flexmock(config_paths=[])}
|
||||
|
||||
logs = tuple(module.collect_configuration_run_summary_logs({}, args=args))
|
||||
logs = tuple(module.collect_configuration_run_summary_logs({}, arguments=arguments))
|
||||
|
||||
assert any(log for log in logs if log.levelno == module.logging.CRITICAL)
|
||||
|
||||
Reference in New Issue
Block a user