Use my lazr.config megamerge branch for now, even though it's still under
[mailman.git] / mailman / config / config.py
blobf3bbed3163efe046dc2ed62644db5d87f53333d0
1 # Copyright (C) 2006-2008 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
8 # any later version.
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """Configuration file loading and management."""
20 __metaclass__ = type
21 __all__ = [
22 'Configuration',
26 import os
27 import sys
28 import errno
29 import logging
31 from StringIO import StringIO
32 from lazr.config import ConfigSchema, as_boolean
33 from pkg_resources import resource_string
35 from mailman import Defaults
36 from mailman import version
37 from mailman.core import errors
38 from mailman.domain import Domain
39 from mailman.languages import LanguageManager
42 SPACE = ' '
46 class Configuration(object):
47 """The core global configuration object."""
49 def __init__(self):
50 self.domains = {} # email host -> IDomain
51 self.qrunners = {}
52 self.qrunner_shortcuts = {}
53 self.switchboards = {}
54 self.languages = LanguageManager()
55 self.QFILE_SCHEMA_VERSION = version.QFILE_SCHEMA_VERSION
56 self._config = None
57 # Create various registries.
58 self.archivers = {}
59 self.chains = {}
60 self.rules = {}
61 self.handlers = {}
62 self.pipelines = {}
63 self.commands = {}
65 def _clear(self):
66 """Clear the cached configuration variables."""
67 # First, stop all registered qrunners.
68 for runner in self.qrunners.values():
69 runner.stop()
70 self.domains.clear()
71 self.switchboards.clear()
72 self.qrunners.clear()
73 self.qrunner_shortcuts.clear()
74 self.languages = LanguageManager()
76 def __getattr__(self, name):
77 """Delegate to the configuration object."""
78 return getattr(self._config, name)
80 def load(self, filename=None):
81 """Load the configuration from the schema and config files."""
82 schema_string = resource_string('mailman.config', 'schema.cfg')
83 schema = ConfigSchema('schema.cfg', StringIO(schema_string))
84 # If a configuration file was given, load it now too. First, load the
85 # absolute minimum default configuration, then if a configuration
86 # filename was given by the user, push it.
87 config_string = resource_string('mailman.config', 'mailman.cfg')
88 self._config = schema.loadFile(StringIO(config_string), 'mailman.cfg')
89 if filename is not None:
90 with open(filename) as user_config:
91 self._config.push(user_config.read())
92 self._post_process()
94 def push(self, config_name, config_string):
95 """Push a new configuration onto the stack."""
96 self._clear()
97 self._config.push(config_name, config_string)
98 self._post_process()
100 def pop(self, config_name):
101 """Pop a configuration from the stack."""
102 self._clear()
103 self._config.pop(config_name)
104 self._post_process()
106 def _post_process(self):
107 """Perform post-processing after loading the configuration files."""
108 # Set up the domains.
109 domains = self._config.getByCategory('domain', [])
110 for section in domains:
111 domain = Domain(section.email_host, section.base_url,
112 section.description, section.contact_address)
113 if domain.email_host in self.domains:
114 raise errors.BadDomainSpecificationError(
115 'Duplicate email host: %s' % domain.email_host)
116 # Make sure there's only one mapping for the url_host
117 if domain.url_host in self.domains.values():
118 raise errors.BadDomainSpecificationError(
119 'Duplicate url host: %s' % domain.url_host)
120 # We'll do the reverse mappings on-demand. There shouldn't be too
121 # many virtual hosts that it will really matter that much.
122 self.domains[domain.email_host] = domain
123 # Set up directories.
124 self.BIN_DIR = os.path.abspath(os.path.dirname(sys.argv[0]))
125 self.VAR_DIR = var_dir = self._config.mailman.var_dir
126 # Now that we've loaded all the configuration files we're going to
127 # load, set up some useful directories.
128 join = os.path.join
129 self.LIST_DATA_DIR = join(var_dir, 'lists')
130 self.LOG_DIR = join(var_dir, 'logs')
131 self.LOCK_DIR = lockdir = join(var_dir, 'locks')
132 self.DATA_DIR = datadir = join(var_dir, 'data')
133 self.ETC_DIR = etcdir = join(var_dir, 'etc')
134 self.SPAM_DIR = join(var_dir, 'spam')
135 self.EXT_DIR = join(var_dir, 'ext')
136 self.QUEUE_DIR = join(var_dir, 'qfiles')
137 self.MESSAGES_DIR = join(var_dir, 'messages')
138 self.PUBLIC_ARCHIVE_FILE_DIR = join(var_dir, 'archives', 'public')
139 self.PRIVATE_ARCHIVE_FILE_DIR = join(var_dir, 'archives', 'private')
140 # Other useful files
141 self.PIDFILE = join(datadir, 'master-qrunner.pid')
142 self.SITE_PW_FILE = join(datadir, 'adm.pw')
143 self.LISTCREATOR_PW_FILE = join(datadir, 'creator.pw')
144 self.CONFIG_FILE = join(etcdir, 'mailman.cfg')
145 self.LOCK_FILE = join(lockdir, 'master-qrunner')
146 # Set up the switchboards.
147 from mailman.queue import Switchboard
148 Switchboard.initialize()
149 # Set up all the languages.
150 languages = self._config.getByCategory('language', [])
151 for language in languages:
152 code = language.name.split('.')[1]
153 self.languages.add_language(code, language.description,
154 language.charset, language.enabled)
155 # Always enable the server default language, which must be defined.
156 self.languages.enable_language(self._config.mailman.default_language)
158 @property
159 def logger_configs(self):
160 """Return all log config sections."""
161 return self._config.getByCategory('logging', [])
163 @property
164 def paths(self):
165 """Return a substitution dictionary of all path variables."""
166 return dict((k, self.__dict__[k])
167 for k in self.__dict__
168 if k.endswith('_DIR'))
170 def ensure_directories_exist(self):
171 """Create all path directories if they do not exist."""
172 for variable, directory in self.paths.items():
173 try:
174 os.makedirs(directory, 02775)
175 except OSError, e:
176 if e.errno <> errno.EEXIST:
177 raise
179 @property
180 def qrunner_configs(self):
181 for section in self._config.getByCategory('qrunner', []):
182 yield section
184 @property
185 def header_matches(self):
186 """Iterate over all spam matching headers.
188 Values are 3-tuples of (header, pattern, chain)
190 matches = self._config.getByCategory('spam.headers', [])
191 for match in matches:
192 yield (matches.header, matches.pattern, matches.chain)