1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17  import os, weakref 
 18   
 19  from zope.interface import implements 
 20  from twisted.python import log 
 21  from twisted.application import strports, service 
 22  from twisted.web import server, distrib, static 
 23  from twisted.spread import pb 
 24  from twisted.web.util import Redirect 
 25   
 26  from buildbot.interfaces import IStatusReceiver 
 27   
 28  from buildbot.status.web.base import StaticFile, createJinjaEnv 
 29  from buildbot.status.web.feeds import Rss20StatusResource, \ 
 30       Atom10StatusResource 
 31  from buildbot.status.web.waterfall import WaterfallStatusResource 
 32  from buildbot.status.web.console import ConsoleStatusResource 
 33  from buildbot.status.web.olpb import OneLinePerBuild 
 34  from buildbot.status.web.grid import GridStatusResource, TransposedGridStatusResource 
 35  from buildbot.status.web.changes import ChangesResource 
 36  from buildbot.status.web.builder import BuildersResource 
 37  from buildbot.status.web.buildstatus import BuildStatusStatusResource 
 38  from buildbot.status.web.slaves import BuildSlavesResource 
 39  from buildbot.status.web.status_json import JsonStatusResource 
 40  from buildbot.status.web.about import AboutBuildbot 
 41  from buildbot.status.web.authz import Authz 
 42  from buildbot.status.web.auth import AuthFailResource 
 43  from buildbot.status.web.root import RootPage 
 44  from buildbot.status.web.change_hook import ChangeHookResource 
 45   
 46   
 47   
 48   
 50      implements(IStatusReceiver) 
 51       
 52       
 53       
 54       
 55       
 56       
 57   
 58      """ 
 59      The webserver provided by this class has the following resources: 
 60   
 61       /waterfall : the big time-oriented 'waterfall' display, with links 
 62                    to individual changes, builders, builds, steps, and logs. 
 63                    A number of query-arguments can be added to influence 
 64                    the display. 
 65       /rss : a rss feed summarizing all failed builds. The same 
 66              query-arguments used by 'waterfall' can be added to 
 67              influence the feed output. 
 68       /atom : an atom feed summarizing all failed builds. The same 
 69               query-arguments used by 'waterfall' can be added to 
 70               influence the feed output. 
 71       /grid : another summary display that shows a grid of builds, with 
 72               sourcestamps on the x axis, and builders on the y.  Query 
 73               arguments similar to those for the waterfall can be added. 
 74       /tgrid : similar to the grid display, but the commits are down the 
 75                left side, and the build hosts are across the top. 
 76       /builders/BUILDERNAME: a page summarizing the builder. This includes 
 77                              references to the Schedulers that feed it, 
 78                              any builds currently in the queue, which 
 79                              buildslaves are designated or attached, and a 
 80                              summary of the build process it uses. 
 81       /builders/BUILDERNAME/builds/NUM: a page describing a single Build 
 82       /builders/BUILDERNAME/builds/NUM/steps/STEPNAME: describes a single step 
 83       /builders/BUILDERNAME/builds/NUM/steps/STEPNAME/logs/LOGNAME: a StatusLog 
 84       /builders/_all/{force,stop}: force a build/stop building on all builders. 
 85       /buildstatus?builder=...&number=...: an embedded iframe for the console 
 86       /changes : summarize all ChangeSources 
 87       /changes/CHANGENUM: a page describing a single Change 
 88       /buildslaves : list all BuildSlaves 
 89       /buildslaves/SLAVENAME : describe a single BuildSlave 
 90       /one_line_per_build : summarize the last few builds, one line each 
 91       /one_line_per_build/BUILDERNAME : same, but only for a single builder 
 92       /about : describe this buildmaster (Buildbot and support library versions) 
 93       /change_hook[/DIALECT] : accepts changes from external sources, optionally 
 94                                choosing the dialect that will be permitted 
 95                                (i.e. github format, etc..) 
 96   
 97       and more!  see the manual. 
 98   
 99   
100      All URLs for pages which are not defined here are used to look 
101      for files in PUBLIC_HTML, which defaults to BASEDIR/public_html. 
102      This means that /robots.txt or /favicon.ico can be placed in 
103      that directory 
104   
105      This webserver uses the jinja2 template system to generate the web pages 
106      (see http://jinja.pocoo.org/2/) and by default loads pages from the 
107      buildbot.status.web.templates package. Any file here can be overridden by placing 
108      a corresponding file in the master's 'templates' directory. 
109   
110      The main customization points are layout.html which loads style sheet 
111      (css) and provides header and footer content, and root.html, which 
112      generates the root page. 
113   
114      All of the resources provided by this service use relative URLs to reach 
115      each other. The only absolute links are the c['titleURL'] links at the 
116      top and bottom of the page, and the buildbot home-page link at the 
117      bottom. 
118   
119      Buildbot uses some generic classes to identify the type of object, and 
120      some more specific classes for the various kinds of those types. It does 
121      this by specifying both in the class attributes where applicable, 
122      separated by a space. It is important that in your CSS you declare the 
123      more generic class styles above the more specific ones. For example, 
124      first define a style for .Event, and below that for .SUCCESS 
125   
126      The following CSS class names are used: 
127          - Activity, Event, BuildStep, LastBuild: general classes 
128          - waiting, interlocked, building, offline, idle: Activity states 
129          - start, running, success, failure, warnings, skipped, exception: 
130            LastBuild and BuildStep states 
131          - Change: box with change 
132          - Builder: box for builder name (at top) 
133          - Project 
134          - Time 
135   
136      """ 
137   
138       
139       
140       
141       
142       
143   
144 -    def __init__(self, http_port=None, distrib_port=None, allowForce=None, 
145                   public_html="public_html", site=None, numbuilds=20, 
146                   num_events=200, num_events_max=None, auth=None, 
147                   order_console_by_time=False, changecommentlink=None, 
148                   revlink=None, projects=None, repositories=None, 
149                   authz=None, logRotateLength=None, maxRotatedFiles=None, 
150                   change_hook_dialects = {}, provide_feeds=None): 
 151          """Run a web server that provides Buildbot status. 
152   
153          @type  http_port: int or L{twisted.application.strports} string 
154          @param http_port: a strports specification describing which port the 
155                            buildbot should use for its web server, with the 
156                            Waterfall display as the root page. For backwards 
157                            compatibility this can also be an int. Use 
158                            'tcp:8000' to listen on that port, or 
159                            'tcp:12345:interface=127.0.0.1' if you only want 
160                            local processes to connect to it (perhaps because 
161                            you are using an HTTP reverse proxy to make the 
162                            buildbot available to the outside world, and do not 
163                            want to make the raw port visible). 
164   
165          @type  distrib_port: int or L{twisted.application.strports} string 
166          @param distrib_port: Use this if you want to publish the Waterfall 
167                               page using web.distrib instead. The most common 
168                               case is to provide a string that is an absolute 
169                               pathname to the unix socket on which the 
170                               publisher should listen 
171                               (C{os.path.expanduser(~/.twistd-web-pb)} will 
172                               match the default settings of a standard 
173                               twisted.web 'personal web server'). Another 
174                               possibility is to pass an integer, which means 
175                               the publisher should listen on a TCP socket, 
176                               allowing the web server to be on a different 
177                               machine entirely. Both forms are provided for 
178                               backwards compatibility; the preferred form is a 
179                               strports specification like 
180                               'unix:/home/buildbot/.twistd-web-pb'. Providing 
181                               a non-absolute pathname will probably confuse 
182                               the strports parser. 
183   
184          @param allowForce: deprecated; use authz instead 
185          @param auth: deprecated; use with authz 
186   
187          @param authz: a buildbot.status.web.authz.Authz instance giving the authorization 
188                             parameters for this view 
189   
190          @param public_html: the path to the public_html directory for this display, 
191                              either absolute or relative to the basedir.  The default 
192                              is 'public_html', which selects BASEDIR/public_html. 
193   
194          @type site: None or L{twisted.web.server.Site} 
195          @param site: Use this if you want to define your own object instead of 
196                       using the default.` 
197   
198          @type numbuilds: int 
199          @param numbuilds: Default number of entries in lists at the /one_line_per_build 
200          and /builders/FOO URLs.  This default can be overriden both programatically --- 
201          by passing the equally named argument to constructors of OneLinePerBuildOneBuilder 
202          and OneLinePerBuild --- and via the UI, by tacking ?numbuilds=xy onto the URL. 
203   
204          @type num_events: int 
205          @param num_events: Default number of events to show in the waterfall. 
206   
207          @type num_events_max: int 
208          @param num_events_max: The maximum number of events that are allowed to be 
209          shown in the waterfall.  The default value of C{None} will disable this 
210          check 
211   
212          @type auth: a L{status.web.auth.IAuth} or C{None} 
213          @param auth: an object that performs authentication to restrict access 
214                       to the C{allowForce} features. Ignored if C{allowForce} 
215                       is not C{True}. If C{auth} is C{None}, people can force or 
216                       stop builds without auth. 
217   
218          @type order_console_by_time: bool 
219          @param order_console_by_time: Whether to order changes (commits) in the console 
220                       view according to the time they were created (for VCS like Git) or 
221                       according to their integer revision numbers (for VCS like SVN). 
222   
223          @type changecommentlink: callable, dict, tuple (2 or 3 strings) or C{None} 
224          @param changecommentlink: adds links to ticket/bug ids in change comments, 
225              see buildbot.status.web.base.changecommentlink for details 
226   
227          @type revlink: callable, dict, string or C{None} 
228          @param revlink: decorations revision ids with links to a web-view, 
229              see buildbot.status.web.base.revlink for details 
230   
231          @type projects: callable, dict or c{None} 
232          @param projects: maps project identifiers to URLs, so that any project listed 
233              is automatically decorated with a link to it's front page. 
234              see buildbot.status.web.base.dictlink for details 
235   
236          @type repositories: callable, dict or c{None} 
237          @param repositories: maps repository identifiers to URLs, so that any project listed 
238              is automatically decorated with a link to it's web view. 
239              see buildbot.status.web.base.dictlink for details 
240   
241          @type logRotateLength: None or int 
242          @param logRotateLength: file size at which the http.log is rotated/reset. 
243              If not set, the value set in the buildbot.tac will be used,  
244               falling back to the BuildMaster's default value (1 Mb). 
245           
246          @type maxRotatedFiles: None or int 
247          @param maxRotatedFiles: number of old http.log files to keep during log rotation. 
248              If not set, the value set in the buildbot.tac will be used,  
249               falling back to the BuildMaster's default value (10 files).        
250           
251          @type  change_hook_dialects: None or dict 
252          @param change_hook_dialects: If empty, disables change_hook support, otherwise       
253                                       whitelists valid dialects. In the format of 
254                                       {"dialect1": "Option1", "dialect2", None} 
255                                       Where the values are options that will be passed 
256                                       to the dialect 
257                                        
258                                       To enable the DEFAULT handler, use a key of DEFAULT 
259                                        
260                                        
261           
262       
263          @type  provide_feeds: None or list 
264          @param provide_feeds: If empty, provides atom, json, and rss feeds. 
265                                Otherwise, a dictionary of strings of 
266                                the type of feeds provided.  Current 
267                                possibilities are "atom", "json", and "rss" 
268          """ 
269   
270          service.MultiService.__init__(self) 
271          if type(http_port) is int: 
272              http_port = "tcp:%d" % http_port 
273          self.http_port = http_port 
274          if distrib_port is not None: 
275              if type(distrib_port) is int: 
276                  distrib_port = "tcp:%d" % distrib_port 
277              if distrib_port[0] in "/~.":  
278                  distrib_port = "unix:%s" % distrib_port 
279          self.distrib_port = distrib_port 
280          self.num_events = num_events 
281          if num_events_max: 
282              assert num_events_max >= num_events 
283              self.num_events_max = num_events_max 
284          self.public_html = public_html 
285   
286           
287          if authz: 
288              if allowForce is not None: 
289                  raise ValueError("cannot use both allowForce and authz parameters") 
290              if auth: 
291                  raise ValueError("cannot use both auth and authz parameters (pass " 
292                                   "auth as an Authz parameter)") 
293          else: 
294               
295              if allowForce and auth: 
296                  authz = Authz(auth=auth, default_action="auth") 
297              elif allowForce: 
298                  authz = Authz(default_action=True) 
299              else: 
300                  if auth: 
301                      log.msg("Warning: Ignoring authentication. Search for 'authorization'" 
302                              " in the manual") 
303                  authz = Authz()  
304   
305          self.authz = authz 
306   
307          self.orderConsoleByTime = order_console_by_time 
308   
309           
310          self.site = site 
311   
312           
313          self.logRotateLength = logRotateLength 
314          self.maxRotatedFiles = maxRotatedFiles         
315   
316           
317          self.childrenToBeAdded = {} 
318          self.setupUsualPages(numbuilds=numbuilds, num_events=num_events, 
319                               num_events_max=num_events_max) 
320   
321           
322          self.templates = createJinjaEnv(revlink, changecommentlink, 
323                                          repositories, projects) 
324   
325           
326           
327          self.channels = weakref.WeakKeyDictionary() 
328           
329           
330          self.change_hook_dialects = {} 
331          if change_hook_dialects: 
332              self.change_hook_dialects = change_hook_dialects 
333              self.putChild("change_hook", ChangeHookResource(dialects = self.change_hook_dialects)) 
334   
335           
336          if provide_feeds is None: 
337              self.provide_feeds = ["atom", "json", "rss"] 
338          else: 
339              self.provide_feeds = provide_feeds 
 340   
