| OLD | NEW |
| (Empty) |
| 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 | |
| 11 from buildbot.steps.shell import WithProperties | |
| 12 | |
| 13 import zlib, bz2, base64 | |
| 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 | |
| 52 @type extraRecipients: tuple of string | |
| 53 @param extraRecipients: E-mail addresses of recipients. This should at | |
| 54 least include the tinderbox daemon. | |
| 55 | |
| 56 @type categories: list of strings | |
| 57 @param categories: a list of category names to serve status | |
| 58 information for. Defaults to None (all | |
| 59 categories). Use either builders or categories, | |
| 60 but not both. | |
| 61 | |
| 62 @type builders: list of strings | |
| 63 @param builders: a list of builder names for which mail should be | |
| 64 sent. Defaults to None (send mail for all builds). | |
| 65 Use either builders or categories, but not both. | |
| 66 | |
| 67 @type relayhost: string | |
| 68 @param relayhost: the host to which the outbound SMTP connection | |
| 69 should be made. Defaults to 'localhost' | |
| 70 | |
| 71 @type subject: string | |
| 72 @param subject: a string to be used as the subject line of the message. | |
| 73 %(builder)s will be replaced with the name of the | |
| 74 %builder which provoked the message. | |
| 75 This parameter is not significant for the tinderbox | |
| 76 daemon. | |
| 77 | |
| 78 @type binaryURL: string | |
| 79 @param binaryURL: If specified, this should be the location where final | |
| 80 binary for a build is located. | |
| 81 (ie. http://www.myproject.org/nightly/08-08-2006.tgz) | |
| 82 It will be posted to the Tinderbox. | |
| 83 | |
| 84 @type logCompression: string | |
| 85 @param logCompression: The type of compression to use on the log. | |
| 86 Valid options are"bzip2" and "gzip". gzip is | |
| 87 only known to work on Python 2.4 and above. | |
| 88 | |
| 89 @type errorparser: string | |
| 90 @param errorparser: The error parser that the Tinderbox server | |
| 91 should use when scanning the log file. | |
| 92 Default is "unix". | |
| 93 | |
| 94 @type columnName: string | |
| 95 @param columnName: When columnName is None, use the buildername as | |
| 96 the Tinderbox column name. When columnName is a | |
| 97 string this exact string will be used for all | |
| 98 builders that this TinderboxMailNotifier cares | |
| 99 about (not recommended). When columnName is a | |
| 100 WithProperties instance it will be interpolated | |
| 101 as such. See WithProperties for more detail. | |
| 102 @type useChangeTime: bool | |
| 103 @param useChangeTime: When True, the time of the first Change for a | |
| 104 build is used as the builddate. When False, | |
| 105 the current time is used as the builddate. | |
| 106 """ | |
| 107 | |
| 108 mail.MailNotifier.__init__(self, fromaddr, categories=categories, | |
| 109 builders=builders, relayhost=relayhost, | |
| 110 subject=subject, | |
| 111 extraRecipients=extraRecipients, | |
| 112 sendToInterestedUsers=False) | |
| 113 self.tree = tree | |
| 114 self.binaryURL = binaryURL | |
| 115 self.logCompression = logCompression | |
| 116 self.errorparser = errorparser | |
| 117 self.useChangeTime = useChangeTime | |
| 118 assert columnName is None or type(columnName) is str \ | |
| 119 or isinstance(columnName, WithProperties), \ | |
| 120 "columnName must be None, a string, or a WithProperties instance" | |
| 121 self.columnName = columnName | |
| 122 | |
| 123 def buildStarted(self, name, build): | |
| 124 builder = build.getBuilder() | |
| 125 if self.builders is not None and name not in self.builders: | |
| 126 return # ignore this Build | |
| 127 if self.categories is not None and \ | |
| 128 builder.category not in self.categories: | |
| 129 return # ignore this build | |
| 130 self.buildMessage(name, build, "building") | |
| 131 | |
| 132 def buildMessage(self, name, build, results): | |
| 133 text = "" | |
| 134 res = "" | |
| 135 # shortform | |
| 136 t = "tinderbox:" | |
| 137 | |
| 138 text += "%s tree: %s\n" % (t, self.tree) | |
| 139 # the start time | |
| 140 # getTimes() returns a fractioned time that tinderbox doesn't understand | |
| 141 builddate = int(build.getTimes()[0]) | |
| 142 # attempt to pull a Change time from this Build's Changes. | |
| 143 # if that doesn't work, fall back on the current time | |
| 144 if self.useChangeTime: | |
| 145 try: | |
| 146 builddate = build.getChanges()[-1].when | |
| 147 except: | |
| 148 pass | |
| 149 text += "%s builddate: %s\n" % (t, builddate) | |
| 150 text += "%s status: " % t | |
| 151 | |
| 152 if results == "building": | |
| 153 res = "building" | |
| 154 text += res | |
| 155 elif results == SUCCESS: | |
| 156 res = "success" | |
| 157 text += res | |
| 158 elif results == WARNINGS: | |
| 159 res = "testfailed" | |
| 160 text += res | |
| 161 else: | |
| 162 res += "busted" | |
| 163 text += res | |
| 164 | |
| 165 text += "\n"; | |
| 166 | |
| 167 if self.columnName is None: | |
| 168 # use the builder name | |
| 169 text += "%s build: %s\n" % (t, name) | |
| 170 elif type(self.columnName) is str: | |
| 171 # use the exact string given | |
| 172 text += "%s build: %s\n" % (t, self.columnName) | |
| 173 elif isinstance(self.columnName, WithProperties): | |
| 174 # interpolate the WithProperties instance, use that | |
| 175 text += "%s build: %s\n" % (t, build.getProperties().render(self.col
umnName)) | |
| 176 else: | |
| 177 raise Exception("columnName is an unhandled value") | |
| 178 text += "%s errorparser: %s\n" % (t, self.errorparser) | |
| 179 | |
| 180 # if the build just started... | |
| 181 if results == "building": | |
| 182 text += "%s END\n" % t | |
| 183 # if the build finished... | |
| 184 else: | |
| 185 text += "%s binaryurl: %s\n" % (t, self.binaryURL) | |
| 186 text += "%s logcompression: %s\n" % (t, self.logCompression) | |
| 187 | |
| 188 # logs will always be appended | |
| 189 logEncoding = "" | |
| 190 tinderboxLogs = "" | |
| 191 for log in build.getLogs(): | |
| 192 l = "" | |
| 193 if self.logCompression == "bzip2": | |
| 194 compressedLog = bz2.compress(log.getText()) | |
| 195 l = base64.encodestring(compressedLog) | |
| 196 logEncoding = "base64"; | |
| 197 elif self.logCompression == "gzip": | |
| 198 compressedLog = zlib.compress(log.getText()) | |
| 199 l = base64.encodestring(compressedLog) | |
| 200 logEncoding = "base64"; | |
| 201 else: | |
| 202 l = log.getText() | |
| 203 tinderboxLogs += l | |
| 204 | |
| 205 text += "%s logencoding: %s\n" % (t, logEncoding) | |
| 206 text += "%s END\n\n" % t | |
| 207 text += tinderboxLogs | |
| 208 text += "\n" | |
| 209 | |
| 210 m = Message() | |
| 211 m.set_payload(text) | |
| 212 | |
| 213 m['Date'] = formatdate(localtime=True) | |
| 214 m['Subject'] = self.subject % { 'result': res, | |
| 215 'builder': name, | |
| 216 } | |
| 217 m['From'] = self.fromaddr | |
| 218 # m['To'] is added later | |
| 219 | |
| 220 d = defer.DeferredList([]) | |
| 221 d.addCallback(self._gotRecipients, self.extraRecipients, m) | |
| 222 return d | |
| 223 | |
| OLD | NEW |