Remove legacy configuration parsing code, no longer needed with upgrade-borgmatic-config gone (#529).
This commit is contained in:
parent
37a0a0c421
commit
b9a11e860d
4 changed files with 0 additions and 376 deletions
|
@ -46,8 +46,6 @@ from borgmatic.verbosity import verbosity_to_log_level
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
LEGACY_CONFIG_PATH = '/etc/borgmatic/config'
|
|
||||||
|
|
||||||
|
|
||||||
def run_configuration(config_filename, config, arguments):
|
def run_configuration(config_filename, config, arguments):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -1,146 +0,0 @@
|
||||||
from collections import OrderedDict, namedtuple
|
|
||||||
from configparser import RawConfigParser
|
|
||||||
|
|
||||||
Section_format = namedtuple('Section_format', ('name', 'options'))
|
|
||||||
Config_option = namedtuple('Config_option', ('name', 'value_type', 'required'))
|
|
||||||
|
|
||||||
|
|
||||||
def option(name, value_type=str, required=True):
|
|
||||||
'''
|
|
||||||
Given a config file option name, an expected type for its value, and whether it's required,
|
|
||||||
return a Config_option capturing that information.
|
|
||||||
'''
|
|
||||||
return Config_option(name, value_type, required)
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_FORMAT = (
|
|
||||||
Section_format(
|
|
||||||
'location',
|
|
||||||
(
|
|
||||||
option('source_directories'),
|
|
||||||
option('one_file_system', value_type=bool, required=False),
|
|
||||||
option('remote_path', required=False),
|
|
||||||
option('repository'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Section_format(
|
|
||||||
'storage',
|
|
||||||
(
|
|
||||||
option('encryption_passphrase', required=False),
|
|
||||||
option('compression', required=False),
|
|
||||||
option('umask', required=False),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Section_format(
|
|
||||||
'retention',
|
|
||||||
(
|
|
||||||
option('keep_within', required=False),
|
|
||||||
option('keep_hourly', int, required=False),
|
|
||||||
option('keep_daily', int, required=False),
|
|
||||||
option('keep_weekly', int, required=False),
|
|
||||||
option('keep_monthly', int, required=False),
|
|
||||||
option('keep_yearly', int, required=False),
|
|
||||||
option('prefix', required=False),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Section_format(
|
|
||||||
'consistency', (option('checks', required=False), option('check_last', required=False))
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_configuration_format(parser, config_format):
|
|
||||||
'''
|
|
||||||
Given an open RawConfigParser and an expected config file format, validate that the parsed
|
|
||||||
configuration file has the expected sections, that any required options are present in those
|
|
||||||
sections, and that there aren't any unexpected options.
|
|
||||||
|
|
||||||
A section is required if any of its contained options are required.
|
|
||||||
|
|
||||||
Raise ValueError if anything is awry.
|
|
||||||
'''
|
|
||||||
section_names = set(parser.sections())
|
|
||||||
required_section_names = tuple(
|
|
||||||
section.name
|
|
||||||
for section in config_format
|
|
||||||
if any(option.required for option in section.options)
|
|
||||||
)
|
|
||||||
|
|
||||||
unknown_section_names = section_names - set(
|
|
||||||
section_format.name for section_format in config_format
|
|
||||||
)
|
|
||||||
if unknown_section_names:
|
|
||||||
raise ValueError(f"Unknown config sections found: {', '.join(unknown_section_names)}")
|
|
||||||
|
|
||||||
missing_section_names = set(required_section_names) - section_names
|
|
||||||
if missing_section_names:
|
|
||||||
raise ValueError(f"Missing config sections: {', '.join(missing_section_names)}")
|
|
||||||
|
|
||||||
for section_format in config_format:
|
|
||||||
if section_format.name not in section_names:
|
|
||||||
continue
|
|
||||||
|
|
||||||
option_names = parser.options(section_format.name)
|
|
||||||
expected_options = section_format.options
|
|
||||||
|
|
||||||
unexpected_option_names = set(option_names) - set(
|
|
||||||
option.name for option in expected_options
|
|
||||||
)
|
|
||||||
|
|
||||||
if unexpected_option_names:
|
|
||||||
raise ValueError(
|
|
||||||
f"Unexpected options found in config section {section_format.name}: {', '.join(sorted(unexpected_option_names))}",
|
|
||||||
)
|
|
||||||
|
|
||||||
missing_option_names = tuple(
|
|
||||||
option.name
|
|
||||||
for option in expected_options
|
|
||||||
if option.required
|
|
||||||
if option.name not in option_names
|
|
||||||
)
|
|
||||||
|
|
||||||
if missing_option_names:
|
|
||||||
raise ValueError(
|
|
||||||
f"Required options missing from config section {section_format.name}: {', '.join(missing_option_names)}",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_section_options(parser, section_format):
|
|
||||||
'''
|
|
||||||
Given an open RawConfigParser and an expected section format, return the option values from that
|
|
||||||
section as a dict mapping from option name to value. Omit those options that are not present in
|
|
||||||
the parsed options.
|
|
||||||
|
|
||||||
Raise ValueError if any option values cannot be coerced to the expected Python data type.
|
|
||||||
'''
|
|
||||||
type_getter = {str: parser.get, int: parser.getint, bool: parser.getboolean}
|
|
||||||
|
|
||||||
return OrderedDict(
|
|
||||||
(option.name, type_getter[option.value_type](section_format.name, option.name))
|
|
||||||
for option in section_format.options
|
|
||||||
if parser.has_option(section_format.name, option.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_configuration(config_filename, config_format):
|
|
||||||
'''
|
|
||||||
Given a config filename and an expected config file format, return the parsed configuration
|
|
||||||
as a namedtuple with one attribute for each parsed section.
|
|
||||||
|
|
||||||
Raise IOError if the file cannot be read, or ValueError if the format is not as expected.
|
|
||||||
'''
|
|
||||||
parser = RawConfigParser()
|
|
||||||
if not parser.read(config_filename):
|
|
||||||
raise ValueError(f'Configuration file cannot be opened: {config_filename}')
|
|
||||||
|
|
||||||
validate_configuration_format(parser, config_format)
|
|
||||||
|
|
||||||
# Describes a parsed configuration, where each attribute is the name of a configuration file
|
|
||||||
# section and each value is a dict of that section's parsed options.
|
|
||||||
Parsed_config = namedtuple(
|
|
||||||
'Parsed_config', (section_format.name for section_format in config_format)
|
|
||||||
)
|
|
||||||
|
|
||||||
return Parsed_config(
|
|
||||||
*(parse_section_options(parser, section_format) for section_format in config_format)
|
|
||||||
)
|
|
|
@ -1,18 +0,0 @@
|
||||||
import string
|
|
||||||
from collections import OrderedDict
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
from borgmatic.config import legacy as module
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_section_options_with_punctuation_should_return_section_options():
|
|
||||||
parser = module.RawConfigParser()
|
|
||||||
parser.read_file(StringIO(f'[section]\nfoo: {string.punctuation}\n'))
|
|
||||||
|
|
||||||
section_format = module.Section_format(
|
|
||||||
'section', (module.Config_option('foo', str, required=True),)
|
|
||||||
)
|
|
||||||
|
|
||||||
config = module.parse_section_options(parser, section_format)
|
|
||||||
|
|
||||||
assert config == OrderedDict((('foo', string.punctuation),))
|
|
|
@ -1,210 +0,0 @@
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from flexmock import flexmock
|
|
||||||
|
|
||||||
from borgmatic.config import legacy as module
|
|
||||||
|
|
||||||
|
|
||||||
def test_option_should_create_config_option():
|
|
||||||
option = module.option('name', bool, required=False)
|
|
||||||
|
|
||||||
assert option == module.Config_option('name', bool, False)
|
|
||||||
|
|
||||||
|
|
||||||
def test_option_should_create_config_option_with_defaults():
|
|
||||||
option = module.option('name')
|
|
||||||
|
|
||||||
assert option == module.Config_option('name', str, True)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_configuration_format_with_valid_config_should_not_raise():
|
|
||||||
parser = flexmock()
|
|
||||||
parser.should_receive('sections').and_return(('section', 'other'))
|
|
||||||
parser.should_receive('options').with_args('section').and_return(('stuff',))
|
|
||||||
parser.should_receive('options').with_args('other').and_return(('such',))
|
|
||||||
config_format = (
|
|
||||||
module.Section_format(
|
|
||||||
'section', options=(module.Config_option('stuff', str, required=True),)
|
|
||||||
),
|
|
||||||
module.Section_format('other', options=(module.Config_option('such', str, required=True),)),
|
|
||||||
)
|
|
||||||
|
|
||||||
module.validate_configuration_format(parser, config_format)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_configuration_format_with_missing_required_section_should_raise():
|
|
||||||
parser = flexmock()
|
|
||||||
parser.should_receive('sections').and_return(('section',))
|
|
||||||
config_format = (
|
|
||||||
module.Section_format(
|
|
||||||
'section', options=(module.Config_option('stuff', str, required=True),)
|
|
||||||
),
|
|
||||||
# At least one option in this section is required, so the section is required.
|
|
||||||
module.Section_format(
|
|
||||||
'missing',
|
|
||||||
options=(
|
|
||||||
module.Config_option('such', str, required=False),
|
|
||||||
module.Config_option('things', str, required=True),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.validate_configuration_format(parser, config_format)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_configuration_format_with_missing_optional_section_should_not_raise():
|
|
||||||
parser = flexmock()
|
|
||||||
parser.should_receive('sections').and_return(('section',))
|
|
||||||
parser.should_receive('options').with_args('section').and_return(('stuff',))
|
|
||||||
config_format = (
|
|
||||||
module.Section_format(
|
|
||||||
'section', options=(module.Config_option('stuff', str, required=True),)
|
|
||||||
),
|
|
||||||
# No options in the section are required, so the section is optional.
|
|
||||||
module.Section_format(
|
|
||||||
'missing',
|
|
||||||
options=(
|
|
||||||
module.Config_option('such', str, required=False),
|
|
||||||
module.Config_option('things', str, required=False),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
module.validate_configuration_format(parser, config_format)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_configuration_format_with_unknown_section_should_raise():
|
|
||||||
parser = flexmock()
|
|
||||||
parser.should_receive('sections').and_return(('section', 'extra'))
|
|
||||||
config_format = (module.Section_format('section', options=()),)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.validate_configuration_format(parser, config_format)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_configuration_format_with_missing_required_option_should_raise():
|
|
||||||
parser = flexmock()
|
|
||||||
parser.should_receive('sections').and_return(('section',))
|
|
||||||
parser.should_receive('options').with_args('section').and_return(('option',))
|
|
||||||
config_format = (
|
|
||||||
module.Section_format(
|
|
||||||
'section',
|
|
||||||
options=(
|
|
||||||
module.Config_option('option', str, required=True),
|
|
||||||
module.Config_option('missing', str, required=True),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.validate_configuration_format(parser, config_format)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_configuration_format_with_missing_optional_option_should_not_raise():
|
|
||||||
parser = flexmock()
|
|
||||||
parser.should_receive('sections').and_return(('section',))
|
|
||||||
parser.should_receive('options').with_args('section').and_return(('option',))
|
|
||||||
config_format = (
|
|
||||||
module.Section_format(
|
|
||||||
'section',
|
|
||||||
options=(
|
|
||||||
module.Config_option('option', str, required=True),
|
|
||||||
module.Config_option('missing', str, required=False),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
module.validate_configuration_format(parser, config_format)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_configuration_format_with_extra_option_should_raise():
|
|
||||||
parser = flexmock()
|
|
||||||
parser.should_receive('sections').and_return(('section',))
|
|
||||||
parser.should_receive('options').with_args('section').and_return(('option', 'extra'))
|
|
||||||
config_format = (
|
|
||||||
module.Section_format(
|
|
||||||
'section', options=(module.Config_option('option', str, required=True),)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.validate_configuration_format(parser, config_format)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_section_options_should_return_section_options():
|
|
||||||
parser = flexmock()
|
|
||||||
parser.should_receive('get').with_args('section', 'foo').and_return('value')
|
|
||||||
parser.should_receive('getint').with_args('section', 'bar').and_return(1)
|
|
||||||
parser.should_receive('getboolean').never()
|
|
||||||
parser.should_receive('has_option').with_args('section', 'foo').and_return(True)
|
|
||||||
parser.should_receive('has_option').with_args('section', 'bar').and_return(True)
|
|
||||||
|
|
||||||
section_format = module.Section_format(
|
|
||||||
'section',
|
|
||||||
(
|
|
||||||
module.Config_option('foo', str, required=True),
|
|
||||||
module.Config_option('bar', int, required=True),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
config = module.parse_section_options(parser, section_format)
|
|
||||||
|
|
||||||
assert config == OrderedDict((('foo', 'value'), ('bar', 1)))
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_section_options_for_missing_section_should_return_empty_dict():
|
|
||||||
parser = flexmock()
|
|
||||||
parser.should_receive('get').never()
|
|
||||||
parser.should_receive('getint').never()
|
|
||||||
parser.should_receive('getboolean').never()
|
|
||||||
parser.should_receive('has_option').with_args('section', 'foo').and_return(False)
|
|
||||||
parser.should_receive('has_option').with_args('section', 'bar').and_return(False)
|
|
||||||
|
|
||||||
section_format = module.Section_format(
|
|
||||||
'section',
|
|
||||||
(
|
|
||||||
module.Config_option('foo', str, required=False),
|
|
||||||
module.Config_option('bar', int, required=False),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
config = module.parse_section_options(parser, section_format)
|
|
||||||
|
|
||||||
assert config == OrderedDict()
|
|
||||||
|
|
||||||
|
|
||||||
def insert_mock_parser():
|
|
||||||
parser = flexmock()
|
|
||||||
parser.should_receive('read').and_return([flexmock()])
|
|
||||||
module.RawConfigParser = lambda: parser
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_configuration_should_return_section_configs():
|
|
||||||
parser = insert_mock_parser()
|
|
||||||
config_format = (flexmock(name='items'), flexmock(name='things'))
|
|
||||||
mock_module = flexmock(module)
|
|
||||||
mock_module.should_receive('validate_configuration_format').with_args(
|
|
||||||
parser, config_format
|
|
||||||
).once()
|
|
||||||
mock_section_configs = (flexmock(), flexmock())
|
|
||||||
|
|
||||||
for section_format, section_config in zip(config_format, mock_section_configs):
|
|
||||||
mock_module.should_receive('parse_section_options').with_args(
|
|
||||||
parser, section_format
|
|
||||||
).and_return(section_config).once()
|
|
||||||
|
|
||||||
parsed_config = module.parse_configuration('filename', config_format)
|
|
||||||
|
|
||||||
assert parsed_config == type(parsed_config)(*mock_section_configs)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_configuration_with_file_open_error_should_raise():
|
|
||||||
parser = insert_mock_parser()
|
|
||||||
parser.should_receive('read').and_return([])
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.parse_configuration('filename', config_format=flexmock())
|
|
Loading…
Reference in a new issue