Package Products :: Package ZenReports :: Module ReportMail |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenReports.ReportMail

      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  import sys
     15  import base64
     16  import urllib2
     17  from HTMLParser import HTMLParser
     18  from urlparse import urlparse, urlunparse
     19  import mimetypes
     20  from email.MIMEText import MIMEText
     21  from email.MIMEMultipart import MIMEMultipart
     22  from email.MIMEImage import MIMEImage
     23  from email.MIMEBase import MIMEBase
     24
     25  import Globals
     26  from Products.ZenUtils.ZenScriptBase import ZenScriptBase
     27  from Products.ZenUtils import Utils
     28  import md5
     29
     30 -def sibling(url, path):
     31      parts = list(urlparse(url))
     32      parts[2] = '/'.join(parts[2].split('/')[:-1] + [path])
     33      return urlunparse(parts[0:3] + ['', '',''])
     34
     35 -class Page(HTMLParser):
     36      """Turn an html page into a mime-encoded multi-part email.
     37      Turn the <title> into the subject and keep only the text of the
     38      content pane.  Url references are turned into absolute references,
     39      and images are sent with the page."""
     40
     41 -    def __init__(self, user, passwd, div, comment):
     42          HTMLParser.__init__(self)
     43          self.user = user
     44          self.passwd = passwd
     45          self.html = []
     46          self.images = {}
     47          self.contentPane = 0
     48          self.inTitle = False
     49          self.title = ''
     50          self.div = div
     51          self.comment = comment
     52          self.csv = None
     53
     54 -    def fetchImage(self, url):
     55          return self.slurp(url).read()
     56
     57 -    def absolute(self, url):
     58          url = url.strip()
     59          if url.startswith('http'):
     60              return url
     61          if url.startswith('/'):
     62              base = list(urlparse(self.base))
     63              base[2] = url
     64              return urlunparse(base[0:3] + ['', '',''])
     65          return sibling(self.base, url)
     66
     67 -    def alter(self, attrs, name, function):
     68          result = []
     69          for a, v in attrs:
     70              if a.lower() == name:
     71                  v = function(v)
     72              result.append( (a, v) )
     73          return result
     74
     75 -    def updateSrc(self, attrs):
     76          def cache(v):
     77              if v not in self.images:
     78                  v = self.absolute(v)
     79                  name = 'img%s.png' % md5.md5(v).hexdigest()
     80                  self.images[v] = (name, self.fetchImage(v))
     81              v, _ = self.images[v]
     82              return 'cid:%s' % v
     83          return self.alter(attrs, 'src', cache)
     84
     85 -    def updateHref(self, attrs):
     86          return self.alter(attrs, 'href', self.absolute)
     87
     88 -    def handle_starttag(self, tag, attrs):
     89          tag = tag.upper()
     90          if tag == 'TITLE':
     91              self.inTitle = True
     92          if tag == 'IMG':
     93              attrs = self.updateSrc(attrs)
     94          if tag == 'A':
     95              attrs = self.updateHref(attrs)
     96          if tag == 'DIV':
     97              if ('id',self.div) in attrs:
     98                  self.contentPane = 1
     99              elif self.contentPane:
    100                  self.contentPane += 1
    101          if self.contentPane:
    102              attrs = ' '.join([("%s=%s" % (a, repr(v))) for a, v in attrs])
    103              if attrs: attrs = ' ' + attrs
    104              self.html.append('<%s%s>' % (tag, attrs))
    105
    106 -    def handle_endtag(self, tag):
    107          tag = tag.upper()
    108          if tag == 'TITLE':
    109              self.inTitle = False
    110          if self.contentPane:
    111              self.html.append('</%s>' % tag.upper())
    112          if tag == 'DIV':
    113              if self.contentPane:
    114                  self.contentPane -= 1
    115
    116 -    def handle_data(self, data):
    117          if self.contentPane:
    118              self.html.append(data)
    119          if self.inTitle:
    120              self.title += data
    121
    122 -    def handleCSV(self, data):
    123          self.csv = data
    124
    125 -    def slurp(self, url):
    126          req = urllib2.Request(url)
    127          encoded = base64.encodestring('%s:%s' % (self.user, self.passwd))[:-1]
    128          req.add_header("Authorization", "Basic %s" % encoded)
    129          try:
    130              result = urllib2.urlopen(req)
    131          except urllib2.HTTPError:
    132              import StringIO
    133              result = StringIO.StringIO('')
    134          return result
    135
    136 -    def fetch(self, url):
    137          url = url.replace(' ', '%20')
    138          self.base = url.strip()
    139          response = self.slurp(url)
    140
    141          # Handle CSV.
    142          if hasattr(response, 'headers') and \
    143              response.headers.get('Content-Type') == 'application/vnd.ms-excel':
    144              self.handleCSV(response.read())
    145          else:
    146              # Handle everything else as HTML.
    147              self.feed(response.read())
    148
    149 -    def mail(self):
    150          msg = MIMEMultipart('related')
    151          msg.preamble = 'This is a multi-part message in MIME format'
    152          if self.csv is not None:
    153              txt = MIMEText(self.comment, 'plain')
    154              msg.attach(txt)
    155              csv = MIMEBase('application', 'vnd.ms-excel')
    156              csv.add_header('Content-ID', '<Zenoss Report>')
    157              csv.add_header('Content-Disposition', 'attachment',
    158                  filename='zenoss_report.csv')
    159              csv.set_payload(self.csv)
    160              msg.attach(csv)
    161          else:
    162              txt = MIMEText(''.join(self.html), 'html')
    163              msg.attach(txt)
    164          for url, (name, img) in self.images.items():
    165              ctype, encoding = mimetypes.guess_type(url)
    166              if ctype == None:
    167                  ctype = 'image/png'
    168              maintype, subtype = ctype.split('/', 1)
    169              img = MIMEImage(img, subtype)
    170              img.add_header('Content-ID', '<%s>' % name)
    171              msg.attach(img)
    172          return msg
    173
    174 -class NoDestinationAddressForUser(Exception): pass
    175 -class UnknownUser(Exception): pass
    176
    177 -class ReportMail(ZenScriptBase):
    178
    179 -    def run(self):
    180          'Fetch a report by URL and post as a mime encoded email'
    181          self.connect()
    182          o = self.options
    183          if not o.passwd and not o.url:
    184              self.log.error("No zenoss password or url provided")
    185              sys.exit(1)
    186          try:
    187              user = self.dmd.ZenUsers._getOb(o.user)
    188          except AttributeError:
    189              self.log.error("Unknown user %s" % o.user)
    190              sys.exit(1)
    191
    192          if not o.addresses and user.email:
    193              o.addresses = [user.email]
    194          if not o.addresses:
    195              self.log.error("No address for user %s" % o.user)
    196              sys.exit(1)
    197          page = Page(o.user, o.passwd, o.div, o.comment)
    198          url = self.mangleUrl(o.url)
    199          page.fetch(url)
    200          msg = page.mail()
    201          if o.subject:
    202              msg['Subject'] = o.subject
    203          elif page.title:
    204              msg['Subject'] = page.title
    205          else:
    206              msg['Subject'] = 'Zenoss Report'
    207          msg['From'] = o.fromAddress
    208          msg['To'] = ', '.join(o.addresses)
    209          result, errorMsg = Utils.sendEmail(msg,
    210                                             self.dmd.smtpHost,
    211                                             self.dmd.smtpPort,
    212                                             self.dmd.smtpUseTLS,
    213                                             self.dmd.smtpUser,
    214                                             self.dmd.smtpPass)
    215          if result:
    216              self.log.debug("sent email: %s to:%s", msg.as_string(), o.addresses)
    217          else:
    218              self.log.info("failed to send email to %s: %s %s",
    219                            o.addresses, msg.as_string(), errorMsg)
    220              sys.exit(1)
    221          sys.exit(0)
    222
    223 -    def mangleUrl(self, url):
    224          if url.find('/zport/dmd/reports#reporttree:') != -1 :
    225              urlSplit = url.split('/zport/dmd/reports#reporttree:')
    226              url = urlSplit[0] + urlSplit[1].replace('.', '/')
    227          if url.find('adapt=false') == -1 :
    228              url += '?adapt=false' if url.find('?') == -1 else '&adapt=false'
    229          return url
    230
    231 -    def buildOptions(self):
    232          ZenScriptBase.buildOptions(self)
    233          self.parser.add_option('--url', '-u',
    234                                 dest='url',
    235                                 default=None,
    236                                 help='URL of report to send')
    237          self.parser.add_option('--user', '-U',
    238                                 dest='user',
    239                                 default='admin',
    240                                 help="User to log into Zenoss")
    241          self.parser.add_option('--passwd', '-p',
    242                                 dest='passwd',
    243                                 help="Password to log into Zenoss")
    244          self.parser.add_option('--address', '-a',
    245                                 dest='addresses',
    246                                 default=[],
    247                                 action='append',
    248                                 help='Email address destination '
    249                                 '(may be given more than once).  Default value'
    250                                 "comes from the user's profile.")
    251          self.parser.add_option('--subject', '-s',
    252                                 dest='subject',
    253                                 default='',
    254                                 help='Subject line for email message.'
    255                                 'Default value is the title of the html page.')
    256          self.parser.add_option('--from', '-f',
    257                                 dest='fromAddress',
    258                                 default='zenoss@localhost',
    259                                 help='Origination address')
    260          self.parser.add_option('--div', '-d',
    261                                 dest='div',
    262                                 default='contentPane',
    263                                 help='DIV to extract from URL')
    264          self.parser.add_option('--comment', '-c',
    265                                 dest='comment',
    266                                 default='Report CSV attached.',
    267                                 help='Comment to include in body of CSV reports')
    268
    269
    270  if __name__ == '__main__':
    271      ReportMail().run()
    272