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

# Source Code for Module Products.ZenReports.ReportMail

      1  ##############################################################################
      2  #
      3  # Copyright (C) Zenoss, Inc. 2007, all rights reserved.
      4  #
      5  # This content is made available according to terms specified in
      6  # License.zenoss under the directory where your Zenoss product is installed.
      7  #
      8  ##############################################################################
      9
     10
     11  import sys
     12  import base64
     13  import urllib2
     14  from HTMLParser import HTMLParser
     15  from urlparse import urlparse, urlunparse
     16  import mimetypes
     17  from email.MIMEText import MIMEText
     18  from email.MIMEMultipart import MIMEMultipart
     19  from email.MIMEImage import MIMEImage
     20  from email.MIMEBase import MIMEBase
     21
     22  import Globals
     23  from Products.ZenUtils.ZenScriptBase import ZenScriptBase
     24  from Products.ZenUtils import Utils
     25  import md5
     26
     27 -def sibling(url, path):
     28      parts = list(urlparse(url))
     29      parts[2] = '/'.join(parts[2].split('/')[:-1] + [path])
     30      return urlunparse(parts[0:3] + ['', '',''])
     31
     32 -class Page(HTMLParser):
     33      """Turn an html page into a mime-encoded multi-part email.
     34      Turn the <title> into the subject and keep only the text of the
     35      content pane.  Url references are turned into absolute references,
     36      and images are sent with the page."""
     37
     38 -    def __init__(self, user, passwd, div, comment):
     39          HTMLParser.__init__(self)
     40          self.user = user
     41          self.passwd = passwd
     42          self.html = []
     43          self.images = {}
     44          self.contentPane = 0
     45          self.inTitle = False
     46          self.title = ''
     47          self.div = div
     48          self.comment = comment
     49          self.csv = None
     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 handleCSV(self, data):
    120          self.csv = data
    121
    122 -    def slurp(self, url):
    123          req = urllib2.Request(url)
    124          encoded = base64.encodestring('%s:%s' % (self.user, self.passwd))[:-1]
    125          req.add_header("Authorization", "Basic %s" % encoded)
    126          try:
    127              result = urllib2.urlopen(req)
    128          except urllib2.HTTPError:
    129              import StringIO
    130              result = StringIO.StringIO('')
    131          return result
    132
    133 -    def fetch(self, url):
    134          url = url.replace(' ', '%20')
    135          self.base = url.strip()
    136          response = self.slurp(url)
    137
    138          # Handle CSV.
    139          if hasattr(response, 'headers') and \
    140              response.headers.get('Content-Type') == 'application/vnd.ms-excel':
    141              self.handleCSV(response.read())
    142          else:
    143              # Handle everything else as HTML.
    144              self.feed(response.read())
    145
    146 -    def mail(self):
    147          msg = MIMEMultipart('related')
    148          msg.preamble = 'This is a multi-part message in MIME format'
    149          if self.csv is not None:
    150              txt = MIMEText(self.comment, 'plain')
    151              msg.attach(txt)
    152              csv = MIMEBase('application', 'vnd.ms-excel')
    153              csv.add_header('Content-ID', '<Zenoss Report>')
    154              csv.add_header('Content-Disposition', 'attachment',
    155                  filename='zenoss_report.csv')
    156              csv.set_payload(self.csv)
    157              msg.attach(csv)
    158          else:
    159              txt = MIMEText(''.join(self.html), 'html')
    160              msg.attach(txt)
    161          for url, (name, img) in self.images.items():
    162              ctype, encoding = mimetypes.guess_type(url)
    163              if ctype == None:
    164                  ctype = 'image/png'
    165              maintype, subtype = ctype.split('/', 1)
    166              img = MIMEImage(img, subtype)
    167              img.add_header('Content-ID', '<%s>' % name)
    168              msg.attach(img)
    169          return msg
    170
    171 -class NoDestinationAddressForUser(Exception): pass
    172 -class UnknownUser(Exception): pass
    173
    174 -class ReportMail(ZenScriptBase):
    175
    176 -    def run(self):
    177          'Fetch a report by URL and post as a mime encoded email'
    178          self.connect()
    179          o = self.options
    180          if not o.passwd and not o.url:
    181              self.log.error("No zenoss password or url provided")
    182              sys.exit(1)
    183          try:
    184              user = self.dmd.ZenUsers._getOb(o.user)
    185          except AttributeError:
    186              self.log.error("Unknown user %s" % o.user)
    187              sys.exit(1)
    188
    189          if not o.addresses and user.email:
    190              o.addresses = [user.email]
    191          if not o.addresses:
    192              self.log.error("No address for user %s" % o.user)
    193              sys.exit(1)
    194          page = Page(o.user, o.passwd, o.div, o.comment)
    195          url = self.mangleUrl(o.url)
    196          page.fetch(url)
    197          msg = page.mail()
    198          if o.subject:
    199              msg['Subject'] = o.subject
    200          elif page.title:
    201              msg['Subject'] = page.title
    202          else:
    203              msg['Subject'] = 'Zenoss Report'
    204          msg['From'] = o.fromAddress
    205          msg['To'] = ', '.join(o.addresses)
    206          result, errorMsg = Utils.sendEmail(msg,
    207                                             self.dmd.smtpHost,
    208                                             self.dmd.smtpPort,
    209                                             self.dmd.smtpUseTLS,
    210                                             self.dmd.smtpUser,
    211                                             self.dmd.smtpPass)
    212          if result:
    213              self.log.debug("sent email: %s to:%s", msg.as_string(), o.addresses)
    214          else:
    215              self.log.info("failed to send email to %s: %s %s",
    216                            o.addresses, msg.as_string(), errorMsg)
    217              sys.exit(1)
    218          sys.exit(0)
    219
    220 -    def mangleUrl(self, url):
    221          if url.find('/zport/dmd/reports#reporttree:') != -1 :
    222              urlSplit = url.split('/zport/dmd/reports#reporttree:')
    223              url = urlSplit[0] + urlSplit[1].replace('.', '/')
    224          if url.find('adapt=false') == -1 :
    225              url += '?adapt=false' if url.find('?') == -1 else '&adapt=false'
    226          return url
    227
    228 -    def buildOptions(self):
    229          ZenScriptBase.buildOptions(self)
    230          self.parser.add_option('--url', '-u',
    231                                 dest='url',
    232                                 default=None,
    233                                 help='URL of report to send')
    234          self.parser.add_option('--user', '-U',
    235                                 dest='user',
    236                                 default='admin',
    237                                 help="User to log into Zenoss")
    238          self.parser.add_option('--passwd', '-p',
    239                                 dest='passwd',
    240                                 help="Password to log into Zenoss")
    241          self.parser.add_option('--address', '-a',
    242                                 dest='addresses',
    243                                 default=[],
    244                                 action='append',
    245                                 help='Email address destination '
    246                                 '(may be given more than once).  Default value'
    247                                 "comes from the user's profile.")
    248          self.parser.add_option('--subject', '-s',
    249                                 dest='subject',
    250                                 default='',
    251                                 help='Subject line for email message.'
    252                                 'Default value is the title of the html page.')
    253          self.parser.add_option('--from', '-f',
    254                                 dest='fromAddress',
    255                                 default='zenoss@localhost',
    256                                 help='Origination address')
    257          self.parser.add_option('--div', '-d',
    258                                 dest='div',
    259                                 default='contentPane',
    260                                 help='DIV to extract from URL')
    261          self.parser.add_option('--comment', '-c',
    262                                 dest='comment',
    263                                 default='Report CSV attached.',
    264                                 help='Comment to include in body of CSV reports')
    265
    266
    267  if __name__ == '__main__':
    268      ReportMail().run()
    269