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  from buildbot import config 
 26  from buildbot.interfaces import IStatusReceiver 
 27  from buildbot.status.web.base import StaticFile, createJinjaEnv 
 28  from buildbot.status.web.feeds import Rss20StatusResource, \ 
 29       Atom10StatusResource 
 30  from buildbot.status.web.waterfall import WaterfallStatusResource 
 31  from buildbot.status.web.console import ConsoleStatusResource 
 32  from buildbot.status.web.olpb import OneLinePerBuild 
 33  from buildbot.status.web.grid import GridStatusResource 
 34  from buildbot.status.web.grid import 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,AuthzFailResource, LoginResource, LogoutResource 
 43  from buildbot.status.web.root import RootPage 
 44  from buildbot.status.web.users import UsersResource 
 45  from buildbot.status.web.change_hook import ChangeHookResource 
 46   
 47  # this class contains the WebStatus class.  Basic utilities are in base.py, 
 48  # and specific pages are each in their own module. 
 49   
50 -class WebStatus(service.MultiService):
51 implements(IStatusReceiver) 52 # TODO: IStatusReceiver is really about things which subscribe to hear 53 # about buildbot events. We need a different interface (perhaps a parent 54 # of IStatusReceiver) for status targets that don't subscribe, like the 55 # WebStatus class. buildbot.master.BuildMaster.loadConfig:737 asserts 56 # that everything in c['status'] provides IStatusReceiver, but really it 57 # should check that they provide IStatusTarget instead. 58 59 """ 60 The webserver provided by this class has the following resources: 61 62 /waterfall : the big time-oriented 'waterfall' display, with links 63 to individual changes, builders, builds, steps, and logs. 64 A number of query-arguments can be added to influence 65 the display. 66 /rss : a rss feed summarizing all failed builds. The same 67 query-arguments used by 'waterfall' can be added to 68 influence the feed output. 69 /atom : an atom feed summarizing all failed builds. The same 70 query-arguments used by 'waterfall' can be added to 71 influence the feed output. 72 /grid : another summary display that shows a grid of builds, with 73 sourcestamps on the x axis, and builders on the y. Query 74 arguments similar to those for the waterfall can be added. 75 /tgrid : similar to the grid display, but the commits are down the 76 left side, and the build hosts are across the top. 77 /builders/BUILDERNAME: a page summarizing the builder. This includes 78 references to the Schedulers that feed it, 79 any builds currently in the queue, which 80 buildslaves are designated or attached, and a 81 summary of the build process it uses. 82 /builders/BUILDERNAME/builds/NUM: a page describing a single Build 83 /builders/BUILDERNAME/builds/NUM/steps/STEPNAME: describes a single step 84 /builders/BUILDERNAME/builds/NUM/steps/STEPNAME/logs/LOGNAME: a StatusLog 85 /builders/_all/{force,stop}: force a build/stop building on all builders. 86 /buildstatus?builder=...&number=...: an embedded iframe for the console 87 /changes : summarize all ChangeSources 88 /changes/CHANGENUM: a page describing a single Change 89 /buildslaves : list all BuildSlaves 90 /buildslaves/SLAVENAME : describe a single BuildSlave 91 /one_line_per_build : summarize the last few builds, one line each 92 /one_line_per_build/BUILDERNAME : same, but only for a single builder 93 /about : describe this buildmaster (Buildbot and support library versions) 94 /change_hook[/DIALECT] : accepts changes from external sources, optionally 95 choosing the dialect that will be permitted 96 (i.e. github format, etc..) 97 98 and more! see the manual. 99 100 101 All URLs for pages which are not defined here are used to look 102 for files in PUBLIC_HTML, which defaults to BASEDIR/public_html. 103 This means that /robots.txt or /favicon.ico can be placed in 104 that directory 105 106 This webserver uses the jinja2 template system to generate the web pages 107 (see http://jinja.pocoo.org/2/) and by default loads pages from the 108 buildbot.status.web.templates package. Any file here can be overridden by placing 109 a corresponding file in the master's 'templates' directory. 110 111 The main customization points are layout.html which loads style sheet 112 (css) and provides header and footer content, and root.html, which 113 generates the root page. 114 115 All of the resources provided by this service use relative URLs to reach 116 each other. The only absolute links are the c['titleURL'] links at the 117 top and bottom of the page, and the buildbot home-page link at the 118 bottom. 119 120 Buildbot uses some generic classes to identify the type of object, and 121 some more specific classes for the various kinds of those types. It does 122 this by specifying both in the class attributes where applicable, 123 separated by a space. It is important that in your CSS you declare the 124 more generic class styles above the more specific ones. For example, 125 first define a style for .Event, and below that for .SUCCESS 126 127 The following CSS class names are used: 128 - Activity, Event, BuildStep, LastBuild: general classes 129 - waiting, interlocked, building, offline, idle: Activity states 130 - start, running, success, failure, warnings, skipped, exception: 131 LastBuild and BuildStep states 132 - Change: box with change 133 - Builder: box for builder name (at top) 134 - Project 135 - Time 136 137 """ 138 139 # we are not a ComparableMixin, and therefore the webserver will be 140 # rebuilt every time we reconfig. This is because WebStatus.putChild() 141 # makes it too difficult to tell whether two instances are the same or 142 # not (we'd have to do a recursive traversal of all children to discover 143 # all the changes). 144
145 - def __init__(self, http_port=None, distrib_port=None, allowForce=None, 146 public_html="public_html", site=None, numbuilds=20, 147 num_events=200, num_events_max=None, auth=None, 148 order_console_by_time=False, changecommentlink=None, 149 revlink=None, projects=None, repositories=None, 150 authz=None, logRotateLength=None, maxRotatedFiles=None, 151 change_hook_dialects = {}, provide_feeds=None, jinja_loaders=None):
152 """Run a web server that provides Buildbot status. 153 154 @type http_port: int or L{twisted.application.strports} string 155 @param http_port: a strports specification describing which port the 156 buildbot should use for its web server, with the 157 Waterfall display as the root page. For backwards 158 compatibility this can also be an int. Use 159 'tcp:8000' to listen on that port, or 160 'tcp:12345:interface=127.0.0.1' if you only want 161 local processes to connect to it (perhaps because 162 you are using an HTTP reverse proxy to make the 163 buildbot available to the outside world, and do not 164 want to make the raw port visible). 165 166 @type distrib_port: int or L{twisted.application.strports} string 167 @param distrib_port: Use this if you want to publish the Waterfall 168 page using web.distrib instead. The most common 169 case is to provide a string that is an absolute 170 pathname to the unix socket on which the 171 publisher should listen 172 (C{os.path.expanduser(~/.twistd-web-pb)} will 173 match the default settings of a standard 174 twisted.web 'personal web server'). Another 175 possibility is to pass an integer, which means 176 the publisher should listen on a TCP socket, 177 allowing the web server to be on a different 178 machine entirely. Both forms are provided for 179 backwards compatibility; the preferred form is a 180 strports specification like 181 'unix:/home/buildbot/.twistd-web-pb'. Providing 182 a non-absolute pathname will probably confuse 183 the strports parser. 184 185 @param allowForce: deprecated; use authz instead 186 @param auth: deprecated; use with authz 187 188 @param authz: a buildbot.status.web.authz.Authz instance giving the authorization 189 parameters for this view 190 191 @param public_html: the path to the public_html directory for this display, 192 either absolute or relative to the basedir. The default 193 is 'public_html', which selects BASEDIR/public_html. 194 195 @type site: None or L{twisted.web.server.Site} 196 @param site: Use this if you want to define your own object instead of 197 using the default.` 198 199 @type numbuilds: int 200 @param numbuilds: Default number of entries in lists at the /one_line_per_build 201 and /builders/FOO URLs. This default can be overriden both programatically --- 202 by passing the equally named argument to constructors of OneLinePerBuildOneBuilder 203 and OneLinePerBuild --- and via the UI, by tacking ?numbuilds=xy onto the URL. 204 205 @type num_events: int 206 @param num_events: Default number of events to show in the waterfall. 207 208 @type num_events_max: int 209 @param num_events_max: The maximum number of events that are allowed to be 210 shown in the waterfall. The default value of C{None} will disable this 211 check 212 213 @type auth: a L{status.web.auth.IAuth} or C{None} 214 @param auth: an object that performs authentication to restrict access 215 to the C{allowForce} features. Ignored if C{allowForce} 216 is not C{True}. If C{auth} is C{None}, people can force or 217 stop builds without auth. 218 219 @type order_console_by_time: bool 220 @param order_console_by_time: Whether to order changes (commits) in the console 221 view according to the time they were created (for VCS like Git) or 222 according to their integer revision numbers (for VCS like SVN). 223 224 @type changecommentlink: callable, dict, tuple (2 or 3 strings) or C{None} 225 @param changecommentlink: adds links to ticket/bug ids in change comments, 226 see buildbot.status.web.base.changecommentlink for details 227 228 @type revlink: callable, dict, string or C{None} 229 @param revlink: decorations revision ids with links to a web-view, 230 see buildbot.status.web.base.revlink for details 231 232 @type projects: callable, dict or c{None} 233 @param projects: maps project identifiers to URLs, so that any project listed 234 is automatically decorated with a link to it's front page. 235 see buildbot.status.web.base.dictlink for details 236 237 @type repositories: callable, dict or c{None} 238 @param repositories: maps repository identifiers to URLs, so that any project listed 239 is automatically decorated with a link to it's web view. 240 see buildbot.status.web.base.dictlink for details 241 242 @type logRotateLength: None or int 243 @param logRotateLength: file size at which the http.log is rotated/reset. 244 If not set, the value set in the buildbot.tac will be used, 245 falling back to the BuildMaster's default value (1 Mb). 246 247 @type maxRotatedFiles: None or int 248 @param maxRotatedFiles: number of old http.log files to keep during log rotation. 249 If not set, the value set in the buildbot.tac will be used, 250 falling back to the BuildMaster's default value (10 files). 251 252 @type change_hook_dialects: None or dict 253 @param change_hook_dialects: If empty, disables change_hook support, otherwise 254 whitelists valid dialects. In the format of 255 {"dialect1": "Option1", "dialect2", None} 256 Where the values are options that will be passed 257 to the dialect 258 259 To enable the DEFAULT handler, use a key of DEFAULT 260 261 262 263 264 @type provide_feeds: None or list 265 @param provide_feeds: If empty, provides atom, json, and rss feeds. 266 Otherwise, a dictionary of strings of 267 the type of feeds provided. Current 268 possibilities are "atom", "json", and "rss" 269 270 @type jinja_loaders: None or list 271 @param jinja_loaders: If not empty, a list of additional Jinja2 loader 272 objects to search for templates. 273 """ 274 275 service.MultiService.__init__(self) 276 if type(http_port) is int: 277 http_port = "tcp:%d" % http_port 278 self.http_port = http_port 279 if distrib_port is not None: 280 if type(distrib_port) is int: 281 distrib_port = "tcp:%d" % distrib_port 282 if distrib_port[0] in "/~.": # pathnames 283 distrib_port = "unix:%s" % distrib_port 284 self.distrib_port = distrib_port 285 self.num_events = num_events 286 if num_events_max: 287 if num_events_max < num_events: 288 config.error( 289 "num_events_max must be greater than num_events") 290 self.num_events_max = num_events_max 291 self.public_html = public_html 292 293 # make up an authz if allowForce was given 294 if authz: 295 if allowForce is not None: 296 config.error( 297 "cannot use both allowForce and authz parameters") 298 if auth: 299 config.error( 300 "cannot use both auth and authz parameters (pass " + 301 "auth as an Authz parameter)") 302 else: 303 # invent an authz 304 if allowForce and auth: 305 authz = Authz(auth=auth, default_action="auth") 306 elif allowForce: 307 authz = Authz(default_action=True) 308 else: 309 if auth: 310 log.msg("Warning: Ignoring authentication. Search for 'authorization'" 311 " in the manual") 312 authz = Authz() # no authorization for anything 313 314 self.authz = authz 315 316 self.orderConsoleByTime = order_console_by_time 317 318 # If we were given a site object, go ahead and use it. (if not, we add one later) 319 self.site = site 320 321 # store the log settings until we create the site object 322 self.logRotateLength = logRotateLength 323 self.maxRotatedFiles = maxRotatedFiles 324 325 # create the web site page structure 326 self.childrenToBeAdded = {} 327 self.setupUsualPages(numbuilds=numbuilds, num_events=num_events, 328 num_events_max=num_events_max) 329 330 self.revlink = revlink 331 self.changecommentlink = changecommentlink 332 self.repositories = repositories 333 self.projects = projects 334 335 # keep track of cached connections so we can break them when we shut 336 # down. See ticket #102 for more details. 337 self.channels = weakref.WeakKeyDictionary() 338 339 # do we want to allow change_hook 340 self.change_hook_dialects = {} 341 if change_hook_dialects: 342 self.change_hook_dialects = change_hook_dialects 343 self.putChild("change_hook", ChangeHookResource(dialects = self.change_hook_dialects)) 344 345 # Set default feeds 346 if provide_feeds is None: 347 self.provide_feeds = ["atom", "json", "rss"] 348 else: 349 self.provide_feeds = provide_feeds 350 351 self.jinja_loaders = jinja_loaders
352
353 - def setupUsualPages(self, numbuilds, num_events, num_events_max):
354 #self.putChild("", IndexOrWaterfallRedirection()) 355 self.putChild("waterfall", WaterfallStatusResource(num_events=num_events, 356 num_events_max=num_events_max)) 357 self.putChild("grid", GridStatusResource()) 358 self.putChild("console", ConsoleStatusResource( 359 orderByTime=self.orderConsoleByTime)) 360 self.putChild("tgrid", TransposedGridStatusResource()) 361 self.putChild("builders", BuildersResource()) # has builds/steps/logs 362 self.putChild("one_box_per_builder", Redirect("builders")) 363 self.putChild("changes", ChangesResource()) 364 self.putChild("buildslaves", BuildSlavesResource()) 365 self.putChild("buildstatus", BuildStatusStatusResource()) 366 self.putChild("one_line_per_build", 367 OneLinePerBuild(numbuilds=numbuilds)) 368 self.putChild("about", AboutBuildbot()) 369 self.putChild("authfail", AuthFailResource()) 370 self.putChild("authzfail", AuthzFailResource()) 371 self.putChild("users", UsersResource()) 372 self.putChild("login", LoginResource()) 373 self.putChild("logout", LogoutResource())
374
375 - def __repr__(self):
376 if self.http_port is None: 377 return "<WebStatus on path %s at %s>" % (self.distrib_port, 378 hex(id(self))) 379 if self.distrib_port is None: 380 return "<WebStatus on port %s at %s>" % (self.http_port, 381 hex(id(self))) 382 return ("<WebStatus on port %s and path %s at %s>" % 383 (self.http_port, self.distrib_port, hex(id(self))))
384
385 - def setServiceParent(self, parent):
386 # this class keeps a *separate* link to the buildmaster, rather than 387 # just using self.parent, so that when we are "disowned" (and thus 388 # parent=None), any remaining HTTP clients of this WebStatus will still 389 # be able to get reasonable results. 390 self.master = parent.master 391 392 # set master in IAuth instance 393 if self.authz.auth: 394 self.authz.auth.master = self.master 395 396 def either(a,b): # a if a else b for py2.4 397 if a: 398 return a 399 else: 400 return b
401 402 rotateLength = either(self.logRotateLength, self.master.log_rotation.rotateLength) 403 maxRotatedFiles = either(self.maxRotatedFiles, self.master.log_rotation.maxRotatedFiles) 404 405 # Set up the jinja templating engine. 406 if self.revlink: 407 revlink = self.revlink 408 else: 409 revlink = self.master.config.revlink 410 self.templates = createJinjaEnv(revlink, self.changecommentlink, 411 self.repositories, self.projects, self.jinja_loaders) 412 413 if not self.site: 414 415 class RotateLogSite(server.Site): 416 def _openLogFile(self, path): 417 try: 418 from twisted.python.logfile import LogFile 419 log.msg("Setting up http.log rotating %s files of %s bytes each" % 420 (maxRotatedFiles, rotateLength)) 421 if hasattr(LogFile, "fromFullPath"): # not present in Twisted-2.5.0 422 return LogFile.fromFullPath(path, rotateLength=rotateLength, maxRotatedFiles=maxRotatedFiles) 423 else: 424 log.msg("WebStatus: rotated http logs are not supported on this version of Twisted") 425 except ImportError, e: 426 log.msg("WebStatus: Unable to set up rotating http.log: %s" % e) 427 428 # if all else fails, just call the parent method 429 return server.Site._openLogFile(self, path)
430 431 # this will be replaced once we've been attached to a parent (and 432 # thus have a basedir and can reference BASEDIR) 433 root = static.Data("placeholder", "text/plain") 434 httplog = os.path.abspath(os.path.join(self.master.basedir, "http.log")) 435 self.site = RotateLogSite(root, logPath=httplog) 436 437 # the following items are accessed by HtmlResource when it renders 438 # each page. 439 self.site.buildbot_service = self 440 441 if self.http_port is not None: 442 s = strports.service(self.http_port, self.site) 443 s.setServiceParent(self) 444 if self.distrib_port is not None: 445 f = pb.PBServerFactory(distrib.ResourcePublisher(self.site)) 446 s = strports.service(self.distrib_port, f) 447 s.setServiceParent(self) 448 449 self.setupSite() 450 451 service.MultiService.setServiceParent(self, parent) 452
453 - def setupSite(self):
454 # this is responsible for creating the root resource. It isn't done 455 # at __init__ time because we need to reference the parent's basedir. 456 htmldir = os.path.abspath(os.path.join(self.master.basedir, self.public_html)) 457 if os.path.isdir(htmldir): 458 log.msg("WebStatus using (%s)" % htmldir) 459 else: 460 log.msg("WebStatus: warning: %s is missing. Do you need to run" 461 " 'buildbot upgrade-master' on this buildmaster?" % htmldir) 462 # all static pages will get a 404 until upgrade-master is used to 463 # populate this directory. Create the directory, though, since 464 # otherwise we get internal server errors instead of 404s. 465 os.mkdir(htmldir) 466 467 root = StaticFile(htmldir) 468 root_page = RootPage() 469 root.putChild("", root_page) 470 root.putChild("shutdown", root_page) 471 root.putChild("cancel_shutdown", root_page) 472 473 for name, child_resource in self.childrenToBeAdded.iteritems(): 474 root.putChild(name, child_resource) 475 476 status = self.getStatus() 477 if "rss" in self.provide_feeds: 478 root.putChild("rss", Rss20StatusResource(status)) 479 if "atom" in self.provide_feeds: 480 root.putChild("atom", Atom10StatusResource(status)) 481 if "json" in self.provide_feeds: 482 root.putChild("json", JsonStatusResource(status)) 483 484 self.site.resource = root
485
486 - def putChild(self, name, child_resource):
487 """This behaves a lot like root.putChild() . """ 488 self.childrenToBeAdded[name] = child_resource
489
490 - def registerChannel(self, channel):
491 self.channels[channel] = 1 # weakrefs
492
493 - def stopService(self):
494 for channel in self.channels: 495 try: 496 channel.transport.loseConnection() 497 except: 498 log.msg("WebStatus.stopService: error while disconnecting" 499 " leftover clients") 500 log.err() 501 return service.MultiService.stopService(self)
502
503 - def getStatus(self):
504 return self.master.getStatus()
505
506 - def getChangeSvc(self):
507 return self.master.change_svc
508
509 - def getPortnum(self):
510 # this is for the benefit of unit tests 511 s = list(self)[0] 512 return s._port.getHost().port
513 514 # What happened to getControl?! 515 # 516 # instead of passing control objects all over the place in the web 517 # code, at the few places where a control instance is required we 518 # find the requisite object manually, starting at the buildmaster. 519 # This is in preparation for removal of the IControl hierarchy 520 # entirely. 521
522 - def checkConfig(self, otherStatusReceivers, errors):
523 duplicate_webstatus=0 524 for osr in otherStatusReceivers: 525 if isinstance(osr,WebStatus): 526 if osr is self: 527 continue 528 # compare against myself and complain if the settings conflict 529 if self.http_port == osr.http_port: 530 if duplicate_webstatus == 0: 531 duplicate_webstatus = 2 532 else: 533 duplicate_webstatus += 1 534 535 if duplicate_webstatus: 536 errors.addError( 537 "%d Webstatus objects have same port: %s" 538 % (duplicate_webstatus, self.http_port), 539 )
540 541 # resources can get access to the IStatus by calling 542 # request.site.buildbot_service.getStatus() 543