diff --git a/AUTHORS b/AUTHORS index d66be94f..e2274500 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-borgmatic-config command 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/validate_config.py b/borgmatic/commands/validate_config.py new file mode 100644 index 00000000..c7aef4b6 --- /dev/null +++ b/borgmatic/commands/validate_config.py @@ -0,0 +1,56 @@ +from argparse import ArgumentParser +import sys +import logging + +from borgmatic.config import collect, validate + +logger = logging.getLogger(__name__) + + +def parse_arguments(*arguments): + ''' + Given command-line arguments with which this script was invoked, parse the arguments and return + them as an ArgumentParser instance. + ''' + config_paths = collect.get_default_config_paths() + + parser = ArgumentParser(description='Validate borgmatic configuration file(s).') + parser.add_argument( + '-c', + '--config', + nargs='+', + dest='config_paths', + default=config_paths, + help='Configuration filenames or directories, defaults to: {}'.format( + ' '.join(config_paths) + ), + ) + + return parser.parse_args(arguments) + + +def main(): # pragma: no cover + args = parse_arguments(*sys.argv[1:]) + + logging.basicConfig(level=logging.INFO, format='%(message)s') + + config_filenames = tuple(collect.collect_config_filenames(args.config_paths)) + if len(config_filenames) == 0: + logger.critical('No files to validate found') + sys.exit(1) + + found_issues = False + for config_filename in config_filenames: + try: + validate.parse_configuration( + config_filename, validate.schema_filename() + ) + except (ValueError, OSError, validate.Validation_error) as error: + logging.critical('{}: Error parsing configuration file'.format(config_filename)) + logging.critical(error) + found_issues = True + + if found_issues: + sys.exit(1) + else: + logger.info('All given configuration files are valid: {}'.format(config_filenames)) diff --git a/setup.py b/setup.py index 31b18666..76d391b3 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,7 @@ setup( 'borgmatic = borgmatic.commands.borgmatic:main', 'upgrade-borgmatic-config = borgmatic.commands.convert_config:main', 'generate-borgmatic-config = borgmatic.commands.generate_config:main', + 'validate-borgmatic-config = borgmatic.commands.validate_config:main', ] }, obsoletes=['atticmatic'], diff --git a/tests/end-to-end/test_borgmatic.py b/tests/end-to-end/test_borgmatic.py index 620e67a9..40879265 100644 --- a/tests/end-to-end/test_borgmatic.py +++ b/tests/end-to-end/test_borgmatic.py @@ -29,6 +29,36 @@ def generate_configuration(config_path, repository_path): config_file.close() +def validate_valid_configuration(config_path): + ''' + Validate borgmatic configuration which is valid. + ''' + subprocess.check_call( + 'validate-borgmatic-config --config {}'.format(config_path).split(' ') + ) + + +def validate_invalid_configuration(config_path): + ''' + Validate borgmatic configuration which is invalid. + ''' + + config = ( + open(config_path) + .read() + .replace('keep_daily: 7', 'keep_daily: "7"') + ) + config_file = open(config_path, 'w') + config_file.write(config) + config_file.close() + + exit_code = subprocess.call( + 'validate-borgmatic-config --config {}'.format(config_path).split(' ') + ) + + assert exit_code == 1 + + def test_borgmatic_command(): # Create a Borg repository. temporary_directory = tempfile.mkdtemp() @@ -41,7 +71,11 @@ def test_borgmatic_command(): try: config_path = os.path.join(temporary_directory, 'test.yaml') + invalid_config_path = os.path.join(temporary_directory, 'test_invalid.yaml') generate_configuration(config_path, repository_path) + generate_configuration(invalid_config_path, repository_path) + validate_valid_configuration(config_path) + validate_invalid_configuration(invalid_config_path) subprocess.check_call( 'borgmatic -v 2 --config {} --init --encryption repokey'.format(config_path).split(' ')