From 4c0e2cab78f2b13823a8f44f957911422770b960 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Tue, 11 Apr 2023 10:49:09 -0700 Subject: [PATCH] View the results of configuration file merging via "validate-borgmatic-config --show" flag (#673). --- NEWS | 3 +++ borgmatic/commands/validate_config.py | 26 +++++++++++++++---- docs/how-to/make-per-application-backups.md | 17 ++++++++++++ docs/how-to/set-up-backups.md | 3 +++ tests/end-to-end/test_validate_config.py | 14 ++++++++++ .../commands/test_validate_config.py | 9 +++++++ 6 files changed, 67 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index 81ebcf3..c9869b9 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,9 @@ "match_archives" configuration option for the "transfer", "list", "rlist", and "info" actions. * #668: Fix error when running the "prune" action with both "archive_name_format" and "prefix" options set. + * #673: View the results of configuration file merging via "validate-borgmatic-config --show" flag. + See the documentation for more information: + https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/#debugging-includes * Add optional support for running end-to-end tests and building documentation with rootless Podman instead of Docker. diff --git a/borgmatic/commands/validate_config.py b/borgmatic/commands/validate_config.py index 44c0082..8aa8d32 100644 --- a/borgmatic/commands/validate_config.py +++ b/borgmatic/commands/validate_config.py @@ -2,6 +2,7 @@ import logging import sys from argparse import ArgumentParser +import borgmatic.config.generate from borgmatic.config import collect, validate logger = logging.getLogger(__name__) @@ -23,16 +24,22 @@ def parse_arguments(*arguments): default=config_paths, help=f'Configuration filenames or directories, defaults to: {config_paths}', ) + parser.add_argument( + '-s', + '--show', + action='store_true', + help='Show the validated configuration after all include merging has occurred', + ) return parser.parse_args(arguments) def main(): # pragma: no cover - args = parse_arguments(*sys.argv[1:]) + arguments = parse_arguments(*sys.argv[1:]) logging.basicConfig(level=logging.INFO, format='%(message)s') - config_filenames = tuple(collect.collect_config_filenames(args.config_paths)) + config_filenames = tuple(collect.collect_config_filenames(arguments.config_paths)) if len(config_filenames) == 0: logger.critical('No files to validate found') sys.exit(1) @@ -40,13 +47,22 @@ def main(): # pragma: no cover found_issues = False for config_filename in config_filenames: try: - validate.parse_configuration(config_filename, validate.schema_filename()) + config, parse_logs = validate.parse_configuration( + config_filename, validate.schema_filename() + ) except (ValueError, OSError, validate.Validation_error) as error: logging.critical(f'{config_filename}: Error parsing configuration file') logging.critical(error) found_issues = True + else: + for log in parse_logs: + logger.handle(log) + + if arguments.show: + print('---') + print(borgmatic.config.generate.render_configuration(config)) if found_issues: sys.exit(1) - else: - logger.info(f"All given configuration files are valid: {', '.join(config_filenames)}") + + logger.info(f"All given configuration files are valid: {', '.join(config_filenames)}") diff --git a/docs/how-to/make-per-application-backups.md b/docs/how-to/make-per-application-backups.md index 99f665b..ab29347 100644 --- a/docs/how-to/make-per-application-backups.md +++ b/docs/how-to/make-per-application-backups.md @@ -276,6 +276,23 @@ include, the local file's option takes precedence. list values are appended together. +## Debugging includes + +New in version 1.7.12 If you'd +like to see what the loaded configuration looks like after includes get merged +in, run `validate-borgmatic-config` on your configuration file: + +```bash +sudo validate-borgmatic-config --show +``` + +You'll need to specify your configuration file with `--config` if it's not in +a default location. + +This will output the merged configuration as borgmatic sees it, which can be +helpful for understanding how your includes work in practice. + + ## Configuration overrides In more complex multi-application setups, you may want to override particular diff --git a/docs/how-to/set-up-backups.md b/docs/how-to/set-up-backups.md index 01d79ff..917eb43 100644 --- a/docs/how-to/set-up-backups.md +++ b/docs/how-to/set-up-backups.md @@ -180,6 +180,9 @@ following command is available for that: sudo validate-borgmatic-config ``` +You'll need to specify your configuration file with `--config` if it's not in +a default location. + This command's exit status (`$?` in Bash) is zero when configuration is valid and non-zero otherwise. diff --git a/tests/end-to-end/test_validate_config.py b/tests/end-to-end/test_validate_config.py index d41464e..5446503 100644 --- a/tests/end-to-end/test_validate_config.py +++ b/tests/end-to-end/test_validate_config.py @@ -1,5 +1,6 @@ import os import subprocess +import sys import tempfile @@ -26,3 +27,16 @@ def test_validate_config_command_with_invalid_configuration_fails(): exit_code = subprocess.call(f'validate-borgmatic-config --config {config_path}'.split(' ')) assert exit_code == 1 + + +def test_validate_config_command_with_show_flag_displays_configuration(): + with tempfile.TemporaryDirectory() as temporary_directory: + config_path = os.path.join(temporary_directory, 'test.yaml') + + subprocess.check_call(f'generate-borgmatic-config --destination {config_path}'.split(' ')) + output = subprocess.check_output( + f'validate-borgmatic-config --config {config_path} --show'.split(' ') + ).decode(sys.stdout.encoding) + + assert 'location:' in output + assert 'repositories:' in output diff --git a/tests/integration/commands/test_validate_config.py b/tests/integration/commands/test_validate_config.py index cb0144d..78887e7 100644 --- a/tests/integration/commands/test_validate_config.py +++ b/tests/integration/commands/test_validate_config.py @@ -18,3 +18,12 @@ def test_parse_arguments_with_multiple_config_paths_parses_as_list(): parser = module.parse_arguments('--config', 'myconfig', 'otherconfig') assert parser.config_paths == ['myconfig', 'otherconfig'] + + +def test_parse_arguments_supports_show_flag(): + config_paths = ['default'] + flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths) + + parser = module.parse_arguments('--config', 'myconfig', '--show') + + assert parser.show