341 -    def setupUsualPages(self, numbuilds, num_events, num_events_max): 
 342           
343          self.putChild("waterfall", WaterfallStatusResource(num_events=num_events, 
344                                          num_events_max=num_events_max)) 
345          self.putChild("grid", GridStatusResource()) 
346          self.putChild("console", ConsoleStatusResource( 
347                  orderByTime=self.orderConsoleByTime)) 
348          self.putChild("tgrid", TransposedGridStatusResource()) 
349          self.putChild("builders", BuildersResource())  
350          self.putChild("one_box_per_builder", Redirect("builders")) 
351          self.putChild("changes", ChangesResource()) 
352          self.putChild("buildslaves", BuildSlavesResource()) 
353          self.putChild("buildstatus", BuildStatusStatusResource()) 
354          self.putChild("one_line_per_build", 
355                        OneLinePerBuild(numbuilds=numbuilds)) 
356          self.putChild("about", AboutBuildbot()) 
357          self.putChild("authfail", AuthFailResource()) 
 358   
359   
361          if self.http_port is None: 
362              return "<WebStatus on path %s at %s>" % (self.distrib_port, 
363                                                       hex(id(self))) 
364          if self.distrib_port is None: 
365              return "<WebStatus on port %s at %s>" % (self.http_port, 
366                                                       hex(id(self))) 
367          return ("<WebStatus on port %s and path %s at %s>" % 
368                  (self.http_port, self.distrib_port, hex(id(self)))) 
 369   
