diff options
Diffstat (limited to 'src/mailman/config')
| -rw-r--r-- | src/mailman/config/config.py | 59 | ||||
| -rw-r--r-- | src/mailman/config/postfix.cfg | 8 | ||||
| -rw-r--r-- | src/mailman/config/schema.cfg | 14 | ||||
| -rw-r--r-- | src/mailman/config/tests/test_configuration.py | 50 |
4 files changed, 122 insertions, 9 deletions
diff --git a/src/mailman/config/config.py b/src/mailman/config/config.py index f6c39fcec..0293dacd4 100644 --- a/src/mailman/config/config.py +++ b/src/mailman/config/config.py @@ -22,14 +22,17 @@ from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'Configuration', + 'external_configuration', + 'load_external' ] import os import sys +from ConfigParser import SafeConfigParser from lazr.config import ConfigSchema, as_boolean -from pkg_resources import resource_stream +from pkg_resources import resource_filename, resource_stream, resource_string from string import Template from zope.component import getUtility from zope.event import notify @@ -39,7 +42,7 @@ import mailman.templates from mailman import version from mailman.interfaces.configuration import ( - ConfigurationUpdatedEvent, IConfiguration) + ConfigurationUpdatedEvent, IConfiguration, MissingConfigurationFileError) from mailman.interfaces.languages import ILanguageManager from mailman.utilities.filesystem import makedirs from mailman.utilities.modules import call_name @@ -235,3 +238,55 @@ class Configuration: """Iterate over all the language configuration sections.""" for section in self._config.getByCategory('language', []): yield section + + + +def load_external(path, encoding=None): + """Load the configuration file named by path. + + :param path: A string naming the location of the external configuration + file. This is either an absolute file system path or a special + ``python:`` path. When path begins with ``python:``, the rest of the + value must name a ``.cfg`` file located within Python's import path, + however the trailing ``.cfg`` suffix is implied (don't provide it + here). + :param encoding: The encoding to apply to the data read from path. If + None, then bytes will be returned. + :return: A unicode string or bytes, depending on ``encoding``. + """ + # Is the context coming from a file system or Python path? + if path.startswith('python:'): + resource_path = path[7:] + package, dot, resource = resource_path.rpartition('.') + config_string = resource_string(package, resource + '.cfg') + else: + with open(path, 'rb') as fp: + config_string = fp.read() + if encoding is None: + return config_string + return config_string.decode(encoding) + + +def external_configuration(path): + """Parse the configuration file named by path. + + :param path: A string naming the location of the external configuration + file. This is either an absolute file system path or a special + ``python:`` path. When path begins with ``python:``, the rest of the + value must name a ``.cfg`` file located within Python's import path, + however the trailing ``.cfg`` suffix is implied (don't provide it + here). + :return: A `ConfigParser` instance. + """ + # Is the context coming from a file system or Python path? + if path.startswith('python:'): + resource_path = path[7:] + package, dot, resource = resource_path.rpartition('.') + cfg_path = resource_filename(package, resource + '.cfg') + else: + cfg_path = path + parser = SafeConfigParser() + files = parser.read(cfg_path) + if files != [cfg_path]: + raise MissingConfigurationFileError(path) + return parser diff --git a/src/mailman/config/postfix.cfg b/src/mailman/config/postfix.cfg new file mode 100644 index 000000000..9bdba221e --- /dev/null +++ b/src/mailman/config/postfix.cfg @@ -0,0 +1,8 @@ +[postfix] +# Additional configuration variables for the postfix MTA. + +# This variable describe the program to use for regenerating the transport map +# db file, from the associated plain text files. The file being updated will +# be appended to this string (with a separating space), so it must be +# appropriate for os.system(). +postmap_command: /usr/sbin/postmap diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg index e36d33c10..4e454fc86 100644 --- a/src/mailman/config/schema.cfg +++ b/src/mailman/config/schema.cfg @@ -159,7 +159,7 @@ wait: 10s # system paths must be absolute since no guarantees are made about the current # working directory. Python paths should not include the trailing .cfg, which # the file must end with. -path: python:mailman.config.passlib +configuration: python:mailman.config.passlib # When Mailman generates them, this is the default length of passwords. password_length: 8 @@ -513,11 +513,13 @@ max_autoresponses_per_day: 10 # may wish to remove these headers by setting this to 'yes'. remove_dkim_headers: no -# This variable describe the program to use for regenerating the transport map -# db file, from the associated plain text files. The file being updated will -# be appended to this string (with a separating space), so it must be -# appropriate for os.system(). -postfix_map_cmd: /usr/sbin/postmap +# Where can we find the mail server specific configuration file? The path can +# be either a file system path or a Python import path. If the value starts +# with python: then it is a Python import path, otherwise it is a file system +# path. File system paths must be absolute since no guarantees are made about +# the current working directory. Python paths should not include the trailing +# .cfg, which the file must end with. +configuration: python:mailman.config.postfix [bounces] diff --git a/src/mailman/config/tests/test_configuration.py b/src/mailman/config/tests/test_configuration.py index 88e00cbb9..08f27c094 100644 --- a/src/mailman/config/tests/test_configuration.py +++ b/src/mailman/config/tests/test_configuration.py @@ -22,12 +22,17 @@ from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'TestConfiguration', + 'TestExternal', ] import unittest -from mailman.interfaces.configuration import ConfigurationUpdatedEvent +from pkg_resources import resource_filename + +from mailman.config.config import external_configuration, load_external +from mailman.interfaces.configuration import ( + ConfigurationUpdatedEvent, MissingConfigurationFileError) from mailman.testing.helpers import configuration, event_subscribers from mailman.testing.layers import ConfigLayer @@ -51,3 +56,46 @@ class TestConfiguration(unittest.TestCase): # for the push leaving 'my test' on the top of the stack, and one for # the pop, leaving the ConfigLayer's 'test config' on top. self.assertEqual(events, ['my test', 'test config']) + + + +class TestExternal(unittest.TestCase): + """Test external configuration file loading APIs.""" + + def test_load_external_by_filename_as_bytes(self): + filename = resource_filename('mailman.config', 'postfix.cfg') + contents = load_external(filename) + self.assertIsInstance(contents, bytes) + self.assertEqual(contents[:9], b'[postfix]') + + def test_load_external_by_path_as_bytes(self): + contents = load_external('python:mailman.config.postfix') + self.assertIsInstance(contents, bytes) + self.assertEqual(contents[:9], b'[postfix]') + + def test_load_external_by_filename_as_string(self): + filename = resource_filename('mailman.config', 'postfix.cfg') + contents = load_external(filename, encoding='utf-8') + self.assertIsInstance(contents, unicode) + self.assertEqual(contents[:9], '[postfix]') + + def test_load_external_by_path_as_string(self): + contents = load_external('python:mailman.config.postfix', 'utf-8') + self.assertIsInstance(contents, unicode) + self.assertEqual(contents[:9], '[postfix]') + + def test_external_configuration_by_filename(self): + filename = resource_filename('mailman.config', 'postfix.cfg') + parser = external_configuration(filename) + self.assertEqual(parser.get('postfix', 'postmap_command'), + '/usr/sbin/postmap') + + def test_external_configuration_by_path(self): + parser = external_configuration('python:mailman.config.postfix') + self.assertEqual(parser.get('postfix', 'postmap_command'), + '/usr/sbin/postmap') + + def test_missing_configuration_file(self): + with self.assertRaises(MissingConfigurationFileError) as cm: + external_configuration('path:mailman.config.missing') + self.assertEqual(cm.exception.path, 'path:mailman.config.missing') |
