From 5b337e55ca45538931574e861f1a3b62a6178ab9 Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Sun, 5 May 2019 22:37:36 +0200 Subject: [PATCH] Add --validate action: Validate borgmatic configuration file and exit Useful when generating the borgmatic configuration file with configuration management and before moving the generated file in place checking if it is actually valid. The added unit tests are currently incomplete. --- AUTHORS | 2 +- borgmatic/commands/borgmatic.py | 40 ++++++++++++-------- tests/integration/commands/test_borgmatic.py | 16 ++++++++ tests/unit/commands/test_borgmatic.py | 14 +++---- 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/AUTHORS b/AUTHORS index d66be94f..500a9d67 100644 --- a/AUTHORS +++ b/AUTHORS @@ -7,6 +7,6 @@ Johannes Feichtner: Support for user hooks Michele Lazzeri: Custom archive names Nick Whyte: Support prefix filtering for archive consistency checks newtonne: Read encryption password from external file -Robin `ypid` Schneider: Support additional options of Borg +Robin `ypid` Schneider: Support additional options of Borg and add --validate action Scott Squires: Custom archive names Thomas LÉVEIL: Support for a keep_minutely prune option. Support for the --json option diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 4ab5a75b..20e8f64f 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -47,6 +47,9 @@ def parse_arguments(*arguments): ) actions_group = parser.add_argument_group('actions') + actions_group.add_argument( + '-V', '--validate', dest='validate', action='store_true', help='Validate borgmatic configuration file and exit' + ) actions_group.add_argument( '-I', '--init', dest='init', action='store_true', help='Initialize an empty Borg repository' ) @@ -420,23 +423,24 @@ def collect_configuration_run_summary_logs(config_filenames, args): # Execute the actions corresponding to each configuration file. json_results = [] - for config_filename, config in configs.items(): - try: - json_results.extend(list(run_configuration(config_filename, config, args))) - yield logging.makeLogRecord( - dict( - levelno=logging.INFO, - msg='{}: Successfully ran configuration file'.format(config_filename), + if not args.validate: + for config_filename, config in configs.items(): + try: + json_results.extend(list(run_configuration(config_filename, config, args))) + yield logging.makeLogRecord( + dict( + levelno=logging.INFO, + msg='{}: Successfully ran configuration file'.format(config_filename), + ) ) - ) - except (ValueError, OSError, CalledProcessError) as error: - yield logging.makeLogRecord( - dict( - levelno=logging.CRITICAL, - msg='{}: Error running configuration file'.format(config_filename), + except (ValueError, OSError, CalledProcessError) as error: + yield logging.makeLogRecord( + dict( + levelno=logging.CRITICAL, + msg='{}: Error running configuration file'.format(config_filename), + ) ) - ) - yield logging.makeLogRecord(dict(levelno=logging.CRITICAL, msg=error)) + yield logging.makeLogRecord(dict(levelno=logging.CRITICAL, msg=error)) if json_results: sys.stdout.write(json.dumps(json_results)) @@ -475,6 +479,7 @@ def main(): # pragma: no cover sys.exit(0) config_filenames = tuple(collect.collect_config_filenames(args.config_paths)) + logger.debug('Ensuring legacy configuration is upgraded') convert.guard_configuration_upgraded(LEGACY_CONFIG_PATH, config_filenames) @@ -484,4 +489,9 @@ def main(): # pragma: no cover [logger.handle(log) for log in summary_logs if log.levelno >= logger.getEffectiveLevel()] if any(log.levelno == logging.CRITICAL for log in summary_logs): + if args.validate: + sys.exit(1) exit_with_help_link() + + if args.validate: + print('Configuration files are valid.') diff --git a/tests/integration/commands/test_borgmatic.py b/tests/integration/commands/test_borgmatic.py index 704dc7c9..dae26628 100644 --- a/tests/integration/commands/test_borgmatic.py +++ b/tests/integration/commands/test_borgmatic.py @@ -268,3 +268,19 @@ def test_borgmatic_version_matches_news_version(): news_version = open('NEWS').readline() assert borgmatic_version == news_version + + +def test_borgmatic_validate_error(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + borgmatic_output = subprocess.check_output(('borgmatic', '--validate')).decode('ascii') + + assert borgmatic_output == 'Configuration files are valid.' + + +def test_borgmatic_validate_ok(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + borgmatic_output = subprocess.check_output(('borgmatic', '--validate')).decode('ascii') + + assert borgmatic_output == 'Configuration files are valid.' diff --git a/tests/unit/commands/test_borgmatic.py b/tests/unit/commands/test_borgmatic.py index e5610878..be0af053 100644 --- a/tests/unit/commands/test_borgmatic.py +++ b/tests/unit/commands/test_borgmatic.py @@ -6,7 +6,7 @@ from borgmatic.commands import borgmatic as module def test_collect_configuration_run_summary_logs_info_for_success(): flexmock(module.validate).should_receive('parse_configuration').and_return({'test.yaml': {}}) flexmock(module).should_receive('run_configuration').and_return([]) - args = flexmock(extract=False, list=False) + args = flexmock(validate=False, extract=False, list=False) logs = tuple(module.collect_configuration_run_summary_logs(('test.yaml',), args=args)) @@ -17,7 +17,7 @@ def test_collect_configuration_run_summary_logs_info_for_success_with_extract(): flexmock(module.validate).should_receive('parse_configuration').and_return({'test.yaml': {}}) 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') + args = flexmock(validate=False, extract=True, list=False, repository='repo') logs = tuple(module.collect_configuration_run_summary_logs(('test.yaml',), args=args)) @@ -51,7 +51,7 @@ def test_collect_configuration_run_summary_logs_critical_for_list_with_archive_a def test_collect_configuration_run_summary_logs_info_for_success_with_list(): flexmock(module.validate).should_receive('parse_configuration').and_return({'test.yaml': {}}) flexmock(module).should_receive('run_configuration').and_return([]) - args = flexmock(extract=False, list=True, repository='repo', archive=None) + args = flexmock(validate=False, extract=False, list=True, repository='repo', archive=None) logs = tuple(module.collect_configuration_run_summary_logs(('test.yaml',), args=args)) @@ -60,7 +60,7 @@ def test_collect_configuration_run_summary_logs_info_for_success_with_list(): def test_collect_configuration_run_summary_logs_critical_for_parse_error(): flexmock(module.validate).should_receive('parse_configuration').and_raise(ValueError) - args = flexmock(extract=False, list=False) + args = flexmock(validate=False, extract=False, list=False) logs = tuple(module.collect_configuration_run_summary_logs(('test.yaml',), args=args)) @@ -71,7 +71,7 @@ def test_collect_configuration_run_summary_logs_critical_for_run_error(): flexmock(module.validate).should_receive('parse_configuration').and_return({'test.yaml': {}}) flexmock(module.validate).should_receive('guard_configuration_contains_repository') flexmock(module).should_receive('run_configuration').and_raise(ValueError) - args = flexmock(extract=False, list=False) + args = flexmock(validate=False, extract=False, list=False) logs = tuple(module.collect_configuration_run_summary_logs(('test.yaml',), args=args)) @@ -81,7 +81,7 @@ def test_collect_configuration_run_summary_logs_critical_for_run_error(): def test_collect_configuration_run_summary_logs_critical_for_missing_configs(): flexmock(module.validate).should_receive('parse_configuration').and_return({'test.yaml': {}}) flexmock(module).should_receive('run_configuration').and_return([]) - args = flexmock(config_paths=(), extract=False, list=False) + args = flexmock(config_paths=(), validate=False, extract=False, list=False) logs = tuple(module.collect_configuration_run_summary_logs(config_filenames=(), args=args)) @@ -96,6 +96,6 @@ 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) + args = flexmock(validate=False, extract=False, list=False) tuple(module.collect_configuration_run_summary_logs(('test.yaml', 'test2.yaml'), args=args)) -- 2.45.1