Support for several more borgmatic/borg info command-line flags (#193).

This commit is contained in:
Dan Helfman 2019-06-25 10:46:55 -07:00
parent c644270599
commit 86dbc00cbe
6 changed files with 134 additions and 35 deletions

6
NEWS
View file

@ -1,6 +1,8 @@
1.3.11.dev0 1.3.11.dev0
* #193: Pass through several "borg list" flags like --short, --format, --sort-by, --first, --last, * #193: Pass through several "borg list" and "borg info" flags like --short, --format, --sort-by,
etc. via borgmatic list command-line flags. --first, --last, etc. via borgmatic command-line flags.
* Add borgmatic info --repository and --archive command-line flags to display info for individual
repositories or archives.
1.3.10 1.3.10
* #198: Fix for Borg create error output not showing up at borgmatic verbosity level zero. * #198: Fix for Borg create error output not showing up at borgmatic verbosity level zero.

View file

@ -1,26 +1,44 @@
import logging import logging
from borgmatic.borg.flags import make_flags, make_flags_from_arguments
from borgmatic.execute import execute_command from borgmatic.execute import execute_command
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def display_archives_info( def display_archives_info(
repository, storage_config, local_path='borg', remote_path=None, json=False repository, storage_config, info_arguments, local_path='borg', remote_path=None
): ):
''' '''
Given a local or remote repository path, and a storage config dict, display summary information Given a local or remote repository path, a storage config dict, and the arguments to the info
for Borg archives in the repository or return JSON summary information. action, display summary information for Borg archives in the repository or return JSON summary
information.
''' '''
lock_wait = storage_config.get('lock_wait', None) lock_wait = storage_config.get('lock_wait', None)
full_command = ( full_command = (
(local_path, 'info', repository) (
+ (('--remote-path', remote_path) if remote_path else ()) local_path,
+ (('--lock-wait', str(lock_wait)) if lock_wait else ()) 'info',
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ()) '::'.join((repository, info_arguments.archive))
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ()) if info_arguments.archive
+ (('--json',) if json else ()) else repository,
)
+ (
('--info',)
if logger.getEffectiveLevel() == logging.INFO and not info_arguments.json
else ()
)
+ (
('--debug', '--show-rc')
if logger.isEnabledFor(logging.DEBUG) and not info_arguments.json
else ()
)
+ make_flags('remote-path', remote_path)
+ make_flags('lock-wait', lock_wait)
+ make_flags_from_arguments(info_arguments, excludes=('repository', 'archive'))
) )
return execute_command(full_command, output_log_level=None if json else logging.WARNING) return execute_command(
full_command, output_log_level=None if info_arguments.json else logging.WARNING
)

View file

