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

Source Code for Module buildbot.status.tinderbox

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