Package ZenUtils :: Module CmdBase
[hide private]
[frames] | no frames]

Source Code for Module ZenUtils.CmdBase

  1  ########################################################################### 
  2  # 
  3  # This program is part of Zenoss Core, an open source monitoring platform. 
  4  # Copyright (C) 2007, Zenoss Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify it 
  7  # under the terms of the GNU General Public License version 2 as published by 
  8  # the Free Software Foundation. 
  9  # 
 10  # For complete information please visit: http://www.zenoss.com/oss/ 
 11  # 
 12  ########################################################################### 
 13   
 14  __doc__="""CmdBase 
 15   
 16  Provide utility functions for logging and config file parsing 
 17  to command-line programs 
 18  """ 
 19   
 20  import os 
 21  import sys 
 22  import datetime 
 23  import logging 
 24   
 25  from logging import handlers 
 26  from optparse import OptionParser, SUPPRESS_HELP, NO_DEFAULT 
 27  from urllib import quote 
 28   
 29  # There is a nasty incompatibility between pkg_resources and twisted. 
 30  # This pkg_resources import works around the problem. 
 31  # See http://dev.zenoss.org/trac/ticket/3146 for details 
 32  from Products.ZenUtils.PkgResources import pkg_resources 
 33   
 34  from Products.ZenUtils.Utils import unused 
 35  unused(pkg_resources) 
 36   
