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

Source Code for Module buildbot.status.web.baseweb

  1   
  2  import os, sys, urllib, weakref 
  3  from itertools import count 
  4   
  5  from zope.interface import implements 
  6  from twisted.python import log 
  7  from twisted.application import strports, service 
  8  from twisted.web import server, distrib, static, html 
  9  from twisted.spread import pb 
 10   
 11  from buildbot.interfaces import IControl, IStatusReceiver 
 12   
 13  from buildbot.status.web.base import HtmlResource, Box, \ 
 14       build_get_class, ICurrentBox, OneLineMixin, map_branches, \ 
 15       make_stop_form, make_force_build_form 
 16  from buildbot.status.web.feeds import Rss20StatusResource, \ 
 17       Atom10StatusResource 
 18  from buildbot.status.web.waterfall import WaterfallStatusResource 
 19  from buildbot.status.web.console import ConsoleStatusResource 
 20  from buildbot.status.web.grid import GridStatusResource, TransposedGridStatusResource 
 21  from buildbot.status.web.changes import ChangesResource 
 22  from buildbot.status.web.builder import BuildersResource 
 23  from buildbot.status.web.buildstatus import BuildStatusStatusResource  
 24  from buildbot.status.web.slaves import BuildSlavesResource 
 25  from buildbot.status.web.xmlrpc import XMLRPCServer 
 26  from buildbot.status.web.about import AboutBuildbot 
 27  from buildbot.status.web.auth import IAuth, AuthFailResource 
 28   
 29  # this class contains the status services (WebStatus and the older Waterfall) 
 30  # which can be put in c['status']. It also contains some of the resources 
 31  # that are attached to the WebStatus at various well-known URLs, which the 
 32  # admin might wish to attach (using WebStatus.putChild) at other URLs. 
 33   
 34   
