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 or (at your 
  8  # option) any later version as published by 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