Add --validate action: Validate borgmatic configuration file and exit
the build failed Details

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.
This commit is contained in:
Robin Schneider 2019-05-05 22:37:36 +02:00
parent 1c88dda76a
commit 5b337e55ca
No known key found for this signature in database
GPG Key ID: A81E8006DC95EFE6
4 changed files with 49 additions and 23 deletions

View File

@ -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

View File

@ -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.')

View File

@ -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.'

View File

@ -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))