VirtualMailManager/Config.py
branchv0.6.x
changeset 185 6e1ef32fbd82
parent 180 f8279c90e99c
child 187 38b9a9859749
equal deleted inserted replaced
184:d0425225ce52 185:6e1ef32fbd82
    34 
    34 
    35 
    35 
    36 from shutil import copy2
    36 from shutil import copy2
    37 from ConfigParser import (Error, MissingSectionHeaderError, NoOptionError,
    37 from ConfigParser import (Error, MissingSectionHeaderError, NoOptionError,
    38                           NoSectionError, ParsingError, RawConfigParser)
    38                           NoSectionError, ParsingError, RawConfigParser)
    39 from cStringIO import StringIO
    39 from cStringIO import StringIO# TODO: move interactive stff to cli
    40 
    40 
    41 from __main__ import os, ENCODING, ERR, get_unicode, w_std
    41 import VirtualMailManager.constants.ERROR as ERR
    42 from Exceptions import VMMConfigException
    42 
       
    43 from VirtualMailManager import ENCODING, exec_ok, get_unicode, is_dir
       
    44 from VirtualMailManager.cli import w_std# move to cli
       
    45 from VirtualMailManager.Exceptions import VMMConfigException
    43 
    46 
    44 
    47 
    45 class BadOptionError(Error):
    48 class BadOptionError(Error):
    46     """Raised when a option isn't in the format 'section.option'."""
    49     """Raised when a option isn't in the format 'section.option'."""
    47     pass
    50     pass
    60                        option, section))
    63                        option, section))
    61 
    64 
    62 
    65 
    63 class LazyConfig(RawConfigParser):
    66 class LazyConfig(RawConfigParser):
    64     """The **lazy** derivate of the `RawConfigParser`.
    67     """The **lazy** derivate of the `RawConfigParser`.
    65     
    68 
    66     There are two additional getters:
    69     There are two additional getters:
    67     
    70 
    68     `LazyConfig.pget()`
    71     `LazyConfig.pget()`
    69         The polymorphic getter, which returns a option's value with the
    72         The polymorphic getter, which returns a option's value with the
    70         appropriate type.
    73         appropriate type.
    71     `LazyConfig.dget()`
    74     `LazyConfig.dget()`
    72         Like `LazyConfig.pget()`, but returns the option's default, from
    75         Like `LazyConfig.pget()`, but returns the option's default, from
   125         Else one of the following exceptions will be thrown:
   128         Else one of the following exceptions will be thrown:
   126 
   129 
   127             * `BadOptionError`
   130             * `BadOptionError`
   128             * `NoSectionError`
   131             * `NoSectionError`
   129             * `NoOptionError`
   132             * `NoOptionError`
   130         """ 
   133         """
   131         sect_opt = section_option.lower().split('.')
   134         sect_opt = section_option.lower().split('.')
   132         if len(sect_opt) != 2:# do we need a regexp to check the format?
   135         if len(sect_opt) != 2:# do we need a regexp to check the format?
   133             raise BadOptionError(
   136             raise BadOptionError(
   134                         _(u'Bad format: “%s” - expected: section.option') % \
   137                         _(u'Bad format: “%s” - expected: section.option') % \
   135                         get_unicode(section_option))
   138                         get_unicode(section_option))
   156         if '__name__' in d: del d['__name__']
   159         if '__name__' in d: del d['__name__']
   157         return d.iteritems()
   160         return d.iteritems()
   158 
   161 
   159     def dget(self, option):
   162     def dget(self, option):
   160         """Returns the value of the `option`.
   163         """Returns the value of the `option`.
   161         
   164 
   162         If the option could not be found in the configuration file, the
   165         If the option could not be found in the configuration file, the
   163         configured default value, from ``LazyConfig._cfg`` will be
   166         configured default value, from ``LazyConfig._cfg`` will be
   164         returned.
   167         returned.
   165         
   168 
   166         Arguments:
   169         Arguments:
   167         
   170 
   168         `option` : string
   171         `option` : string
   169             the configuration option in the form
   172             the configuration option in the form
   170             "``section``\ **.**\ ``option``"
   173             "``section``\ **.**\ ``option``"
   171 
   174 
   172         Throws a `NoDefaultError`, if no default value was passed to
   175         Throws a `NoDefaultError`, if no default value was passed to
   186         section, option = self.__get_section_option(option)
   189         section, option = self.__get_section_option(option)
   187         return self._cfg[section][option].getter(section, option)
   190         return self._cfg[section][option].getter(section, option)
   188 
   191 
   189     def set(self, option, value):
   192     def set(self, option, value):
   190         """Set the value of an option.
   193         """Set the value of an option.
   191         
   194 
   192         Throws a ``ValueError`` if `value` couldn't be converted to
   195         Throws a ``ValueError`` if `value` couldn't be converted to
   193         ``LazyConfigOption.cls``"""
   196         ``LazyConfigOption.cls``"""
   194         section, option = self.__get_section_option(option)
   197         section, option = self.__get_section_option(option)
   195         val = self._cfg[section][option].cls(value)
   198         val = self._cfg[section][option].cls(value)
   196         if not self._cfg[section][option].validate is None:
   199         if not self._cfg[section][option].validate is None:
   200         RawConfigParser.set(self, section, option, val)
   203         RawConfigParser.set(self, section, option, val)
   201         self._modified = True
   204         self._modified = True
   202 
   205 
   203     def has_section(self, section):
   206     def has_section(self, section):
   204         """Checks if ``section`` is a known configuration section."""
   207         """Checks if ``section`` is a known configuration section."""
   205         return section.lower() in self._cfg 
   208         return section.lower() in self._cfg
   206 
   209 
   207     def has_option(self, option):
   210     def has_option(self, option):
   208         """Checks if the option (section\ **.**\ option) is a known
   211         """Checks if the option (section\ **.**\ option) is a known
   209         configuration option."""
   212         configuration option."""
   210         try:
   213         try:
   211             self.__get_section_option(option)
   214             self.__get_section_option(option)
   212             return True
   215             return True
   213         except(BadOptionError, NoSectionError, NoOptionError):
   216         except(BadOptionError, NoSectionError, NoOptionError):
   214             return False
   217             return False
   215 
       
   216 
   218 
   217 
   219 
   218 class LazyConfigOption(object):
   220 class LazyConfigOption(object):
   219     """A simple container class for configuration settings.
   221     """A simple container class for configuration settings.
   220 
   222 
   264 
   266 
   265     def __init__(self, filename):
   267     def __init__(self, filename):
   266         """Creates a new Config instance
   268         """Creates a new Config instance
   267 
   269 
   268         Arguments:
   270         Arguments:
   269      
   271 
   270         ``filename``
   272         ``filename``
   271             path to the configuration file
   273             path to the configuration file
   272         """
   274         """
   273         LazyConfig.__init__(self)
   275         LazyConfig.__init__(self)
   274         self.__cfgFileName = filename
   276         self.__cfgFileName = filename
   288                 'pop3' :            LCO(bool_t, True,  self.get_boolean),
   290                 'pop3' :            LCO(bool_t, True,  self.get_boolean),
   289                 'sieve':            LCO(bool_t, True,  self.get_boolean),
   291                 'sieve':            LCO(bool_t, True,  self.get_boolean),
   290                 'smtp' :            LCO(bool_t, True,  self.get_boolean),
   292                 'smtp' :            LCO(bool_t, True,  self.get_boolean),
   291             },
   293             },
   292             'bin': {
   294             'bin': {
   293                 'dovecotpw': LCO(str, '/usr/sbin/dovecotpw', self.get,
   295                 'dovecotpw': LCO(str, '/usr/sbin/dovecotpw', self.get, exec_ok),
   294                                  self.exec_ok),
   296                 'du':        LCO(str, '/usr/bin/du',        self.get, exec_ok),
   295                 'du':        LCO(str, '/usr/bin/du', self.get, self.exec_ok),
   297                 'postconf':  LCO(str, '/usr/sbin/postconf', self.get, exec_ok),
   296                 'postconf':  LCO(str, '/usr/sbin/postconf', self.get,
       
   297                                  self.exec_ok),
       
   298             },
   298             },
   299             'database': {
   299             'database': {
   300                 'host': LCO(str, 'localhost', self.get),
   300                 'host': LCO(str, 'localhost', self.get),
   301                 'name': LCO(str, 'mailsys',   self.get),
   301                 'name': LCO(str, 'mailsys',   self.get),
   302                 'pass': LCO(str, None,        self.get),
   302                 'pass': LCO(str, None,        self.get),
   311             'maildir': {
   311             'maildir': {
   312                 'folders': LCO(str, 'Drafts:Sent:Templates:Trash', self.get),
   312                 'folders': LCO(str, 'Drafts:Sent:Templates:Trash', self.get),
   313                 'name':    LCO(str, 'Maildir',                     self.get),
   313                 'name':    LCO(str, 'Maildir',                     self.get),
   314             },
   314             },
   315             'misc': {
   315             'misc': {
   316                 'base_directory':  LCO(str, '/srv/mail', self.get, self.is_dir),
   316                 'base_directory':  LCO(str, '/srv/mail', self.get, is_dir),
   317                 'dovecot_version': LCO(int, 12,          self.getint),
   317                 'dovecot_version': LCO(int, 12,          self.getint),
   318                 'gid_mail':        LCO(int, 8,           self.getint),
   318                 'gid_mail':        LCO(int, 8,           self.getint),
   319                 'password_scheme': LCO(str, 'CRAM-MD5',  self.get,
   319                 'password_scheme': LCO(str, 'CRAM-MD5',  self.get,
   320                                        self.known_scheme),
   320                                        self.known_scheme),
   321                 'transport':       LCO(str, 'dovecot:',  self.get),
   321                 'transport':       LCO(str, 'dovecot:',  self.get),
   339     def check(self):
   339     def check(self):
   340         """Performs a configuration check.
   340         """Performs a configuration check.
   341 
   341 
   342         Raises a VMMConfigException if the check fails.
   342         Raises a VMMConfigException if the check fails.
   343         """
   343         """
       
   344         # TODO: There are only two settings w/o defaults.
       
   345         #       So there is no need for cStringIO
   344         if not self.__chkCfg():
   346         if not self.__chkCfg():
   345             errmsg = StringIO()
   347             errmsg = StringIO()
   346             errmsg.write(_(u'Missing options, which have no default value.\n'))
   348             errmsg.write(_(u'Missing options, which have no default value.\n'))
   347             errmsg.write(_(u'Using configuration file: %s\n') %\
   349             errmsg.write(_(u'Using configuration file: %s\n') %\
   348                          self.__cfgFileName)
   350                          self.__cfgFileName)
   354 
   356 
   355     def getsections(self):
   357     def getsections(self):
   356         """Returns an iterator object for all configuration sections."""
   358         """Returns an iterator object for all configuration sections."""
   357         return self._cfg.iterkeys()
   359         return self._cfg.iterkeys()
   358 
   360 
   359     def is_dir(self, path):
       
   360         """Checks if ``path`` is a directory.
       
   361         
       
   362         Throws a `ConfigValueError` if ``path`` is not a directory.
       
   363         """
       
   364         path = self.__expand_path(path)
       
   365         if not os.path.isdir(path):
       
   366             raise ConfigValueError(_(u'“%s” is not a directory') % \
       
   367                                    get_unicode(path))
       
   368         return path
       
   369 
       
   370     def exec_ok(self, binary):
       
   371         """Checks if the ``binary`` exists and if it is executable.
       
   372         
       
   373         Throws a `ConfigValueError` if the ``binary`` isn't a file or is
       
   374         not executable.
       
   375         """
       
   376         binary = self.__expand_path(binary)
       
   377         if not os.path.isfile(binary):
       
   378             raise ConfigValueError(_(u'“%s” is not a file') % \
       
   379                                    get_unicode(binary))
       
   380         if not os.access(binary, os.X_OK):
       
   381             raise ConfigValueError(_(u'File is not executable: “%s”') % \
       
   382                                    get_unicode(binary))
       
   383         return binary
       
   384 
       
   385     def known_scheme(self, scheme):
   361     def known_scheme(self, scheme):
   386         """Converts ``scheme`` to upper case and checks if is known by
   362         """Converts ``scheme`` to upper case and checks if is known by
   387         Dovecot (listed in VirtualMailManager.SCHEMES).
   363         Dovecot (listed in VirtualMailManager.SCHEMES).
   388         
   364 
   389         Throws a `ConfigValueError` if the scheme is not listed in
   365         Throws a `ConfigValueError` if the scheme is not listed in
   390         VirtualMailManager.SCHEMES.
   366         VirtualMailManager.SCHEMES.
   391         """
   367         """
   392         scheme = scheme.upper()
   368         scheme = scheme.upper()
   393         # TODO: VMM.SCHEMES
   369         # TODO: VMM.SCHEMES
   402         """Interactive method for configuring all options in the given sections
   378         """Interactive method for configuring all options in the given sections
   403 
   379 
   404         Arguments:
   380         Arguments:
   405         sections -- list of strings with section names
   381         sections -- list of strings with section names
   406         """
   382         """
       
   383         # TODO: Derivate CliConfig from Config an move the interactive
       
   384         #       stuff to CliConfig
   407         input_fmt = _(u'Enter new value for option %(option)s \
   385         input_fmt = _(u'Enter new value for option %(option)s \
   408 [%(current_value)s]: ')
   386 [%(current_value)s]: ')
   409         failures = 0
   387         failures = 0
   410 
   388 
   411         w_std(_(u'Using configuration file: %s\n') % self.__cfgFileName)
   389         w_std(_(u'Using configuration file: %s\n') % self.__cfgFileName)
   433         if self._modified:
   411         if self._modified:
   434             self.__saveChanges()
   412             self.__saveChanges()
   435 
   413 
   436     def __saveChanges(self):
   414     def __saveChanges(self):
   437         """Writes changes to the configuration file."""
   415         """Writes changes to the configuration file."""
       
   416         # TODO: Move interactive stuff to CliConfig
   438         copy2(self.__cfgFileName, self.__cfgFileName+'.bak')
   417         copy2(self.__cfgFileName, self.__cfgFileName+'.bak')
   439         self.__cfgFile = open(self.__cfgFileName, 'w')
   418         self.__cfgFile = open(self.__cfgFileName, 'w')
   440         self.write(self.__cfgFile)
   419         self.write(self.__cfgFile)
   441         self.__cfgFile.close()
   420         self.__cfgFile.close()
   442 
   421 
   443     def __chkCfg(self):
   422     def __chkCfg(self):
   444         """Checks all section's options for settings w/o default values.
   423         """Checks all section's options for settings w/o default values.
   445         
   424 
   446         Returns ``True`` if everything is fine, else ``False``."""
   425         Returns ``True`` if everything is fine, else ``False``."""
   447         errors = False
   426         errors = False
   448         for section in self._cfg.iterkeys():
   427         for section in self._cfg.iterkeys():
   449             missing = []
   428             missing = []
   450             for option, value in self._cfg[section].iteritems():
   429             for option, value in self._cfg[section].iteritems():
   454                     errors = True
   433                     errors = True
   455             if len(missing):
   434             if len(missing):
   456                 self.__missing[section] = missing
   435                 self.__missing[section] = missing
   457         return not errors
   436         return not errors
   458 
   437 
   459     def __expand_path(self, path):
       
   460         """Expands paths, starting with ``.`` or ``~``, to an absolute path."""
       
   461         if path.startswith('.'):
       
   462             return os.path.abspath(path)
       
   463         if path.startswith('~'):
       
   464             return os.path.expanduser(path)
       
   465         return path