summaryrefslogtreecommitdiff
path: root/src/mailman/config
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/config')
-rw-r--r--src/mailman/config/config.py59
-rw-r--r--src/mailman/config/postfix.cfg8
-rw-r--r--src/mailman/config/schema.cfg14
-rw-r--r--src/mailman/config/tests/test_configuration.py50
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')