Require "prefix" in retention section when "archive_name_format" is set.
This commit is contained in:
parent
f1c07b5cf5
commit
43d0e597a2
6 changed files with 79 additions and 25 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,6 +1,8 @@
|
||||||
*.egg-info
|
*.egg-info
|
||||||
*.pyc
|
*.pyc
|
||||||
*.swp
|
*.swp
|
||||||
|
.cache
|
||||||
|
.coverage
|
||||||
.tox
|
.tox
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
|
|
6
NEWS
6
NEWS
|
@ -2,8 +2,10 @@
|
||||||
* #16, #38: Support for user-defined hooks before/after backup, or on error.
|
* #16, #38: Support for user-defined hooks before/after backup, or on error.
|
||||||
* #33: Improve clarity of logging spew at high verbosity levels.
|
* #33: Improve clarity of logging spew at high verbosity levels.
|
||||||
* #29: Support for using tilde in source directory path to reference home directory.
|
* #29: Support for using tilde in source directory path to reference home directory.
|
||||||
* Converted main source repository from Mercurial to Git.
|
* Require "prefix" in retention section when "archive_name_format" is set. This is to avoid
|
||||||
* Updated dead links to Borg documentation.
|
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
|
1.1.8
|
||||||
* #39: Fix to make /etc/borgmatic/config.yaml optional rather than required when using the default
|
* #39: Fix to make /etc/borgmatic/config.yaml optional rather than required when using the default
|
||||||
|
|
|
@ -94,7 +94,9 @@ map:
|
||||||
desc: |
|
desc: |
|
||||||
Name of the archive. Borg placeholders can be used. See the output of
|
Name of the archive. Borg placeholders can be used. See the output of
|
||||||
"borg help placeholders" for details. Default is
|
"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}"
|
example: "{hostname}-documents-{now}"
|
||||||
retention:
|
retention:
|
||||||
desc: |
|
desc: |
|
||||||
|
|
|
@ -25,6 +25,31 @@ class Validation_error(ValueError):
|
||||||
self.config_filename = config_filename
|
self.config_filename = config_filename
|
||||||
self.error_messages = error_messages
|
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):
|
def parse_configuration(config_filename, schema_filename):
|
||||||
'''
|
'''
|
||||||
|
@ -58,19 +83,6 @@ def parse_configuration(config_filename, schema_filename):
|
||||||
if validator.validation_errors:
|
if validator.validation_errors:
|
||||||
raise Validation_error(config_filename, validator.validation_errors)
|
raise Validation_error(config_filename, validator.validation_errors)
|
||||||
|
|
||||||
|
apply_logical_validation(config_filename, parsed_result)
|
||||||
|
|
||||||
return 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)
|
|
||||||
|
|
|
@ -148,10 +148,3 @@ def test_parse_configuration_raises_for_validation_error():
|
||||||
|
|
||||||
with pytest.raises(module.Validation_error):
|
with pytest.raises(module.Validation_error):
|
||||||
module.parse_configuration('config.yaml', 'schema.yaml')
|
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)
|
|
||||||
|
|
43
borgmatic/tests/unit/config/test_validate.py
Normal file
43
borgmatic/tests/unit/config/test_validate.py
Normal 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},
|
||||||
|
},
|
||||||
|
)
|
Loading…
Reference in a new issue