]> git.proxmox.com Git - ceph.git/blame - ceph/src/ceph-volume/ceph_volume/configuration.py
update sources to v12.2.3
[ceph.git] / ceph / src / ceph-volume / ceph_volume / configuration.py
CommitLineData
d2e6a577
FG
1try:
2 import configparser
3except ImportError:
4 import ConfigParser as configparser
b32b8144 5import contextlib
d2e6a577
FG
6import logging
7import os
8import re
9from ceph_volume import terminal
10from ceph_volume import exceptions
11
12
13logger = logging.getLogger(__name__)
14
15
16class _TrimIndentFile(object):
17 """
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
21 ConfigParser.
22 """
23 def __init__(self, fp):
24 self.fp = fp
25
26 def readline(self):
27 line = self.fp.readline()
28 return line.lstrip(' \t')
29
30 def __iter__(self):
31 return iter(self.readline, '')
32
33
34def load(abspath=None):
b32b8144
FG
35 if not os.path.exists(abspath):
36 raise exceptions.ConfigurationError(abspath=abspath)
37
d2e6a577 38 parser = Conf()
b32b8144 39
d2e6a577 40 try:
b32b8144
FG
41 ceph_file = open(abspath)
42 trimmed_conf = _TrimIndentFile(ceph_file)
43 with contextlib.closing(ceph_file):
44 parser.readfp(trimmed_conf)
45 return parser
d2e6a577 46 except configparser.ParsingError as error:
d2e6a577 47 logger.exception('Unable to parse INI-style file: %s' % abspath)
b32b8144
FG
48 terminal.error(str(error))
49 raise RuntimeError('Unable to read configuration file: %s' % abspath)
d2e6a577
FG
50
51
52class Conf(configparser.SafeConfigParser):
53 """
54 Subclasses from SafeConfigParser to give a few helpers for Ceph
55 configuration.
56 """
57
58 def read_path(self, path):
59 self.path = path
60 return self.read(path)
61
62 def is_valid(self):
d2e6a577
FG
63 try:
64 self.get('global', 'fsid')
65 except (configparser.NoSectionError, configparser.NoOptionError):
66 raise exceptions.ConfigurationKeyError('global', 'fsid')
67
b32b8144
FG
68 def optionxform(self, s):
69 s = s.replace('_', ' ')
70 s = '_'.join(s.split())
71 return s
72
d2e6a577
FG
73 def get_safe(self, section, key, default=None):
74 """
75 Attempt to get a configuration value from a certain section
76 in a ``cfg`` object but returning None if not found. Avoids the need
77 to be doing try/except {ConfigParser Exceptions} every time.
78 """
79 self.is_valid()
80 try:
81 return self.get(section, key)
82 except (configparser.NoSectionError, configparser.NoOptionError):
83 return default
84
85 def get_list(self, section, key, default=None, split=','):
86 """
87 Assumes that the value for a given key is going to be a list separated
88 by commas. It gets rid of trailing comments. If just one item is
89 present it returns a list with a single item, if no key is found an
90 empty list is returned.
91
92 Optionally split on other characters besides ',' and return a fallback
93 value if no items are found.
94 """
95 self.is_valid()
96 value = self.get_safe(section, key, [])
97 if value == []:
98 if default is not None:
99 return default
100 return value
101
102 # strip comments
103 value = re.split(r'\s+#', value)[0]
104
105 # split on commas
106 value = value.split(split)
107
108 # strip spaces
109 return [x.strip() for x in value]
b32b8144
FG
110
111 # XXX Almost all of it lifted from the original ConfigParser._read method,
112 # except for the parsing of '#' in lines. This is only a problem in Python 2.7, and can be removed
113 # once tooling is Python3 only with `Conf(inline_comment_prefixes=('#',';'))`
114 def _read(self, fp, fpname):
115 """Parse a sectioned setup file.
116
117 The sections in setup file contains a title line at the top,
118 indicated by a name in square brackets (`[]'), plus key/value
119 options lines, indicated by `name: value' format lines.
120 Continuations are represented by an embedded newline then
121 leading whitespace. Blank lines, lines beginning with a '#',
122 and just about everything else are ignored.
123 """
124 cursect = None # None, or a dictionary
125 optname = None
126 lineno = 0
127 e = None # None, or an exception
128 while True:
129 line = fp.readline()
130 if not line:
131 break
132 lineno = lineno + 1
133 # comment or blank line?
134 if line.strip() == '' or line[0] in '#;':
135 continue
136 if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
137 # no leading whitespace
138 continue
139 # continuation line?
140 if line[0].isspace() and cursect is not None and optname:
141 value = line.strip()
142 if value:
143 cursect[optname].append(value)
144 # a section header or option header?
145 else:
146 # is it a section header?
147 mo = self.SECTCRE.match(line)
148 if mo:
149 sectname = mo.group('header')
150 if sectname in self._sections:
151 cursect = self._sections[sectname]
152 elif sectname == 'DEFAULT':
153 cursect = self._defaults
154 else:
155 cursect = self._dict()
156 cursect['__name__'] = sectname
157 self._sections[sectname] = cursect
158 # So sections can't start with a continuation line
159 optname = None
160 # no section header in the file?
161 elif cursect is None:
162 raise configparser.MissingSectionHeaderError(fpname, lineno, line)
163 # an option line?
164 else:
165 mo = self._optcre.match(line)
166 if mo:
167 optname, vi, optval = mo.group('option', 'vi', 'value')
168 optname = self.optionxform(optname.rstrip())
169 # This check is fine because the OPTCRE cannot
170 # match if it would set optval to None
171 if optval is not None:
172 # XXX Added support for '#' inline comments
173 if vi in ('=', ':') and (';' in optval or '#' in optval):
174 # strip comments
175 optval = re.split(r'\s+(;|#)', optval)[0]
176 # if what is left is comment as a value, fallback to an empty string
177 # that is: `foo = ;` would mean `foo` is '', which brings parity with
178 # what ceph-conf tool does
179 if optval in [';','#']:
180 optval = ''
181 optval = optval.strip()
182 # allow empty values
183 if optval == '""':
184 optval = ''
185 cursect[optname] = [optval]
186 else:
187 # valueless option handling
188 cursect[optname] = optval
189 else:
190 # a non-fatal parsing error occurred. set up the
191 # exception but keep going. the exception will be
192 # raised at the end of the file and will contain a
193 # list of all bogus lines
194 if not e:
195 e = configparser.ParsingError(fpname)
196 e.append(lineno, repr(line))
197 # if any parsing errors occurred, raise an exception
198 if e:
199 raise e
200
201 # join the multi-line values collected while reading
202 all_sections = [self._defaults]
203 all_sections.extend(self._sections.values())
204 for options in all_sections:
205 for name, val in options.items():
206 if isinstance(val, list):
207 options[name] = '\n'.join(val)