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