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 from copy import copy
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, BadOptionError, Values, Option
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, zenPath
39 unused(pkg_resources)
40
42
43
45 if re.match(r'^\d+$', value):
46 value = int(value)
47 else:
48 intval = getattr(logging, value.upper(), None)
49 if intval:
50 value = intval
51 else:
52 raise OptionValueError('"%s" is not a valid log level.' % value)
53
54 return value
55
56 -def remove_args(argv, remove_args_novals, remove_args_vals):
57 """
58 Removes arguments from the argument list. Arguments in
59 remove_args_novals have no arguments. Arguments in
60 remove_args_vals have arguments, either in the format
61 --arg=<val> or --arg <val>.
62 """
63 new_args = []
64 it = iter(argv)
65 for arg in it:
66 if arg in remove_args_novals:
67 continue
68 add_arg = True
69 for remove_arg in remove_args_vals:
70 if remove_arg == arg:
71 add_arg = False
72 it.next()
73 break
74 elif arg.startswith(remove_arg + '='):
75 add_arg = False
76 break
77 if add_arg:
78 new_args.append(arg)
79 return new_args
80
85
86
88 """
89 Class used for all Zenoss commands
90 """
91
92 doesLogging = True
93
94 - def __init__(self, noopts=0, args=None):
95
96 zope.component.provideAdapter(DefaultTraversable, (None,))
97
98
99 import Products.ZenossStartup
100 unused(Products.ZenossStartup)
101 if not zcml._initialized:
102 import Products.Five, Products.ZenModel, Products.ZenRelations, Products.Zuul
103 try:
104 zcml.load_config('meta.zcml', Products.Five)
105 zcml.load_config('indexing.zcml', Products.ZenModel)
106 zcml.load_config('zendoc.zcml', Products.ZenModel)
107 zcml.load_config('configure.zcml', Products.ZenRelations)
108 zcml.load_config('configure.zcml', Products.Zuul)
109 zcml.load_config('configure.zcml', Products.ZenUtils)
110 except AttributeError:
111
112
113 pass
114 import Products.ZenMessaging
115 zcml.load_config('configure.zcml', Products.ZenMessaging)
116 import Products.ZenWidgets
117 load_config_override('scriptmessaging.zcml', Products.ZenWidgets)
118
119 self.usage = "%prog [options]"
120 self.noopts = noopts
121 self.inputArgs = args
122
123
124
125 if self.inputArgs is None:
126 self.inputArgs = sys.argv[1:]
127
128 self.parser = None
129 self.args = []
130
131 self.buildParser()
132 self.buildOptions()
133
134
135
136 self.parser.defaults = self.getGlobalConfigFileDefaults()
137 self.parseOptions()
138 if self.options.configfile:
139 self.parser.defaults = self.getConfigFileDefaults(self.options.configfile)
140
141
142
143 self.parseOptions()
144
145 if self.doesLogging:
146 self.setupLogging()
147
148
164
165
167 """
168 Basic options setup. Other classes should call this before adding
169 more options
170 """
171 self.buildParser()
172 if self.doesLogging:
173 self.parser.add_option('-v', '--logseverity',
174 dest='logseverity',
175 default='INFO',
176 type='loglevel',
177 help='Logging severity threshold',
178 )
179
180 self.parser.add_option('--logpath',dest='logpath',
181 help='Override the default logging path')
182
183 self.parser.add_option('--maxlogsize',
184 dest='maxLogKiloBytes',
185 help='Max size of log file in KB; default 10240',
186 default=10240,
187 type='int')
188
189 self.parser.add_option('--maxbackuplogs',
190 dest='maxBackupLogs',
191 help='Max number of back up log files; default 3',
192 default=3,
193 type='int')
194
195 self.parser.add_option("-C", "--configfile",
196 dest="configfile",
197 help="Use an alternate configuration file" )
198
199 self.parser.add_option("--genconf",
200 action="store_true",
201 default=False,
202 help="Generate a template configuration file" )
203
204 self.parser.add_option("--genxmltable",
205 action="store_true",
206 default=False,
207 help="Generate a Docbook table showing command-line switches." )
208
209 self.parser.add_option("--genxmlconfigs",
210 action="store_true",
211 default=False,
212 help="Generate an XML file containing command-line switches." )
213
214
216 """
217 Uses the optparse parse previously populated and performs common options.
218 """
219
220 if self.noopts:
221 args = []
222 else:
223 args = self.inputArgs
224
225 (self.options, self.args) = self.parser.parse_args(args=args)
226
227 if self.options.genconf:
228 self.generate_configs( self.parser, self.options )
229
230 if self.options.genxmltable:
231 self.generate_xml_table( self.parser, self.options )
232
233 if self.options.genxmlconfigs:
234 self.generate_xml_configs( self.parser, self.options )
235
236
238 """
239 Parse a config file which has key-value pairs delimited by white space,
240 and update the parser's option defaults with these values.
241
242 @parameter filename: name of configuration file
243 @type filename: string
244 """
245
246 options = self.parser.get_default_values()
247 lines = self.loadConfigFile(filename)
248 if lines:
249 lines, errors = self.validateConfigFile(filename, lines)
250
251 args = self.getParamatersFromConfig(lines)
252 try:
253 self.parser._process_args([], args, options)
254 except (BadOptionError, OptionValueError) as err:
255 print >>sys.stderr, 'WARN: %s in config file %s' % (err, filename)
256
257 return options.__dict__
258
259
261 """
262 Parse a config file which has key-value pairs delimited by white space,
263 and update the parser's option defaults with these values.
264 """
265
266 filename = zenPath('etc', 'global.conf')
267 options = self.parser.get_default_values()
268 lines = self.loadConfigFile(filename)
269 if lines:
270 args = self.getParamatersFromConfig(lines)
271
272 try:
273 self.parser._process_args([], args, options)
274 except (BadOptionError, OptionValueError) as err:
275
276 pass
277
278 return options.__dict__
279
280
282 """
283 Parse a config file which has key-value pairs delimited by white space.
284
285 @parameter filename: path to the configuration file
286 @type filename: string
287 """
288 lines = []
289 try:
290 with open(filename) as file:
291 for line in file:
292 if line.lstrip().startswith('#') or line.strip() == '':
293 lines.append(dict(type='comment', line=line))
294 else:
295 try:
296
297
298 key, value = (re.split(r'[\s:=]+', line.strip(), 1) + ['',])[:2]
299 except ValueError:
300 lines.append(dict(type='option', line=line, key=line.strip(), value=None, option=None))
301 else:
302 option = self.parser.get_option('--%s' % key)
303 lines.append(dict(type='option', line=line, key=key, value=value, option=option))
304 except IOError as e:
305 errorMessage = 'WARN: unable to read config file {filename} \
306 -- skipping. ({exceptionName}: {exception})'.format(
307 filename=filename,
308 exceptionName=e.__class__.__name__,
309 exception=e
310 )
311 print >>sys.stderr, errorMessage
312 return []
313
314 return lines
315
316
318 """
319 Validate config file lines which has key-value pairs delimited by white space,
320 and validate that the keys exist for this command's option parser. If
321 the option does not exist or has an empty value it will comment it out
322 in the config file.
323
324 @parameter filename: path to the configuration file
325 @type filename: string
326 @parameter lines: lines from config parser
327 @type lines: list
328 @parameter correctErrors: Whether or not invalid conf values should be
329 commented out.
330 @type correctErrors: boolean
331 """
332
333 output = []
334 errors = []
335 validLines = []
336 date = datetime.datetime.now().isoformat()
337 errorTemplate = '## Commenting out by config parser on %s: %%s\n' % date
338
339 for lineno, line in enumerate(lines):
340 if line['type'] == 'comment':
341 output.append(line['line'])
342 elif line['type'] == 'option':
343 if line['value'] is None:
344 errors.append((lineno + 1, 'missing value for "%s"' % line['key']))
345 output.append(errorTemplate % 'missing value')
346 output.append('## %s' % line['line'])
347 elif line['option'] is None:
348 errors.append((lineno + 1, 'unknown option "%s"' % line['key']))
349 output.append(errorTemplate % 'unknown option')
350 output.append('## %s' % line['line'])
351 else:
352 validLines.append(line)
353 output.append(line['line'])
354 else:
355 errors.append((lineno + 1, 'unknown line "%s"' % line['line']))
356 output.append(errorTemplate % 'unknown line')
357 output.append('## %s' % line['line'])
358
359 if errors:
360 if correctErrors:
361 for lineno, message in errors:
362 print >>sys.stderr, 'INFO: Commenting out %s on line %d in %s' % (message, lineno, filename)
363
364 with open(filename, 'w') as file:
365 file.writelines(output)
366
367 if warnErrors:
368 for lineno, message in errors:
369 print >>sys.stderr, 'WARN: %s on line %d in %s' % (message, lineno, filename)
370
371 return validLines, errors
372
373
375 args = []
376 validkeys = self.parser.get_default_values().__dict__.keys()
377
378 for line in lines:
379 if line.get('type', None) == 'option' and line['key'] in validkeys:
380 args += ['--%s' % line['key'], line['value']]
381
382 return args
383
384
386 """
387 Set common logging options
388 """
389 rlog = logging.getLogger()
390 rlog.setLevel(logging.WARN)
391 mname = self.__class__.__name__
392 self.log = logging.getLogger("zen."+ mname)
393 zlog = logging.getLogger("zen")
394 try:
395 loglevel = int(self.options.logseverity)
396 except ValueError:
397 loglevel = getattr(logging, self.options.logseverity.upper(), logging.INFO)
398 zlog.setLevel(loglevel)
399
400 logdir = self.checkLogpath()
401 if logdir:
402 logfile = os.path.join(logdir, mname.lower()+".log")
403 maxBytes = self.options.maxLogKiloBytes * 1024
404 backupCount = self.options.maxBackupLogs
405 h = logging.handlers.RotatingFileHandler(logfile, maxBytes, backupCount)
406 h.setFormatter(logging.Formatter(
407 "%(asctime)s %(levelname)s %(name)s: %(message)s",
408 "%Y-%m-%d %H:%M:%S"))
409 rlog.addHandler(h)
410 else:
411 logging.basicConfig()
412
413
415 """
416 Validate the logpath is valid
417 """
418 if not self.options.logpath:
419 return None
420 else:
421 logdir = self.options.logpath
422 if not os.path.exists(logdir):
423
424 try:
425 os.makedirs(logdir)
426 except OSError, ex:
427 raise SystemExit("logpath:%s doesn't exist and cannot be created" % logdir)
428 elif not os.path.isdir(logdir):
429 raise SystemExit("logpath:%s exists but is not a directory" % logdir)
430 return logdir
431
432
486
487
488
490 """
491 Create a configuration file based on the long-form of the option names
492
493 @parameter parser: an optparse parser object which contains defaults, help
494 @parameter options: parsed options list containing actual values
495 """
496
497
498
499
500 unused(options)
501 daemon_name= os.path.basename( sys.argv[0] )
502 daemon_name= daemon_name.replace( '.py', '' )
503
504 print """#
505 # Configuration file for %s
506 #
507 # To enable a particular option, uncomment the desired entry.
508 #
509 # Parameter Setting
510 # --------- -------""" % ( daemon_name )
511
512
513 options_to_ignore= ( 'help', 'version', '', 'genconf', 'genxmltable' )
514
515
516
517
518
519
520
521 import re
522 for opt in parser.option_list:
523 if opt.help is SUPPRESS_HELP:
524 continue
525
526
527
528
529 option_name= re.sub( r'.*/--', '', "%s" % opt )
530
531
532
533
534 option_name= re.sub( r'^--', '', "%s" % option_name )
535
536
537
538
539 if option_name in options_to_ignore:
540 continue
541
542
543
544
545
546
547 value= getattr( parser.values, opt.dest )
548
549 default_value= parser.defaults.get( opt.dest )
550 if default_value is NO_DEFAULT or default_value is None:
551 default_value= ""
552 default_string= ""
553 if default_value != "":
554 default_string= ", default: " + str( default_value )
555
556 comment= self.pretty_print_config_comment( opt.help + default_string )
557
558
559
560
561
562 print """#
563 # %s
564 #%s %s""" % ( comment, option_name, value )
565
566
567
568
569 print "#"
570 sys.exit( 0 )
571
572
573
575 """
576 Create a Docbook table based on the long-form of the option names
577
578 @parameter parser: an optparse parser object which contains defaults, help
579 @parameter options: parsed options list containing actual values
580 """
581
582
583
584
585 unused(options)
586 daemon_name= os.path.basename( sys.argv[0] )
587 daemon_name= daemon_name.replace( '.py', '' )
588
589 print """<?xml version="1.0" encoding="UTF-8"?>
590
591 <section version="4.0" xmlns="http://docbook.org/ns/docbook"
592 xmlns:xlink="http://www.w3.org/1999/xlink"
593 xmlns:xi="http://www.w3.org/2001/XInclude"
594 xmlns:svg="http://www.w3.org/2000/svg"
595 xmlns:mml="http://www.w3.org/1998/Math/MathML"
596 xmlns:html="http://www.w3.org/1999/xhtml"
597 xmlns:db="http://docbook.org/ns/docbook"
598
599 xml:id="%s.options"
600 >
601
602 <title>%s Options</title>
603 <para />
604 <table frame="all">
605 <caption>%s <indexterm><primary>Daemons</primary><secondary>%s</secondary></indexterm> options</caption>
606 <tgroup cols="2">
607 <colspec colname="option" colwidth="1*" />
608 <colspec colname="description" colwidth="2*" />
609 <thead>
610 <row>
611 <entry> <para>Option</para> </entry>
612 <entry> <para>Description</para> </entry>
613 </row>
614 </thead>
615 <tbody>
616 """ % ( daemon_name, daemon_name, daemon_name, daemon_name )
617
618
619 options_to_ignore= ( 'help', 'version', '', 'genconf', 'genxmltable' )
620
621
622
623
624
625
626
627 import re
628 for opt in parser.option_list:
629 if opt.help is SUPPRESS_HELP:
630 continue
631
632
633
634
635
636
637 all_options= '<literal>' + re.sub( r'/', '</literal>,</para> <para><literal>', "%s" % opt ) + '</literal>'
638
639
640
641
642 option_name= re.sub( r'.*/--', '', "%s" % opt )
643 option_name= re.sub( r'^--', '', "%s" % option_name )
644 if option_name in options_to_ignore:
645 continue
646
647 default_value= parser.defaults.get( opt.dest )
648 if default_value is NO_DEFAULT or default_value is None:
649 default_value= ""
650 default_string= ""
651 if default_value != "":
652 default_string= "<para> Default: <literal>" + str( default_value ) + "</literal></para>\n"
653
654 comment= self.pretty_print_config_comment( opt.help )
655
656
657
658
659 if opt.action in [ 'store_true', 'store_false' ]:
660 print """<row>
661 <entry> <para>%s</para> </entry>
662 <entry>
663 <para>%s</para>
664 %s</entry>
665 </row>
666 """ % ( all_options, comment, default_string )
667
668 else:
669 target= '=<replaceable>' + opt.dest.lower() + '</replaceable>'
670 all_options= all_options + target
671 all_options= re.sub( r',', target + ',', all_options )
672 print """<row>
673 <entry> <para>%s</para> </entry>
674 <entry>
675 <para>%s</para>
676 %s</entry>
677 </row>
678 """ % ( all_options, comment, default_string )
679
680
681
682
683
684
685 print """</tbody></tgroup>
686 </table>
687 <para />
688 </section>
689 """
690 sys.exit( 0 )
691
692
693
695 """
696 Create an XML file that can be used to create Docbook files
697 as well as used as the basis for GUI-based daemon option
698 configuration.
699 """
700
701
702
703
704 unused(options)
705 daemon_name= os.path.basename( sys.argv[0] )
706 daemon_name= daemon_name.replace( '.py', '' )
707
708 export_date = datetime.datetime.now()
709
710 print """<?xml version="1.0" encoding="UTF-8"?>
711
712 <!-- Default daemon configuration generated on %s -->
713 <configuration id="%s" >
714
715 """ % ( export_date, daemon_name )
716
717 options_to_ignore= (
718 'help', 'version', '', 'genconf', 'genxmltable',
719 'genxmlconfigs',
720 )
721
722
723
724
725
726
727
728 import re
729 for opt in parser.option_list:
730 if opt.help is SUPPRESS_HELP:
731 continue
732
733
734
735
736 option_name= re.sub( r'.*/--', '', "%s" % opt )
737 option_name= re.sub( r'^--', '', "%s" % option_name )
738 if option_name in options_to_ignore:
739 continue
740
741 default_value= parser.defaults.get( opt.dest )
742 if default_value is NO_DEFAULT or default_value is None:
743 default_string= ""
744 else:
745 default_string= str( default_value )
746
747
748
749
750 if opt.action in [ 'store_true', 'store_false' ]:
751 print """ <option id="%s" type="%s" default="%s" help="%s" />
752 """ % ( option_name, "boolean", default_string, quote(opt.help), )
753
754 else:
755 target= opt.dest.lower()
756 print """ <option id="%s" type="%s" default="%s" target="%s" help="%s" />
757 """ % ( option_name, opt.type, quote(default_string), target, quote(opt.help), )
758
759
760
761
762
763 print """
764 </configuration>
765 """
766 sys.exit( 0 )
767