4 import ConfigParser as configparser
9 from ceph_volume import terminal
10 from ceph_volume import exceptions
13 logger = logging.getLogger(__name__)
16 class _TrimIndentFile(object):
18 This is used to take a file-like object and removes any
19 leading tabs from each line when it's read. This is important
20 because some ceph configuration files include tabs which break
23 def __init__(self, fp):
27 line = self.fp.readline()
28 return line.lstrip(' \t')
31 return iter(self.readline, '')
34 def load(abspath=None):
35 if not os.path.exists(abspath):
36 raise exceptions.ConfigurationError(abspath=abspath)
41 ceph_file = open(abspath)
42 trimmed_conf = _TrimIndentFile(ceph_file)
43 with contextlib.closing(ceph_file):
44 parser.readfp(trimmed_conf)
46 except configparser.ParsingError as error:
47 logger.exception('Unable to parse INI-style file: %s' % abspath)
48 terminal.error(str(error))
49 raise RuntimeError('Unable to read configuration file: %s' % abspath)
52 class Conf(configparser.SafeConfigParser):
54 Subclasses from SafeConfigParser to give a few helpers for Ceph
58 def read_path(self, path):
60 return self.read(path)
64 self.get('global', 'fsid')
65 except (configparser.NoSectionError, configparser.NoOptionError):
66 raise exceptions.ConfigurationKeyError('global', 'fsid')
68 def get_safe(self, section, key, default=None):
70 Attempt to get a configuration value from a certain section
71 in a ``cfg`` object but returning None if not found. Avoids the need
72 to be doing try/except {ConfigParser Exceptions} every time.
76 return self.get(section, key)
77 except (configparser.NoSectionError, configparser.NoOptionError):
80 def get_list(self, section, key, default=None, split=','):
82 Assumes that the value for a given key is going to be a list separated
83 by commas. It gets rid of trailing comments. If just one item is
84 present it returns a list with a single item, if no key is found an
85 empty list is returned.
87 Optionally split on other characters besides ',' and return a fallback
88 value if no items are found.
91 value = self.get_safe(section, key, [])
93 if default is not None:
98 value = re.split(r'\s+#', value)[0]
101 value = value.split(split)
104 return [x.strip() for x in value]
106 # XXX Almost all of it lifted from the original ConfigParser._read method,
107 # except for the parsing of '#' in lines. This is only a problem in Python 2.7, and can be removed
108 # once tooling is Python3 only with `Conf(inline_comment_prefixes=('#',';'))`
109 def _read(self, fp, fpname):
110 """Parse a sectioned setup file.
112 The sections in setup file contains a title line at the top,
113 indicated by a name in square brackets (`[]'), plus key/value
114 options lines, indicated by `name: value' format lines.
115 Continuations are represented by an embedded newline then
116 leading whitespace. Blank lines, lines beginning with a '#',
117 and just about everything else are ignored.
119 cursect = None # None, or a dictionary
122 e = None # None, or an exception
128 # comment or blank line?
129 if line.strip() == '' or line[0] in '#;':
131 if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
132 # no leading whitespace
135 if line[0].isspace() and cursect is not None and optname:
138 cursect[optname].append(value)
139 # a section header or option header?
141 # is it a section header?
142 mo = self.SECTCRE.match(line)
144 sectname = mo.group('header')
145 if sectname in self._sections:
146 cursect = self._sections[sectname]
147 elif sectname == 'DEFAULT':
148 cursect = self._defaults
150 cursect = self._dict()
151 cursect['__name__'] = sectname
152 self._sections[sectname] = cursect
153 # So sections can't start with a continuation line
155 # no section header in the file?
156 elif cursect is None:
157 raise configparser.MissingSectionHeaderError(fpname, lineno, line)
160 mo = self._optcre.match(line)
162 optname, vi, optval = mo.group('option', 'vi', 'value')
163 optname = self.optionxform(optname.rstrip())
164 # This check is fine because the OPTCRE cannot
165 # match if it would set optval to None
166 if optval is not None:
167 # XXX Added support for '#' inline comments
168 if vi in ('=', ':') and (';' in optval or '#' in optval):
170 optval = re.split(r'\s+(;|#)', optval)[0]
171 # if what is left is comment as a value, fallback to an empty string
172 # that is: `foo = ;` would mean `foo` is '', which brings parity with
173 # what ceph-conf tool does
174 if optval in [';','#']:
176 optval = optval.strip()
180 cursect[optname] = [optval]
182 # valueless option handling
183 cursect[optname] = optval
185 # a non-fatal parsing error occurred. set up the
186 # exception but keep going. the exception will be
187 # raised at the end of the file and will contain a
188 # list of all bogus lines
190 e = configparser.ParsingError(fpname)
191 e.append(lineno, repr(line))
192 # if any parsing errors occurred, raise an exception
196 # join the multi-line values collected while reading
197 all_sections = [self._defaults]
198 all_sections.extend(self._sections.values())
199 for options in all_sections:
200 for name, val in options.items():
201 if isinstance(val, list):
202 options[name] = '\n'.join(val)