371          service.MultiService.setServiceParent(self, parent) 
372   
373           
374           
375           
376           
377          self.master = parent 
378           
379          def either(a,b):  
380              if a: 
381                  return a 
382              else: 
383                  return b 
 384           
385          rotateLength = either(self.logRotateLength, self.master.log_rotation.rotateLength) 
386          maxRotatedFiles = either(self.maxRotatedFiles, self.master.log_rotation.maxRotatedFiles) 
387   
388          if not self.site: 
389               
390              class RotateLogSite(server.Site): 
391                  def _openLogFile(self, path): 
392                      try: 
393                          from twisted.python.logfile import LogFile 
394                          log.msg("Setting up http.log rotating %s files of %s bytes each" % 
395                                  (maxRotatedFiles, rotateLength))             
396                          if hasattr(LogFile, "fromFullPath"):  
397                              return LogFile.fromFullPath(path, rotateLength=rotateLength, maxRotatedFiles=maxRotatedFiles) 
398                          else: 
399                              log.msg("WebStatus: rotated http logs are not supported on this version of Twisted") 
400                      except ImportError, e: 
401                          log.msg("WebStatus: Unable to set up rotating http.log: %s" % e) 
402   
403                       
404                      return server.Site._openLogFile(self, path) 
 405   
406               
407               
408              root = static.Data("placeholder", "text/plain") 
409              httplog = os.path.abspath(os.path.join(self.master.basedir, "http.log")) 
410              self.site = RotateLogSite(root, logPath=httplog) 
411   
412           
413           
414          self.site.buildbot_service = self 
415   
416          if self.http_port is not None: 
417              s = strports.service(self.http_port, self.site) 
418              s.setServiceParent(self) 
419          if self.distrib_port is not None: 
420              f = pb.PBServerFactory(distrib.ResourcePublisher(self.site)) 
421              s = strports.service(self.distrib_port, f) 
422              s.setServiceParent(self) 
423   
424          self.setupSite() 
425   
458   
459 -    def putChild(self, name, child_resource): 
 460          """This behaves a lot like root.putChild() . """ 
461          self.childrenToBeAdded[name] = child_resource 
 462   
464          self.channels[channel] = 1  
 465   
467          for channel in self.channels: 
468              try: 
469                  channel.transport.loseConnection() 
470              except: 
471                  log.msg("WebStatus.stopService: error while disconnecting" 
472                          " leftover clients") 
473                  log.err() 
474          return service.MultiService.stopService(self) 
 475   
478   
481   
483           
484          s = list(self)[0] 
485          return s._port.getHost().port 
 486   
487       
488       
489       
490       
491       
492       
493       
494   
495   
496   
497