Package buildbot :: Package status :: Package web :: Module baseweb
[frames] | no frames]

Source Code for Module buildbot.status.web.baseweb

  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  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  # this class contains the WebStatus class.  Basic utilities are in base.py, 
 47  # and specific pages are each in their own module. 
 48   
49 -class WebStatus(service.MultiService):
50 implements(IStatusReceiver) 51 # TODO: IStatusReceiver is really about things which subscribe to hear 52 # about buildbot events. We need a different interface (perhaps a parent 53 # of IStatusReceiver) for status targets that don't subscribe, like the 54 # WebStatus class. buildbot.master.BuildMaster.loadConfig:737 asserts 55 # that everything in c['status'] provides IStatusReceiver, but really it 56 # should check that they provide IStatusTarget instead. 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 # we are not a ComparableMixin, and therefore the webserver will be 139 # rebuilt every time we reconfig. This is because WebStatus.putChild() 140 # makes it too difficult to tell whether two instances are the same or 141 # not (we'd have to do a recursive traversal of all children to discover 142 # all the changes). 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 "/~.": # pathnames 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 # make up an authz if allowForce was given 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 # invent an authz 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() # no authorization for anything 304 305 self.authz = authz 306 307 self.orderConsoleByTime = order_console_by_time 308 309 # If we were given a site object, go ahead and use it. (if not, we add one later) 310 self.site = site 311 312 # store the log settings until we create the site object 313 self.logRotateLength = logRotateLength 314 self.maxRotatedFiles = maxRotatedFiles 315 316 # create the web site page structure 317 self.childrenToBeAdded = {} 318 self.setupUsualPages(numbuilds=numbuilds, num_events=num_events, 319 num_events_max=num_events_max) 320 321 # Set up the jinja templating engine. 322 self.templates = createJinjaEnv(revlink, changecommentlink, 323 repositories, projects) 324 325 # keep track of cached connections so we can break them when we shut 326 # down. See ticket #102 for more details. 327 self.channels = weakref.WeakKeyDictionary() 328 329 # do we want to allow change_hook 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 # Set default feeds 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 #self.putChild("", IndexOrWaterfallRedirection()) 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()) # has builds/steps/logs 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
360 - def __repr__(self):
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
370 - def setServiceParent(self, parent):
371 service.MultiService.setServiceParent(self, parent) 372 373 # this class keeps a *separate* link to the buildmaster, rather than 374 # just using self.parent, so that when we are "disowned" (and thus 375 # parent=None), any remaining HTTP clients of this WebStatus will still 376 # be able to get reasonable results. 377 self.master = parent 378 379 def either(a,b): # a if a else b for py2.4 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"): # not present in Twisted-2.5.0 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 # if all else fails, just call the parent method 404 return server.Site._openLogFile(self, path)
405 406 # this will be replaced once we've been attached to a parent (and 407 # thus have a basedir and can reference BASEDIR) 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 # the following items are accessed by HtmlResource when it renders 413 # each page. 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
426 - def setupSite(self):
427 # this is responsible for creating the root resource. It isn't done 428 # at __init__ time because we need to reference the parent's basedir. 429 htmldir = os.path.abspath(os.path.join(self.master.basedir, self.public_html)) 430 if os.path.isdir(htmldir): 431 log.msg("WebStatus using (%s)" % htmldir) 432 else: 433 log.msg("WebStatus: warning: %s is missing. Do you need to run" 434 " 'buildbot upgrade-master' on this buildmaster?" % htmldir) 435 # all static pages will get a 404 until upgrade-master is used to 436 # populate this directory. Create the directory, though, since 437 # otherwise we get internal server errors instead of 404s. 438 os.mkdir(htmldir) 439 440 root = StaticFile(htmldir) 441 root_page = RootPage() 442 root.putChild("", root_page) 443 root.putChild("shutdown", root_page) 444 root.putChild("cancel_shutdown", root_page) 445 446 for name, child_resource in self.childrenToBeAdded.iteritems(): 447 root.putChild(name, child_resource) 448 449 status = self.getStatus() 450 if "rss" in self.provide_feeds: 451 root.putChild("rss", Rss20StatusResource(status)) 452 if "atom" in self.provide_feeds: 453 root.putChild("atom", Atom10StatusResource(status)) 454 if "json" in self.provide_feeds: 455 root.putChild("json", JsonStatusResource(status)) 456 457 self.site.resource = root
458
459 - def putChild(self, name, child_resource):
460 """This behaves a lot like root.putChild() . """ 461 self.childrenToBeAdded[name] = child_resource
462
463 - def registerChannel(self, channel):
464 self.channels[channel] = 1 # weakrefs
465
466 - def stopService(self):
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
476 - def getStatus(self):
477 return self.master.getStatus()
478
479 - def getChangeSvc(self):
480 return self.master.change_svc
481
482 - def getPortnum(self):
483 # this is for the benefit of unit tests 484 s = list(self)[0] 485 return s._port.getHost().port
486 487 # What happened to getControl?! 488 # 489 # instead of passing control objects all over the place in the web 490 # code, at the few places where a control instance is required we 491 # find the requisite object manually, starting at the buildmaster. 492 # This is in preparation for removal of the IControl hierarchy 493 # entirely. 494 495 # resources can get access to the IStatus by calling 496 # request.site.buildbot_service.getStatus() 497