Package buildbot :: Package status :: Module tinderbox
[frames] | no frames]

Source Code for Module buildbot.status.tinderbox

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16   
 17  from email.Message import Message 
 18  from email.Utils import formatdate 
 19   
 20  from zope.interface import implements 
 21  from twisted.internet import defer 
 22   
 23  from buildbot import interfaces 
 24  from buildbot.status import mail 
 25  from buildbot.status.results import SUCCESS, WARNINGS, EXCEPTION, RETRY 
 26  from buildbot.steps.shell import WithProperties 
 27   
 28  import gzip, bz2, base64, re, cStringIO 
29 30 # TODO: docs, maybe a test of some sort just to make sure it actually imports 31 # and can format email without raising an exception. 32 33 -class TinderboxMailNotifier(mail.MailNotifier):
34 """This is a Tinderbox status notifier. It can send e-mail to a number of 35 different tinderboxes or people. E-mails are sent at the beginning and 36 upon completion of each build. It can be configured to send out e-mails 37 for only certain builds. 38 39 The most basic usage is as follows:: 40 TinderboxMailNotifier(fromaddr="buildbot@localhost", 41 tree="MyTinderboxTree", 42 extraRecipients=["tinderboxdaemon@host.org"]) 43 44 The builder name (as specified in master.cfg) is used as the "build" 45 tinderbox option. 46 47 """ 48 implements(interfaces.IEmailSender) 49 50 compare_attrs = ["extraRecipients", "fromaddr", "categories", "builders", 51 "addLogs", "relayhost", "subject", "binaryURL", "tree", 52 "logCompression", "errorparser", "columnName", 53 "useChangeTime"] 54
55 - def __init__(self, fromaddr, tree, extraRecipients, 56 categories=None, builders=None, relayhost="localhost", 57 subject="buildbot %(result)s in %(builder)s", binaryURL="", 58 logCompression="", errorparser="unix", columnName=None, 59 useChangeTime=False):
60 """ 61 @type fromaddr: string 62 @param fromaddr: the email address to be used in the 'From' header. 63 64 @type tree: string 65 @param tree: The Tinderbox tree to post to. 66 When tree is a WithProperties instance it will be 67 interpolated as such. See WithProperties for more detail 68 69 @type extraRecipients: tuple of string 70 @param extraRecipients: E-mail addresses of recipients. This should at 71 least include the tinderbox daemon. 72 73 @type categories: list of strings 74 @param categories: a list of category names to serve status 75 information for. Defaults to None (all 76 categories). Use either builders or categories, 77 but not both. 78 79 @type builders: list of strings 80 @param builders: a list of builder names for which mail should be 81 sent. Defaults to None (send mail for all builds). 82 Use either builders or categories, but not both. 83 84 @type relayhost: string 85 @param relayhost: the host to which the outbound SMTP connection 86 should be made. Defaults to 'localhost' 87 88 @type subject: string 89 @param subject: a string to be used as the subject line of the message. 90 %(builder)s will be replaced with the name of the 91 %builder which provoked the message. 92 This parameter is not significant for the tinderbox 93 daemon. 94 95 @type binaryURL: string 96 @param binaryURL: If specified, this should be the location where final 97 binary for a build is located. 98 (ie. http://www.myproject.org/nightly/08-08-2006.tgz) 99 It will be posted to the Tinderbox. 100 101 @type logCompression: string 102 @param logCompression: The type of compression to use on the log. 103 Valid options are"bzip2" and "gzip". gzip is 104 only known to work on Python 2.4 and above. 105 106 @type errorparser: string 107 @param errorparser: The error parser that the Tinderbox server 108 should use when scanning the log file. 109 Default is "unix". 110 111 @type columnName: string 112 @param columnName: When columnName is None, use the buildername as 113 the Tinderbox column name. When columnName is a 114 string this exact string will be used for all 115 builders that this TinderboxMailNotifier cares 116 about (not recommended). When columnName is a 117 WithProperties instance it will be interpolated 118 as such. See WithProperties for more detail. 119 @type useChangeTime: bool 120 @param useChangeTime: When True, the time of the first Change for a 121 build is used as the builddate. When False, 122 the current time is used as the builddate. 123 """ 124 125 mail.MailNotifier.__init__(self, fromaddr, categories=categories, 126 builders=builders, relayhost=relayhost, 127 subject=subject, 128 extraRecipients=extraRecipients, 129 sendToInterestedUsers=False) 130 assert isinstance(tree, basestring) \ 131 or isinstance(tree, WithProperties), \ 132 "tree must be a string or a WithProperties instance" 133 self.tree = tree 134 self.binaryURL = binaryURL 135 self.logCompression = logCompression 136 self.errorparser = errorparser 137 self.useChangeTime = useChangeTime 138 assert columnName is None or type(columnName) is str \ 139 or isinstance(columnName, WithProperties), \ 140 "columnName must be None, a string, or a WithProperties instance" 141 self.columnName = columnName
142
143 - def buildStarted(self, name, build):
144 builder = build.getBuilder() 145 if self.builders is not None and name not in self.builders: 146 return # ignore this Build 147 if self.categories is not None and \ 148 builder.category not in self.categories: 149 return # ignore this build 150 self.buildMessage(name, build, "building")
151 152 @defer.inlineCallbacks
153 - def buildMessage(self, name, build, results):
154 text = "" 155 res = "" 156 # shortform 157 t = "tinderbox:" 158 159 tree = yield build.render(self.tree) 160 text += "%s tree: %s\n" % (t, tree) 161 162 # the start time 163 # getTimes() returns a fractioned time that tinderbox doesn't understand 164 builddate = int(build.getTimes()[0]) 165 # attempt to pull a Change time from this Build's Changes. 166 # if that doesn't work, fall back on the current time 167 if self.useChangeTime: 168 try: 169 builddate = build.getChanges()[-1].when 170 except: 171 pass 172 text += "%s builddate: %s\n" % (t, builddate) 173 text += "%s status: " % t 174 175 if results == "building": 176 res = "building" 177 text += res 178 elif results == SUCCESS: 179 res = "success" 180 text += res 181 elif results == WARNINGS: 182 res = "testfailed" 183 text += res 184 elif results in (EXCEPTION, RETRY): 185 res = "exception" 186 text += res 187 else: 188 res += "busted" 189 text += res 190 191 text += "\n"; 192 193 if self.columnName is None: 194 # use the builder name 195 text += "%s build: %s\n" % (t, name) 196 else: 197 columnName = yield build.render(self.columnName) 198 text += "%s build: %s\n" % (t, columnName) 199 text += "%s errorparser: %s\n" % (t, self.errorparser) 200 201 # if the build just started... 202 if results == "building": 203 text += "%s END\n" % t 204 # if the build finished... 205 else: 206 text += "%s binaryurl: %s\n" % (t, self.binaryURL) 207 text += "%s logcompression: %s\n" % (t, self.logCompression) 208 209 # logs will always be appended 210 logEncoding = "" 211 tinderboxLogs = "" 212 for bs in build.getSteps(): 213 # Make sure that shortText is a regular string, so that bad 214 # data in the logs don't generate UnicodeDecodeErrors 215 shortText = "%s\n" % ' '.join(bs.getText()).encode('ascii', 'replace') 216 217 # ignore steps that haven't happened 218 if not re.match(".*[^\s].*", shortText): 219 continue 220 # we ignore TinderboxPrint's here so we can do things like: 221 # ShellCommand(command=['echo', 'TinderboxPrint:', ...]) 222 if re.match(".+TinderboxPrint.*", shortText): 223 shortText = shortText.replace("TinderboxPrint", 224 "Tinderbox Print") 225 logs = bs.getLogs() 226 227 tinderboxLogs += "======== BuildStep started ========\n" 228 tinderboxLogs += shortText 229 tinderboxLogs += "=== Output ===\n" 230 for log in logs: 231 logText = log.getTextWithHeaders() 232 # Because we pull in the log headers here we have to ignore 233 # some of them. Basically, if we're TinderboxPrint'ing in 234 # a ShellCommand, the only valid one(s) are at the start 235 # of a line. The others are prendeded by whitespace, quotes, 236 # or brackets/parentheses 237 for line in logText.splitlines(): 238 if re.match(".+TinderboxPrint.*", line): 239 line = line.replace("TinderboxPrint", 240 "Tinderbox Print") 241 tinderboxLogs += line + "\n" 242 243 tinderboxLogs += "=== Output ended ===\n" 244 tinderboxLogs += "======== BuildStep ended ========\n" 245 246 if self.logCompression == "bzip2": 247 cLog = bz2.compress(tinderboxLogs) 248 tinderboxLogs = base64.encodestring(cLog) 249 logEncoding = "base64" 250 elif self.logCompression == "gzip": 251 cLog = cStringIO.StringIO() 252 gz = gzip.GzipFile(mode="w", fileobj=cLog) 253 gz.write(tinderboxLogs) 254 gz.close() 255 cLog = cLog.getvalue() 256 tinderboxLogs = base64.encodestring(cLog) 257 logEncoding = "base64" 258 259 text += "%s logencoding: %s\n" % (t, logEncoding) 260 text += "%s END\n\n" % t 261 text += tinderboxLogs 262 text += "\n" 263 264 m = Message() 265 m.set_payload(text) 266 267 m['Date'] = formatdate(localtime=True) 268 m['Subject'] = self.subject % { 'result': res, 269 'builder': name, 270 } 271 m['From'] = self.fromaddr 272 # m['To'] is added later 273 274 d = defer.DeferredList([]) 275 d.addCallback(self._gotRecipients, self.extraRecipients, m) 276 defer.returnValue((yield d))
277