From 81739791e0323b7531628ddcea32d51967ed9b6b Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Thu, 19 Sep 2019 11:43:53 -0700 Subject: [PATCH] Override configured consistency checks via "borgmatic check --only" command-line flag (#210). --- NEWS | 3 ++- borgmatic/borg/check.py | 25 +++++++++++++++++-------- borgmatic/commands/arguments.py | 8 ++++++++ borgmatic/commands/borgmatic.py | 7 ++++++- setup.py | 2 +- tests/unit/borg/test_check.py | 12 ++++++++++++ 6 files changed, 46 insertions(+), 11 deletions(-) diff --git a/NEWS b/NEWS index 6304ced..deb42b0 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,6 @@ -1.3.16.dev0 +1.3.16 * #210: Support for Borg check --verify-data flag via borgmatic "data" consistency check. + * #210: Override configured consistency checks via "borgmatic check --only" command-line flag. * When generating sample configuration with generate-borgmatic-config, add a space after each "#" comment indicator. diff --git a/borgmatic/borg/check.py b/borgmatic/borg/check.py index 08924c2..8f8da23 100644 --- a/borgmatic/borg/check.py +++ b/borgmatic/borg/check.py @@ -10,9 +10,10 @@ DEFAULT_PREFIX = '{hostname}-' logger = logging.getLogger(__name__) -def _parse_checks(consistency_config): +def _parse_checks(consistency_config, only_checks=None): ''' - Given a consistency config with a "checks" list, transform it to a tuple of named checks to run. + Given a consistency config with a "checks" list, and an optional list of override checks, + transform them a tuple of named checks to run. For example, given a retention config of: @@ -22,12 +23,14 @@ def _parse_checks(consistency_config): ('repository', 'archives') - If no "checks" option is present, return the DEFAULT_CHECKS. If the checks value is the string - "disabled", return an empty tuple, meaning that no checks should be run. + If no "checks" option is present in the config, return the DEFAULT_CHECKS. If the checks value + is the string "disabled", return an empty tuple, meaning that no checks should be run. If the "data" option is present, then make sure the "archives" option is included as well. ''' - checks = [check.lower() for check in (consistency_config.get('checks', []) or [])] + checks = [ + check.lower() for check in (only_checks or consistency_config.get('checks', []) or []) + ] if checks == ['disabled']: return () @@ -83,15 +86,21 @@ def _make_check_flags(checks, check_last=None, prefix=None): def check_archives( - repository, storage_config, consistency_config, local_path='borg', remote_path=None + repository, + storage_config, + consistency_config, + local_path='borg', + remote_path=None, + only_checks=None, ): ''' Given a local or remote repository path, a storage config dict, a consistency config dict, - and a local/remote commands to run, check the contained Borg archives for consistency. + local/remote commands to run, and an optional list of checks to use instead of configured + checks, check the contained Borg archives for consistency. If there are no consistency checks to run, skip running them. ''' - checks = _parse_checks(consistency_config) + checks = _parse_checks(consistency_config, only_checks) check_last = consistency_config.get('check_last', None) lock_wait = None diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 0171a95..b9f7818 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -212,6 +212,14 @@ def parse_arguments(*unparsed_arguments): add_help=False, ) check_group = check_parser.add_argument_group('check arguments') + check_group.add_argument( + '--only', + metavar='CHECK', + choices=('repository', 'archives', 'data', 'extract'), + dest='only', + action='append', + help='Run a particular consistency check instead of configured checks (can specify multiple times)', + ) check_group.add_argument('-h', '--help', action='help', help='Show this help message and exit') extract_parser = subparsers.add_parser( diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 63ddc8b..7035502 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -147,7 +147,12 @@ def run_actions( 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 + repository, + storage, + consistency, + local_path=local_path, + remote_path=remote_path, + only_checks=arguments['check'].only, ) if 'extract' in arguments: if arguments['extract'].repository is None or repository == arguments['extract'].repository: diff --git a/setup.py b/setup.py index 443a041..af122d3 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -VERSION = '1.3.16.dev0' +VERSION = '1.3.16' setup( diff --git a/tests/unit/borg/test_check.py b/tests/unit/borg/test_check.py index 3802cee..4675a5e 100644 --- a/tests/unit/borg/test_check.py +++ b/tests/unit/borg/test_check.py @@ -58,6 +58,18 @@ def test_parse_checks_with_data_check_passes_through_archives(): assert checks == ('data', 'archives') +def test_parse_checks_prefers_override_checks_to_configured_checks(): + checks = module._parse_checks({'checks': ['archives']}, only_checks=['repository', 'extract']) + + assert checks == ('repository', 'extract') + + +def test_parse_checks_with_override_data_check_also_injects_archives(): + checks = module._parse_checks({'checks': ['extract']}, only_checks=['data']) + + assert checks == ('data', 'archives') + + def test_make_check_flags_with_repository_check_returns_flag(): flags = module._make_check_flags(('repository',))