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

Unified 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, 4 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 side-by-side diff with in-line comments
Download patch
Index: gatekeeper_mailer.py
diff --git a/gatekeeper_mailer.py b/gatekeeper_mailer.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1e179125cb278bec2d6405dd095aa303024f6e4
--- /dev/null
+++ b/gatekeeper_mailer.py
@@ -0,0 +1,282 @@
+#!/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.
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Provides mailer support for gatekeeper_ng.
+
+This module containes mail templates to notify tree watchers
+when the tree is closed.
+"""
+
+import cStringIO
+import urllib
+
+# From buildbot's results.py.
+SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY = range(6)
+Results = ["success", "warnings", "failure", "skipped", "exception", "retry"]
Vadim Sh. 2013/08/26 21:30:41 Why camelcase here instead all CAPS?
+
+
+def _GenBuildBox(buildstatus, waterfall_url, styles):
Vadim Sh. 2013/08/26 21:30:41 Also why camel case in functions instead of PEP8?.
+ """Generates a box for one build."""
+ class_ = Results[buildstatus['result']]
+ style = ''
+ if class_ and class_ in styles:
+ style = styles[class_]
+ reason = buildstatus['reason']
+ url = '%sbuilders/%s/builds/%d' % (
+ waterfall_url,
+ urllib.quote(buildstatus['builderName'], safe=''),
+ buildstatus['number'])
+ fmt = ('<tr><td style="%s"><a title="Reason: %s" href="%s">'
+ 'Build %d'
+ '</a></td></tr>')
+ return fmt % (style, reason, url, buildstatus['number'])
+
+
+# escape taken from twisted/web/microdom.py
+# order is important
+HTML_ESCAPE_CHARS = (('&', '&amp;'), # don't add any entities before this one
+ ('<', '&lt;'),
+ ('>', '&gt;'),
+ ('"', '&quot;'))
+
+
+def escape(text, chars=HTML_ESCAPE_CHARS):
+ "Escape a few XML special chars with XML entities."
+ for s, h in chars:
+ text = text.replace(s, h)
+ return text
+
+
+def _GenStepBox(stepstatus, styles):
+ """Generates a box for one step."""
+ class_ = Results[stepstatus['results']]
+ # class == running or results
+ style = ''
+ if class_ and class_ in styles:
+ style = styles[class_]
+ text = stepstatus['text'] or []
+ text = text[:]
+ for steplog in stepstatus['logs']:
+ logname = steplog[0]
+ url = steplog[1]
+ text.append('<a href="%s">%s</a>' % (url, escape(logname)))
+ for urlname, target in stepstatus['urls'].iteritems():
+ text.append('<a href="%s">%s</a>' % (target, escape(urlname)))
+ fmt = '<tr><td style="%s">%s</td></tr>'
+ return fmt % (style, '<br/>'.join(text))
+
+
+class MailTemplate(object):
+ """Encapsulates a buildbot status email."""
+
+ # Extracted from
+ # http://src.chromium.org/svn/trunk/tools/buildbot/master.chromium/
+ # public_html/buildbot.css
+ DEFAULT_STYLES = {
+ 'BuildStep': '',
+ 'start': ('color: #666666; background-color: #fffc6c;'
+ 'border-color: #C5C56D;'),
+ 'success': ('color: #FFFFFF; background-color: #8fdf5f; '
+ 'border-color: #4F8530;'),
+ 'failure': ('color: #FFFFFF; background-color: #e98080; '
+ 'border-color: #A77272;'),
+ 'warnings': ('color: #FFFFFF; background-color: #ffc343; '
+ 'border-color: #C29D46;'),
+ 'exception': ('color: #FFFFFF; background-color: #e0b0ff; '
+ 'border-color: #ACA0B3;'),
+ 'offline': ('color: #FFFFFF; background-color: #e0b0ff; '
+ 'border-color: #ACA0B3;'),
+ }
+
+ # Generate a HTML table looking like the waterfall.
+ # WARNING: Gmail ignores embedded CSS style. I don't know how to fix that so
+ # meanwhile, I just won't embedded the CSS style.
+ html_template = (
+"""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>%s</title>
+</head>
+<body>
+ <a href="%s">%s</a><p>
+ %s<p>
+ <a href="%s">%s</a><p>
+ Revision: %s<br>
+"""
+)
+
+ # Simpler text content for non-html aware clients.
+ text_template = (
+"""%s
+
+%s
+
+%swaterfall?builder=%s
+
+--=> %s <=--
+
+Revision: %s
+Blame list: %s
+
+Buildbot waterfall: http://build.chromium.org/
+"""
+)
+
+ change_tmpl = (
+"""
+<p>Changed by: <b>%(who)s</b><br />
+Changed at: <b>%(at)s</b><br />
+%(repository)s
+%(branch)s
+%(revision)s
+<br />
+
+Changed files:
+%(files)s
+
+Comments:
+%(comments)s
+
+Properties:
+%(properties)s
+</p>
+"""
+)
+
+ status_header = 'Automatically closing tree for "%(steps)s" on "%(builder)s"'
+
+ def __init__(self, waterfall_url, build_url,
+ project_name,
+ fromaddr,
+ reply_to=None,
+ subject='buildbot %(result)s in %(projectName)s on %(builder)s, '
+ 'revision %(revision)s'):
+
+ self.reply_to = reply_to
+ self.fromaddr = fromaddr
+ self.subject = subject
+ self.waterfall_url = waterfall_url
+ self.build_url = build_url
+ self.project_name = project_name
+
+ @staticmethod
+ def UL(lst):
+ # from third_party/twisted_10_2/twisted/web/html.py
+ io = cStringIO.StringIO()
+ io.write("<ul>\n")
+ for el in lst:
+ io.write("<li> %s</li>\n" % el)
+ io.write("</ul>")
+ return io.getvalue()
+
+ def changeToHTML(self, change):
+ # Ported from third_party/buildbot_8_4p1/buildbot/changes.py.
+
+ links = []
+ for f in change['files']:
+ if f['url'] is not None:
+ # could get confused
+ links.append('<a href="%s"><b>%s</b></a>' % (
+ f['url'], f['name']))
+ else:
+ links.append('<b>%s</b>' % f['name'])
+
+ revision = ''
+ if change['revision']:
+ if change.get('revlink'):
+ revision = 'Revision: <a href="%s"><b>%s</b></a>\n' % (
+ change['revlink'], change['revision'])
+ else:
+ revision = "Revision: <b>%s</b><br />\n" % change['revision']
+
+ repository = ''
+ if change['repository']:
+ repository = "Repository: <b>%s</b><br />\n" % change['repository']
+
+ branch = ''
+ if change['branch']:
+ branch = "Branch: <b>%s</b><br />\n" % change['branch']
+
+ properties = []
+ for prop in change['properties']:
+ properties.append("%s: %s<br />" % (prop[0], prop[1]))
+
+ kwargs = { 'who' : escape(change['who']),
+ 'at' : change['at'],
+ 'files' : self.UL(links) + '\n',
+ 'repository': repository,
+ 'revision' : revision,
+ 'branch' : branch,
+ 'comments' : '<pre>' + escape(change['comments']) + '</pre>',
+ 'properties': self.UL(properties) + '\n' }
+ return self.change_tmpl % kwargs
+
+ def genMessageContent(self, build_status):
+ builder_name = build_status['builderName']
+ us_steps = ','.join(build_status['unsatisfied'])
+ revisions_list = build_status['revisions']
+ status_text = self.status_header % {
+ 'builder': builder_name,
+ 'steps': us_steps,
+ }
+ # Use the first line as a title.
+ status_title = status_text.split('\n', 1)[0]
+ blame_list = ','.join(build_status['blamelist'])
+ revisions_string = ''
+ latest_revision = 0
+ if revisions_list:
+ revisions_string = ', '.join([str(rev) for rev in revisions_list])
+ latest_revision = max([rev for rev in revisions_list])
+ if build_status['result'] == FAILURE:
+ result = 'failure'
+ else:
+ result = 'warning'
+
+ html_content = self.html_template % (status_title, self.waterfall_url,
+ self.waterfall_url,
+ status_text.replace('\n', "<br>\n"), self.build_url,
+ self.build_url, revisions_string)
+
+ # Only include the blame list if relevant.
+ html_content += " Blame list: %s<p>\n" % blame_list
+
+ build_boxes = [_GenBuildBox(build_status, self.waterfall_url,
+ self.DEFAULT_STYLES)]
+ build_boxes.extend([_GenStepBox(step,
+ self.DEFAULT_STYLES)
+ for step in build_status['steps']
+ if step['started'] and step['text']])
+ table_content = ''.join(build_boxes)
+ html_content += (
+ ('<table style="border-spacing: 1px 1px; font-weight: bold;'
+ ' padding: 3px 0px 3px 0px; text-align: center;">\n') +
+ table_content +
+ '</table>\n')
+
+ html_content += "<p>"
+ # Add the change list descriptions. getChanges() returns a tuple of
+ # buildbot.changes.changes.Change
+ for change in build_status['changes']:
+ html_content += self.changeToHTML(change)
+ html_content += "</body>\n</html>"
+
+ text_content = self.text_template % (status_title,
+ self.build_url,
+ urllib.quote(self.waterfall_url, '/:'),
+ urllib.quote(builder_name),
+ status_text,
+ revisions_string,
+ blame_list)
+
+ subject = self.subject % {
+ 'result': result,
+ 'projectName': self.project_name,
+ 'builder': builder_name,
+ 'reason': build_status['reason'],
+ 'revision': str(latest_revision),
+ 'buildnumber': str(build_status['number']),
+ }
+
+ return text_content, html_content, subject

Powered by Google App Engine
This is Rietveld 408576698