# # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # import os import urllib import re from email.Message import Message from email.Utils import formatdate from email.MIMEText import MIMEText from twisted.internet import defer from twisted.application import service from buildbot.status.builder import FAILURE, SUCCESS, WARNINGS from buildbot.status.mail import MailNotifier class SVNMailNotifier(MailNotifier): """Implement custom status mails for the Subversion project""" def __init__(self, fromaddr, mode="all", categories=None, builders=None, addLogs=False, relayhost="localhost", subject="buildbot %(result)s in %(builder)s", lookup=None, extraRecipients=[], sendToInterestedUsers=True, body="", replytoaddr=""): """ @type body: string @param body: a string to be used as the body of the message. @type replytoaddr: string @param replytoaddr: the email address to be used in the 'Reply-To' header. """ self.body = body self.replytoaddr = replytoaddr # pass the rest of the parameters to our parent. MailNotifier.__init__(self, fromaddr, mode, categories, builders, addLogs, relayhost, subject, lookup, extraRecipients, sendToInterestedUsers) def buildMessage(self, name, build, results): if self.mode == "all": intro = "The Buildbot has finished a build of %s.\n" % name elif self.mode == "failing": intro = "The Buildbot has detected a failed build of %s.\n" % name else: intro = "The Buildbot has detected a new failure of %s.\n" % name # buildurl buildurl = self.status.getURLForThing(build) # lgo: url's are already quoted now. # if buildurl: # buildurl = urllib.quote(buildurl, '/:') # buildboturl buildboturl = self.status.getBuildbotURL() # if url: # buildboturl = urllib.quote(url, '/:') # reason of build buildreason = build.getReason() # source stamp patch = None ss = build.getSourceStamp() if ss is None: source = "unavailable" else: if build.getChanges(): revision = max([int(c.revision) for c in build.getChanges()]) source = "" if ss.branch is None: ss.branch = "trunk" source += "[branch %s] " % ss.branch if revision: source += str(revision) else: source += "HEAD" if ss.patch is not None: source += " (plus patch)" # actual buildslave buildslave = build.getSlavename() # TODO: maybe display changes here? or in an attachment? # status t = build.getText() if t: t = ": " + " ".join(t) else: t = "" if results == SUCCESS: status = "Build succeeded!\n" res = "PASS" elif results == WARNINGS: status = "Build Had Warnings%s\n" % t res = "WARN" else: status = "BUILD FAILED%s\n" % t res = "FAIL" if build.getLogs(): log = build.getLogs()[-1] laststep = log.getStep().getName() lastlog = log.getText() # only give me the last lines of the log files. lines = re.split('\n', lastlog) lastlog = '' for logline in lines[max(0, len(lines)-100):]: lastlog = lastlog + logline # TODO: it would be nice to provide a URL for the specific build # here. That involves some coordination with html.Waterfall . # Ideally we could do: # helper = self.parent.getServiceNamed("html") # if helper: # url = helper.getURLForBuild(build) text = self.body % { 'result': res, 'builder': name, 'revision': revision, 'branch': ss.branch, 'blamelist': ",".join(build.getResponsibleUsers()), 'buildurl': buildurl, 'buildboturl': buildboturl, 'reason': buildreason, 'source': source, 'intro': intro, 'status': status, 'slave': buildslave, 'laststep': laststep, 'lastlog': lastlog, } haveAttachments = False if ss.patch or self.addLogs: haveAttachments = True if not canDoAttachments: log.msg("warning: I want to send mail with attachments, " "but this python is too old to have " "email.MIMEMultipart . Please upgrade to python-2.3 " "or newer to enable addLogs=True") if haveAttachments and canDoAttachments: m = MIMEMultipart() m.attach(MIMEText(text)) else: m = Message() m.set_payload(text) m['Date'] = formatdate(localtime=True) m['Subject'] = self.subject % { 'result': res, 'builder': name, 'revision': revision, 'branch': ss.branch } m['From'] = self.fromaddr # m['To'] is added later m['Reply-To'] = self.replytoaddr if ss.patch: a = MIMEText(patch) a.add_header('Content-Disposition', "attachment", filename="source patch") m.attach(a) if self.addLogs: for log in build.getLogs(): name = "%s.%s" % (log.getStep().getName(), log.getName()) a = MIMEText(log.getText()) a.add_header('Content-Disposition', "attachment", filename=name) m.attach(a) # now, who is this message going to? dl = [] recipients = self.extraRecipients[:] if self.sendToInterestedUsers and self.lookup: for u in build.getInterestedUsers(): d = defer.maybeDeferred(self.lookup.getAddress, u) d.addCallback(recipients.append) dl.append(d) d = defer.DeferredList(dl) d.addCallback(self._gotRecipients, recipients, m) return d