1
2
3
4
5
6
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
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):
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
139 if hasattr(response, 'headers') and \
140 response.headers.get('Content-Type') == 'application/vnd.ms-excel':
141 self.handleCSV(response.read())
142 else:
143
144 self.feed(response.read())
145
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
173
175
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
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
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