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