Require "prefix" in retention section when "archive_name_format" is set.

This commit is contained in:
Dan 2017-10-29 19:36:26 -07:00
parent f1c07b5cf5
commit 43d0e597a2
6 changed files with 79 additions and 25 deletions

2
.gitignore vendored
View File

@ -1,6 +1,8 @@
*.egg-info
*.pyc
*.swp
.cache
.coverage
.tox
build
dist

6
NEWS
View File

@ -2,8 +2,10 @@
* #16, #38: Support for user-defined hooks before/after backup, or on error.
* #33: Improve clarity of logging spew at high verbosity levels.
* #29: Support for using tilde in source directory path to reference home directory.
* Converted main source repository from Mercurial to Git.
* Updated dead links to Borg documentation.
* Require "prefix" in retention section when "archive_name_format" is set. This is to avoid
accidental pruning of archives with a different archive name format.
* Convert main source repository from Mercurial to Git.
* Update dead links to Borg documentation.
1.1.8
* #39: Fix to make /etc/borgmatic/config.yaml optional rather than required when using the default

View File

@ -94,7 +94,9 @@ map:
desc: |
Name of the archive. Borg placeholders can be used. See the output of
"borg help placeholders" for details. Default is
"{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}"
"{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this option, you must
also specify a prefix in the retention section to avoid accidental pruning of
archives with a different archive name format.
example: "{hostname}-documents-{now}"
retention:
desc: |

View File

@ -25,6 +25,31 @@ class Validation_error(ValueError):
self.config_filename = config_filename
self.error_messages = error_messages
def __str__(self):
'''
Render a validation error as a user-facing string.
'''
return 'An error occurred while parsing a configuration file at {}:\n'.format(
self.config_filename
) + '\n'.join(self.error_messages)
def apply_logical_validation(config_filename, parsed_configuration):
'''
Given a parsed and schematically valid configuration as a data structure of nested dicts (see
below), run through any additional logical validation checks. If there are any such validation
problems, raise a Validation_error.
'''
archive_name_format = parsed_configuration.get('storage', {}).get('archive_name_format')
prefix = parsed_configuration.get('retention', {}).get('prefix')
if archive_name_format and not prefix:
raise Validation_error(
config_filename, (
'If you provide an archive_name_format, you must also specify a retention prefix.',
)
)
def parse_configuration(config_filename, schema_filename):
'''
@ -58,19 +83,6 @@ def parse_configuration(config_filename, schema_filename):
if validator.validation_errors:
raise Validation_error(config_filename, validator.validation_errors)
apply_logical_validation(config_filename, parsed_result)
return parsed_result
def display_validation_error(validation_error):
'''
Given a Validation_error, display its error messages to stderr.
'''
print(
'An error occurred while parsing a configuration file at {}:'.format(
validation_error.config_filename
),
file=sys.stderr,
)
for error in validation_error.error_messages:
print(error, file=sys.stderr)

View File

@ -148,10 +148,3 @@ def test_parse_configuration_raises_for_validation_error():
with pytest.raises(module.Validation_error):
module.parse_configuration('config.yaml', 'schema.yaml')
def test_display_validation_error_does_not_raise():
flexmock(sys.modules['builtins']).should_receive('print')
error = module.Validation_error('config.yaml', ('oops', 'uh oh'))
module.display_validation_error(error)

View File

@ -0,0 +1,43 @@
import pytest
from borgmatic.config import validate as module
def test_validation_error_str_contains_error_messages_and_config_filename():
error = module.Validation_error('config.yaml', ('oops', 'uh oh'))
result = str(error)
assert 'config.yaml' in result
assert 'oops' in result
assert 'uh oh' in result
def test_apply_logical_validation_raises_if_archive_name_format_present_without_prefix():
with pytest.raises(module.Validation_error):
module.apply_logical_validation(
'config.yaml',
{
'storage': {'archive_name_format': '{hostname}-{now}'},
'retention': {'keep_daily': 7},
},
)
def test_apply_logical_validation_does_not_raise_if_archive_name_format_and_prefix_present():
module.apply_logical_validation(
'config.yaml',
{
'storage': {'archive_name_format': '{hostname}-{now}'},
'retention': {'prefix': '{hostname}-'},
},
)
def test_apply_logical_validation_does_not_raise_otherwise():
module.apply_logical_validation(
'config.yaml',
{
'retention': {'keep_secondly': 1000},
},
)