diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 7d31dde..6aaad9d 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -46,8 +46,6 @@ from borgmatic.verbosity import verbosity_to_log_level logger = logging.getLogger(__name__) -LEGACY_CONFIG_PATH = '/etc/borgmatic/config' - def run_configuration(config_filename, config, arguments): ''' diff --git a/borgmatic/config/legacy.py b/borgmatic/config/legacy.py deleted file mode 100644 index ec1e50a..0000000 --- a/borgmatic/config/legacy.py +++ /dev/null @@ -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) - ) diff --git a/tests/integration/config/test_legacy.py b/tests/integration/config/test_legacy.py deleted file mode 100644 index c73e7ee..0000000 --- a/tests/integration/config/test_legacy.py +++ /dev/null @@ -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),)) diff --git a/tests/unit/config/test_legacy.py b/tests/unit/config/test_legacy.py deleted file mode 100644 index 230aa74..0000000 --- a/tests/unit/config/test_legacy.py +++ /dev/null @@ -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())