]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/ceph-volume/ceph_volume/configuration.py
update sources to v12.2.3
[ceph.git] / ceph / src / ceph-volume / ceph_volume / configuration.py
index eda58f09b95c145944b1506b9ce5eaaf538b45e2..2b9cd9fdc7f1319e7f94d98d0bcd72a551b8307a 100644 (file)
@@ -2,6 +2,7 @@ try:
     import configparser
 except ImportError:
     import ConfigParser as configparser
+import contextlib
 import logging
 import os
 import re
@@ -31,14 +32,21 @@ class _TrimIndentFile(object):
 
 
 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):
@@ -52,14 +60,16 @@ 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
@@ -97,3 +107,101 @@ class Conf(configparser.SafeConfigParser):
 
         # 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)