Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(181)

Side by Side Diff: gatekeeper_mailer.py

Issue 19878007: Add build mailer capability to support gatekeeper_ng. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/chromium-build@master
Patch Set: Adds datastore-based auth. Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
Vadim Sh. 2013/08/26 21:30:41 I'm assuming this is being converted to jinja2 now
Mike Stip (use stip instead) 2013/08/29 19:56:46 Done.
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Provides mailer support for gatekeeper_ng.
7
8 This module containes mail templates to notify tree watchers
9 when the tree is closed.
10 """
11
12 import cStringIO
13 import urllib
14
15 # From buildbot's results.py.
16 SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY = range(6)
17 Results = ["success", "warnings", "failure", "skipped", "exception", "retry"]
Vadim Sh. 2013/08/26 21:30:41 Why camelcase here instead all CAPS?
18
19
20 def _GenBuildBox(buildstatus, waterfall_url, styles):
Vadim Sh. 2013/08/26 21:30:41 Also why camel case in functions instead of PEP8?.
21 """Generates a box for one build."""
22 class_ = Results[buildstatus['result']]
23 style = ''
24 if class_ and class_ in styles:
25 style = styles[class_]
26 reason = buildstatus['reason']
27 url = '%sbuilders/%s/builds/%d' % (
28 waterfall_url,
29 urllib.quote(buildstatus['builderName'], safe=''),
30 buildstatus['number'])
31 fmt = ('<tr><td style="%s"><a title="Reason: %s" href="%s">'
32 'Build %d'
33 '</a></td></tr>')
34 return fmt % (style, reason, url, buildstatus['number'])
35
36
37 # escape taken from twisted/web/microdom.py
38 # order is important
39 HTML_ESCAPE_CHARS = (('&', '&amp;'), # don't add any entities before this one
40 ('<', '&lt;'),
41 ('>', '&gt;'),
42 ('"', '&quot;'))
43
44
45 def escape(text, chars=HTML_ESCAPE_CHARS):
46 "Escape a few XML special chars with XML entities."
47 for s, h in chars:
48 text = text.replace(s, h)
49 return text
50
51
52 def _GenStepBox(stepstatus, styles):
53 """Generates a box for one step."""
54 class_ = Results[stepstatus['results']]
55 # class == running or results
56 style = ''
57 if class_ and class_ in styles:
58 style = styles[class_]
59 text = stepstatus['text'] or []
60 text = text[:]
61 for steplog in stepstatus['logs']:
62 logname = steplog[0]
63 url = steplog[1]
64 text.append('<a href="%s">%s</a>' % (url, escape(logname)))
65 for urlname, target in stepstatus['urls'].iteritems():
66 text.append('<a href="%s">%s</a>' % (target, escape(urlname)))
67 fmt = '<tr><td style="%s">%s</td></tr>'
68 return fmt % (style, '<br/>'.join(text))
69
70
71 class MailTemplate(object):
72 """Encapsulates a buildbot status email."""
73
74 # Extracted from
75 # http://src.chromium.org/svn/trunk/tools/buildbot/master.chromium/
76 # public_html/buildbot.css
77 DEFAULT_STYLES = {
78 'BuildStep': '',
79 'start': ('color: #666666; background-color: #fffc6c;'
80 'border-color: #C5C56D;'),
81 'success': ('color: #FFFFFF; background-color: #8fdf5f; '
82 'border-color: #4F8530;'),
83 'failure': ('color: #FFFFFF; background-color: #e98080; '
84 'border-color: #A77272;'),
85 'warnings': ('color: #FFFFFF; background-color: #ffc343; '
86 'border-color: #C29D46;'),
87 'exception': ('color: #FFFFFF; background-color: #e0b0ff; '
88 'border-color: #ACA0B3;'),
89 'offline': ('color: #FFFFFF; background-color: #e0b0ff; '
90 'border-color: #ACA0B3;'),
91 }
92
93 # Generate a HTML table looking like the waterfall.
94 # WARNING: Gmail ignores embedded CSS style. I don't know how to fix that so
95 # meanwhile, I just won't embedded the CSS style.
96 html_template = (
97 """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
98 <html xmlns="http://www.w3.org/1999/xhtml">
99 <head>
100 <title>%s</title>
101 </head>
102 <body>
103 <a href="%s">%s</a><p>
104 %s<p>
105 <a href="%s">%s</a><p>
106 Revision: %s<br>
107 """
108 )
109
110 # Simpler text content for non-html aware clients.
111 text_template = (
112 """%s
113
114 %s
115
116 %swaterfall?builder=%s
117
118 --=> %s <=--
119
120 Revision: %s
121 Blame list: %s
122
123 Buildbot waterfall: http://build.chromium.org/
124 """
125 )
126
127 change_tmpl = (
128 """
129 <p>Changed by: <b>%(who)s</b><br />
130 Changed at: <b>%(at)s</b><br />
131 %(repository)s
132 %(branch)s
133 %(revision)s
134 <br />
135
136 Changed files:
137 %(files)s
138
139 Comments:
140 %(comments)s
141
142 Properties:
143 %(properties)s
144 </p>
145 """
146 )
147
148 status_header = 'Automatically closing tree for "%(steps)s" on "%(builder)s"'
149
150 def __init__(self, waterfall_url, build_url,
151 project_name,
152 fromaddr,
153 reply_to=None,
154 subject='buildbot %(result)s in %(projectName)s on %(builder)s, '
155 'revision %(revision)s'):
156
157 self.reply_to = reply_to
158 self.fromaddr = fromaddr
159 self.subject = subject
160 self.waterfall_url = waterfall_url
161 self.build_url = build_url
162 self.project_name = project_name
163
164 @staticmethod
165 def UL(lst):
166 # from third_party/twisted_10_2/twisted/web/html.py
167 io = cStringIO.StringIO()
168 io.write("<ul>\n")
169 for el in lst:
170 io.write("<li> %s</li>\n" % el)
171 io.write("</ul>")
172 return io.getvalue()
173
174 def changeToHTML(self, change):
175 # Ported from third_party/buildbot_8_4p1/buildbot/changes.py.
176
177 links = []
178 for f in change['files']:
179 if f['url'] is not None:
180 # could get confused
181 links.append('<a href="%s"><b>%s</b></a>' % (
182 f['url'], f['name']))
183 else:
184 links.append('<b>%s</b>' % f['name'])
185
186 revision = ''
187 if change['revision']:
188 if change.get('revlink'):
189 revision = 'Revision: <a href="%s"><b>%s</b></a>\n' % (
190 change['revlink'], change['revision'])
191 else:
192 revision = "Revision: <b>%s</b><br />\n" % change['revision']
193
194 repository = ''
195 if change['repository']:
196 repository = "Repository: <b>%s</b><br />\n" % change['repository']
197
198 branch = ''
199 if change['branch']:
200 branch = "Branch: <b>%s</b><br />\n" % change['branch']
201
202 properties = []
203 for prop in change['properties']:
204 properties.append("%s: %s<br />" % (prop[0], prop[1]))
205
206 kwargs = { 'who' : escape(change['who']),
207 'at' : change['at'],
208 'files' : self.UL(links) + '\n',
209 'repository': repository,
210 'revision' : revision,
211 'branch' : branch,
212 'comments' : '<pre>' + escape(change['comments']) + '</pre>',
213 'properties': self.UL(properties) + '\n' }
214 return self.change_tmpl % kwargs
215
216 def genMessageContent(self, build_status):
217 builder_name = build_status['builderName']
218 us_steps = ','.join(build_status['unsatisfied'])
219 revisions_list = build_status['revisions']
220 status_text = self.status_header % {
221 'builder': builder_name,
222 'steps': us_steps,
223 }
224 # Use the first line as a title.
225 status_title = status_text.split('\n', 1)[0]
226 blame_list = ','.join(build_status['blamelist'])
227 revisions_string = ''
228 latest_revision = 0
229 if revisions_list:
230 revisions_string = ', '.join([str(rev) for rev in revisions_list])
231 latest_revision = max([rev for rev in revisions_list])
232 if build_status['result'] == FAILURE:
233 result = 'failure'
234 else:
235 result = 'warning'
236
237 html_content = self.html_template % (status_title, self.waterfall_url,
238 self.waterfall_url,
239 status_text.replace('\n', "<br>\n"), self.build_url,
240 self.build_url, revisions_string)
241
242 # Only include the blame list if relevant.
243 html_content += " Blame list: %s<p>\n" % blame_list
244
245 build_boxes = [_GenBuildBox(build_status, self.waterfall_url,
246 self.DEFAULT_STYLES)]
247 build_boxes.extend([_GenStepBox(step,
248 self.DEFAULT_STYLES)
249 for step in build_status['steps']
250 if step['started'] and step['text']])
251 table_content = ''.join(build_boxes)
252 html_content += (
253 ('<table style="border-spacing: 1px 1px; font-weight: bold;'
254 ' padding: 3px 0px 3px 0px; text-align: center;">\n') +
255 table_content +
256 '</table>\n')
257
258 html_content += "<p>"
259 # Add the change list descriptions. getChanges() returns a tuple of
260 # buildbot.changes.changes.Change
261 for change in build_status['changes']:
262 html_content += self.changeToHTML(change)
263 html_content += "</body>\n</html>"
264
265 text_content = self.text_template % (status_title,
266 self.build_url,
267 urllib.quote(self.waterfall_url, '/:'),
268 urllib.quote(builder_name),
269 status_text,
270 revisions_string,
271 blame_list)
272
273 subject = self.subject % {
274 'result': result,
275 'projectName': self.project_name,
276 'builder': builder_name,
277 'reason': build_status['reason'],
278 'revision': str(latest_revision),
279 'buildnumber': str(build_status['number']),
280 }
281
282 return text_content, html_content, subject
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698