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['projectURL'] 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 = {}):
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 """ 264 265 service.MultiService.__init__(self) 266 if type(http_port) is int: 267 http_port = "tcp:%d" % http_port 268 self.http_port = http_port 269 if distrib_port is not None: 270 if type(distrib_port) is int: 271 distrib_port = "tcp:%d" % distrib_port 272 if distrib_port[0] in "/~.": # pathnames 273 distrib_port = "unix:%s" % distrib_port 274 self.distrib_port = distrib_port 275 self.num_events = num_events 276 if num_events_max: 277 assert num_events_max >= num_events 278 self.num_events_max = num_events_max 279 self.public_html = public_html 280 281 # make up an authz if allowForce was given 282 if authz: 283 if allowForce is not None: 284 raise ValueError("cannot use both allowForce and authz parameters") 285 if auth: 286 raise ValueError("cannot use both auth and authz parameters (pass " 287 "auth as an Authz parameter)") 288 else: 289 # invent an authz 290 if allowForce and auth: 291 authz = Authz(auth=auth, default_action="auth") 292 elif allowForce: 293 authz = Authz(default_action=True) 294 else: 295 if auth: 296 log.msg("Warning: Ignoring authentication. Search for 'authorization'" 297 " in the manual") 298 authz = Authz() # no authorization for anything 299 300 self.authz = authz 301 302 self.orderConsoleByTime = order_console_by_time 303 304 # If we were given a site object, go ahead and use it. (if not, we add one later) 305 self.site = site 306 307 # store the log settings until we create the site object 308 self.logRotateLength = logRotateLength 309 self.maxRotatedFiles = maxRotatedFiles 310 311 # create the web site page structure 312 self.childrenToBeAdded = {} 313 self.setupUsualPages(numbuilds=numbuilds, num_events=num_events, 314 num_events_max=num_events_max) 315 316 # Set up the jinja templating engine. 317 self.templates = createJinjaEnv(revlink, changecommentlink, 318 repositories, projects) 319 320 # keep track of cached connections so we can break them when we shut 321 # down. See ticket #102 for more details. 322 self.channels = weakref.WeakKeyDictionary() 323 324 # do we want to allow change_hook 325 self.change_hook_dialects = {} 326 if change_hook_dialects: 327 self.change_hook_dialects = change_hook_dialects 328 self.putChild("change_hook", ChangeHookResource(dialects = self.change_hook_dialects))
329
330 - def setupUsualPages(self, numbuilds, num_events, num_events_max):
331 #self.putChild("", IndexOrWaterfallRedirection()) 332 self.putChild("waterfall", WaterfallStatusResource(num_events=num_events, 333 num_events_max=num_events_max)) 334 self.putChild("grid", GridStatusResource()) 335 self.putChild("console", ConsoleStatusResource( 336 orderByTime=self.orderConsoleByTime)) 337 self.putChild("tgrid", TransposedGridStatusResource()) 338 self.putChild("builders", BuildersResource()) # has builds/steps/logs 339 self.putChild("one_box_per_builder", Redirect("builders")) 340 self.putChild("changes", ChangesResource()) 341 self.putChild("buildslaves", BuildSlavesResource()) 342 self.putChild("buildstatus", BuildStatusStatusResource()) 343 self.putChild("one_line_per_build", 344 OneLinePerBuild(numbuilds=numbuilds)) 345 self.putChild("about", AboutBuildbot()) 346 self.putChild("authfail", AuthFailResource())
347 348
349 - def __repr__(self):
350 if self.http_port is None: 351 return "<WebStatus on path %s at %s>" % (self.distrib_port, 352 hex(id(self))) 353 if self.distrib_port is None: 354 return "<WebStatus on port %s at %s>" % (self.http_port, 355 hex(id(self))) 356 return ("<WebStatus on port %s and path %s at %s>" % 357 (self.http_port, self.distrib_port, hex(id(self))))
358
359 - def setServiceParent(self, parent):
360 service.MultiService.setServiceParent(self, parent) 361 362 # this class keeps a *separate* link to the buildmaster, rather than 363 # just using self.parent, so that when we are "disowned" (and thus 364 # parent=None), any remaining HTTP clients of this WebStatus will still 365 # be able to get reasonable results. 366 self.master = parent 367 368 def either(a,b): # a if a else b for py2.4 369 if a: 370 return a 371 else: 372 return b
373 374 rotateLength = either(self.logRotateLength, self.master.log_rotation.rotateLength) 375 maxRotatedFiles = either(self.maxRotatedFiles, self.master.log_rotation.maxRotatedFiles) 376 377 if not self.site: 378 379 class RotateLogSite(server.Site): 380 def _openLogFile(self, path): 381 try: 382 from twisted.python.logfile import LogFile 383 log.msg("Setting up http.log rotating %s files of %s bytes each" % 384 (maxRotatedFiles, rotateLength)) 385 if hasattr(LogFile, "fromFullPath"): # not present in Twisted-2.5.0 386 return LogFile.fromFullPath(path, rotateLength=rotateLength, maxRotatedFiles=maxRotatedFiles) 387 else: 388 log.msg("WebStatus: rotated http logs are not supported on this version of Twisted") 389 except ImportError, e: 390 log.msg("WebStatus: Unable to set up rotating http.log: %s" % e) 391 392 # if all else fails, just call the parent method 393 return server.Site._openLogFile(self, path)
394 395 # this will be replaced once we've been attached to a parent (and 396 # thus have a basedir and can reference BASEDIR) 397 root = static.Data("placeholder", "text/plain") 398 httplog = os.path.abspath(os.path.join(self.master.basedir, "http.log")) 399 self.site = RotateLogSite(root, logPath=httplog) 400 401 # the following items are accessed by HtmlResource when it renders 402 # each page. 403 self.site.buildbot_service = self 404 405 if self.http_port is not None: 406 s = strports.service(self.http_port, self.site) 407 s.setServiceParent(self) 408 if self.distrib_port is not None: 409 f = pb.PBServerFactory(distrib.ResourcePublisher(self.site)) 410 s = strports.service(self.distrib_port, f) 411 s.setServiceParent(self) 412 413 self.setupSite() 414
415 - def setupSite(self):
416 # this is responsible for creating the root resource. It isn't done 417 # at __init__ time because we need to reference the parent's basedir. 418 htmldir = os.path.abspath(os.path.join(self.master.basedir, self.public_html)) 419 if os.path.isdir(htmldir): 420 log.msg("WebStatus using (%s)" % htmldir) 421 else: 422 log.msg("WebStatus: warning: %s is missing. Do you need to run" 423 " 'buildbot upgrade-master' on this buildmaster?" % htmldir) 424 # all static pages will get a 404 until upgrade-master is used to 425 # populate this directory. Create the directory, though, since 426 # otherwise we get internal server errors instead of 404s. 427 os.mkdir(htmldir) 428 429 root = StaticFile(htmldir) 430 root_page = RootPage() 431 root.putChild("", root_page) 432 root.putChild("shutdown", root_page) 433 root.putChild("cancel_shutdown", root_page) 434 435 for name, child_resource in self.childrenToBeAdded.iteritems(): 436 root.putChild(name, child_resource) 437 438 status = self.getStatus() 439 root.putChild("rss", Rss20StatusResource(status)) 440 root.putChild("atom", Atom10StatusResource(status)) 441 root.putChild("json", JsonStatusResource(status)) 442 443 self.site.resource = root
444
445 - def putChild(self, name, child_resource):
446 """This behaves a lot like root.putChild() . """ 447 self.childrenToBeAdded[name] = child_resource
448
449 - def registerChannel(self, channel):
450 self.channels[channel] = 1 # weakrefs
451
452 - def stopService(self):
453 for channel in self.channels: 454 try: 455 channel.transport.loseConnection() 456 except: 457 log.msg("WebStatus.stopService: error while disconnecting" 458 " leftover clients") 459 log.err() 460 return service.MultiService.stopService(self)
461
462 - def getStatus(self):
463 return self.master.getStatus()
464
465 - def getChangeSvc(self):
466 return self.master.change_svc
467
468 - def getPortnum(self):
469 # this is for the benefit of unit tests 470 s = list(self)[0] 471 return s._port.getHost().port
472 473 # What happened to getControl?! 474 # 475 # instead of passing control objects all over the place in the web 476 # code, at the few places where a control instance is required we 477 # find the requisite object manually, starting at the buildmaster. 478 # This is in preparation for removal of the IControl hierarchy 479 # entirely. 480 481 # resources can get access to the IStatus by calling 482 # request.site.buildbot_service.getStatus() 483