VirtualMailManager/Config.py
branchv0.6.x
changeset 215 33f727efa7c4
parent 211 0b129678cfe1
child 216 0c8c053b451c
equal deleted inserted replaced
214:84e6e898e6c5 215:33f727efa7c4
    15 from cStringIO import StringIO# TODO: move interactive stff to cli
    15 from cStringIO import StringIO# TODO: move interactive stff to cli
    16 
    16 
    17 from VirtualMailManager import exec_ok, get_unicode, is_dir
    17 from VirtualMailManager import exec_ok, get_unicode, is_dir
    18 from VirtualMailManager.constants.ERROR import CONF_ERROR
    18 from VirtualMailManager.constants.ERROR import CONF_ERROR
    19 from VirtualMailManager.Exceptions import VMMConfigException
    19 from VirtualMailManager.Exceptions import VMMConfigException
       
    20 
       
    21 
       
    22 _ = lambda msg: msg
    20 
    23 
    21 
    24 
    22 class BadOptionError(Error):
    25 class BadOptionError(Error):
    23     """Raised when a option isn't in the format 'section.option'."""
    26     """Raised when a option isn't in the format 'section.option'."""
    24     pass
    27     pass
    48     `dget()`
    51     `dget()`
    49       Like `LazyConfig.pget()`, but returns the option's default, from
    52       Like `LazyConfig.pget()`, but returns the option's default, from
    50       `LazyConfig._cfg['sectionname']['optionname'].default`, if the
    53       `LazyConfig._cfg['sectionname']['optionname'].default`, if the
    51       option is not configured in a ini-like configuration file.
    54       option is not configured in a ini-like configuration file.
    52 
    55 
    53     `set()` differs from `RawConfigParser`'s `set()` method. `set()` takes
    56     `set()` differs from `RawConfigParser`'s `set()` method. `set()`
    54     the `section` and `option` arguments combined to a single string in the
    57     takes the `section` and `option` arguments combined to a single
    55     form "section.option".
    58     string in the form "section.option".
       
    59 
    56     """
    60     """
    57 
    61 
    58     def __init__(self):
    62     def __init__(self):
    59         RawConfigParser.__init__(self)
    63         RawConfigParser.__init__(self)
    60         self._modified = False
    64         self._modified = False
       
    65         # sample _cfg dict.  Create your own in your derived class.
    61         self._cfg = {
    66         self._cfg = {
    62             'sectionname': {
    67             'sectionname': {
    63                 'optionname': LazyConfigOption(int, 1, self.getint)
    68                 'optionname': LazyConfigOption(int, 1, self.getint),
    64             }
    69             }
    65         }
    70         }
    66         """sample _cfg dictionary. Create your own in your derived class."""
       
    67 
    71 
    68     def bool_new(self, value):
    72     def bool_new(self, value):
    69         """Converts the string `value` into a `bool` and returns it.
    73         """Converts the string `value` into a `bool` and returns it.
    70 
    74 
    71         | '1', 'on', 'yes' and 'true' will become `True`
    75         | '1', 'on', 'yes' and 'true' will become `True`
    80         else:
    84         else:
    81             raise ConfigValueError(_(u"Not a boolean: '%s'") %
    85             raise ConfigValueError(_(u"Not a boolean: '%s'") %
    82                                    get_unicode(value))
    86                                    get_unicode(value))
    83 
    87 
    84     def getboolean(self, section, option):
    88     def getboolean(self, section, option):
    85         """Returns the boolean value of the option, in the given section.
    89         """Returns the boolean value of the option, in the given
       
    90         section.
    86 
    91 
    87         For a boolean True, the value must be set to '1', 'on', 'yes',
    92         For a boolean True, the value must be set to '1', 'on', 'yes',
    88         'true' or True. For a boolean False, the value must set to '0',
    93         'true' or True. For a boolean False, the value must set to '0',
    89         'off', 'no', 'false' or False.
    94         'off', 'no', 'false' or False.
    90         If the option has another value assigned this method will raise a
    95         If the option has another value assigned this method will raise
    91         ValueError.
    96         a ValueError.
       
    97 
    92         """
    98         """
    93         # if the setting was modified it may be still a boolean value lets see
    99         # if the setting was modified it may be still a boolean value lets see
    94         tmp = self.get(section, option)
   100         tmp = self.get(section, option)
    95         if isinstance(tmp, bool):
   101         if isinstance(tmp, bool):
    96             return tmp
   102             return tmp
    97         if not tmp.lower() in self._boolean_states:
   103         if not tmp.lower() in self._boolean_states:
    98             raise ValueError, 'Not a boolean: %s' % tmp
   104             raise ValueError('Not a boolean: %s' % tmp)
    99         return self._boolean_states[tmp.lower()]
   105         return self._boolean_states[tmp.lower()]
   100 
   106 
   101     def _get_section_option(self, section_option):
   107     def _get_section_option(self, section_option):
   102         """splits ``section_option`` (section\ **.**\ option) in two parts
   108         """splits ``section_option`` (section.option) in two parts and
   103         and returns them as list ``[section, option]``, if:
   109         returns them as list ``[section, option]``, if:
   104 
   110 
   105             * it likes the format of ``section_option``
   111           * it likes the format of ``section_option``
   106             * the ``section`` is known
   112           * the ``section`` is known
   107             * the ``option`` is known
   113           * the ``option`` is known
   108 
   114 
   109         Else one of the following exceptions will be thrown:
   115         Else one of the following exceptions will be thrown:
   110 
   116 
   111             * `BadOptionError`
   117           * `BadOptionError`
   112             * `NoSectionError`
   118           * `NoSectionError`
   113             * `NoOptionError`
   119           * `NoOptionError`
       
   120 
   114         """
   121         """
   115         sect_opt = section_option.lower().split('.')
   122         sect_opt = section_option.lower().split('.')
   116         # TODO: cache it
   123         # TODO: cache it
   117         if len(sect_opt) != 2:# do we need a regexp to check the format?
   124         if len(sect_opt) != 2:# do we need a regexp to check the format?
   118             raise BadOptionError(
   125             raise BadOptionError(
   123         if not sect_opt[1] in self._cfg[sect_opt[0]]:
   130         if not sect_opt[1] in self._cfg[sect_opt[0]]:
   124             raise NoOptionError(sect_opt[1], sect_opt[0])
   131             raise NoOptionError(sect_opt[1], sect_opt[0])
   125         return sect_opt
   132         return sect_opt
   126 
   133 
   127     def items(self, section):
   134     def items(self, section):
   128         """returns an iterable that returns key, value ``tuples`` from the
   135         """returns an iterable that returns key, value ``tuples`` from
   129         given ``section``."""
   136         the given ``section``.
       
   137 
       
   138         """
   130         if section in self._sections:# check if the section was parsed
   139         if section in self._sections:# check if the section was parsed
   131             d2 = self._sections[section]
   140             sect = self._sections[section]
   132         elif not section in self._cfg:
   141         elif not section in self._cfg:
   133             raise NoSectionError(section)
   142             raise NoSectionError(section)
   134         else:
   143         else:
   135             return ((k, self._cfg[section][k].default) \
   144             return ((k, self._cfg[section][k].default) \
   136                     for k in self._cfg[section].iterkeys())
   145                     for k in self._cfg[section].iterkeys())
   137         # still here? Get defaults and merge defaults with configured setting
   146         # still here? Get defaults and merge defaults with configured setting
   138         d = dict((k, self._cfg[section][k].default) \
   147         defaults = dict((k, self._cfg[section][k].default) \
   139                  for k in self._cfg[section].iterkeys())
   148                         for k in self._cfg[section].iterkeys())
   140         d.update(d2)
   149         defaults.update(sect)
   141         if '__name__' in d:
   150         if '__name__' in defaults:
   142             del d['__name__']
   151             del defaults['__name__']
   143         return d.iteritems()
   152         return defaults.iteritems()
   144 
   153 
   145     def dget(self, option):
   154     def dget(self, option):
   146         """Returns the value of the `option`.
   155         """Returns the value of the `option`.
   147 
   156 
   148         If the option could not be found in the configuration file, the
   157         If the option could not be found in the configuration file, the
   150         returned.
   159         returned.
   151 
   160 
   152         Arguments:
   161         Arguments:
   153 
   162 
   154         `option` : string
   163         `option` : string
   155             the configuration option in the form
   164             the configuration option in the form "section.option"
   156             "``section``\ **.**\ ``option``"
       
   157 
   165 
   158         Throws a `NoDefaultError`, if no default value was passed to
   166         Throws a `NoDefaultError`, if no default value was passed to
   159         `LazyConfigOption.__init__()` for the `option`.
   167         `LazyConfigOption.__init__()` for the `option`.
       
   168 
   160         """
   169         """
   161         section, option = self._get_section_option(option)
   170         section, option = self._get_section_option(option)
   162         try:
   171         try:
   163             return self._cfg[section][option].getter(section, option)
   172             return self._cfg[section][option].getter(section, option)
   164         except (NoSectionError, NoOptionError):
   173         except (NoSectionError, NoOptionError):
   174 
   183 
   175     def set(self, option, value):
   184     def set(self, option, value):
   176         """Set the `value` of the `option`.
   185         """Set the `value` of the `option`.
   177 
   186 
   178         Throws a `ValueError` if `value` couldn't be converted using
   187         Throws a `ValueError` if `value` couldn't be converted using
   179         `LazyConfigOption.cls`"""
   188         `LazyConfigOption.cls`.
       
   189 
       
   190         """
   180         section, option = self._get_section_option(option)
   191         section, option = self._get_section_option(option)
   181         val = self._cfg[section][option].cls(value)
   192         val = self._cfg[section][option].cls(value)
   182         if self._cfg[section][option].validate:
   193         if self._cfg[section][option].validate:
   183             val = self._cfg[section][option].validate(val)
   194             val = self._cfg[section][option].validate(val)
   184         if not RawConfigParser.has_section(self, section):
   195         if not RawConfigParser.has_section(self, section):
   189     def has_section(self, section):
   200     def has_section(self, section):
   190         """Checks if `section` is a known configuration section."""
   201         """Checks if `section` is a known configuration section."""
   191         return section.lower() in self._cfg
   202         return section.lower() in self._cfg
   192 
   203 
   193     def has_option(self, option):
   204     def has_option(self, option):
   194         """Checks if the option (section.option) is a known configuration
   205         """Checks if the option (section.option) is a known
   195         option."""
   206         configuration option.
       
   207 
       
   208         """
   196         try:
   209         try:
   197             self._get_section_option(option)
   210             self._get_section_option(option)
   198             return True
   211             return True
   199         except(BadOptionError, NoSectionError, NoOptionError):
   212         except(BadOptionError, NoSectionError, NoOptionError):
   200             return False
   213             return False
   208     """A simple container class for configuration settings.
   221     """A simple container class for configuration settings.
   209 
   222 
   210     `LazyConfigOption` instances are required by `LazyConfig` instances,
   223     `LazyConfigOption` instances are required by `LazyConfig` instances,
   211     and instances of classes derived from `LazyConfig`, like the
   224     and instances of classes derived from `LazyConfig`, like the
   212     `Config` class.
   225     `Config` class.
       
   226 
   213     """
   227     """
   214     __slots__ = ('__cls', '__default', '__getter', '__validate')
   228     __slots__ = ('__cls', '__default', '__getter', '__validate')
   215 
   229 
   216     def __init__(self, cls, default, getter, validate=None):
   230     def __init__(self, cls, default, getter, validate=None):
   217         """Creates a new `LazyConfigOption` instance.
   231         """Creates a new `LazyConfigOption` instance.
   219         Arguments:
   233         Arguments:
   220 
   234 
   221         `cls` : type
   235         `cls` : type
   222           The class/type of the option's value
   236           The class/type of the option's value
   223         `default`
   237         `default`
   224           Default value of the option. Use ``None`` if the option should not
   238           Default value of the option. Use ``None`` if the option should
   225           have a default value.
   239           not have a default value.
   226         `getter` : callable
   240         `getter` : callable
   227           A method's name of `RawConfigParser` and derived classes, to get a
   241           A method's name of `RawConfigParser` and derived classes, to
   228           option's value, e.g. `self.getint`.
   242           get a option's value, e.g. `self.getint`.
   229         `validate` : NoneType or a callable
   243         `validate` : NoneType or a callable
   230           None or any method, that takes one argument, in order to check the
   244           None or any method, that takes one argument, in order to
   231           value, when `LazyConfig.set()` is called.
   245           check the value, when `LazyConfig.set()` is called.
       
   246 
   232         """
   247         """
   233         self.__cls = cls
   248         self.__cls = cls
   234         if not default is None:# enforce the type of the default value
   249         if not default is None:# enforce the type of the default value
   235             self.__default = self.__cls(default)
   250             self.__default = self.__cls(default)
   236         else:
   251         else:
   244                             validate.__class__.__name__)
   259                             validate.__class__.__name__)
   245         self.__validate = validate
   260         self.__validate = validate
   246 
   261 
   247     @property
   262     @property
   248     def cls(self):
   263     def cls(self):
   249         """The class of the option's value e.g. `str`, `unicode` or `bool`"""
   264         """The class of the option's value e.g. `str`, `unicode` or
       
   265         `bool`.
       
   266 
       
   267         """
   250         return self.__cls
   268         return self.__cls
   251 
   269 
   252     @property
   270     @property
   253     def default(self):
   271     def default(self):
   254         """The option's default value, may be `None`"""
   272         """The option's default value, may be `None`"""
   273 
   291 
   274         Arguments:
   292         Arguments:
   275 
   293 
   276         `filename` : str
   294         `filename` : str
   277           path to the configuration file
   295           path to the configuration file
       
   296 
   278         """
   297         """
   279         LazyConfig.__init__(self)
   298         LazyConfig.__init__(self)
   280         self._cfgFileName = filename
   299         self._cfg_filename = filename
   281         self._cfgFile = None
   300         self._cfg_file = None
   282         self.__missing = {}
   301         self.__missing = {}
   283 
   302 
   284         LCO = LazyConfigOption
   303         LCO = LazyConfigOption
   285         bool_t = self.bool_new
   304         bool_t = self.bool_new
   286         self._cfg = {
   305         self._cfg = {
   287             'account': {
   306             'account': {
   288                 'delete_directory': LCO(bool_t, False, self.getboolean),
   307                 'delete_directory': LCO(bool_t, False, self.getboolean),
   289                 'directory_mode':   LCO(int,    448,   self.getint),
   308                 'directory_mode': LCO(int, 448, self.getint),
   290                 'disk_usage':       LCO(bool_t, False, self.getboolean),
   309                 'disk_usage': LCO(bool_t, False, self.getboolean),
   291                 'password_length':  LCO(int,    8,     self.getint),
   310                 'password_length': LCO(int, 8, self.getint),
   292                 'random_password':  LCO(bool_t, False, self.getboolean),
   311                 'random_password': LCO(bool_t, False, self.getboolean),
   293                 'imap' :            LCO(bool_t, True,  self.getboolean),
   312                 'imap': LCO(bool_t, True, self.getboolean),
   294                 'pop3' :            LCO(bool_t, True,  self.getboolean),
   313                 'pop3': LCO(bool_t, True, self.getboolean),
   295                 'sieve':            LCO(bool_t, True,  self.getboolean),
   314                 'sieve': LCO(bool_t, True, self.getboolean),
   296                 'smtp' :            LCO(bool_t, True,  self.getboolean),
   315                 'smtp': LCO(bool_t, True, self.getboolean),
   297             },
   316             },
   298             'bin': {
   317             'bin': {
   299                 'dovecotpw': LCO(str, '/usr/sbin/dovecotpw', self.get, exec_ok),
   318                 'dovecotpw': LCO(str, '/usr/sbin/dovecotpw', self.get,
   300                 'du':        LCO(str, '/usr/bin/du',        self.get, exec_ok),
   319                                  exec_ok),
   301                 'postconf':  LCO(str, '/usr/sbin/postconf', self.get, exec_ok),
   320                 'du': LCO(str, '/usr/bin/du', self.get, exec_ok),
       
   321                 'postconf': LCO(str, '/usr/sbin/postconf', self.get, exec_ok),
   302             },
   322             },
   303             'database': {
   323             'database': {
   304                 'host': LCO(str, 'localhost', self.get),
   324                 'host': LCO(str, 'localhost', self.get),
   305                 'name': LCO(str, 'mailsys',   self.get),
   325                 'name': LCO(str, 'mailsys', self.get),
   306                 'pass': LCO(str, None,        self.get),
   326                 'pass': LCO(str, None, self.get),
   307                 'user': LCO(str, None,        self.get),
   327                 'user': LCO(str, None, self.get),
   308             },
   328             },
   309             'domain': {
   329             'domain': {
   310                 'auto_postmaster':  LCO(bool_t, True,  self.getboolean),
   330                 'auto_postmaster': LCO(bool_t, True, self.getboolean),
   311                 'delete_directory': LCO(bool_t, False, self.getboolean),
   331                 'delete_directory': LCO(bool_t, False, self.getboolean),
   312                 'directory_mode':   LCO(int,    504,   self.getint),
   332                 'directory_mode': LCO(int, 504, self.getint),
   313                 'force_deletion':   LCO(bool_t, False, self.getboolean),
   333                 'force_deletion': LCO(bool_t, False, self.getboolean),
   314             },
   334             },
   315             'maildir': {
   335             'maildir': {
   316                 'folders': LCO(str, 'Drafts:Sent:Templates:Trash', self.get),
   336                 'folders': LCO(str, 'Drafts:Sent:Templates:Trash', self.get),
   317                 'name':    LCO(str, 'Maildir',                     self.get),
   337                 'name': LCO(str, 'Maildir', self.get),
   318             },
   338             },
   319             'misc': {
   339             'misc': {
   320                 'base_directory':  LCO(str, '/srv/mail', self.get, is_dir),
   340                 'base_directory': LCO(str, '/srv/mail', self.get, is_dir),
   321                 'dovecot_version': LCO(int, 12,          self.getint),
   341                 'dovecot_version': LCO(int, 12, self.getint),
   322                 'gid_mail':        LCO(int, 8,           self.getint),
   342                 'gid_mail': LCO(int, 8, self.getint),
   323                 'password_scheme': LCO(str, 'CRAM-MD5',  self.get,
   343                 'password_scheme': LCO(str, 'CRAM-MD5', self.get,
   324                                        self.known_scheme),
   344                                        self.known_scheme),
   325                 'transport':       LCO(str, 'dovecot:',  self.get),
   345                 'transport': LCO(str, 'dovecot:', self.get),
   326             },
   346             },
   327         }
   347         }
   328 
       
   329     def configure(self, sections):
       
   330         raise NotImplementedError
       
   331 
   348 
   332     def load(self):
   349     def load(self):
   333         """Loads the configuration, read only.
   350         """Loads the configuration, read only.
   334 
   351 
   335         Raises a VMMConfigException if the configuration syntax is invalid.
   352         Raises a VMMConfigException if the configuration syntax is
       
   353         invalid.
       
   354 
   336         """
   355         """
   337         try:
   356         try:
   338             self._cfgFile = open(self._cfgFileName, 'r')
   357             self._cfg_file = open(self._cfg_filename, 'r')
   339             self.readfp(self._cfgFile)
   358             self.readfp(self._cfg_file)
   340         except (MissingSectionHeaderError, ParsingError), e:
   359         except (MissingSectionHeaderError, ParsingError), err:
   341             raise VMMConfigException(str(e), CONF_ERROR)
   360             raise VMMConfigException(str(err), CONF_ERROR)
   342         finally:
   361         finally:
   343             if self._cfgFile and not self._cfgFile.closed:
   362             if self._cfg_file and not self._cfg_file.closed:
   344                 self._cfgFile.close()
   363                 self._cfg_file.close()
   345 
   364 
   346     def check(self):
   365     def check(self):
   347         """Performs a configuration check.
   366         """Performs a configuration check.
   348 
   367 
   349         Raises a VMMConfigException if the check fails.
   368         Raises a VMMConfigException if the check fails.
       
   369 
   350         """
   370         """
   351         # TODO: There are only two settings w/o defaults.
   371         # TODO: There are only two settings w/o defaults.
   352         #       So there is no need for cStringIO
   372         #       So there is no need for cStringIO
   353         if not self.__chkCfg():
   373         if not self.__chk_cfg():
   354             errmsg = StringIO()
   374             errmsg = StringIO()
   355             errmsg.write(_(u'Missing options, which have no default value.\n'))
   375             errmsg.write(_(u'Missing options, which have no default value.\n'))
   356             errmsg.write(_(u'Using configuration file: %s\n') %
   376             errmsg.write(_(u'Using configuration file: %s\n') %
   357                          self._cfgFileName)
   377                          self._cfg_filename)
   358             for section, options in self.__missing.iteritems():
   378             for section, options in self.__missing.iteritems():
   359                 errmsg.write(_(u'* Section: %s\n') % section)
   379                 errmsg.write(_(u'* Section: %s\n') % section)
   360                 for option in options:
   380                 for option in options:
   361                     errmsg.write((u'    %s\n') % option)
   381                     errmsg.write((u'    %s\n') % option)
   362             raise VMMConfigException(errmsg.getvalue(), CONF_ERROR)
   382             raise VMMConfigException(errmsg.getvalue(), CONF_ERROR)
   365         """Converts `scheme` to upper case and checks if is known by
   385         """Converts `scheme` to upper case and checks if is known by
   366         Dovecot (listed in VirtualMailManager.SCHEMES).
   386         Dovecot (listed in VirtualMailManager.SCHEMES).
   367 
   387 
   368         Throws a `ConfigValueError` if the scheme is not listed in
   388         Throws a `ConfigValueError` if the scheme is not listed in
   369         VirtualMailManager.SCHEMES.
   389         VirtualMailManager.SCHEMES.
       
   390 
   370         """
   391         """
   371         scheme = scheme.upper()
   392         scheme = scheme.upper()
   372         # TODO: VMM.SCHEMES
   393         # TODO: VMM.SCHEMES
   373 
   394 
   374     def unicode(self, section, option):
   395     def unicode(self, section, option):
   375         """Returns the value of the `option` from `section`, converted to
   396         """Returns the value of the `option` from `section`, converted
   376         Unicode."""
   397         to Unicode.
       
   398 
       
   399         """
   377         return get_unicode(self.get(section, option))
   400         return get_unicode(self.get(section, option))
   378 
   401 
   379     def __chkCfg(self):
   402     def __chk_cfg(self):
   380         """Checks all section's options for settings w/o a default value.
   403         """Checks all section's options for settings w/o a default
   381 
   404         value.
   382         Returns `True` if everything is fine, else `False`."""
   405 
       
   406         Returns `True` if everything is fine, else `False`.
       
   407 
       
   408         """
   383         errors = False
   409         errors = False
   384         for section in self._cfg.iterkeys():
   410         for section in self._cfg.iterkeys():
   385             missing = []
   411             missing = []
   386             for option, value in self._cfg[section].iteritems():
   412             for option, value in self._cfg[section].iteritems():
   387                 if (value.default is None and
   413                 if (value.default is None and
   388                     not RawConfigParser.has_option(self, section, option)):
   414                     not RawConfigParser.has_option(self, section, option)):
   389                         missing.append(option)
   415                     missing.append(option)
   390                         errors = True
   416                     errors = True
   391             if missing:
   417             if missing:
   392                 self.__missing[section] = missing
   418                 self.__missing[section] = missing
   393         return not errors
   419         return not errors
       
   420 
       
   421 
       
   422 del _