import configparser
except ImportError:
import ConfigParser as configparser
+import contextlib
import logging
import os
import re
def load(abspath=None):
+ if not os.path.exists(abspath):
+ raise exceptions.ConfigurationError(abspath=abspath)
+
parser = Conf()
+
try:
- parser.read_path(abspath)
- return parser
+ ceph_file = open(abspath)
+ trimmed_conf = _TrimIndentFile(ceph_file)
+ with contextlib.closing(ceph_file):
+ parser.readfp(trimmed_conf)
+ return parser
except configparser.ParsingError as error:
- terminal.error('Unable to read configuration file: %s' % abspath)
- terminal.error(str(error))
logger.exception('Unable to parse INI-style file: %s' % abspath)
+ terminal.error(str(error))
+ raise RuntimeError('Unable to read configuration file: %s' % abspath)
class Conf(configparser.SafeConfigParser):
return self.read(path)
def is_valid(self):
- if not os.path.exists(self.path):
- raise exceptions.ConfigurationError(abspath=self.path)
-
try:
self.get('global', 'fsid')
except (configparser.NoSectionError, configparser.NoOptionError):
raise exceptions.ConfigurationKeyError('global', 'fsid')
+ def optionxform(self, s):
+ s = s.replace('_', ' ')
+ s = '_'.join(s.split())
+ return s
+
def get_safe(self, section, key, default=None):
"""
Attempt to get a configuration value from a certain section
# strip spaces
return [x.strip() for x in value]
+
+ # XXX Almost all of it lifted from the original ConfigParser._read method,
+ # except for the parsing of '#' in lines. This is only a problem in Python 2.7, and can be removed
+ # once tooling is Python3 only with `Conf(inline_comment_prefixes=('#',';'))`
+ def _read(self, fp, fpname):
+ """Parse a sectioned setup file.
+
+ The sections in setup file contains a title line at the top,
+ indicated by a name in square brackets (`[]'), plus key/value
+ options lines, indicated by `name: value' format lines.
+ Continuations are represented by an embedded newline then
+ leading whitespace. Blank lines, lines beginning with a '#',
+ and just about everything else are ignored.
+ """
+ cursect = None # None, or a dictionary
+ optname = None
+ lineno = 0
+ e = None # None, or an exception
+ while True:
+ line = fp.readline()
+ if not line:
+ break
+ lineno = lineno + 1
+ # comment or blank line?
+ if line.strip() == '' or line[0] in '#;':
+ continue
+ if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
+ # no leading whitespace
+ continue
+ # continuation line?
+ if line[0].isspace() and cursect is not None and optname:
+ value = line.strip()
+ if value:
+ cursect[optname].append(value)
+ # a section header or option header?
+ else:
+ # is it a section header?
+ mo = self.SECTCRE.match(line)
+ if mo:
+ sectname = mo.group('header')
+ if sectname in self._sections:
+ cursect = self._sections[sectname]
+ elif sectname == 'DEFAULT':
+ cursect = self._defaults
+ else:
+ cursect = self._dict()
+ cursect['__name__'] = sectname
+ self._sections[sectname] = cursect
+ # So sections can't start with a continuation line
+ optname = None
+ # no section header in the file?
+ elif cursect is None:
+ raise configparser.MissingSectionHeaderError(fpname, lineno, line)
+ # an option line?
+ else:
+ mo = self._optcre.match(line)
+ if mo:
+ optname, vi, optval = mo.group('option', 'vi', 'value')
+ optname = self.optionxform(optname.rstrip())
+ # This check is fine because the OPTCRE cannot
+ # match if it would set optval to None
+ if optval is not None:
+ # XXX Added support for '#' inline comments
+ if vi in ('=', ':') and (';' in optval or '#' in optval):
+ # strip comments
+ optval = re.split(r'\s+(;|#)', optval)[0]
+ # if what is left is comment as a value, fallback to an empty string
+ # that is: `foo = ;` would mean `foo` is '', which brings parity with
+ # what ceph-conf tool does
+ if optval in [';','#']:
+ optval = ''
+ optval = optval.strip()
+ # allow empty values
+ if optval == '""':
+ optval = ''
+ cursect[optname] = [optval]
+ else:
+ # valueless option handling
+ cursect[optname] = optval
+ else:
+ # a non-fatal parsing error occurred. set up the
+ # exception but keep going. the exception will be
+ # raised at the end of the file and will contain a
+ # list of all bogus lines
+ if not e:
+ e = configparser.ParsingError(fpname)
+ e.append(lineno, repr(line))
+ # if any parsing errors occurred, raise an exception
+ if e:
+ raise e
+
+ # join the multi-line values collected while reading
+ all_sections = [self._defaults]
+ all_sections.extend(self._sections.values())
+ for options in all_sections:
+ for name, val in options.items():
+ if isinstance(val, list):
+ options[name] = '\n'.join(val)