1
2
3
4
5
6
7
8
9
10
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 import re
25
26 import zope.component
27 from zope.traversing.adapters import DefaultTraversable
28 from Products.Five import zcml
29
30 from optparse import OptionParser, SUPPRESS_HELP, NO_DEFAULT, OptionValueError
31 from urllib import quote
32
33
34
35
36 from Products.ZenUtils.PkgResources import pkg_resources
37
38 from Products.ZenUtils.Utils import unused, load_config_override
39 unused(pkg_resources)
40
42
44 """
45 Class used for all Zenoss commands
46 """
47
48 doesLogging = True
49
51
52 zope.component.provideAdapter(DefaultTraversable, (None,))
53
54
55 import Products.ZenossStartup
56 unused(Products.ZenossStartup)
57 if not zcml._initialized:
58 import Products.Five, Products.ZenModel, Products.ZenRelations, Products.Zuul
59 try:
60 zcml.load_config('meta.zcml', Products.Five)
61 zcml.load_config('indexing.zcml', Products.ZenModel)
62 zcml.load_config('zendoc.zcml', Products.ZenModel)
63 zcml.load_config('configure.zcml', Products.ZenRelations)
64 zcml.load_config('configure.zcml', Products.Zuul)
65 except AttributeError:
66
67
68 pass
69 import Products.ZenWidgets
70 load_config_override('scriptmessaging.zcml', Products.ZenWidgets)
71
72 self.usage = "%prog [options]"
73 self.noopts = noopts
74 self.args = []
75 self.parser = None
76 self.buildParser()
77 self.buildOptions()
78
79 self.parseOptions()
80 if self.options.configfile:
81 self.getConfigFileDefaults( self.options.configfile )
82
83
84
85
86 self.parseOptions()
87 if self.doesLogging:
88 self.setupLogging()
89
90
92 """
93 Parse a config file which has key-value pairs delimited by white space,
94 and update the parser's option defaults with these values.
95
96 @parameter filename: name of configuration file
97 @type filename: string
98 """
99 outlines = []
100
101 try:
102 configFile = open(filename)
103 lines = configFile.readlines()
104 configFile.close()
105 except:
106 import traceback
107 print >>sys.stderr, "WARN: unable to read config file %s -- skipping" % \
108 filename
109 traceback.print_exc(0)
110 return
111
112 lineno = 0
113 modified = False
114 for line in lines:
115 outlines.append(line)
116 lineno += 1
117 if line.lstrip().startswith('#'): continue
118 if line.strip() == '': continue
119
120 try:
121 key, value = line.strip().split(None, 1)
122 except ValueError:
123 print >>sys.stderr, "WARN: missing value on line %d" % lineno
124 continue
125 flag= "--%s" % key
126 option= self.parser.get_option( flag )
127 if option is None:
128 print >>sys.stderr, "INFO: Commenting out unknown option '%s' found " \
129 "on line %d in config file" % (key, lineno)
130
131 outlines = outlines[:-1]
132 outlines.append('## %s' % line)
133 modified = True
134 continue
135
136
137
138 try:
139 if option.action in [ "store_true", "store_false" ]:
140 if value in ['True', 'true']:
141 value = True
142 else:
143 value = False
144 self.parser.set_default( option.dest, value )
145 else:
146 self.parser.set_default( option.dest, type(option.type)(value) )
147 except:
148 print >>sys.stderr, "Bad configuration value for" \
149 " %s at line %s, value = %s (type %s)" % (
150 option.dest, lineno, value, option.type )
151
152
153
154 if modified:
155 configFile = file(filename, 'w')
156 configFile.writelines(outlines)
157 configFile.close()
158
160 """
161 Validate the logpath is valid
162 """
163 if not self.options.logpath:
164 return None
165 else:
166 logdir = self.options.logpath
167 if not os.path.exists(logdir):
168
169 try:
170 os.makedirs(logdir)
171 except OSError, ex:
172 raise SystemExit("logpath:%s doesn't exist and cannot be created" % logdir)
173 elif not os.path.isdir(logdir):
174 raise SystemExit("logpath:%s exists but is not a directory" % logdir)
175 return logdir
176
178 """
179 Set common logging options
180 """
181 rlog = logging.getLogger()
182 rlog.setLevel(logging.WARN)
183 mname = self.__class__.__name__
184 self.log = logging.getLogger("zen."+ mname)
185 zlog = logging.getLogger("zen")
186 zlog.setLevel(self.options.logseverity)
187 logdir = self.checkLogpath()
188 if logdir:
189 logfile = os.path.join(logdir, mname.lower()+".log")
190 maxBytes = self.options.maxLogKiloBytes * 1024
191 backupCount = self.options.maxBackupLogs
192 h = logging.handlers.RotatingFileHandler(logfile, maxBytes, backupCount)
193 h.setFormatter(logging.Formatter(
194 "%(asctime)s %(levelname)s %(name)s: %(message)s",
195 "%Y-%m-%d %H:%M:%S"))
196 rlog.addHandler(h)
197 else:
198 logging.basicConfig()
199
200
215
217 """
218 Basic options setup. Other classes should call this before adding
219 more options
220 """
221 self.buildParser()
222 if self.doesLogging:
223 def parseLogSeverity(option, opt, value, parser):
224 if re.match(r'^\d+$', value):
225 value = int(value)
226 else:
227 intval = getattr(logging, value.upper(), None)
228 if intval:
229 value = intval
230 else:
231 raise OptionValueError('"%s" is not a valid log level.' % value)
232
233 setattr(parser.values, option.dest, value)
234
235 self.parser.add_option('-v', '--logseverity',
236 dest='logseverity',
237 default=20,
238 type='string',
239 action='callback',
240 help='Logging severity threshold',
241 callback=parseLogSeverity)
242
243 self.parser.add_option('--logpath',dest='logpath',
244 help='Override the default logging path')
245
246 self.parser.add_option('--maxlogsize',
247 dest='maxLogKiloBytes',
248 help='Max size of log file in KB; default 10240',
249 default=10240,
250 type='int')
251
252 self.parser.add_option('--maxbackuplogs',
253 dest='maxBackupLogs',
254 help='Max number of back up log files; default 3',
255 default=3,
256 type='int')
257
258 self.parser.add_option("-C", "--configfile",
259 dest="configfile",
260 help="Use an alternate configuration file" )
261
262 self.parser.add_option("--genconf",
263 action="store_true",
264 default=False,
265 help="Generate a template configuration file" )
266
267 self.parser.add_option("--genxmltable",
268 action="store_true",
269 default=False,
270 help="Generate a Docbook table showing command-line switches." )
271
272 self.parser.add_option("--genxmlconfigs",
273 action="store_true",
274 default=False,
275 help="Generate an XML file containing command-line switches." )
276
277
278
279
333
334
335
337 """
338 Create a configuration file based on the long-form of the option names
339
340 @parameter parser: an optparse parser object which contains defaults, help
341 @parameter options: parsed options list containing actual values
342 """
343
344
345
346
347 unused(options)
348 daemon_name= os.path.basename( sys.argv[0] )
349 daemon_name= daemon_name.replace( '.py', '' )
350
351 print """#
352 # Configuration file for %s
353 #
354 # To enable a particular option, uncomment the desired entry.
355 #
356 # Parameter Setting
357 # --------- -------""" % ( daemon_name )
358
359
360 options_to_ignore= ( 'help', 'version', '', 'genconf', 'genxmltable' )
361
362
363
364
365
366
367
368 import re
369 for opt in parser.option_list:
370 if opt.help is SUPPRESS_HELP:
371 continue
372
373
374
375
376 option_name= re.sub( r'.*/--', '', "%s" % opt )
377
378
379
380
381 option_name= re.sub( r'^--', '', "%s" % option_name )
382
383
384
385
386 if option_name in options_to_ignore:
387 continue
388
389
390
391
392
393
394 value= getattr( parser.values, opt.dest )
395
396 default_value= parser.defaults.get( opt.dest )
397 if default_value is NO_DEFAULT or default_value is None:
398 default_value= ""
399 default_string= ""
400 if default_value != "":
401 default_string= ", default: " + str( default_value )
402
403 comment= self.pretty_print_config_comment( opt.help + default_string )
404
405
406
407
408
409 print """#
410 # %s
411 #%s %s""" % ( comment, option_name, value )
412
413
414
415
416 print "#"
417 sys.exit( 0 )
418
419
420
422 """
423 Create a Docbook table based on the long-form of the option names
424
425 @parameter parser: an optparse parser object which contains defaults, help
426 @parameter options: parsed options list containing actual values
427 """
428
429
430
431
432 unused(options)
433 daemon_name= os.path.basename( sys.argv[0] )
434 daemon_name= daemon_name.replace( '.py', '' )
435
436 print """<?xml version="1.0" encoding="UTF-8"?>
437
438 <section version="4.0" xmlns="http://docbook.org/ns/docbook"
439 xmlns:xlink="http://www.w3.org/1999/xlink"
440 xmlns:xi="http://www.w3.org/2001/XInclude"
441 xmlns:svg="http://www.w3.org/2000/svg"
442 xmlns:mml="http://www.w3.org/1998/Math/MathML"
443 xmlns:html="http://www.w3.org/1999/xhtml"
444 xmlns:db="http://docbook.org/ns/docbook"
445
446 xml:id="%s.options"
447 >
448
449 <title>%s Options</title>
450 <para />
451 <table frame="all">
452 <caption>%s <indexterm><primary>Daemons</primary><secondary>%s</secondary></indexterm> options</caption>
453 <tgroup cols="2">
454 <colspec colname="option" colwidth="1*" />
455 <colspec colname="description" colwidth="2*" />
456 <thead>
457 <row>
458 <entry> <para>Option</para> </entry>
459 <entry> <para>Description</para> </entry>
460 </row>
461 </thead>
462 <tbody>
463 """ % ( daemon_name, daemon_name, daemon_name, daemon_name )
464
465
466 options_to_ignore= ( 'help', 'version', '', 'genconf', 'genxmltable' )
467
468
469
470
471
472
473
474 import re
475 for opt in parser.option_list:
476 if opt.help is SUPPRESS_HELP:
477 continue
478
479
480
481
482
483
484 all_options= '<literal>' + re.sub( r'/', '</literal>,</para> <para><literal>', "%s" % opt ) + '</literal>'
485
486
487
488
489 option_name= re.sub( r'.*/--', '', "%s" % opt )
490 option_name= re.sub( r'^--', '', "%s" % option_name )
491 if option_name in options_to_ignore:
492 continue
493
494 default_value= parser.defaults.get( opt.dest )
495 if default_value is NO_DEFAULT or default_value is None:
496 default_value= ""
497 default_string= ""
498 if default_value != "":
499 default_string= "<para> Default: <literal>" + str( default_value ) + "</literal></para>\n"
500
501 comment= self.pretty_print_config_comment( opt.help )
502
503
504
505
506 if opt.action in [ 'store_true', 'store_false' ]:
507 print """<row>
508 <entry> <para>%s</para> </entry>
509 <entry>
510 <para>%s</para>
511 %s</entry>
512 </row>
513 """ % ( all_options, comment, default_string )
514
515 else:
516 target= '=<replaceable>' + opt.dest.lower() + '</replaceable>'
517 all_options= all_options + target
518 all_options= re.sub( r',', target + ',', all_options )
519 print """<row>
520 <entry> <para>%s</para> </entry>
521 <entry>
522 <para>%s</para>
523 %s</entry>
524 </row>
525 """ % ( all_options, comment, default_string )
526
527
528
529
530
531
532 print """</tbody></tgroup>
533 </table>
534 <para />
535 </section>
536 """
537 sys.exit( 0 )
538
539
540
542 """
543 Create an XML file that can be used to create Docbook files
544 as well as used as the basis for GUI-based daemon option
545 configuration.
546 """
547
548
549
550
551 unused(options)
552 daemon_name= os.path.basename( sys.argv[0] )
553 daemon_name= daemon_name.replace( '.py', '' )
554
555 export_date = datetime.datetime.now()
556
557 print """<?xml version="1.0" encoding="UTF-8"?>
558
559 <!-- Default daemon configuration generated on %s -->
560 <configuration id="%s" >
561
562 """ % ( export_date, daemon_name )
563
564 options_to_ignore= (
565 'help', 'version', '', 'genconf', 'genxmltable',
566 'genxmlconfigs',
567 )
568
569
570
571
572
573
574
575 import re
576 for opt in parser.option_list:
577 if opt.help is SUPPRESS_HELP:
578 continue
579
580
581
582
583 option_name= re.sub( r'.*/--', '', "%s" % opt )
584 option_name= re.sub( r'^--', '', "%s" % option_name )
585 if option_name in options_to_ignore:
586 continue
587
588 default_value= parser.defaults.get( opt.dest )
589 if default_value is NO_DEFAULT or default_value is None:
590 default_string= ""
591 else:
592 default_string= str( default_value )
593
594
595
596
597 if opt.action in [ 'store_true', 'store_false' ]:
598 print """ <option id="%s" type="%s" default="%s" help="%s" />
599 """ % ( option_name, "boolean", default_string, quote(opt.help), )
600
601 else:
602 target= opt.dest.lower()
603 print """ <option id="%s" type="%s" default="%s" target="%s" help="%s" />
604 """ % ( option_name, opt.type, quote(default_string), target, quote(opt.help), )
605
606
607
608
609
610 print """
611 </configuration>
612 """
613 sys.exit( 0 )
614
615
616
618 """
619 Uses the optparse parse previously populated and performs common options.
620 """
621
622 if self.noopts:
623 args = []
624 else:
625 import sys
626 args = sys.argv[1:]
627 (self.options, self.args) = self.parser.parse_args(args=args)
628
629 if self.options.genconf:
630 self.generate_configs( self.parser, self.options )
631
632 if self.options.genxmltable:
633 self.generate_xml_table( self.parser, self.options )
634
635 if self.options.genxmlconfigs:
636 self.generate_xml_configs( self.parser, self.options )
637