From 4cdff74e9b953118c3afb6bddaefeb6a266a2995 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Wed, 18 Sep 2019 16:52:27 -0700 Subject: [PATCH] Support for Borg check --verify-data flag via borgmatic "data" consistency check (#210). --- NEWS | 1 + borgmatic/borg/check.py | 20 ++++++++++++-------- borgmatic/config/schema.yaml | 13 +++++++------ tests/unit/borg/test_check.py | 24 ++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/NEWS b/NEWS index 6a10c2a..6304ced 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,5 @@ 1.3.16.dev0 + * #210: Support for Borg check --verify-data flag via borgmatic "data" consistency check. * 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 393ab57..08924c2 100644 --- a/borgmatic/borg/check.py +++ b/borgmatic/borg/check.py @@ -24,14 +24,17 @@ def _parse_checks(consistency_config): 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 the "data" option is present, then make sure the "archives" option is included as well. ''' - checks = consistency_config.get('checks', []) or [] + checks = [check.lower() for check in (consistency_config.get('checks', []) or [])] if checks == ['disabled']: return () - return ( - tuple(check for check in checks if check.lower() not in ('disabled', '')) or DEFAULT_CHECKS - ) + if 'data' in checks and 'archives' not in checks: + checks.append('archives') + + return tuple(check for check in checks if check not in ('disabled', '')) or DEFAULT_CHECKS def _make_check_flags(checks, check_last=None, prefix=None): @@ -68,13 +71,14 @@ def _make_check_flags(checks, check_last=None, prefix=None): 'Ignoring consistency prefix option, as "archives" is not in consistency checks.' ) + common_flags = last_flags + prefix_flags + (('--verify-data',) if 'data' in checks else ()) + if set(DEFAULT_CHECKS).issubset(set(checks)): - return last_flags + prefix_flags + return common_flags return ( tuple('--{}-only'.format(check) for check in checks if check in DEFAULT_CHECKS) - + last_flags - + prefix_flags + + common_flags ) @@ -91,7 +95,7 @@ def check_archives( check_last = consistency_config.get('check_last', None) lock_wait = None - if set(checks).intersection(set(DEFAULT_CHECKS)): + if set(checks).intersection(set(DEFAULT_CHECKS + ('data',))): remote_path_flags = ('--remote-path', remote_path) if remote_path else () lock_wait = storage_config.get('lock_wait', None) lock_wait_flags = ('--lock-wait', str(lock_wait)) if lock_wait else () diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 521dce7..34f8bee 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -290,14 +290,15 @@ map: checks: seq: - type: str - enum: ['repository', 'archives', 'extract', 'disabled'] + enum: ['repository', 'archives', 'data', 'extract', 'disabled'] unique: true desc: | - List of one or more consistency checks to run: "repository", "archives", and/or - "extract". Defaults to "repository" and "archives". Set to "disabled" to disable - all consistency checks. "repository" checks the consistency of the repository, - "archive" checks all of the archives, and "extract" does an extraction dry-run - of the most recent archive. + List of one or more consistency checks to run: "repository", "archives", "data", + and/or "extract". Defaults to "repository" and "archives". Set to "disabled" to + disable all consistency checks. "repository" checks the consistency of the + repository, "archives" checks all of the archives, "data" verifies the integrity + of the data within the archives, and "extract" does an extraction dry-run of the + most recent archive. Note that "data" implies "archives". example: - repository - archives diff --git a/tests/unit/borg/test_check.py b/tests/unit/borg/test_check.py index 2dc0e11..3802cee 100644 --- a/tests/unit/borg/test_check.py +++ b/tests/unit/borg/test_check.py @@ -46,12 +46,36 @@ def test_parse_checks_with_disabled_returns_no_checks(): assert checks == () +def test_parse_checks_with_data_check_also_injects_archives(): + checks = module._parse_checks({'checks': ['data']}) + + assert checks == ('data', 'archives') + + +def test_parse_checks_with_data_check_passes_through_archives(): + checks = module._parse_checks({'checks': ['data', 'archives']}) + + assert checks == ('data', 'archives') + + def test_make_check_flags_with_repository_check_returns_flag(): flags = module._make_check_flags(('repository',)) assert flags == ('--repository-only',) +def test_make_check_flags_with_archives_check_returns_flag(): + flags = module._make_check_flags(('archives',)) + + assert flags == ('--archives-only',) + + +def test_make_check_flags_with_data_check_returns_flag(): + flags = module._make_check_flags(('data',)) + + assert flags == ('--verify-data',) + + def test_make_check_flags_with_extract_omits_extract_flag(): flags = module._make_check_flags(('extract',))