2
This module is used to publish the report into a set of predefined
3
publisher classes. You can write your own, as long as they contain the
4
__init__ and publish methods.
7
# Copyright (C) 2003 by Duke University
9
# This program is free software; you can redistribute it and/or
10
# modify it under the terms of the GNU General Public License
11
# as published by the Free Software Foundation; either version 2
12
# of the License, or (at your option) any later version.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU General Public License for more details.
19
# You should have received a copy of the GNU General Public License
20
# along with this program; if not, write to the Free Software
21
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
24
# $Id: publishers.py,v 1.19.2.1 2004/02/09 20:31:42 icon Exp $
26
# @Author Konstantin Ryabitsev <icon@linux.duke.edu>
27
# @version $Date: 2004/02/09 20:31:42 $
39
if 'mkdtemp' not in dir(tempfile):
41
# Must be python < 2.3
44
import mytempfile as tempfile
46
def make_html_page(template, starttime, endtime, title, module_reports,
49
Make a html page out of a set of parameters, which include
50
module reports. Used by most, if not all, publishers.
52
logger.put(5, '>make_html_page')
53
logger.put(3, 'Making a standard report page')
54
fmtstr = re.sub(re.compile('%'), '%%', template)
55
fmtstr = re.sub(re.compile('@@STARTTIME@@'), '%(starttime)s', fmtstr)
56
fmtstr = re.sub(re.compile('@@ENDTIME@@'), '%(endtime)s', fmtstr)
57
fmtstr = re.sub(re.compile('@@TITLE@@'), '%(title)s', fmtstr)
58
fmtstr = re.sub(re.compile('@@HOSTNAME@@'), '%(hostname)s', fmtstr)
59
fmtstr = re.sub(re.compile('@@MODULE_REPORTS@@'), '%(allrep)s', fmtstr)
60
fmtstr = re.sub(re.compile('@@UNPARSED_STRINGS@@'), '%(unparsed)s', fmtstr)
61
fmtstr = re.sub(re.compile('@@VERSION@@'), '%(version)s', fmtstr)
62
logger.put(5, 'fmtstr=%s' % fmtstr)
65
valumap['starttime'] = starttime
66
valumap['endtime'] = endtime
67
valumap['title'] = title
68
valumap['hostname'] = socket.gethostname()
70
logger.put(3, 'Concatenating the module reports together')
72
for modrep in module_reports:
73
logger.puthang(3, 'Processing report for "%s"' % modrep.name)
74
allrep = '%s\n<h2>%s</h2>\n%s' % (allrep, modrep.name,
78
allrep = 'No module reports'
79
valumap['allrep'] = allrep
81
if unparsed is not None:
82
logger.put(3, 'Regexing <, > and &')
83
unparsed = re.sub(re.compile('&'), '&', unparsed)
84
unparsed = re.sub(re.compile('<'), '<', unparsed)
85
unparsed = re.sub(re.compile('>'), '>', unparsed)
86
logger.put(3, 'Wrapping unparsed strings into <pre>')
87
unparsed = '<pre>\n%s</pre>' % unparsed
89
unparsed = 'No unparsed strings'
90
valumap['unparsed'] = unparsed
91
valumap['version'] = epylog.VERSION
93
endpage = fmtstr % valumap
94
logger.put(5, 'htmlreport follows:')
95
logger.put(5, endpage)
96
logger.put(5, '<make_html_page')
99
def do_chunked_gzip(infh, outfh, filename, logger):
101
A memory-friendly way of compressing the data.
103
gzfh = gzip.GzipFile('rawlogs', fileobj=outfh)
104
bartotal = infh.tell()
106
bartitle = 'Gzipping %s' % filename
108
logger.put(3, 'Doing chunked read from infh into gzfh')
110
chunk = infh.read(epylog.CHUNK_SIZE)
112
logger.put(5, 'Reached EOF')
115
bardone += len(chunk)
116
logger.progressbar(1, bartitle, bardone, bartotal)
117
logger.put(3, 'Wrote %d bytes' % len(chunk))
119
logger.endbar(1, bartitle, 'gzipped down to %d bytes' % outfh.tell())
121
def mail_smtp(smtpserv, fromaddr, toaddr, msg, logger):
123
Send mail using smtp.
125
logger.put(5, '>publishers.mail_smtp')
127
logger.puthang(3, 'Mailing it via the SMTP server %s' % smtpserv)
128
server = smtplib.SMTP(smtpserv)
129
server.sendmail(fromaddr, toaddr, msg)
132
logger.put(5, '<publishers.mail_smtp')
134
def mail_sendmail(sendmail, msg, logger):
136
Send mail using sendmail.
138
logger.put(5, '>publishers.mail_sendmail')
139
logger.puthang(3, 'Mailing the message via sendmail')
140
p = os.popen(sendmail, 'w')
144
logger.put(5, '<publishers.mail_sendmail')
148
This publisher sends the results of an epylog run as an email message.
151
name = 'Mail Publisher'
153
def __init__(self, sec, config, logger):
154
logger.put(5, '>MailPublisher.__init__')
156
self.tmpprefix = config.tmpprefix
158
logger.put(3, 'Looking for required elements in mail method config')
160
mailto = config.get(self.section, 'mailto')
161
addrs = mailto.split(',')
165
logger.put(5, 'adding mailto=%s' % addr)
166
self.mailto.append(addr)
167
except: self.mailto = ['root']
169
try: format = config.get(self.section, 'format')
170
except: format = 'both'
172
if (format != 'plain') and (format != 'html') and (format != 'both'):
173
msg = ('Format for Mail Publisher must be either "html", "plain",'
174
+ ' or "both." Format "%s" is unknown') % format
175
raise epylog.ConfigError(msg, logger)
179
logger.put(3, 'Plaintext version requested. Checking for lynx')
180
try: lynx = config.get(self.section, 'lynx')
182
lynx = '/usr/bin/lynx'
183
if not os.access(lynx, os.X_OK):
184
msg = 'Could not find "%s"' % lynx
185
raise epylog.ConfigError(msg, logger)
187
logger.put(3, 'Lynx found in "%s" and is executable' % self.lynx)
190
include_rawlogs = config.getboolean(self.section,'include_rawlogs')
191
except: include_rawlogs = 1
194
try: rawlogs = int(config.get(self.section, 'rawlogs_limit'))
195
except: rawlogs = 200
196
self.rawlogs = rawlogs * 1024
197
else: self.rawlogs = 0
199
try: self.smtpserv = config.get(self.section, 'smtpserv')
200
except: self.smtpserv = 'localhost'
202
logger.put(5, 'format=%s' % self.format)
203
logger.put(5, 'rawlogs=%d' % self.rawlogs)
204
logger.put(5, 'smtpserv=%s' % self.smtpserv)
206
logger.put(5, '<MailPublisher.__init__')
208
def publish(self, template, starttime, endtime, title, module_reports,
209
unparsed_strings, rawfh):
211
logger.put(5, '>MailPublisher.publish')
212
logger.puthang(3, 'Creating a standard html page report')
213
html_report = make_html_page(template, starttime, endtime, title,
214
module_reports, unparsed_strings, logger)
215
self.htmlrep = html_report
219
if self.format != 'html':
220
tempfile.tempdir = self.tmpprefix
221
logger.puthang(3, 'Creating a plaintext format of the report')
222
htmlfile = tempfile.mktemp('.html')
223
tfh = open(htmlfile, 'w')
224
tfh.write(html_report)
226
logger.put(3, 'HTML report is in "%s"' % htmlfile)
227
plainfile = tempfile.mktemp('PLAIN')
228
logger.put(3, 'PLAIN report will go into "%s"' % plainfile)
229
logger.put(3, 'Making a syscall to "%s"' % self.lynx)
230
exitcode = os.system('%s -dump %s > %s 2>/dev/null'
231
% (self.lynx, htmlfile, plainfile))
232
if exitcode or not os.access(plainfile, os.R_OK):
233
msg = 'Error making a call to "%s"' % self.lynx
234
raise epylog.SysCallError(msg, logger)
235
logger.puthang(3, 'Reading in the plain version')
236
tfh = open(plainfile)
237
self.plainrep = tfh.read()
239
logger.put(5, 'plainrep follows:')
240
logger.put(5, self.plainrep)
246
# GzipFile doesn't work with StringIO. :/ Bleh.
248
tempfile.tempdir = self.tmpprefix
249
outfh = open(tempfile.mktemp('GZIP'), 'w+')
250
do_chunked_gzip(rawfh, outfh, 'rawlogs', logger)
252
if size > self.rawlogs:
253
logger.put(1, '%d is over the defined max of "%d"'
254
% (size, self.rawlogs))
255
logger.put(1, 'Not attaching the raw logs')
258
logger.put(5, 'Reading in the gzipped logs')
260
self.gzlogs = outfh.read()
264
# Using MimeWriter, since package 'email' doesn't come with rhl-7.3
267
logger.puthang(3, 'Creating an email message')
268
import StringIO, MimeWriter
269
fh = StringIO.StringIO()
270
logger.put(5, 'Creating a main header')
271
mw = MimeWriter.MimeWriter(fh)
272
mw.addheader('Subject', title)
273
if len(self.mailto) > 1:
275
tostr = string.join(self.mailto, ', ')
277
tostr = self.mailto[0]
278
mw.addheader('To', tostr)
279
mw.addheader('X-Mailer', epylog.VERSION)
282
if self.rawlogs > 0 and self.format == 'both':
283
logger.put(5, 'Making a html + plain + gzip message')
284
self._mk_both_rawlogs()
285
elif self.rawlogs > 0 and self.format == 'html':
286
logger.put(5, 'Making a html + gzip message')
287
self._mk_html_rawlogs()
288
elif self.rawlogs > 0 and self.format == 'plain':
289
logger.put(5, 'Making a plain + gzip message')
290
self._mk_plain_rawlogs()
291
elif self.rawlogs == 0 and self.format == 'both':
292
logger.put(5, 'Making a html + plain message')
293
self._mk_both_nologs()
294
elif self.rawlogs == 0 and self.format == 'html':
295
logger.put(5, 'Making a html message')
296
self._mk_html_nologs()
297
elif self.rawlogs == 0 and self.format == 'plain':
298
logger.put(5, 'Making a plain message')
299
self._mk_plain_nologs()
305
logger.put(5, 'Message follows')
307
logger.put(5, 'End of message')
309
logger.put(3, 'Figuring out if we are using sendmail or smtplib')
310
if re.compile('^/').search(self.smtpserv):
311
mail_sendmail(self.smtpserv, msg, logger)
313
fromaddr = 'root@%s' % socket.gethostname()
314
mail_smtp(self.smtpserv, fromaddr, self.mailto, msg, logger)
315
logger.put(1, 'Mailed the report to: %s' % tostr)
316
logger.put(5, '<MailPublisher.publish')
318
def _mk_both_rawlogs(self):
320
Make an email message that includes html, plaintext, and gzipped raw
321
logs sections. Most painful.
323
self.logger.put(5, '>MailPublisher._mk_both_rawlogs')
327
mixed_mw.addheader('Mime-Version', '1.0')
328
logger.put(5, 'Creating a multipart/mixed part')
329
mixed_mw.startmultipartbody('mixed')
331
logger.put(5, 'Creating a multipart/alternative part')
332
alt_mw = mixed_mw.nextpart()
333
alt_mw.startmultipartbody('alternative')
335
logger.put(5, 'Creating a text/plain part')
336
plain_mw = alt_mw.nextpart()
337
plain_mw.addheader('Content-Transfer-Encoding', '8bit')
338
plain_fh = plain_mw.startbody('text/plain; charset=iso-8859-1')
339
plain_fh.write(self.plainrep)
341
logger.put(5, 'Creating a text/html part')
342
html_mw = alt_mw.nextpart()
343
html_mw.addheader('Content-Transfer-Encoding', '8bit')
344
html_fh = html_mw.startbody('text/html; charset=iso-8859-1')
345
html_fh.write(self.htmlrep)
348
logger.put(5, 'Creating an application/gzip part')
349
gzip_mw = mixed_mw.nextpart()
350
gzip_mw.addheader('Content-Transfer-Encoding', 'base64')
351
gzip_mw.addheader('Content-Disposition',
352
'attachment; filename=rawlogs.gz')
353
gzip_fh = gzip_mw.startbody('application/gzip; NAME=rawlogs.gz')
354
gzip_fh.write(base64.encodestring(self.gzlogs))
356
self.logger.put(5, '<MailPublisher._mk_both_rawlogs')
358
def _mk_html_rawlogs(self):
360
Make an email message that includes html and gzipped raw logs sections.
362
self.logger.put(5, '>MailPublisher._mk_html_rawlogs')
366
mixed_mw.addheader('Mime-Version', '1.0')
367
logger.put(5, 'Creating a multipart/mixed part')
368
mixed_mw.startmultipartbody('mixed')
370
logger.put(5, 'Creating a text/html part')
371
html_mw = mixed_mw.nextpart()
372
html_mw.addheader('Content-Transfer-Encoding', '8bit')
373
html_fh = html_mw.startbody('text/html; charset=iso-8859-1')
374
html_fh.write(self.htmlrep)
376
logger.put(5, 'Creating an application/gzip part')
377
gzip_mw = mixed_mw.nextpart()
378
gzip_mw.addheader('Content-Transfer-Encoding', 'base64')
379
gzip_mw.addheader('Content-Disposition',
380
'attachment; filename=rawlogs.gz')
381
gzip_fh = gzip_mw.startbody('application/gzip; NAME=rawlogs.gz')
382
gzip_fh.write(base64.encodestring(self.gzlogs))
384
self.logger.put(5, '<MailPublisher._mk_html_rawlogs')
386
def _mk_plain_rawlogs(self):
388
Make an email message that includes plaintext and gzipped raw logs
391
self.logger.put(5, '>MailPublisher._mk_plain_rawlogs')
395
mixed_mw.addheader('Mime-Version', '1.0')
396
logger.put(5, 'Creating a multipart/mixed part')
397
mixed_mw.startmultipartbody('mixed')
399
logger.put(5, 'Creating a text/plain part')
400
plain_mw = mixed_mw.nextpart()
401
plain_mw.addheader('Content-Transfer-Encoding', '8bit')
402
plain_fh = plain_mw.startbody('text/plain; charset=iso-8859-1')
403
plain_fh.write(self.plainrep)
405
logger.put(5, 'Creating an application/gzip part')
406
gzip_mw = mixed_mw.nextpart()
407
gzip_mw.addheader('Content-Transfer-Encoding', 'base64')
408
gzip_mw.addheader('Content-Disposition',
409
'attachment; filename=rawlogs.gz')
410
gzip_fh = gzip_mw.startbody('application/gzip; NAME=rawlogs.gz')
411
gzip_fh.write(base64.encodestring(self.gzlogs))
413
self.logger.put(5, '<MailPublisher._mk_plain_rawlogs')
415
def _mk_both_nologs(self):
417
Make a message that just includes html and plaintext sections.
419
self.logger.put(5, '>MailPublisher._mk_both_nologs')
422
alt_mw.addheader('Mime-Version', '1.0')
423
logger.put(5, 'Creating a multipart/alternative part')
424
alt_mw.startmultipartbody('alternative')
426
logger.put(5, 'Creating a text/plain part')
427
plain_mw = alt_mw.nextpart()
428
plain_mw.addheader('Content-Transfer-Encoding', '8bit')
429
plain_fh = plain_mw.startbody('text/plain; charset=iso-8859-1')
430
plain_fh.write(self.plainrep)
432
logger.put(5, 'Creating a text/html part')
433
html_mw = alt_mw.nextpart()
434
html_mw.addheader('Content-Transfer-Encoding', '8bit')
435
html_fh = html_mw.startbody('text/html; charset=iso-8859-1')
436
html_fh.write(self.htmlrep)
439
self.logger.put(5, '<MailPublisher._mk_both_nologs')
441
def _mk_html_nologs(self):
443
Make a message that just includes HTML-formatted section.
445
self.logger.put(5, '>MailPublisher._mk_html_nologs')
448
alt_mw.addheader('Mime-Version', '1.0')
449
logger.put(5, 'Creating a multipart/alternative part')
450
alt_mw.startmultipartbody('alternative')
451
logger.put(5, 'Creating a text/html part')
452
html_mw = alt_mw.nextpart()
453
html_mw.addheader('Content-Transfer-Encoding', '8bit')
454
html_fh = html_mw.startbody('text/html; charset=iso-8859-1')
455
html_fh.write(self.htmlrep)
457
self.logger.put(5, '<MailPublisher._mk_html_nologs')
459
def _mk_plain_nologs(self):
461
Make a message that just includes a plaintext-formatted section.
463
self.logger.put(5, '>MailPublisher._mk_plain_nologs')
466
logger.put(5, 'Creating a text/plain part')
467
plain_mw.addheader('Content-Transfer-Encoding', '8bit')
468
plain_fh = plain_mw.startbody('text/plain; charset=iso-8859-1')
469
plain_fh.write(self.plainrep)
470
self.logger.put(5, '<MailPublisher._mk_plain_nologs')
475
FilePublisher publishes the results of an Epylog run into a set of files
476
and directories on the hard drive.
478
name = 'File Publisher'
479
def __init__(self, sec, config, logger):
480
logger.put(5, '>FilePublisher.__init__')
482
self.tmpprefix = config.tmpprefix
483
logger.put(3, 'Looking for required elements in file method config')
484
msg = 'Required attribute "%s" not found'
485
try: expire = int(config.get(sec, 'expire_in'))
486
except: epylog.ConfigError(msg % 'expire_in', logger)
488
try: dirmask = config.get(sec, 'dirmask')
489
except: epylog.ConfigError(msg % 'dirmask', logger)
490
try: filemask = config.get(sec, 'filemask')
491
except: epylog.ConfigError(msg % 'filemask', logger)
493
logger.put(3, 'Verifying dirmask and filemask')
494
msg = 'Invalid mask for %s: %s'
495
try: self.dirname = time.strftime(dirmask, time.localtime())
496
except: epylog.ConfigError(msg % ('dirmask', dirmask), logger)
497
try: path = config.get(sec, 'path')
498
except: epylog.ConfigError(msg % 'path', logger)
499
try: self.filename = time.strftime(filemask, time.localtime())
500
except: epylog.ConfigError(msg % ('filemask', filemask), logger)
501
self._prune_old(path, dirmask, expire)
502
self.path = os.path.join(path, self.dirname)
504
logger.put(3, 'Checking if notify is set')
507
notify = config.get(sec, 'notify')
508
for addy in notify.split(','):
510
logger.put(3, 'Will notify: %s' % addy)
511
self.notify.append(addy)
513
try: self.smtpserv = config.get(sec, 'smtpserv')
514
except: self.smtpserv = '/usr/sbin/sendmail -t'
517
self.pubroot = config.get(sec, 'pubroot')
518
logger.put(5, 'pubroot=%s' % self.pubroot)
520
msg = 'File publisher requires a pubroot when notify is set'
521
raise epylog.ConfigError(msg, logger)
523
logger.put(5, 'path=%s' % self.path)
524
logger.put(5, 'filename=%s' % self.filename)
525
logger.put(5, '<FilePublisher.__init__')
527
def _prune_old(self, path, dirmask, expire):
529
Removes the directories that are older than a certain date.
532
logger.put(5, '>FilePublisher._prune_old')
533
logger.put(3, 'Pruning directories older than %d days' % expire)
534
expire_limit = int(time.time()) - (86400 * expire)
535
logger.put(5, 'expire_limit=%d' % expire_limit)
536
if not os.path.isdir(path):
537
logger.put(3, 'Dir %s not found -- skipping pruning' % path)
538
logger.put(5, '<FilePublisher._prune_old')
540
for entry in os.listdir(path):
541
logger.put(5, 'Found: %s' % entry)
542
if os.path.isdir(os.path.join(path, entry)):
543
logger.put(3, 'Found directory %s' % entry)
544
logger.put(4, 'Trying to strptime it into a timestamp')
545
try: stamp = time.mktime(time.strptime(entry, dirmask))
546
except ValueError, e:
547
logger.put(3, 'Dir %s did not match dirmask %s: %s'
548
% (entry, dirmask, e))
549
logger.put(3, 'Skipping %s' % entry)
551
if stamp < expire_limit:
552
logger.put(3, '%s is older than expire limit')
553
shutil.rmtree(os.path.join(path, entry))
554
logger.put(1, 'File Publisher: Pruned old directory: %s'
557
logger.put(3, '%s is still active' % entry)
559
logger.put(3, '%s is not a directory. Skipping.' % entry)
560
logger.put(3, 'Finished with pruning')
561
logger.put(5, '<FilePublisher._prune_old')
563
def publish(self, template, starttime, endtime, title, module_reports,
564
unparsed_strings, rawfh):
566
logger.put(5, '>FilePublisher.publish')
567
logger.put(3, 'Checking and creating the report directories')
568
if not os.path.isdir(self.path):
569
try: os.makedirs(self.path)
571
logger.put(0, 'Error creating directory "%s": %s' %
573
logger.put(0, 'File publisher exiting.')
575
logger.puthang(3, 'Creating a standard html page report')
576
html_report = make_html_page(template, starttime, endtime, title,
577
module_reports, unparsed_strings, logger)
579
filename = '%s.html' % self.filename
580
repfile = os.path.join(self.path, filename)
581
logger.put(3, 'Dumping the report into %s' % repfile)
582
fh = open(repfile, 'w')
583
fh.write(html_report)
585
logger.put(1, 'Report saved in: %s' % self.path)
587
logger.puthang(3, 'Creating an email message')
588
publoc = '%s/%s/%s' % (self.pubroot, self.dirname, filename)
589
msg = 'New Epylog report is available at:\r\n%s' % publoc
590
import StringIO, MimeWriter
591
fh = StringIO.StringIO()
592
logger.put(3, 'Creating a main header')
593
mw = MimeWriter.MimeWriter(fh)
594
mw.addheader('Subject', '%s (report notification)' % title)
595
tostr = ', '.join(self.notify)
596
mw.addheader('To', tostr)
597
mw.addheader('X-Mailer', epylog.VERSION)
598
mw.addheader('Content-Transfer-Encoding', '8bit')
599
bfh = mw.startbody('text/plain; charset=iso-8859-1')
604
logger.put(3, 'Figuring out if we are using sendmail or smtplib')
605
if re.compile('^/').search(self.smtpserv):
606
mail_sendmail(self.smtpserv, msg, logger)
608
fromaddr = 'root@%s' % socket.gethostname()
609
mail_smtp(self.smtpserv, fromaddr, self.notify, msg, logger)
610
logger.put(1, 'Notification mailed to: %s' % tostr)
612
logfilen = '%s.log' % self.filename
613
logfile = os.path.join(self.path, '%s.gz' % logfilen)
614
logger.put(3, 'Gzipping logs and writing them to %s' % logfilen)
615
outfh = open(logfile, 'w+')
616
do_chunked_gzip(rawfh, outfh, logfilen, logger)
618
logger.put(1, 'Gzipped logs saved in: %s' % self.path)
619
logger.put(5, '<FilePublisher.publish')