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 - def buildMessage(self, name, build, results):
153 text = "" 154 res = "" 155 # shortform 156 t = "tinderbox:" 157 158 if type(self.tree) is str: 159 # use the exact string given 160 text += "%s tree: %s\n" % (t, self.tree) 161 elif isinstance(self.tree, WithProperties): 162 # interpolate the WithProperties instance, use that 163 text += "%s tree: %s\n" % (t, build.render(self.tree)) 164 else: 165 raise Exception("tree is an unhandled value") 166 167 # the start time 168 # getTimes() returns a fractioned time that tinderbox doesn't understand 169 builddate = int(build.getTimes()[0]) 170 # attempt to pull a Change time from this Build's Changes. 171 # if that doesn't work, fall back on the current time 172 if self.useChangeTime: 173 try: 174 builddate = build.getChanges()[-1].when 175 except: 176 pass 177 text += "%s builddate: %s\n" % (t, builddate) 178 text += "%s status: " % t 179 180 if results == "building": 181 res = "building" 182 text += res 183 elif results == SUCCESS: 184 res = "success" 185 text += res 186 elif results == WARNINGS: 187 res = "testfailed" 188 text += res 189 elif results in (EXCEPTION, RETRY): 190 res = "exception" 191 text += res 192 else: 193 res += "busted" 194 text += res 195 196 text += "\n"; 197 198 if self.columnName is None: 199 # use the builder name 200 text += "%s build: %s\n" % (t, name) 201 elif type(self.columnName) is str: 202 # use the exact string given 203 text += "%s build: %s\n" % (t, self.columnName) 204 elif isinstance(self.columnName, WithProperties): 205 # interpolate the WithProperties instance, use that 206 text += "%s build: %s\n" % (t, build.render(self.columnName)) 207 else: 208 raise Exception("columnName is an unhandled value") 209 text += "%s errorparser: %s\n" % (t, self.errorparser) 210 211 # if the build just started... 212 if results == "building": 213 text += "%s END\n" % t 214 # if the build finished... 215 else: 216 text += "%s binaryurl: %s\n" % (t, self.binaryURL) 217 text += "%s logcompression: %s\n" % (t, self.logCompression) 218 219 # logs will always be appended 220 logEncoding = "" 221 tinderboxLogs = "" 222 for bs in build.getSteps(): 223 # Make sure that shortText is a regular string, so that bad 224 # data in the logs don't generate UnicodeDecodeErrors 225 shortText = "%s\n" % ' '.join(bs.getText()).encode('ascii', 'replace') 226 227 # ignore steps that haven't happened 228 if not re.match(".*[^\s].*", shortText): 229 continue 230 # we ignore TinderboxPrint's here so we can do things like: 231 # ShellCommand(command=['echo', 'TinderboxPrint:', ...]) 232 if re.match(".+TinderboxPrint.*", shortText): 233 shortText = shortText.replace("TinderboxPrint", 234 "Tinderbox Print") 235 logs = bs.getLogs() 236 237 tinderboxLogs += "======== BuildStep started ========\n" 238 tinderboxLogs += shortText 239 tinderboxLogs += "=== Output ===\n" 240 for log in logs: 241 logText = log.getTextWithHeaders() 242 # Because we pull in the log headers here we have to ignore 243 # some of them. Basically, if we're TinderboxPrint'ing in 244 # a ShellCommand, the only valid one(s) are at the start 245 # of a line. The others are prendeded by whitespace, quotes, 246 # or brackets/parentheses 247 for line in logText.splitlines(): 248 if re.match(".+TinderboxPrint.*", line): 249 line = line.replace("TinderboxPrint", 250 "Tinderbox Print") 251 tinderboxLogs += line + "\n" 252 253 tinderboxLogs += "=== Output ended ===\n" 254 tinderboxLogs += "======== BuildStep ended ========\n" 255 256 if self.logCompression == "bzip2": 257 cLog = bz2.compress(tinderboxLogs) 258 tinderboxLogs = base64.encodestring(cLog) 259 logEncoding = "base64" 260 elif self.logCompression == "gzip": 261 cLog = cStringIO.StringIO() 262 gz = gzip.GzipFile(mode="w", fileobj=cLog) 263 gz.write(tinderboxLogs) 264 gz.close() 265 cLog = cLog.getvalue() 266 tinderboxLogs = base64.encodestring(cLog) 267 logEncoding = "base64" 268 269 text += "%s logencoding: %s\n" % (t, logEncoding) 270 text += "%s END\n\n" % t 271 text += tinderboxLogs 272 text += "\n" 273 274 m = Message() 275 m.set_payload(text) 276 277 m['Date'] = formatdate(localtime=True) 278 m['Subject'] = self.subject % { 'result': res, 279 'builder': name, 280 } 281 m['From'] = self.fromaddr 282 # m['To'] is added later 283 284 d = defer.DeferredList([]) 285 d.addCallback(self._gotRecipients, self.extraRecipients, m) 286 return d
287