1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 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   
 31   
 32   
 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   
151   
153          text = "" 
154          res = "" 
155           
156          t = "tinderbox:" 
157   
158          if type(self.tree) is str: 
159               
160              text += "%s tree: %s\n" % (t, self.tree) 
161          elif isinstance(self.tree, WithProperties): 
162               
163              text += "%s tree: %s\n" % (t, build.render(self.tree)) 
164          else: 
165              raise Exception("tree is an unhandled value") 
166   
167           
168           
169          builddate = int(build.getTimes()[0]) 
170           
171           
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               
200              text += "%s build: %s\n" % (t, name) 
201          elif type(self.columnName) is str: 
202               
203              text += "%s build: %s\n" % (t, self.columnName) 
204          elif isinstance(self.columnName, WithProperties): 
205               
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           
212          if results == "building": 
213              text += "%s END\n" % t 
214           
215          else: 
216              text += "%s binaryurl: %s\n" % (t, self.binaryURL) 
217              text += "%s logcompression: %s\n" % (t, self.logCompression) 
218   
219               
220              logEncoding = "" 
221              tinderboxLogs = "" 
222              for bs in build.getSteps(): 
223                   
224                   
225                  shortText = "%s\n" % ' '.join(bs.getText()).encode('ascii', 'replace') 
226   
227                   
228                  if not re.match(".*[^\s].*", shortText): 
229                      continue 
230                   
231                   
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                       
243                       
244                       
245                       
246                       
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           
283   
284          d = defer.DeferredList([]) 
285          d.addCallback(self._gotRecipients, self.extraRecipients, m) 
286          return d 
  287