2017-07-05 03:32:37 +02:00
|
|
|
import io
|
|
|
|
import string
|
|
|
|
import sys
|
|
|
|
|
|
|
|
import pytest
|
2019-05-13 23:39:10 +02:00
|
|
|
from flexmock import flexmock
|
2017-07-05 03:32:37 +02:00
|
|
|
|
2017-07-09 07:33:51 +02:00
|
|
|
from borgmatic.config import validate as module
|
2017-07-05 03:32:37 +02:00
|
|
|
|
|
|
|
|
2023-04-01 18:40:32 +02:00
|
|
|
def test_schema_filename_returns_plausible_path():
|
2018-09-30 07:45:00 +02:00
|
|
|
schema_path = module.schema_filename()
|
2017-07-05 03:32:37 +02:00
|
|
|
|
|
|
|
assert schema_path.endswith('/schema.yaml')
|
|
|
|
|
|
|
|
|
2017-10-26 07:32:06 +02:00
|
|
|
def mock_config_and_schema(config_yaml, schema_yaml=None):
|
2017-07-05 03:32:37 +02:00
|
|
|
'''
|
2017-10-26 07:32:06 +02:00
|
|
|
Set up mocks for the given config config YAML string and the schema YAML string, or the default
|
|
|
|
schema if no schema is provided. The idea is that that the code under test consumes these mocks
|
|
|
|
when parsing the configuration.
|
2017-07-05 03:32:37 +02:00
|
|
|
'''
|
2017-07-09 07:33:51 +02:00
|
|
|
config_stream = io.StringIO(config_yaml)
|
2022-05-20 02:15:05 +02:00
|
|
|
config_stream.name = 'config.yaml'
|
|
|
|
|
2017-10-26 07:32:06 +02:00
|
|
|
if schema_yaml is None:
|
|
|
|
schema_stream = open(module.schema_filename())
|
|
|
|
else:
|
|
|
|
schema_stream = io.StringIO(schema_yaml)
|
2022-05-20 02:15:05 +02:00
|
|
|
schema_stream.name = 'schema.yaml'
|
2017-10-26 07:32:06 +02:00
|
|
|
|
2017-07-09 08:01:41 +02:00
|
|
|
builtins = flexmock(sys.modules['builtins'])
|
2022-05-20 02:15:05 +02:00
|
|
|
flexmock(module.os).should_receive('getcwd').and_return('/tmp')
|
|
|
|
flexmock(module.os.path).should_receive('isabs').and_return(False)
|
|
|
|
flexmock(module.os.path).should_receive('exists').and_return(True)
|
|
|
|
builtins.should_receive('open').with_args('/tmp/config.yaml').and_return(config_stream)
|
|
|
|
builtins.should_receive('open').with_args('/tmp/schema.yaml').and_return(schema_stream)
|
2017-07-05 03:32:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_configuration_transforms_file_into_mapping():
|
|
|
|
mock_config_and_schema(
|
|
|
|
'''
|
2023-07-09 08:14:30 +02:00
|
|
|
source_directories:
|
|
|
|
- /home
|
|
|
|
- /etc
|
2017-07-05 03:32:37 +02:00
|
|
|
|
2023-07-09 08:14:30 +02:00
|
|
|
repositories:
|
|
|
|
- path: hostname.borg
|
2017-07-05 03:32:37 +02:00
|
|
|
|
2023-07-09 08:14:30 +02:00
|
|
|
keep_minutely: 60
|
|
|
|
keep_hourly: 24
|
|
|
|
keep_daily: 7
|
|
|
|
|
|
|
|
checks:
|
|
|
|
- name: repository
|
|
|
|
- name: archives
|
2017-07-05 03:32:37 +02:00
|
|
|
'''
|
|
|
|
)
|
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
config, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
2017-07-05 03:32:37 +02:00
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
assert config == {
|
2023-07-09 08:14:30 +02:00
|
|
|
'source_directories': ['/home', '/etc'],
|
|
|
|
'repositories': [{'path': 'hostname.borg'}],
|
|
|
|
'keep_daily': 7,
|
|
|
|
'keep_hourly': 24,
|
|
|
|
'keep_minutely': 60,
|
|
|
|
'checks': [{'name': 'repository'}, {'name': 'archives'}],
|
2017-07-05 03:32:37 +02:00
|
|
|
}
|
2022-08-17 19:13:11 +02:00
|
|
|
assert logs == []
|
2017-07-05 03:32:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_configuration_passes_through_quoted_punctuation():
|
|
|
|
escaped_punctuation = string.punctuation.replace('\\', r'\\').replace('"', r'\"')
|
|
|
|
|
|
|
|
mock_config_and_schema(
|
2022-08-17 19:13:11 +02:00
|
|
|
f'''
|
2023-07-09 08:14:30 +02:00
|
|
|
source_directories:
|
|
|
|
- "/home/{escaped_punctuation}"
|
2017-07-05 03:32:37 +02:00
|
|
|
|
2023-07-09 08:14:30 +02:00
|
|
|
repositories:
|
|
|
|
- path: test.borg
|
2022-08-17 19:13:11 +02:00
|
|
|
'''
|
2017-07-05 03:32:37 +02:00
|
|
|
)
|
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
config, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
2017-07-05 03:32:37 +02:00
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
assert config == {
|
2023-07-09 08:14:30 +02:00
|
|
|
'source_directories': [f'/home/{string.punctuation}'],
|
|
|
|
'repositories': [{'path': 'test.borg'}],
|
2017-07-05 03:32:37 +02:00
|
|
|
}
|
2022-08-17 19:13:11 +02:00
|
|
|
assert logs == []
|
2017-07-05 03:32:37 +02:00
|
|
|
|
|
|
|
|
2017-10-26 07:32:06 +02:00
|
|
|
def test_parse_configuration_with_schema_lacking_examples_does_not_raise():
|
|
|
|
mock_config_and_schema(
|
|
|
|
'''
|
2023-07-09 08:14:30 +02:00
|
|
|
source_directories:
|
|
|
|
- /home
|
2017-10-26 07:32:06 +02:00
|
|
|
|
2023-07-09 08:14:30 +02:00
|
|
|
repositories:
|
|
|
|
- path: hostname.borg
|
2017-10-26 07:32:06 +02:00
|
|
|
''',
|
|
|
|
'''
|
|
|
|
map:
|
2023-07-09 08:14:30 +02:00
|
|
|
source_directories:
|
|
|
|
required: true
|
|
|
|
seq:
|
|
|
|
- type: scalar
|
|
|
|
repositories:
|
2017-10-26 07:32:06 +02:00
|
|
|
required: true
|
2023-07-09 08:14:30 +02:00
|
|
|
seq:
|
|
|
|
- type: scalar
|
2018-09-30 07:45:00 +02:00
|
|
|
''',
|
2017-10-26 07:32:06 +02:00
|
|
|
)
|
|
|
|
|
2022-05-20 02:15:05 +02:00
|
|
|
module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
2017-10-26 07:32:06 +02:00
|
|
|
|
|
|
|
|
2023-08-01 23:12:35 +02:00
|
|
|
def test_parse_configuration_inlines_include_inside_deprecated_section():
|
2019-03-06 21:06:27 +01:00
|
|
|
mock_config_and_schema(
|
|
|
|
'''
|
2023-07-09 08:14:30 +02:00
|
|
|
source_directories:
|
|
|
|
- /home
|
2019-03-06 21:06:27 +01:00
|
|
|
|
2023-07-09 08:14:30 +02:00
|
|
|
repositories:
|
|
|
|
- path: hostname.borg
|
2019-03-06 21:06:27 +01:00
|
|
|
|
|
|
|
retention:
|
|
|
|
!include include.yaml
|
|
|
|
'''
|
|
|
|
)
|
|
|
|
builtins = flexmock(sys.modules['builtins'])
|
2022-05-20 02:15:05 +02:00
|
|
|
include_file = io.StringIO(
|
2019-03-06 21:06:27 +01:00
|
|
|
'''
|
|
|
|
keep_daily: 7
|
|
|
|
keep_hourly: 24
|
|
|
|
'''
|
|
|
|
)
|
2022-05-20 02:15:05 +02:00
|
|
|
include_file.name = 'include.yaml'
|
|
|
|
builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
|
2019-03-06 21:06:27 +01:00
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
config, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
2019-03-06 21:06:27 +01:00
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
assert config == {
|
2023-07-09 08:14:30 +02:00
|
|
|
'source_directories': ['/home'],
|
|
|
|
'repositories': [{'path': 'hostname.borg'}],
|
|
|
|
'keep_daily': 7,
|
|
|
|
'keep_hourly': 24,
|
2019-03-06 21:06:27 +01:00
|
|
|
}
|
2023-07-09 08:14:30 +02:00
|
|
|
assert len(logs) == 1
|
2019-03-06 21:06:27 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_configuration_merges_include():
|
|
|
|
mock_config_and_schema(
|
|
|
|
'''
|
2023-07-09 08:14:30 +02:00
|
|
|
source_directories:
|
|
|
|
- /home
|
2019-03-06 21:06:27 +01:00
|
|
|
|
2023-07-09 08:14:30 +02:00
|
|
|
repositories:
|
|
|
|
- path: hostname.borg
|
2019-03-06 21:06:27 +01:00
|
|
|
|
2023-07-09 08:14:30 +02:00
|
|
|
keep_daily: 1
|
|
|
|
<<: !include include.yaml
|
2019-03-06 21:06:27 +01:00
|
|
|
'''
|
|
|
|
)
|
|
|
|
builtins = flexmock(sys.modules['builtins'])
|
2022-05-20 02:15:05 +02:00
|
|
|
include_file = io.StringIO(
|
2019-03-06 21:06:27 +01:00
|
|
|
'''
|
|
|
|
keep_daily: 7
|
|
|
|
keep_hourly: 24
|
|
|
|
'''
|
|
|
|
)
|
2022-05-20 02:15:05 +02:00
|
|
|
include_file.name = 'include.yaml'
|
|
|
|
builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)
|
2019-03-06 21:06:27 +01:00
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
config, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
2019-03-06 21:06:27 +01:00
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
assert config == {
|
2023-07-09 08:14:30 +02:00
|
|
|
'source_directories': ['/home'],
|
|
|
|
'repositories': [{'path': 'hostname.borg'}],
|
|
|
|
'keep_daily': 1,
|
|
|
|
'keep_hourly': 24,
|
2019-03-06 21:06:27 +01:00
|
|
|
}
|
2022-08-17 19:13:11 +02:00
|
|
|
assert logs == []
|
2019-03-06 21:06:27 +01:00
|
|
|
|
|
|
|
|
2017-07-05 03:32:37 +02:00
|
|
|
def test_parse_configuration_raises_for_missing_config_file():
|
|
|
|
with pytest.raises(FileNotFoundError):
|
2022-05-20 02:15:05 +02:00
|
|
|
module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
2017-07-05 03:32:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_configuration_raises_for_missing_schema_file():
|
|
|
|
mock_config_and_schema('')
|
2017-07-09 07:33:51 +02:00
|
|
|
builtins = flexmock(sys.modules['builtins'])
|
2022-05-20 02:15:05 +02:00
|
|
|
builtins.should_receive('open').with_args('/tmp/schema.yaml').and_raise(FileNotFoundError)
|
2017-07-05 03:32:37 +02:00
|
|
|
|
|
|
|
with pytest.raises(FileNotFoundError):
|
2022-05-20 02:15:05 +02:00
|
|
|
module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
2017-07-05 03:32:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_configuration_raises_for_syntax_error():
|
2017-07-09 08:01:41 +02:00
|
|
|
mock_config_and_schema('foo:\nbar')
|
2017-07-05 03:32:37 +02:00
|
|
|
|
2017-07-09 08:01:41 +02:00
|
|
|
with pytest.raises(ValueError):
|
2022-05-20 02:15:05 +02:00
|
|
|
module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
2017-07-05 03:32:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_configuration_raises_for_validation_error():
|
|
|
|
mock_config_and_schema(
|
|
|
|
'''
|
2023-07-09 08:14:30 +02:00
|
|
|
source_directories: yes
|
|
|
|
repositories:
|
|
|
|
- path: hostname.borg
|
2017-07-05 03:32:37 +02:00
|
|
|
'''
|
|
|
|
)
|
|
|
|
|
|
|
|
with pytest.raises(module.Validation_error):
|
2022-05-20 02:15:05 +02:00
|
|
|
module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
2019-12-17 20:46:27 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_configuration_applies_overrides():
|
|
|
|
mock_config_and_schema(
|
|
|
|
'''
|
2023-07-09 08:14:30 +02:00
|
|
|
source_directories:
|
|
|
|
- /home
|
2019-12-17 20:46:27 +01:00
|
|
|
|
2023-07-09 08:14:30 +02:00
|
|
|
repositories:
|
|
|
|
- path: hostname.borg
|
2019-12-17 20:46:27 +01:00
|
|
|
|
2023-07-09 08:14:30 +02:00
|
|
|
local_path: borg1
|
2019-12-17 20:46:27 +01:00
|
|
|
'''
|
|
|
|
)
|
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
config, logs = module.parse_configuration(
|
2022-05-20 02:15:05 +02:00
|
|
|
'/tmp/config.yaml', '/tmp/schema.yaml', overrides=['location.local_path=borg2']
|
2019-12-17 20:46:27 +01:00
|
|
|
)
|
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
assert config == {
|
2023-07-09 08:14:30 +02:00
|
|
|
'source_directories': ['/home'],
|
|
|
|
'repositories': [{'path': 'hostname.borg'}],
|
|
|
|
'local_path': 'borg2',
|
2019-12-17 20:46:27 +01:00
|
|
|
}
|
2022-08-17 19:13:11 +02:00
|
|
|
assert logs == []
|
2020-01-23 22:41:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_parse_configuration_applies_normalization():
|
|
|
|
mock_config_and_schema(
|
|
|
|
'''
|
|
|
|
location:
|
|
|
|
source_directories:
|
|
|
|
- /home
|
|
|
|
|
|
|
|
repositories:
|
2023-06-29 19:03:36 +02:00
|
|
|
- path: hostname.borg
|
2020-01-23 22:41:37 +01:00
|
|
|
|
|
|
|
exclude_if_present: .nobackup
|
|
|
|
'''
|
|
|
|
)
|
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
config, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
|
2020-01-23 22:41:37 +01:00
|
|
|
|
2022-08-17 19:13:11 +02:00
|
|
|
assert config == {
|
2023-07-09 08:14:30 +02:00
|
|
|
'source_directories': ['/home'],
|
|
|
|
'repositories': [{'path': 'hostname.borg'}],
|
|
|
|
'exclude_if_present': ['.nobackup'],
|
2020-01-23 22:41:37 +01:00
|
|
|
}
|
2023-06-29 19:03:36 +02:00
|
|
|
assert logs
|