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