37 -class DMDError: pass
38
39 -class CmdBase:
40 """ 41 Class used for all Zenoss commands 42 """ 43 44 doesLogging = True 45
46 - def __init__(self, noopts=0):
47 48 # We must import ZenossStartup at this point so that all Zenoss daemons 49 # and tools will have any ZenPack monkey-patched methods available. 50 import Products.ZenossStartup 51 unused(Products.ZenossStartup) 52 53 self.usage = "%prog [options]" 54 self.noopts = noopts 55 self.args = [] 56 self.parser = None 57 self.buildParser() 58 self.buildOptions() 59 60 self.parseOptions() 61 if self.options.configfile: 62 self.getConfigFileDefaults( self.options.configfile ) 63 64 # We've updated the parser with defaults from configs, now we need 65 # to reparse our command-line to get the correct overrides from 66 # the command-line 67 self.parseOptions() 68 if self.doesLogging: 69 self.setupLogging()
70 71
72 - def getConfigFileDefaults(self, filename):
73 """ 74 Parse a config file which has key-value pairs delimited by white space, 75 and update the parser's option defaults with these values. 76 77 @parameter filename: name of configuration file 78 @type filename: string 79 """ 80 outlines = [] 81 82 try: 83 configFile = open(filename) 84 lines = configFile.readlines() 85 configFile.close() 86 except: 87 import traceback 88 print >>sys.stderr, "WARN: unable to read config file %s -- skipping" % \ 89 filename 90 traceback.print_exc(0) 91 return 92 93 lineno = 0 94 modified = False 95 for line in lines: 96 outlines.append(line) 97 lineno += 1 98 if line.lstrip().startswith('#'): continue 99 if line.strip() == '': continue 100 101 try: 102 key, value = line.strip().split(None, 1) 103 except ValueError: 104 print >>sys.stderr, "WARN: missing value on line %d" % lineno 105 continue 106 flag= "--%s" % key 107 option= self.parser.get_option( flag ) 108 if option is None: 109 print >>sys.stderr, "INFO: Commenting out unknown option '%s' found " \ 110 "on line %d in config file" % (key, lineno) 111 #take the last line off the buffer and comment it out 112 outlines = outlines[:-1] 113 outlines.append('## %s' % line) 114 modified = True 115 continue 116 117 # NB: At this stage, optparse accepts even bogus values 118 # It will report unhappiness when it parses the arguments 119 try: 120 if option.action in [ "store_true", "store_false" ]: 121 if value in ['True', 'true']: 122 value = True 123 else: 124 value = False 125 self.parser.set_default( option.dest, value ) 126 else: 127 self.parser.set_default( option.dest, type(option.type)(value) ) 128 except: 129 print >>sys.stderr, "Bad configuration value for" \ 130 " %s at line %s, value = %s (type %s)" % ( 131 option.dest, lineno, value, option.type ) 132 133 #if we found bogus options write out the file with commented out bogus 134 #values 135 if modified: 136 configFile = file(filename, 'w') 137 configFile.writelines(outlines) 138 configFile.close()
139
140 - def setupLogging(self):
141 """ 142 Set common logging options 143 """ 144 rlog = logging.getLogger() 145 rlog.setLevel(logging.WARN) 146 mname = self.__class__.__name__ 147 self.log = logging.getLogger("zen."+ mname) 148 zlog = logging.getLogger("zen") 149 zlog.setLevel(self.options.logseverity) 150 if self.options.logpath: 151 logdir = self.options.logpath 152 if not os.path.isdir(os.path.dirname(logdir)): 153 raise SystemExit("logpath:%s doesn't exist" % logdir) 154 logfile = os.path.join(logdir, mname.lower()+".log") 155 maxBytes = self.options.maxLogKiloBytes * 1024 156 backupCount = self.options.maxBackupLogs 157 h = logging.handlers.RotatingFileHandler(logfile, maxBytes, backupCount) 158 h.setFormatter(logging.Formatter( 159 "%(asctime)s %(levelname)s %(name)s: %(message)s", 160 "%Y-%m-%d %H:%M:%S")) 161 rlog.addHandler(h) 162 else: 163 logging.basicConfig()
164 165
166 - def buildParser(self):
167 """ 168 Create the options parser 169 """ 170 if not self.parser: 171 from Products.ZenModel.ZenossInfo import ZenossInfo 172 try: 173 zinfo= ZenossInfo('') 174 version= str(zinfo.getZenossVersion()) 175 except: 176 from Products.ZenModel.ZVersion import VERSION 177 version= VERSION 178 self.parser = OptionParser(usage=self.usage, 179 version="%prog " + version )
180
181 - def buildOptions(self):
182 """ 183 Basic options setup. Other classes should call this before adding 184 more options 185 """ 186 self.buildParser() 187 if self.doesLogging: 188 self.parser.add_option('-v', '--logseverity', 189 dest='logseverity', 190 default=20, 191 type='int', 192 help='Logging severity threshold') 193 194 self.parser.add_option('--logpath',dest='logpath', 195 help='Override the default logging path') 196 197 self.parser.add_option('--maxlogsize', 198 dest='maxLogKiloBytes', 199 help='Max size of log file in KB; default 10240', 200 default=10240, 201 type='int') 202 203 self.parser.add_option('--maxbackuplogs', 204 dest='maxBackupLogs', 205 help='Max number of back up log files; default 3', 206 default=3, 207 type='int') 208 209 self.parser.add_option("-C", "--configfile", 210 dest="configfile", 211 help="Use an alternate configuration file" ) 212 213 self.parser.add_option("--genconf", 214 action="store_true", 215 default=False, 216 help="Generate a template configuration file" ) 217 218 self.parser.add_option("--genxmltable", 219 action="store_true", 220 default=False, 221 help="Generate a Docbook table showing command-line switches." ) 222 223 self.parser.add_option("--genxmlconfigs", 224 action="store_true", 225 default=False, 226 help="Generate an XML file containing command-line switches." )
227 228 229 230
231 - def pretty_print_config_comment( self, comment ):
232 """ 233 Quick and dirty pretty printer for comments that happen to be longer than can comfortably 234 be seen on the display. 235 """ 236 237 max_size= 40 238 # 239 # As a heuristic we'll accept strings that are +- text_window 240 # size in length. 241 # 242 text_window= 5 243 244 if len( comment ) <= max_size + text_window: 245 return comment 246 247 # 248 # First, take care of embedded newlines and expand them out to array entries 249 # 250 new_comment= [] 251 all_lines= comment.split( '\n' ) 252 for line in all_lines: 253 if len(line) <= max_size + text_window: 254 new_comment.append( line ) 255 continue 256 257 start_position= max_size - text_window 258 while len(line) > max_size + text_window: 259 index= line.find( ' ', start_position ) 260 if index > 0: 261 new_comment.append( line[ 0:index ] ) 262 line= line[ index: ] 263 264 else: 265 if start_position == 0: 266 # 267 # If we get here it means that the line is just one big string with no spaces 268 # in it. There's nothing that we can do except print it out. Doh! 269 # 270 new_comment.append( line ) 271 break 272 273 # 274 # Okay, haven't found anything to split on -- go back and try again 275 # 276 start_position= start_position - text_window 277 if start_position < 0: 278 start_position= 0 279 280 else: 281 new_comment.append( line ) 282 283 return "\n# ".join( new_comment )
284 285 286
287 - def generate_configs( self, parser, options ):
288 """ 289 Create a configuration file based on the long-form of the option names 290 291 @parameter parser: an optparse parser object which contains defaults, help 292 @parameter options: parsed options list containing actual values 293 """ 294 295 # 296 # Header for the configuration file 297 # 298 unused(options) 299 daemon_name= os.path.basename( sys.argv[0] ) 300 daemon_name= daemon_name.replace( '.py', '' ) 301 302 print """# 303 # Configuration file for %s 304 # 305 # To enable a particular option, uncomment the desired entry. 306 # 307 # Parameter Setting 308 # --------- -------""" % ( daemon_name ) 309 310 311 options_to_ignore= ( 'help', 'version', '', 'genconf', 'genxmltable' ) 312 313 # 314 # Create an entry for each of the command line flags 315 # 316 # NB: Ideally, this should print out only the option parser dest 317 # entries, rather than the command line options. 318 # 319 import re 320 for opt in parser.option_list: 321 if opt.help is SUPPRESS_HELP: 322 continue 323 324 # 325 # Get rid of the short version of the command 326 # 327 option_name= re.sub( r'.*/--', '', "%s" % opt ) 328 329 # 330 # And what if there's no short version? 331 # 332 option_name= re.sub( r'^--', '', "%s" % option_name ) 333 334 # 335 # Don't display anything we shouldn't be displaying 336 # 337 if option_name in options_to_ignore: 338 continue 339 340 # 341 # Find the actual value specified on the command line, if any, 342 # and display it 343 # 344 345 value= getattr( parser.values, opt.dest ) 346 347 default_value= parser.defaults.get( opt.dest ) 348 if default_value is NO_DEFAULT or default_value is None: 349 default_value= "" 350 default_string= "" 351 if default_value != "": 352 default_string= ", default: " + str( default_value ) 353 354 comment= self.pretty_print_config_comment( opt.help + default_string ) 355 356 # 357 # NB: I would prefer to use tabs to separate the parameter name 358 # and value, but I don't know that this would work. 359 # 360 print """# 361 # %s 362 #%s %s""" % ( comment, option_name, value ) 363 364 # 365 # Pretty print and exit 366 # 367 print "#" 368 sys.exit( 0 )
369 370 371
372 - def generate_xml_table( self, parser, options ):
373 """ 374 Create a Docbook table based on the long-form of the option names 375 376 @parameter parser: an optparse parser object which contains defaults, help 377 @parameter options: parsed options list containing actual values 378 """ 379 380 # 381 # Header for the configuration file 382 # 383 unused(options) 384 daemon_name= os.path.basename( sys.argv[0] ) 385 daemon_name= daemon_name.replace( '.py', '' ) 386 387 print """<?xml version="1.0" encoding="UTF-8"?> 388 389 <section version="4.0" xmlns="http://docbook.org/ns/docbook" 390 xmlns:xlink="http://www.w3.org/1999/xlink" 391 xmlns:xi="http://www.w3.org/2001/XInclude" 392 xmlns:svg="http://www.w3.org/2000/svg" 393 xmlns:mml="http://www.w3.org/1998/Math/MathML" 394 xmlns:html="http://www.w3.org/1999/xhtml" 395 xmlns:db="http://docbook.org/ns/docbook" 396 397 xml:id="%s.options" 398 > 399 400 <title>%s Options</title> 401 <para /> 402 <table frame="all"> 403 <caption>%s <indexterm><primary>Daemons</primary><secondary>%s</secondary></indexterm> options</caption> 404 <tgroup cols="2"> 405 <colspec colname="option" colwidth="1*" /> 406 <colspec colname="description" colwidth="2*" /> 407 <thead> 408 <row> 409 <entry> <para>Option</para> </entry> 410 <entry> <para>Description</para> </entry> 411 </row> 412 </thead> 413 <tbody> 414 """ % ( daemon_name, daemon_name, daemon_name, daemon_name ) 415 416 417 options_to_ignore= ( 'help', 'version', '', 'genconf', 'genxmltable' ) 418 419 # 420 # Create an entry for each of the command line flags 421 # 422 # NB: Ideally, this should print out only the option parser dest 423 # entries, rather than the command line options. 424 # 425 import re 426 for opt in parser.option_list: 427 if opt.help is SUPPRESS_HELP: 428 continue 429 430 # 431 # Create a Docbook-happy version of the option strings 432 # Yes, <arg></arg> would be better semantically, but the output 433 # just looks goofy in a table. Use literal instead. 434 # 435 all_options= '<literal>' + re.sub( r'/', '</literal>,</para> <para><literal>', "%s" % opt ) + '</literal>' 436 437 # 438 # Don't display anything we shouldn't be displaying 439 # 440 option_name= re.sub( r'.*/--', '', "%s" % opt ) 441 option_name= re.sub( r'^--', '', "%s" % option_name ) 442 if option_name in options_to_ignore: 443 continue 444 445 default_value= parser.defaults.get( opt.dest ) 446 if default_value is NO_DEFAULT or default_value is None: 447 default_value= "" 448 default_string= "" 449 if default_value != "": 450 default_string= "<para> Default: <literal>" + str( default_value ) + "</literal></para>\n" 451 452 comment= self.pretty_print_config_comment( opt.help ) 453 454 # 455 # TODO: Determine the variable name used and display the --option_name=variable_name 456 # 457 if opt.action in [ 'store_true', 'store_false' ]: 458 print """<row> 459 <entry> <para>%s</para> </entry> 460 <entry> 461 <para>%s</para> 462 %s</entry> 463 </row> 464 """ % ( all_options, comment, default_string ) 465 466 else: 467 target= '=<replaceable>' + opt.dest.lower() + '</replaceable>' 468 all_options= all_options + target 469 all_options= re.sub( r',', target + ',', all_options ) 470 print """<row> 471 <entry> <para>%s</para> </entry> 472 <entry> 473 <para>%s</para> 474 %s</entry> 475 </row> 476 """ % ( all_options, comment, default_string ) 477 478 479 480 # 481 # Close the table elements 482 # 483 print """</tbody></tgroup> 484 </table> 485 <para /> 486 </section> 487 """ 488 sys.exit( 0 )
489 490 491
492 - def generate_xml_configs( self, parser, options ):
493 """ 494 Create an XML file that can be used to create Docbook files 495 as well as used as the basis for GUI-based daemon option 496 configuration. 497 """ 498 499 # 500 # Header for the configuration file 501 # 502 unused(options) 503 daemon_name= os.path.basename( sys.argv[0] ) 504 daemon_name= daemon_name.replace( '.py', '' ) 505 506 export_date = datetime.datetime.now() 507 508 print """<?xml version="1.0" encoding="UTF-8"?> 509 510 <!-- Default daemon configuration generated on %s --> 511 <configuration id="%s" > 512 513 """ % ( export_date, daemon_name ) 514 515 options_to_ignore= ( 516 'help', 'version', '', 'genconf', 'genxmltable', 517 'genxmlconfigs', 518 ) 519 520 # 521 # Create an entry for each of the command line flags 522 # 523 # NB: Ideally, this should print out only the option parser dest 524 # entries, rather than the command line options. 525 # 526 import re 527 for opt in parser.option_list: 528 if opt.help is SUPPRESS_HELP: 529 continue 530 531 # 532 # Don't display anything we shouldn't be displaying 533 # 534 option_name= re.sub( r'.*/--', '', "%s" % opt ) 535 option_name= re.sub( r'^--', '', "%s" % option_name ) 536 if option_name in options_to_ignore: 537 continue 538 539 default_value= parser.defaults.get( opt.dest ) 540 if default_value is NO_DEFAULT or default_value is None: 541 default_string= "" 542 else: 543 default_string= str( default_value ) 544 545 # 546 # TODO: Determine the variable name used and display the --option_name=variable_name 547 # 548 if opt.action in [ 'store_true', 'store_false' ]: 549 print """ <option id="%s" type="%s" default="%s" help="%s" /> 550 """ % ( option_name, "boolean", default_string, quote(opt.help), ) 551 552 else: 553 target= opt.dest.lower() 554 print """ <option id="%s" type="%s" default="%s" target="%s" help="%s" /> 555 """ % ( option_name, opt.type, quote(default_string), target, quote(opt.help), ) 556 557 558 # 559 # Close the table elements 560 # 561 print """ 562 </configuration> 563 """ 564 sys.exit( 0 )
565 566 567
568 - def parseOptions(self):
569 """ 570 Uses the optparse parse previously populated and performs common options. 571 """ 572 573 if self.noopts: 574 args = [] 575 else: 576 import sys 577 args = sys.argv[1:] 578 (self.options, self.args) = self.parser.parse_args(args=args) 579 580 if self.options.genconf: 581 self.generate_configs( self.parser, self.options ) 582 583 if self.options.genxmltable: 584 self.generate_xml_table( self.parser, self.options ) 585 586 if self.options.genxmlconfigs: 587 self.generate_xml_configs( self.parser, self.options )
588