Package gofer :: Package agent :: Module config
[hide private]
[frames] | no frames]

Source Code for Module gofer.agent.config

  1  # 
  2  # Copyright (c) 2011 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU Lesser General Public 
  5  # License as published by the Free Software Foundation; either version 
  6  # 2 of the License (LGPLv2) or (at your option) any later version. 
  7  # There is NO WARRANTY for this software, express or implied, 
  8  # including the implied warranties of MERCHANTABILITY, 
  9  # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should 
 10  # have received a copy of LGPLv2 along with this software; if not, see 
 11  # http://www.gnu.org/licenses/old-licenses/lgpl-2.0.txt. 
 12  # 
 13  # Jeff Ortel <jortel@redhat.com> 
 14  # 
 15   
 16   
 17  import os 
 18  import re 
 19  import socket 
 20   
 21  from logging import getLogger 
 22  from iniparse import INIConfig 
 23  from iniparse.config import Undefined 
 24   
 25  from gofer import NAME, Singleton 
 26   
 27  log = getLogger(__name__) 
 28   
 29   
 30  # makes x.y.z safe when section (y) not defined 
31 -def _undefined(self, name):
32 return self
33 34 Undefined.__getattr__ = _undefined 35 36
37 -def ndef(x):
38 """ 39 Section/property not defined. 40 @param x: A section/property 41 @type x: A section or property object. 42 @return: True if not defined. 43 """ 44 return isinstance(x, Undefined)
45
46 -def nvl(x, d=None):
47 """ 48 Not define value. 49 @param x: An object to check. 50 @type x: A section/property 51 @return: d if not defined, else x. 52 """ 53 if ndef(x): 54 return d 55 else: 56 return x
57 58
59 -class Base(INIConfig):
60 """ 61 Base configuration. 62 Uses L{Reader} which provides import. 63 """ 64
65 - def __init__(self, path):
66 """ 67 @param path: The path to an INI file. 68 @type path: str 69 """ 70 fp = Reader(path) 71 try: 72 INIConfig.__init__(self, fp) 73 finally: 74 fp.close()
75 76
77 -class Config(Base):
78 """ 79 The gofer agent configuration. 80 @cvar ROOT: The root configuration directory. 81 @type ROOT: str 82 @cvar PATH: The absolute path to the config directory. 83 @type PATH: str 84 @cvar USER: The path to an alternate configuration file 85 within the user's home. 86 @type USER: str 87 @cvar ALT: The environment variable with a path to an alternate 88 configuration file. 89 @type ALT: str 90 """ 91 __metaclass__ = Singleton 92 93 ROOT = '/etc/%s' % NAME 94 FILE = 'agent.conf' 95 PATH = os.path.join(ROOT, FILE) 96 USER = os.path.join('~/.%s' % NAME, FILE) 97 CNFD = os.path.join(ROOT, 'conf.d') 98 ALT = '%s_OVERRIDE' % NAME.upper() 99
100 - def __init__(self):
101 """ 102 Open the configuration. 103 Merge (in) alternate configuration file when specified 104 by environment variable. 105 """ 106 try: 107 Base.__init__(self, self.PATH) 108 self.__addconfd() 109 altpath = self.__altpath() 110 if altpath: 111 alt = Base(altpath) 112 self.__mergeIn(alt) 113 log.info('merged[in]:%s\n%s', altpath, self) 114 except: 115 log.error(self.PATH, exc_info=1) 116 raise
117
118 - def __update(self, other):
119 """ 120 Update with the specified I{other} configuration. 121 @param other: The conf to update with. 122 @type other: Base 123 @return: self 124 @rtype: L{Config} 125 """ 126 for section in other: 127 for key in other[section]: 128 self[section][key] = other[section][key] 129 return self
130
131 - def __mergeIn(self, other):
132 """ 133 Merge (in) the specified I{other} configuration. 134 @param other: The conf to merge in. 135 @type other: Base 136 @return: self 137 @rtype: L{Config} 138 """ 139 for section in other: 140 if section not in self: 141 continue 142 for key in other[section]: 143 self[section][key] = other[section][key] 144 return self
145
146 - def __mergeOut(self, other):
147 """ 148 Merge (out) to the specified I{other} configuration. 149 @param other: The conf to merge out. 150 @type other: Base 151 @return: self 152 @rtype: L{Config} 153 """ 154 for section in other: 155 if section not in self: 156 continue 157 for key in other[section]: 158 other[section][key] = self[section][key] 159 return self
160
161 - def write(self):
162 """ 163 Write the configuration. 164 """ 165 altpath = self.__altpath() 166 if altpath: 167 alt = self.__read(altpath) 168 self.__mergeOut(alt) 169 log.info('merge[out]:%s\n%s', altpath, alt) 170 path = altpath 171 s = str(alt) 172 else: 173 path = self.PATH 174 s = str(self) 175 fp = open(path, 'w') 176 try: 177 fp.write(s) 178 finally: 179 fp.close()
180
181 - def __altpath(self):
182 """ 183 Get the I{alternate} configuration path. 184 Resolution order: ALT, USER 185 @return: The path to the alternate configuration file. 186 @rtype: str 187 """ 188 path = os.environ.get(self.ALT) 189 if path: 190 return path 191 path = os.path.expanduser(self.USER) 192 if os.path.exists(path): 193 return path 194 else: 195 None
196
197 - def __addconfd(self):
198 """ 199 Read and merge the conf.d files. 200 """ 201 for fn in os.listdir(self.CNFD): 202 path = os.path.join(self.CNFD, fn) 203 cfg = Base(path) 204 self.__update(cfg) 205 log.info('updated with: %s\n%s', path, self)
206 207
208 -class Properties:
209 """ 210 Import property specification. 211 @ivar pattern: The regex for property specification. 212 @type pattern: I{regex.pattern} 213 @ivar vdict: The variable dictionary. 214 @type vdict: dict 215 @ivar plain: The list of I{plan} properties to import. 216 @type plain: [str,..] 217 """ 218 219 pattern = re.compile('([^(]+)(\()([^)]+)(\))') 220
221 - def __init__(self, properties=()):
222 """ 223 @param properties: A list of property specifications. 224 @type properties: [str,..] 225 """ 226 self.vdict = {} 227 self.plain = [] 228 for p in properties: 229 if not p: 230 continue 231 m = self.pattern.match(p) 232 if m: 233 key = m.group(1).strip() 234 value = m.group(3).strip() 235 self.vdict[key] = value 236 else: 237 self.plain.append(p)
238
239 - def isplain(self, property):
240 """ 241 Get whether a property is I{plain} and is to be imported. 242 @param property: A property name. 243 @type property: str 244 @return: True when property is to be imported. 245 @rtype: bool 246 """ 247 return ( property in self.plain )
248
249 - def var(self, property):
250 """ 251 Get the property's declared variable name. 252 @param property: A property name. 253 @type property: str 254 @return: The variable name declared for the property 255 or None when not declared. 256 @rtype: str 257 """ 258 return self.vdict.get(property)
259
260 - def empty(self):
261 """ 262 Get whether the object is empty. 263 @return: True no properties defined. 264 @rtype: bool 265 """ 266 return ( len(self) == 0 )
267
268 - def __iter__(self):
269 keys = self.vdict.keys() 270 keys += self.plain 271 return iter(keys)
272
273 - def __len__(self):
274 return ( len(self.vdict)+len(self.plain) )
275 276
277 -class Import:
278 """ 279 Represents an import directive. 280 @@import:<path>:<section>:<property>, 281 where <property> is: <name>|<name>(<variable>). 282 When the <variable> form is used, a variable is assigned the value 283 to be used as $(var) in the conf rather than imported. 284 @cvar allproperties: An (empty) object representing all properties 285 are to be imported. 286 @type allproperties: L{Properties} 287 @ivar path: The path to the imported ini file. 288 @type path: str 289 @ivar section: The name of the section to be imported. 290 @type section: str 291 @ivar properties: The property specification. 292 @type properties: L{Properties} 293 """ 294 295 allproperties = Properties() 296
297 - def __init__(self, imp):
298 """ 299 @param imp: An import directive. 300 @type imp: str 301 """ 302 part = imp.split(':') 303 self.path = part[1] 304 self.section = None 305 self.properties = self.allproperties 306 if len(part) > 2: 307 self.section = part[2].strip() 308 if len(part) > 3: 309 plist = [s.strip() for s in part[3].split(',')] 310 self.properties = Properties(plist)
311
312 - def __call__(self):
313 """ 314 Execute the import directive. 315 @return: The (imported) lines & declared (vdict) variables. 316 @rtype: tuple(<imported>,<vdict>) 317 """ 318 vdict = {} 319 input = Base(self.path) 320 if not self.section: 321 return (input, vdict) 322 imported = INIConfig() 323 S = input[self.section] 324 if ndef(S): 325 raise Exception, '[%s] not found in %s' % (self.section, self.path) 326 for k in S: 327 v = input[self.section][k] 328 if self.properties.empty() or self.properties.isplain(k): 329 ts = getattr(imported, self.section) 330 setattr(ts, k, v) 331 else: 332 var = self.properties.var(k) 333 if var: 334 var = '$(%s)' % var.strip() 335 vdict[var] = v 336 return (imported, vdict)
337 338
339 -def _cnfvalue(macro):
340 """ 341 configuration macro resolver 342 @return: The resolved configuration value 343 @rtype: str 344 """ 345 n = macro[2:-1] 346 cfg = Config() 347 s,p = n.split('.',1) 348 return nvl(cfg[s][p])
349 350
351 -class Reader:
352 """ 353 File reader. 354 post-process directives. 355 @ivar idx: The line index. 356 @type idx: int 357 @ivar vdict: The variable dictionary. 358 @type vdict: dict 359 @ivar path: The path to a file to read. 360 @type path: str 361 """ 362 363 MACROS = { 364 '%{hostname}':socket.gethostname(), 365 '%{messaging.url}':_cnfvalue, 366 '%{messaging.cacert}':_cnfvalue, 367 '%{messaging.clientcert}':_cnfvalue, 368 } 369
370 - def __init__(self, path):
371 self.idx = 0 372 self.vdict = {} 373 self.path = path 374 log.info('reading: %s', path) 375 f = open(path) 376 try: 377 bfr = f.read() 378 self.lines = self.__post(bfr.split('\n')) 379 finally: 380 f.close()
381
382 - def readline(self):
383 """ 384 read the next line. 385 @return: The line of None on EOF. 386 @rtype: str 387 """ 388 if self.idx < len(self.lines): 389 ln = self.lines[self.idx] 390 self.idx += 1 391 return ln+'\n'
392
393 - def close(self):
394 pass
395
396 - def __post(self, input):
397 """ 398 Post-process file content for directives. 399 @param input: The file content (lines). 400 @type input: list 401 @return: The post processed content. 402 @rtype: list 403 """ 404 output = [] 405 for ln in input: 406 if ln.startswith('@import'): 407 for ln in self.__import(ln): 408 output.append(ln) 409 else: 410 ln = self.__repl(ln) 411 output.append(ln) 412 return output
413
414 - def __import(self, ln):
415 """ 416 Procecss an i{import} directive and return the result. 417 @param ln: A line containing the directive. 418 @type ln: str 419 @return: The import result (lines). 420 @rtype: [str,..] 421 """ 422 log.info('processing: %s', ln) 423 imp = Import(ln) 424 imported, vdict = imp() 425 self.vdict.update(vdict) 426 return str(imported).split('\n')
427
428 - def __repl(self, ln):
429 """ 430 Replace variables contained in the line. 431 @param ln: A file line. 432 @type ln: str 433 @return: The line w/ vars replaced. 434 @rtype: str 435 """ 436 if ln.startswith('#'): 437 return ln 438 for k,v in self.MACROS.items()+self.vdict.items(): 439 if k in ln: 440 if callable(v): 441 v = v(k) 442 log.info('line "%s" s/%s/%s/', ln, k, v) 443 ln = ln.replace(k, v) 444 return ln
445
446 - def __str__(self):
447 return self.path
448 449 450 if __name__ == '__main__': 451 cfg = Config() 452 print cfg 453