The buildbot can also send email when builds finish. The most common use of this is to tell developers when their change has caused the build to fail. It is also quite common to send a message to a mailing list (usually named “builds” or similar) about every build.
The MailNotifier status target is used to accomplish this. You
configure it by specifying who mail should be sent to, under what
circumstances mail should be sent, and how to deliver the mail. It can
be configured to only send out mail for certain builders, and only
send messages when the build fails, or when the builder transitions
from success to failure. It can also be configured to include various
build logs in each message.
By default, the message will be sent to the Interested Users list (see Doing Things With Users), which includes all developers who made changes in the
build. You can add additional recipients with the extraRecipients argument.
You can also add interested users by setting the owners build property
to a list of users in the scheduler constructor (see Configuring Schedulers).
Each MailNotifier sends mail to a single set of recipients. To send different kinds of mail to different recipients, use multiple MailNotifiers.
The following simple example will send an email upon the completion of each build, to just those developers whose Changes were included in the build. The email contains a description of the Build, its results, and URLs where more information can be obtained.
from buildbot.status.mail import MailNotifier
mn = MailNotifier(fromaddr="buildbot@example.org", lookup="example.org")
c['status'].append(mn)
To get a simple one-message-per-build (say, for a mailing list), use
the following form instead. This form does not send mail to individual
developers (and thus does not need the lookup= argument,
explained below), instead it only ever sends mail to the “extra
recipients” named in the arguments:
mn = MailNotifier(fromaddr="buildbot@example.org",
sendToInterestedUsers=False,
extraRecipients=['listaddr@example.org'])
If your SMTP host requires authentication before it allows you to send emails, this can also be done by specifying “smtpUser” and “smptPassword”:
mn = MailNotifier(fromaddr="myuser@gmail.com",
sendToInterestedUsers=False,
extraRecipients=["listaddr@example.org"],
relayhost="smtp.gmail.com", smtpPort=587,
smtpUser="myuser@gmail.com", smtpPassword="mypassword")
If you want to require Transport Layer Security (TLS), then you can also set “useTls”:
mn = MailNotifier(fromaddr="myuser@gmail.com",
sendToInterestedUsers=False,
extraRecipients=["listaddr@example.org"],
useTls=True, relayhost="smtp.gmail.com", smtpPort=587,
smtpUser="myuser@gmail.com", smtpPassword="mypassword")
In some cases it is desirable to have different information then what is
provided in a standard MailNotifier message. For this purpose MailNotifier
provides the argument messageFormatter (a function) which allows for the
creation of messages with unique content.
For example, if only short emails are desired (e.g., for delivery to phones)
from buildbot.status.builder import Results
def messageFormatter(mode, name, build, results, master_status):
result = Results[results]
text = list()
text.append("STATUS: %s" % result.title())
return {
'body' : "\n".join(text),
'type' : 'plain'
}
mn = MailNotifier(fromaddr="buildbot@example.org",
sendToInterestedUsers=False,
mode='problem',
extraRecipients=['listaddr@example.org'],
messageFormatter=messageFormatter)
Another example of a function delivering a customized html email containing the last 80 log lines of the last build step is given below:
from buildbot.status.builder import Results
def html_message_formatter(mode, name, build, results, master_status):
"""Provide a customized message to BuildBot's MailNotifier.
The last 80 lines of the log are provided as well as the changes
relevant to the build. Message content is formatted as html.
"""
result = Results[results]
limit_lines = 80
text = list()
text.append(u'<h4>Build status: %s</h4>' % result.upper())
text.append(u'<table cellspacing="10"><tr>')
text.append(u"<td>Buildslave for this Build:</td><td><b>%s</b></td></tr>" % build.getSlavename())
if master_status.getURLForThing(build):
text.append(u'<tr><td>Complete logs for all build steps:</td><td><a href="%s">%s</a></td></tr>'
% (master_status.getURLForThing(build),
master_status.getURLForThing(build))
)
text.append(u'<tr><td>Build Reason:</td><td>%s</td></tr>' % build.getReason())
source = u""
ss = build.getSourceStamp()
if ss.branch:
source += u"[branch %s] " % ss.branch
if ss.revision:
source += ss.revision
else:
source += u"HEAD"
if ss.patch:
source += u" (plus patch)"
text.append(u"<tr><td>Build Source Stamp:</td><td><b>%s</b></td></tr>" % source)
text.append(u"<tr><td>Blamelist:</td><td>%s</td></tr>" % ",".join(build.getResponsibleUsers()))
text.append(u'</table>')
if ss.changes:
text.append(u'<h4>Recent Changes:</h4>')
for c in ss.changes:
cd = c.asDict()
when = datetime.datetime.fromtimestamp(cd['when'] ).ctime()
text.append(u'<table cellspacing="10">')
text.append(u'<tr><td>Repository:</td><td>%s</td></tr>' % cd['repository'] )
text.append(u'<tr><td>Project:</td><td>%s</td></tr>' % cd['project'] )
text.append(u'<tr><td>Time:</td><td>%s</td></tr>' % when)
text.append(u'<tr><td>Changed by:</td><td>%s</td></tr>' % cd['who'] )
text.append(u'<tr><td>Comments:</td><td>%s</td></tr>' % cd['comments'] )
text.append(u'</table>')
files = cd['files']
if files:
text.append(u'<table cellspacing="10"><tr><th align="left">Files</th><th>URL</th></tr>')
for file in files:
text.append(u'<tr><td>%s:</td><td>%s</td></tr>' % (file['name'], file['url']))
text.append(u'</table>')
text.append(u'<br>')
# get log for last step
logs = build.getLogs()
# logs within a step are in reverse order. Search back until we find stdio
for log in reversed(logs):
if log.getName() == 'stdio':
break
name = "%s.%s" % (log.getStep().getName(), log.getName())
status, dummy = log.getStep().getResults()
content = log.getText().splitlines() # Note: can be VERY LARGE
url = u'%s/steps/%s/logs/%s' % (master_status.getURLForThing(build),
log.getStep().getName(),
log.getName())
text.append(u'<i>Detailed log of last build step:</i> <a href="%s">%s</a>'
% (url, url))
text.append(u'<br>')
text.append(u'<h4>Last %d lines of "%s"</h4>' % (limit_lines, name))
unilist = list()
for line in content[len(content)-limit_lines:]:
unilist.append(cgi.escape(unicode(line,'utf-8')))
text.append(u'<pre>'.join([uniline for uniline in unilist]))
text.append(u'</pre>')
text.append(u'<br><br>')
text.append(u'<b>-The BuildBot</b>')
return {
'body': u"\n".join(text),
'type': 'html'
}
mn = MailNotifier(fromaddr="buildbot@example.org",
sendToInterestedUsers=False,
mode='failing',
extraRecipients=['listaddr@example.org'],
messageFormatter=message_formatter)
fromaddrsendToInterestedUsersextraRecipientssubject%(builder)s will be replaced with the name of the builder which
provoked the message.
modeallchangefailingpassingproblembuilderscategoriesaddLogsaddPatchrelayhostsmtpPortuseTlsTrue (default is False)
MailNotifier sends emails using TLS and authenticates with the
relayhost. When using TLS the arguments smtpUser and
smtpPassword must also be specified.
smtpUserrelayhost.
smtpPasswordrelayhost.
lookupIEmailLookup). Object which provides
IEmailLookup, which is responsible for mapping User names (which come
from the VC system) into valid email addresses. If not provided, the
notifier will only be able to send mail to the addresses in the
extraRecipients list. Most of the time you can use a simple Domain
instance. As a shortcut, you can pass as string: this will be treated
as if you had provided Domain(str). For example,
lookup='twistedmatrix.com' will allow mail to be sent to all
developers whose SVN usernames match their twistedmatrix.com account
names. See buildbot/status/mail.py for more details.
messageFormattermessageFormatter function takes the mail mode (mode), builder
name (name), the build status (build), the result code
(results), and the BuildMaster status (master_status). It
returns a dictionary. The body key gives a string that is the complete
text of the message. The type key is the message type ('plain' or
'html'). The 'html' type should be used when generating an HTML message. The
subject key is optional, but gives the subject for the email.
extraHeadersAs a help to those writing messageFormatter functions, the following
table describes how to get some useful pieces of information from the various
status objects:
name
master_status.getProjectName()
mode (one of all, failing, problem, change, passing)
from buildbot.status.builder import Results
result_str = Results[results]
# one of 'success', 'warnings', 'failure', 'skipped', or 'exception'
master_status.getURLForThing(build)
master_status.getBuildbotURL()
build.getText()
build.getProperties() (a Properties instance)
build.getSlavename()
build.getReason()
build.getResponsibleUsers()
ss = build.getSourceStamp()
if ss:
branch = ss.branch
revision = ss.revision
patch = ss.patch
changes = ss.changes # list
A change object has the following useful information:
whorevisionbranchwhenfilescommentsChange methods asText and asDict can be used to format the
information above. asText returns a list of strings and asDict returns
a dictonary suitable for html/mail rendering.
logs = list()
for log in build.getLogs():
log_name = "%s.%s" % (log.getStep().getName(), log.getName())
log_status, dummy = log.getStep().getResults()
log_body = log.getText().splitlines() # Note: can be VERY LARGE
log_url = '%s/steps/%s/logs/%s' % (master_status.getURLForThing(build),
log.getStep().getName(),
log.getName())
logs.append((log_name, log_url, log_body, log_status))