diff options
Diffstat (limited to 'buildbot/buildbot/status/tinderbox.py')
-rw-r--r-- | buildbot/buildbot/status/tinderbox.py | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/buildbot/buildbot/status/tinderbox.py b/buildbot/buildbot/status/tinderbox.py new file mode 100644 index 0000000..51d404b --- /dev/null +++ b/buildbot/buildbot/status/tinderbox.py @@ -0,0 +1,223 @@ + +from email.Message import Message +from email.Utils import formatdate + +from zope.interface import implements +from twisted.internet import defer + +from buildbot import interfaces +from buildbot.status import mail +from buildbot.status.builder import SUCCESS, WARNINGS +from buildbot.steps.shell import WithProperties + +import zlib, bz2, base64 + +# TODO: docs, maybe a test of some sort just to make sure it actually imports +# and can format email without raising an exception. + +class TinderboxMailNotifier(mail.MailNotifier): + """This is a Tinderbox status notifier. It can send e-mail to a number of + different tinderboxes or people. E-mails are sent at the beginning and + upon completion of each build. It can be configured to send out e-mails + for only certain builds. + + The most basic usage is as follows:: + TinderboxMailNotifier(fromaddr="buildbot@localhost", + tree="MyTinderboxTree", + extraRecipients=["tinderboxdaemon@host.org"]) + + The builder name (as specified in master.cfg) is used as the "build" + tinderbox option. + + """ + implements(interfaces.IEmailSender) + + compare_attrs = ["extraRecipients", "fromaddr", "categories", "builders", + "addLogs", "relayhost", "subject", "binaryURL", "tree", + "logCompression", "errorparser", "columnName", + "useChangeTime"] + + def __init__(self, fromaddr, tree, extraRecipients, + categories=None, builders=None, relayhost="localhost", + subject="buildbot %(result)s in %(builder)s", binaryURL="", + logCompression="", errorparser="unix", columnName=None, + useChangeTime=False): + """ + @type fromaddr: string + @param fromaddr: the email address to be used in the 'From' header. + + @type tree: string + @param tree: The Tinderbox tree to post to. + + @type extraRecipients: tuple of string + @param extraRecipients: E-mail addresses of recipients. This should at + least include the tinderbox daemon. + + @type categories: list of strings + @param categories: a list of category names to serve status + information for. Defaults to None (all + categories). Use either builders or categories, + but not both. + + @type builders: list of strings + @param builders: a list of builder names for which mail should be + sent. Defaults to None (send mail for all builds). + Use either builders or categories, but not both. + + @type relayhost: string + @param relayhost: the host to which the outbound SMTP connection + should be made. Defaults to 'localhost' + + @type subject: string + @param subject: a string to be used as the subject line of the message. + %(builder)s will be replaced with the name of the + %builder which provoked the message. + This parameter is not significant for the tinderbox + daemon. + + @type binaryURL: string + @param binaryURL: If specified, this should be the location where final + binary for a build is located. + (ie. http://www.myproject.org/nightly/08-08-2006.tgz) + It will be posted to the Tinderbox. + + @type logCompression: string + @param logCompression: The type of compression to use on the log. + Valid options are"bzip2" and "gzip". gzip is + only known to work on Python 2.4 and above. + + @type errorparser: string + @param errorparser: The error parser that the Tinderbox server + should use when scanning the log file. + Default is "unix". + + @type columnName: string + @param columnName: When columnName is None, use the buildername as + the Tinderbox column name. When columnName is a + string this exact string will be used for all + builders that this TinderboxMailNotifier cares + about (not recommended). When columnName is a + WithProperties instance it will be interpolated + as such. See WithProperties for more detail. + @type useChangeTime: bool + @param useChangeTime: When True, the time of the first Change for a + build is used as the builddate. When False, + the current time is used as the builddate. + """ + + mail.MailNotifier.__init__(self, fromaddr, categories=categories, + builders=builders, relayhost=relayhost, + subject=subject, + extraRecipients=extraRecipients, + sendToInterestedUsers=False) + self.tree = tree + self.binaryURL = binaryURL + self.logCompression = logCompression + self.errorparser = errorparser + self.useChangeTime = useChangeTime + assert columnName is None or type(columnName) is str \ + or isinstance(columnName, WithProperties), \ + "columnName must be None, a string, or a WithProperties instance" + self.columnName = columnName + + def buildStarted(self, name, build): + builder = build.getBuilder() + if self.builders is not None and name not in self.builders: + return # ignore this Build + if self.categories is not None and \ + builder.category not in self.categories: + return # ignore this build + self.buildMessage(name, build, "building") + + def buildMessage(self, name, build, results): + text = "" + res = "" + # shortform + t = "tinderbox:" + + text += "%s tree: %s\n" % (t, self.tree) + # the start time + # getTimes() returns a fractioned time that tinderbox doesn't understand + builddate = int(build.getTimes()[0]) + # attempt to pull a Change time from this Build's Changes. + # if that doesn't work, fall back on the current time + if self.useChangeTime: + try: + builddate = build.getChanges()[-1].when + except: + pass + text += "%s builddate: %s\n" % (t, builddate) + text += "%s status: " % t + + if results == "building": + res = "building" + text += res + elif results == SUCCESS: + res = "success" + text += res + elif results == WARNINGS: + res = "testfailed" + text += res + else: + res += "busted" + text += res + + text += "\n"; + + if self.columnName is None: + # use the builder name + text += "%s build: %s\n" % (t, name) + elif type(self.columnName) is str: + # use the exact string given + text += "%s build: %s\n" % (t, self.columnName) + elif isinstance(self.columnName, WithProperties): + # interpolate the WithProperties instance, use that + text += "%s build: %s\n" % (t, build.getProperties().render(self.columnName)) + else: + raise Exception("columnName is an unhandled value") + text += "%s errorparser: %s\n" % (t, self.errorparser) + + # if the build just started... + if results == "building": + text += "%s END\n" % t + # if the build finished... + else: + text += "%s binaryurl: %s\n" % (t, self.binaryURL) + text += "%s logcompression: %s\n" % (t, self.logCompression) + + # logs will always be appended + logEncoding = "" + tinderboxLogs = "" + for log in build.getLogs(): + l = "" + if self.logCompression == "bzip2": + compressedLog = bz2.compress(log.getText()) + l = base64.encodestring(compressedLog) + logEncoding = "base64"; + elif self.logCompression == "gzip": + compressedLog = zlib.compress(log.getText()) + l = base64.encodestring(compressedLog) + logEncoding = "base64"; + else: + l = log.getText() + tinderboxLogs += l + + text += "%s logencoding: %s\n" % (t, logEncoding) + text += "%s END\n\n" % t + text += tinderboxLogs + text += "\n" + + m = Message() + m.set_payload(text) + + m['Date'] = formatdate(localtime=True) + m['Subject'] = self.subject % { 'result': res, + 'builder': name, + } + m['From'] = self.fromaddr + # m['To'] is added later + + d = defer.DeferredList([]) + d.addCallback(self._gotRecipients, self.extraRecipients, m) + return d + |