35 -class LastBuild(HtmlResource):
36 - def body(self, request):
37 return "missing\n"
38
39 -def getLastNBuilds(status, numbuilds, builders=[], branches=[]):
40 """Return a list with the last few Builds, sorted by start time. 41 builder_names=None means all builders 42 """ 43 44 # TODO: this unsorts the list of builder names, ick 45 builder_names = set(status.getBuilderNames()) 46 if builders: 47 builder_names = builder_names.intersection(set(builders)) 48 49 # to make sure that we get everything, we must get 'numbuilds' builds 50 # from *each* source, then sort by ending time, then trim to the last 51 # 20. We could be more efficient, but it would require the same 52 # gnarly code that the Waterfall uses to generate one event at a 53 # time. TODO: factor that code out into some useful class. 54 events = [] 55 for builder_name in builder_names: 56 builder = status.getBuilder(builder_name) 57 for build_number in count(1): 58 if build_number > numbuilds: 59 break # enough from this builder, move on to another 60 build = builder.getBuild(-build_number) 61 if not build: 62 break # no more builds here, move on to the next builder 63 #if not build.isFinished(): 64 # continue 65 (build_start, build_end) = build.getTimes() 66 event = (build_start, builder_name, build) 67 events.append(event) 68 def _sorter(a, b): 69 return cmp( a[:2], b[:2] )
70 events.sort(_sorter) 71 # now only return the actual build, and only return some of them 72 return [e[2] for e in events[-numbuilds:]] 73 74 75 # /one_line_per_build 76 # accepts builder=, branch=, numbuilds=, reload=
77 -class OneLinePerBuild(HtmlResource, OneLineMixin):
78 """This shows one line per build, combining all builders together. Useful 79 query arguments: 80 81 numbuilds=: how many lines to display 82 builder=: show only builds for this builder. Multiple builder= arguments 83 can be used to see builds from any builder in the set. 84 reload=: reload the page after this many seconds 85 """ 86 87 title = "Recent Builds" 88
89 - def __init__(self, numbuilds=20):
90 HtmlResource.__init__(self) 91 self.numbuilds = numbuilds
92
93 - def getChild(self, path, req):
94 status = self.getStatus(req) 95 builder = status.getBuilder(path) 96 return OneLinePerBuildOneBuilder(builder, numbuilds=self.numbuilds)
97
98 - def get_reload_time(self, request):
99 if "reload" in request.args: 100 try: 101 reload_time = int(request.args["reload"][0]) 102 return max(reload_time, 15) 103 except ValueError: 104 pass 105 return None
106
107 - def head(self, request):
108 head = '' 109 reload_time = self.get_reload_time(request) 110 if reload_time is not None: 111 head += '<meta http-equiv="refresh" content="%d">\n' % reload_time 112 return head
113
114 - def body(self, req):
115 status = self.getStatus(req) 116 control = self.getControl(req) 117 numbuilds = int(req.args.get("numbuilds", [self.numbuilds])[0]) 118 builders = req.args.get("builder", []) 119 branches = [b for b in req.args.get("branch", []) if b] 120 121 g = status.generateFinishedBuilds(builders, map_branches(branches), 122 numbuilds, max_search=numbuilds) 123 124 data = "" 125 126 # really this is "up to %d builds" 127 html_branches = map(html.escape, branches) 128 data += "<h1>Last %d finished builds: %s</h1>\n" % \ 129 (numbuilds, ", ".join(html_branches)) 130 if builders: 131 html_builders = map(html.escape, builders) 132 data += ("<p>of builders: %s</p>\n" % (", ".join(html_builders))) 133 data += "<ul>\n" 134 got = 0 135 building = False 136 online = 0 137 for build in g: 138 got += 1 139 data += " <li>" + self.make_line(req, build) + "</li>\n" 140 builder_status = build.getBuilder().getState()[0] 141 if builder_status == "building": 142 building = True 143 online += 1 144 elif builder_status != "offline": 145 online += 1 146 if not got: 147 data += " <li>No matching builds found</li>\n" 148 data += "</ul>\n" 149 150 if control is not None: 151 if building: 152 stopURL = "builders/_all/stop" 153 data += make_stop_form(stopURL, self.isUsingUserPasswd(req), 154 True, "Builds") 155 if online: 156 forceURL = "builders/_all/force" 157 data += make_force_build_form(forceURL, 158 self.isUsingUserPasswd(req), True) 159 160 return data
161 162 163 164 # /one_line_per_build/$BUILDERNAME 165 # accepts branch=, numbuilds= 166
167 -class OneLinePerBuildOneBuilder(HtmlResource, OneLineMixin):
168 - def __init__(self, builder, numbuilds=20):
169 HtmlResource.__init__(self) 170 self.builder = builder 171 self.builder_name = builder.getName() 172 self.numbuilds = numbuilds 173 self.title = "Recent Builds of %s" % self.builder_name
174
175 - def body(self, req):
176 status = self.getStatus(req) 177 numbuilds = int(req.args.get("numbuilds", [self.numbuilds])[0]) 178 branches = [b for b in req.args.get("branch", []) if b] 179 180 # walk backwards through all builds of a single builder 181 g = self.builder.generateFinishedBuilds(map_branches(branches), 182 numbuilds) 183 184 data = "" 185 html_branches = map(html.escape, branches) 186 data += ("<h1>Last %d builds of builder %s: %s</h1>\n" % 187 (numbuilds, self.builder_name, ", ".join(html_branches))) 188 data += "<ul>\n" 189 got = 0 190 for build in g: 191 got += 1 192 data += " <li>" + self.make_line(req, build) + "</li>\n" 193 if not got: 194 data += " <li>No matching builds found</li>\n" 195 data += "</ul>\n" 196 197 return data
198 199 # /one_box_per_builder 200 # accepts builder=, branch=
201 -class OneBoxPerBuilder(HtmlResource):
202 """This shows a narrow table with one row per builder. The leftmost column 203 contains the builder name. The next column contains the results of the 204 most recent build. The right-hand column shows the builder's current 205 activity. 206 207 builder=: show only builds for this builder. Multiple builder= arguments 208 can be used to see builds from any builder in the set. 209 """ 210 211 title = "Latest Build" 212
213 - def body(self, req):
214 status = self.getStatus(req) 215 control = self.getControl(req) 216 217 builders = req.args.get("builder", status.getBuilderNames()) 218 branches = [b for b in req.args.get("branch", []) if b] 219 220 data = "" 221 222 html_branches = map(html.escape, branches) 223 data += "<h2>Latest builds: %s</h2>\n" % ", ".join(html_branches) 224 data += "<table>\n" 225 226 building = False 227 online = 0 228 base_builders_url = self.path_to_root(req) + "builders/" 229 for bn in builders: 230 base_builder_url = base_builders_url + urllib.quote(bn, safe='') 231 builder = status.getBuilder(bn) 232 data += "<tr>\n" 233 data += '<td class="box"><a href="%s">%s</a></td>\n' \ 234 % (base_builder_url, html.escape(bn)) 235 builds = list(builder.generateFinishedBuilds(map_branches(branches), 236 num_builds=1)) 237 if builds: 238 b = builds[0] 239 url = (base_builder_url + "/builds/%d" % b.getNumber()) 240 try: 241 label = b.getProperty("got_revision") 242 except KeyError: 243 label = None 244 if not label or len(str(label)) > 20: 245 label = "#%d" % b.getNumber() 246 text = ['<a href="%s">%s</a>' % (url, label)] 247 text.extend(b.getText()) 248 box = Box(text, 249 class_="LastBuild box %s" % build_get_class(b)) 250 data += box.td(align="center") 251 else: 252 data += '<td class="LastBuild box" >no build</td>\n' 253 current_box = ICurrentBox(builder).getBox(status) 254 data += current_box.td(align="center") 255 256 builder_status = builder.getState()[0] 257 if builder_status == "building": 258 building = True 259 online += 1 260 elif builder_status != "offline": 261 online += 1 262 263 data += "</table>\n" 264 265 if control is not None: 266 if building: 267 stopURL = "builders/_all/stop" 268 data += make_stop_form(stopURL, self.isUsingUserPasswd(req), 269 True, "Builds") 270 if online: 271 forceURL = "builders/_all/force" 272 data += make_force_build_form(forceURL, 273 self.isUsingUserPasswd(req), True) 274 275 return data
276 277 278 279 HEADER = ''' 280 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 281 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 282 283 <html 284 xmlns="http://www.w3.org/1999/xhtml" 285 lang="en" 286 xml:lang="en"> 287 ''' 288 289 HEAD_ELEMENTS = [ 290 '<title>%(title)s</title>', 291 '<link href="%(root)sbuildbot.css" rel="stylesheet" type="text/css" />', 292 ] 293 BODY_ATTRS = { 294 'vlink': "#800080", 295 } 296 297 FOOTER = ''' 298 </html> 299 ''' 300 301
302 -class WebStatus(service.MultiService):
303 implements(IStatusReceiver) 304 # TODO: IStatusReceiver is really about things which subscribe to hear 305 # about buildbot events. We need a different interface (perhaps a parent 306 # of IStatusReceiver) for status targets that don't subscribe, like the 307 # WebStatus class. buildbot.master.BuildMaster.loadConfig:737 asserts 308 # that everything in c['status'] provides IStatusReceiver, but really it 309 # should check that they provide IStatusTarget instead. 310 311 """ 312 The webserver provided by this class has the following resources: 313 314 /waterfall : the big time-oriented 'waterfall' display, with links 315 to individual changes, builders, builds, steps, and logs. 316 A number of query-arguments can be added to influence 317 the display. 318 /rss : a rss feed summarizing all failed builds. The same 319 query-arguments used by 'waterfall' can be added to 320 influence the feed output. 321 /atom : an atom feed summarizing all failed builds. The same 322 query-arguments used by 'waterfall' can be added to 323 influence the feed output. 324 /grid : another summary display that shows a grid of builds, with 325 sourcestamps on the x axis, and builders on the y. Query 326 arguments similar to those for the waterfall can be added. 327 /tgrid : similar to the grid display, but the commits are down the 328 left side, and the build hosts are across the top. 329 /builders/BUILDERNAME: a page summarizing the builder. This includes 330 references to the Schedulers that feed it, 331 any builds currently in the queue, which 332 buildslaves are designated or attached, and a 333 summary of the build process it uses. 334 /builders/BUILDERNAME/builds/NUM: a page describing a single Build 335 /builders/BUILDERNAME/builds/NUM/steps/STEPNAME: describes a single step 336 /builders/BUILDERNAME/builds/NUM/steps/STEPNAME/logs/LOGNAME: a StatusLog 337 /builders/BUILDERNAME/builds/NUM/tests : summarize test results 338 /builders/BUILDERNAME/builds/NUM/tests/TEST.NAME: results of one test 339 /builders/_all/{force,stop}: force a build/stop building on all builders. 340 /changes : summarize all ChangeSources 341 /changes/CHANGENUM: a page describing a single Change 342 /schedulers/SCHEDULERNAME: a page describing a Scheduler, including 343 a description of its behavior, a list of the 344 Builders it triggers, and list of the Changes 345 that are queued awaiting the tree-stable 346 timer, and controls to accelerate the timer. 347 /buildslaves : list all BuildSlaves 348 /buildslaves/SLAVENAME : describe a single BuildSlave 349 /one_line_per_build : summarize the last few builds, one line each 350 /one_line_per_build/BUILDERNAME : same, but only for a single builder 351 /one_box_per_builder : show the latest build and current activity 352 /about : describe this buildmaster (Buildbot and support library versions) 353 /xmlrpc : (not yet implemented) an XMLRPC server with build status 354 355 356 All URLs for pages which are not defined here are used to look 357 for files in PUBLIC_HTML, which defaults to BASEDIR/public_html. 358 This means that /robots.txt or /buildbot.css or /favicon.ico can 359 be placed in that directory. 360 361 If an index file (index.html, index.htm, or index, in that order) is 362 present in PUBLIC_HTML, it will be used for the root resource. If not, 363 the default behavior is to put a redirection to the /waterfall page. 364 365 All of the resources provided by this service use relative URLs to reach 366 each other. The only absolute links are the c['projectURL'] links at the 367 top and bottom of the page, and the buildbot home-page link at the 368 bottom. 369 370 This webserver defines class attributes on elements so they can be styled 371 with CSS stylesheets. All pages pull in PUBLIC_HTML/buildbot.css, and you 372 can cause additional stylesheets to be loaded by adding a suitable <link> 373 to the WebStatus instance's .head_elements attribute. 374 375 Buildbot uses some generic classes to identify the type of object, and 376 some more specific classes for the various kinds of those types. It does 377 this by specifying both in the class attributes where applicable, 378 separated by a space. It is important that in your CSS you declare the 379 more generic class styles above the more specific ones. For example, 380 first define a style for .Event, and below that for .SUCCESS 381 382 The following CSS class names are used: 383 - Activity, Event, BuildStep, LastBuild: general classes 384 - waiting, interlocked, building, offline, idle: Activity states 385 - start, running, success, failure, warnings, skipped, exception: 386 LastBuild and BuildStep states 387 - Change: box with change 388 - Builder: box for builder name (at top) 389 - Project 390 - Time 391 392 """ 393 394 # we are not a ComparableMixin, and therefore the webserver will be 395 # rebuilt every time we reconfig. This is because WebStatus.putChild() 396 # makes it too difficult to tell whether two instances are the same or 397 # not (we'd have to do a recursive traversal of all children to discover 398 # all the changes). 399
400 - def __init__(self, http_port=None, distrib_port=None, allowForce=False, 401 public_html="public_html", site=None, numbuilds=20, 402 num_events=200, num_events_max=None, auth=None, 403 order_console_by_time=False):
404 """Run a web server that provides Buildbot status. 405 406 @type http_port: int or L{twisted.application.strports} string 407 @param http_port: a strports specification describing which port the 408 buildbot should use for its web server, with the 409 Waterfall display as the root page. For backwards 410 compatibility this can also be an int. Use 411 'tcp:8000' to listen on that port, or 412 'tcp:12345:interface=127.0.0.1' if you only want 413 local processes to connect to it (perhaps because 414 you are using an HTTP reverse proxy to make the 415 buildbot available to the outside world, and do not 416 want to make the raw port visible). 417 418 @type distrib_port: int or L{twisted.application.strports} string 419 @param distrib_port: Use this if you want to publish the Waterfall 420 page using web.distrib instead. The most common 421 case is to provide a string that is an absolute 422 pathname to the unix socket on which the 423 publisher should listen 424 (C{os.path.expanduser(~/.twistd-web-pb)} will 425 match the default settings of a standard 426 twisted.web 'personal web server'). Another 427 possibility is to pass an integer, which means 428 the publisher should listen on a TCP socket, 429 allowing the web server to be on a different 430 machine entirely. Both forms are provided for 431 backwards compatibility; the preferred form is a 432 strports specification like 433 'unix:/home/buildbot/.twistd-web-pb'. Providing 434 a non-absolute pathname will probably confuse 435 the strports parser. 436 437 @param allowForce: boolean, if True then the webserver will allow 438 visitors to trigger and cancel builds 439 440 @param public_html: the path to the public_html directory for this display, 441 either absolute or relative to the basedir. The default 442 is 'public_html', which selects BASEDIR/public_html. 443 444 @type site: None or L{twisted.web.server.Site} 445 @param site: Use this if you want to define your own object instead of 446 using the default.` 447 448 @type numbuilds: int 449 @param numbuilds: Default number of entries in lists at the /one_line_per_build 450 and /builders/FOO URLs. This default can be overriden both programatically --- 451 by passing the equally named argument to constructors of OneLinePerBuildOneBuilder 452 and OneLinePerBuild --- and via the UI, by tacking ?numbuilds=xy onto the URL. 453 454 @type num_events: int 455 @param num_events: Defaualt number of events to show in the waterfall. 456 457 @type num_events_max: int 458 @param num_events_max: The maximum number of events that are allowed to be 459 shown in the waterfall. The default value of C{None} will disable this 460 check 461 462 @type auth: a L{status.web.auth.IAuth} or C{None} 463 @param auth: an object that performs authentication to restrict access 464 to the C{allowForce} features. Ignored if C{allowForce} 465 is not C{True}. If C{auth} is C{None}, people can force or 466 stop builds without auth. 467 468 @type order_console_by_time: bool 469 @param order_console_by_time: Whether to order changes (commits) in the console 470 view according to the time they were created (for VCS like Git) or 471 according to their integer revision numbers (for VCS like SVN). 472 """ 473 474 service.MultiService.__init__(self) 475 if type(http_port) is int: 476 http_port = "tcp:%d" % http_port 477 self.http_port = http_port 478 if distrib_port is not None: 479 if type(distrib_port) is int: 480 distrib_port = "tcp:%d" % distrib_port 481 if distrib_port[0] in "/~.": # pathnames 482 distrib_port = "unix:%s" % distrib_port 483 self.distrib_port = distrib_port 484 self.allowForce = allowForce 485 self.num_events = num_events 486 if num_events_max: 487 assert num_events_max >= num_events 488 self.num_events_max = num_events_max 489 self.public_html = public_html 490 491 if self.allowForce and auth: 492 assert IAuth.providedBy(auth) 493 self.auth = auth 494 else: 495 if auth: 496 log.msg("Warning: Ignoring authentication. allowForce must be" 497 " set to True use this") 498 self.auth = None 499 500 self.orderConsoleByTime = order_console_by_time 501 502 # If we were given a site object, go ahead and use it. 503 if site: 504 self.site = site 505 else: 506 # this will be replaced once we've been attached to a parent (and 507 # thus have a basedir and can reference BASEDIR) 508 root = static.Data("placeholder", "text/plain") 509 self.site = server.Site(root) 510 self.childrenToBeAdded = {} 511 512 self.setupUsualPages(numbuilds=numbuilds, num_events=num_events, 513 num_events_max=num_events_max) 514 515 # the following items are accessed by HtmlResource when it renders 516 # each page. 517 self.site.buildbot_service = self 518 self.header = HEADER 519 self.head_elements = HEAD_ELEMENTS[:] 520 self.body_attrs = BODY_ATTRS.copy() 521 self.footer = FOOTER 522 self.template_values = {} 523 524 # keep track of cached connections so we can break them when we shut 525 # down. See ticket #102 for more details. 526 self.channels = weakref.WeakKeyDictionary() 527 528 if self.http_port is not None: 529 s = strports.service(self.http_port, self.site) 530 s.setServiceParent(self) 531 if self.distrib_port is not None: 532 f = pb.PBServerFactory(distrib.ResourcePublisher(self.site)) 533 s = strports.service(self.distrib_port, f) 534 s.setServiceParent(self)
535
536 - def setupUsualPages(self, numbuilds, num_events, num_events_max):
537 #self.putChild("", IndexOrWaterfallRedirection()) 538 self.putChild("waterfall", WaterfallStatusResource(num_events=num_events, 539 num_events_max=num_events_max)) 540 self.putChild("grid", GridStatusResource()) 541 self.putChild("console", ConsoleStatusResource( 542 orderByTime=self.orderConsoleByTime)) 543 self.putChild("tgrid", TransposedGridStatusResource()) 544 self.putChild("builders", BuildersResource()) # has builds/steps/logs 545 self.putChild("changes", ChangesResource()) 546 self.putChild("buildslaves", BuildSlavesResource()) 547 self.putChild("buildstatus", BuildStatusStatusResource()) 548 #self.putChild("schedulers", SchedulersResource()) 549 self.putChild("one_line_per_build", 550 OneLinePerBuild(numbuilds=numbuilds)) 551 self.putChild("one_box_per_builder", OneBoxPerBuilder()) 552 self.putChild("xmlrpc", XMLRPCServer()) 553 self.putChild("about", AboutBuildbot()) 554 self.putChild("authfail", AuthFailResource())
555
556 - def __repr__(self):
557 if self.http_port is None: 558 return "<WebStatus on path %s at %s>" % (self.distrib_port, 559 hex(id(self))) 560 if self.distrib_port is None: 561 return "<WebStatus on port %s at %s>" % (self.http_port, 562 hex(id(self))) 563 return ("<WebStatus on port %s and path %s at %s>" % 564 (self.http_port, self.distrib_port, hex(id(self))))
565
566 - def setServiceParent(self, parent):
567 service.MultiService.setServiceParent(self, parent) 568 569 # this class keeps a *separate* link to the buildmaster, rather than 570 # just using self.parent, so that when we are "disowned" (and thus 571 # parent=None), any remaining HTTP clients of this WebStatus will still 572 # be able to get reasonable results. 573 self.master = parent 574 575 self.setupSite()
576
577 - def setupSite(self):
578 # this is responsible for creating the root resource. It isn't done 579 # at __init__ time because we need to reference the parent's basedir. 580 htmldir = os.path.abspath(os.path.join(self.master.basedir, self.public_html)) 581 if os.path.isdir(htmldir): 582 log.msg("WebStatus using (%s)" % htmldir) 583 else: 584 log.msg("WebStatus: warning: %s is missing. Do you need to run" 585 " 'buildbot upgrade-master' on this buildmaster?" % htmldir) 586 # all static pages will get a 404 until upgrade-master is used to 587 # populate this directory. Create the directory, though, since 588 # otherwise we get internal server errors instead of 404s. 589 os.mkdir(htmldir) 590 root = static.File(htmldir) 591 592 for name, child_resource in self.childrenToBeAdded.iteritems(): 593 root.putChild(name, child_resource) 594 595 status = self.getStatus() 596 root.putChild("rss", Rss20StatusResource(status)) 597 root.putChild("atom", Atom10StatusResource(status)) 598 599 self.site.resource = root
600
601 - def putChild(self, name, child_resource):
602 """This behaves a lot like root.putChild() . """ 603 self.childrenToBeAdded[name] = child_resource
604
605 - def registerChannel(self, channel):
606 self.channels[channel] = 1 # weakrefs
607
608 - def stopService(self):
609 for channel in self.channels: 610 try: 611 channel.transport.loseConnection() 612 except: 613 log.msg("WebStatus.stopService: error while disconnecting" 614 " leftover clients") 615 log.err() 616 return service.MultiService.stopService(self)
617
618 - def getStatus(self):
619 return self.master.getStatus()
620
621 - def getControl(self):
622 if self.allowForce: 623 return IControl(self.master) 624 return None
625
626 - def getChangeSvc(self):
627 return self.master.change_svc
628
629 - def getPortnum(self):
630 # this is for the benefit of unit tests 631 s = list(self)[0] 632 return s._port.getHost().port
633
634 - def isUsingUserPasswd(self):
635 """Returns boolean to indicate if this WebStatus uses authentication""" 636 if self.auth: 637 return True 638 return False
639
640 - def authUser(self, user, passwd):
641 """Check that user/passwd is a valid user/pass tuple and can should be 642 allowed to perform the action. If this WebStatus is not password 643 protected, this function returns False.""" 644 if not self.isUsingUserPasswd(): 645 return False 646 if self.auth.authenticate(user, passwd): 647 return True 648 log.msg("Authentication failed for '%s': %s" % (user, 649 self.auth.errmsg())) 650 return False
651 652 # resources can get access to the IStatus by calling 653 # request.site.buildbot_service.getStatus() 654 655 # this is the compatibility class for the old waterfall. It is exactly like a 656 # regular WebStatus except that the root resource (e.g. http://buildbot.net/) 657 # always redirects to a WaterfallStatusResource, and the old arguments are 658 # mapped into the new resource-tree approach. In the normal WebStatus, the 659 # root resource either redirects the browser to /waterfall or serves 660 # PUBLIC_HTML/index.html, and favicon/robots.txt are provided by 661 # having the admin write actual files into PUBLIC_HTML/ . 662 663 # note: we don't use a util.Redirect here because HTTP requires that the 664 # Location: header provide an absolute URI, and it's non-trivial to figure 665 # out our absolute URI from here. 666
667 -class Waterfall(WebStatus):
668 669 if hasattr(sys, "frozen"): 670 # all 'data' files are in the directory of our executable 671 here = os.path.dirname(sys.executable) 672 buildbot_icon = os.path.abspath(os.path.join(here, "buildbot.png")) 673 buildbot_css = os.path.abspath(os.path.join(here, "classic.css")) 674 else: 675 # running from source 676 # the icon is sibpath(__file__, "../buildbot.png") . This is for 677 # portability. 678 up = os.path.dirname 679 buildbot_icon = os.path.abspath(os.path.join(up(up(up(__file__))), 680 "buildbot.png")) 681 buildbot_css = os.path.abspath(os.path.join(up(__file__), 682 "classic.css")) 683 684 compare_attrs = ["http_port", "distrib_port", "allowForce", 685 "categories", "css", "favicon", "robots_txt"] 686
687 - def __init__(self, http_port=None, distrib_port=None, allowForce=True, 688 categories=None, css=buildbot_css, favicon=buildbot_icon, 689 robots_txt=None):
690 import warnings 691 m = ("buildbot.status.html.Waterfall is deprecated as of 0.7.6 " 692 "and will be removed from a future release. " 693 "Please use html.WebStatus instead.") 694 warnings.warn(m, DeprecationWarning) 695 696 WebStatus.__init__(self, http_port, distrib_port, allowForce) 697 self.css = css 698 if css: 699 if os.path.exists(os.path.join("public_html", "buildbot.css")): 700 # they've upgraded, so defer to that copy instead 701 pass 702 else: 703 data = open(css, "rb").read() 704 self.putChild("buildbot.css", static.Data(data, "text/css")) 705 self.favicon = favicon 706 self.robots_txt = robots_txt 707 if favicon: 708 data = open(favicon, "rb").read() 709 self.putChild("favicon.ico", static.Data(data, "image/x-icon")) 710 if robots_txt: 711 data = open(robots_txt, "rb").read() 712 self.putChild("robots.txt", static.Data(data, "text/plain")) 713 self.putChild("", WaterfallStatusResource(categories))
714