@ -224,7 +224,7 @@ def parse_arguments(*unparsed_arguments):
extract_group = extract_parser.add_argument_group('extract arguments') extract_group = extract_parser.add_argument_group('extract arguments')
extract_group.add_argument( extract_group.add_argument(
'--repository', '--repository',
help='Path of repository to use, defaults to the configured repository if there is only one', help='Path of repository to extract, 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('--archive', help='Name of archive to operate on', required=True)
extract_group.add_argument( extract_group.add_argument(
@ -254,9 +254,9 @@ def parse_arguments(*unparsed_arguments):
list_group = list_parser.add_argument_group('list arguments') list_group = list_parser.add_argument_group('list arguments')
list_group.add_argument( list_group.add_argument(
'--repository', '--repository',
help='Path of repository to use, defaults to the configured repository if there is only one', help='Path of repository to list, 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('--archive', help='Name of archive to list')
list_group.add_argument( list_group.add_argument(
'--short', default=False, action='store_true', help='Output only archive or path names' '--short', default=False, action='store_true', help='Output only archive or path names'
) )
@ -301,9 +301,34 @@ def parse_arguments(*unparsed_arguments):
add_help=False, add_help=False,
) )
info_group = info_parser.add_argument_group('info arguments') info_group = info_parser.add_argument_group('info arguments')
info_group.add_argument(
'--repository',
help='Path of repository to show info for, defaults to the configured repository if there is only one',
)
info_group.add_argument('--archive', help='Name of archive to show info for')
info_group.add_argument( info_group.add_argument(
'--json', dest='json', default=False, action='store_true', help='Output results as JSON' '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
) )
info_group.add_argument(
'-P', '--prefix', help='Only show info for archive names starting with this prefix'
)
info_group.add_argument(
'-a',
'--glob-archives',
metavar='GLOB',
help='Only show info for archive names matching this glob',
)
info_group.add_argument(
'--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
)
info_group.add_argument(
'--first',
metavar='N',
help='Show info for first N archives after other filters are applied',
)
info_group.add_argument(
'--last', metavar='N', help='Show info for first N archives after other filters are applied'
)
info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit') 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) arguments = parse_subparser_arguments(unparsed_arguments, top_level_parser, subparsers)

View file

@ -178,16 +178,17 @@ def run_actions(
if json_output: if json_output:
yield json.loads(json_output) yield json.loads(json_output)
if 'info' in arguments: if 'info' in arguments:
logger.info('{}: Displaying summary info for archives'.format(repository)) if arguments['info'].repository is None or repository == arguments['info'].repository:
json_output = borg_info.display_archives_info( logger.info('{}: Displaying summary info for archives'.format(repository))
repository, json_output = borg_info.display_archives_info(
storage, repository,
local_path=local_path, storage,
remote_path=remote_path, info_arguments=arguments['info'],
json=arguments['info'].json, local_path=local_path,
) remote_path=remote_path,
if json_output: )
yield json.loads(json_output) if json_output:
yield json.loads(json_output)
def load_configurations(config_filenames): def load_configurations(config_filenames):

View file

@ -30,7 +30,9 @@ def test_make_flags_from_arguments_flattens_multiple_arguments():
) )
arguments = flexmock(foo='bar', baz='quux') arguments = flexmock(foo='bar', baz='quux')
assert module.make_flags_from_arguments(arguments) == ('foo', 'bar', 'baz', 'quux') assert sorted(module.make_flags_from_arguments(arguments)) == sorted(
('foo', 'bar', 'baz', 'quux')
)
def test_make_flags_from_arguments_excludes_underscored_argument_names(): def test_make_flags_from_arguments_excludes_underscored_argument_names():

View file

@ -1,5 +1,6 @@
import logging import logging
import pytest
from flexmock import flexmock from flexmock import flexmock
from borgmatic.borg import info as module from borgmatic.borg import info as module
@ -14,7 +15,9 @@ def test_display_archives_info_calls_borg_with_parameters():
INFO_COMMAND, output_log_level=logging.WARNING INFO_COMMAND, output_log_level=logging.WARNING
) )
module.display_archives_info(repository='repo', storage_config={}) module.display_archives_info(
repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=False)
)
def test_display_archives_info_with_log_info_calls_borg_with_info_parameter(): def test_display_archives_info_with_log_info_calls_borg_with_info_parameter():
@ -22,7 +25,9 @@ def test_display_archives_info_with_log_info_calls_borg_with_info_parameter():
INFO_COMMAND + ('--info',), output_log_level=logging.WARNING INFO_COMMAND + ('--info',), output_log_level=logging.WARNING
) )
insert_logging_mock(logging.INFO) insert_logging_mock(logging.INFO)
module.display_archives_info(repository='repo', storage_config={}) module.display_archives_info(
repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=False)
)
def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_output(): def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_output():
@ -31,7 +36,9 @@ def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_outpu
).and_return('[]') ).and_return('[]')
insert_logging_mock(logging.INFO) insert_logging_mock(logging.INFO)
json_output = module.display_archives_info(repository='repo', storage_config={}, json=True) json_output = module.display_archives_info(
repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=True)
)
assert json_output == '[]' assert json_output == '[]'
@ -42,7 +49,9 @@ def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter():
) )
insert_logging_mock(logging.DEBUG) insert_logging_mock(logging.DEBUG)
module.display_archives_info(repository='repo', storage_config={}) module.display_archives_info(
repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=False)
)
def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_output(): def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_output():
@ -51,7 +60,9 @@ def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_outp
).and_return('[]') ).and_return('[]')
insert_logging_mock(logging.DEBUG) insert_logging_mock(logging.DEBUG)
json_output = module.display_archives_info(repository='repo', storage_config={}, json=True) json_output = module.display_archives_info(
repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=True)
)
assert json_output == '[]' assert json_output == '[]'
@ -61,17 +72,34 @@ def test_display_archives_info_with_json_calls_borg_with_json_parameter():
INFO_COMMAND + ('--json',), output_log_level=None INFO_COMMAND + ('--json',), output_log_level=None
).and_return('[]') ).and_return('[]')
json_output = module.display_archives_info(repository='repo', storage_config={}, json=True) json_output = module.display_archives_info(
repository='repo', storage_config={}, info_arguments=flexmock(archive=None, json=True)
)
assert json_output == '[]' assert json_output == '[]'
def test_display_archives_info_with_archive_calls_borg_with_archive_parameter():
flexmock(module).should_receive('execute_command').with_args(
('borg', 'info', 'repo::archive'), output_log_level=logging.WARNING
)
module.display_archives_info(
repository='repo', storage_config={}, info_arguments=flexmock(archive='archive', json=False)
)
def test_display_archives_info_with_local_path_calls_borg_via_local_path(): def test_display_archives_info_with_local_path_calls_borg_via_local_path():
flexmock(module).should_receive('execute_command').with_args( flexmock(module).should_receive('execute_command').with_args(
('borg1',) + INFO_COMMAND[1:], output_log_level=logging.WARNING ('borg1',) + INFO_COMMAND[1:], output_log_level=logging.WARNING
) )
module.display_archives_info(repository='repo', storage_config={}, local_path='borg1') module.display_archives_info(
repository='repo',
storage_config={},
info_arguments=flexmock(archive=None, json=False),
local_path='borg1',
)
def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_parameters(): def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_parameters():
@ -79,7 +107,12 @@ def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_para
INFO_COMMAND + ('--remote-path', 'borg1'), output_log_level=logging.WARNING INFO_COMMAND + ('--remote-path', 'borg1'), output_log_level=logging.WARNING
) )
module.display_archives_info(repository='repo', storage_config={}, remote_path='borg1') module.display_archives_info(
repository='repo',
storage_config={},
info_arguments=flexmock(archive=None, json=False),
remote_path='borg1',
)
def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_parameters(): def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_parameters():
@ -88,4 +121,22 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete
INFO_COMMAND + ('--lock-wait', '5'), output_log_level=logging.WARNING INFO_COMMAND + ('--lock-wait', '5'), output_log_level=logging.WARNING
) )
module.display_archives_info(repository='repo', storage_config=storage_config) module.display_archives_info(
repository='repo',
storage_config=storage_config,
info_arguments=flexmock(archive=None, json=False),
)
@pytest.mark.parametrize('argument_name', ('prefix', 'glob_archives', 'sort_by', 'first', 'last'))
def test_display_archives_info_passes_through_arguments_to_borg(argument_name):
flexmock(module).should_receive('execute_command').with_args(
INFO_COMMAND + ('--' + argument_name.replace('_', '-'), 'value'),
output_log_level=logging.WARNING,
)
module.display_archives_info(
repository='repo',
storage_config={},
info_arguments=flexmock(archive=None, json=False, **{argument_name: 'value'}),
)