Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/buildbot/buildbot/status/web/baseweb.py
diff options
context:
space:
mode:
Diffstat (limited to 'buildbot/buildbot/status/web/baseweb.py')
-rw-r--r--buildbot/buildbot/status/web/baseweb.py614
1 files changed, 0 insertions, 614 deletions
diff --git a/buildbot/buildbot/status/web/baseweb.py b/buildbot/buildbot/status/web/baseweb.py
deleted file mode 100644
index a963a9a..0000000
--- a/buildbot/buildbot/status/web/baseweb.py
+++ /dev/null
@@ -1,614 +0,0 @@
-
-import os, sys, urllib, weakref
-from itertools import count
-
-from zope.interface import implements
-from twisted.python import log
-from twisted.application import strports, service
-from twisted.web import server, distrib, static, html
-from twisted.spread import pb
-
-from buildbot.interfaces import IControl, IStatusReceiver
-
-from buildbot.status.web.base import HtmlResource, Box, \
- build_get_class, ICurrentBox, OneLineMixin, map_branches, \
- make_stop_form, make_force_build_form
-from buildbot.status.web.feeds import Rss20StatusResource, \
- Atom10StatusResource
-from buildbot.status.web.waterfall import WaterfallStatusResource
-from buildbot.status.web.grid import GridStatusResource
-from buildbot.status.web.changes import ChangesResource
-from buildbot.status.web.builder import BuildersResource
-from buildbot.status.web.slaves import BuildSlavesResource
-from buildbot.status.web.xmlrpc import XMLRPCServer
-from buildbot.status.web.about import AboutBuildbot
-
-# this class contains the status services (WebStatus and the older Waterfall)
-# which can be put in c['status']. It also contains some of the resources
-# that are attached to the WebStatus at various well-known URLs, which the
-# admin might wish to attach (using WebStatus.putChild) at other URLs.
-
-
-class LastBuild(HtmlResource):
- def body(self, request):
- return "missing\n"
-
-def getLastNBuilds(status, numbuilds, builders=[], branches=[]):
- """Return a list with the last few Builds, sorted by start time.
- builder_names=None means all builders
- """
-
- # TODO: this unsorts the list of builder names, ick
- builder_names = set(status.getBuilderNames())
- if builders:
- builder_names = builder_names.intersection(set(builders))
-
- # to make sure that we get everything, we must get 'numbuilds' builds
- # from *each* source, then sort by ending time, then trim to the last
- # 20. We could be more efficient, but it would require the same
- # gnarly code that the Waterfall uses to generate one event at a
- # time. TODO: factor that code out into some useful class.
- events = []
- for builder_name in builder_names:
- builder = status.getBuilder(builder_name)
- for build_number in count(1):
- if build_number > numbuilds:
- break # enough from this builder, move on to another
- build = builder.getBuild(-build_number)
- if not build:
- break # no more builds here, move on to the next builder
- #if not build.isFinished():
- # continue
- (build_start, build_end) = build.getTimes()
- event = (build_start, builder_name, build)
- events.append(event)
- def _sorter(a, b):
- return cmp( a[:2], b[:2] )
- events.sort(_sorter)
- # now only return the actual build, and only return some of them
- return [e[2] for e in events[-numbuilds:]]
-
-
-# /one_line_per_build
-# accepts builder=, branch=, numbuilds=
-class OneLinePerBuild(HtmlResource, OneLineMixin):
- """This shows one line per build, combining all builders together. Useful
- query arguments:
-
- numbuilds=: how many lines to display
- builder=: show only builds for this builder. Multiple builder= arguments
- can be used to see builds from any builder in the set.
- """
-
- title = "Recent Builds"
-
- def __init__(self, numbuilds=20):
- HtmlResource.__init__(self)
- self.numbuilds = numbuilds
-
- def getChild(self, path, req):
- status = self.getStatus(req)
- builder = status.getBuilder(path)
- return OneLinePerBuildOneBuilder(builder)
-
- def body(self, req):
- status = self.getStatus(req)
- control = self.getControl(req)
- numbuilds = int(req.args.get("numbuilds", [self.numbuilds])[0])
- builders = req.args.get("builder", [])
- branches = [b for b in req.args.get("branch", []) if b]
-
- g = status.generateFinishedBuilds(builders, map_branches(branches),
- numbuilds)
-
- data = ""
-
- # really this is "up to %d builds"
- data += "<h1>Last %d finished builds: %s</h1>\n" % \
- (numbuilds, ", ".join(branches))
- if builders:
- data += ("<p>of builders: %s</p>\n" % (", ".join(builders)))
- data += "<ul>\n"
- got = 0
- building = False
- online = 0
- for build in g:
- got += 1
- data += " <li>" + self.make_line(req, build) + "</li>\n"
- builder_status = build.getBuilder().getState()[0]
- if builder_status == "building":
- building = True
- online += 1
- elif builder_status != "offline":
- online += 1
- if not got:
- data += " <li>No matching builds found</li>\n"
- data += "</ul>\n"
-
- if control is not None:
- if building:
- stopURL = "builders/_all/stop"
- data += make_stop_form(stopURL, True, "Builds")
- if online:
- forceURL = "builders/_all/force"
- data += make_force_build_form(forceURL, True)
-
- return data
-
-
-
-# /one_line_per_build/$BUILDERNAME
-# accepts branch=, numbuilds=
-
-class OneLinePerBuildOneBuilder(HtmlResource, OneLineMixin):
- def __init__(self, builder, numbuilds=20):
- HtmlResource.__init__(self)
- self.builder = builder
- self.builder_name = builder.getName()
- self.numbuilds = numbuilds
- self.title = "Recent Builds of %s" % self.builder_name
-
- def body(self, req):
- status = self.getStatus(req)
- numbuilds = int(req.args.get("numbuilds", [self.numbuilds])[0])
- branches = [b for b in req.args.get("branch", []) if b]
-
- # walk backwards through all builds of a single builder
- g = self.builder.generateFinishedBuilds(map_branches(branches),
- numbuilds)
-
- data = ""
- data += ("<h1>Last %d builds of builder %s: %s</h1>\n" %
- (numbuilds, self.builder_name, ", ".join(branches)))
- data += "<ul>\n"
- got = 0
- for build in g:
- got += 1
- data += " <li>" + self.make_line(req, build) + "</li>\n"
- if not got:
- data += " <li>No matching builds found</li>\n"
- data += "</ul>\n"
-
- return data
-
-# /one_box_per_builder
-# accepts builder=, branch=
-class OneBoxPerBuilder(HtmlResource):
- """This shows a narrow table with one row per builder. The leftmost column
- contains the builder name. The next column contains the results of the
- most recent build. The right-hand column shows the builder's current
- activity.
-
- builder=: show only builds for this builder. Multiple builder= arguments
- can be used to see builds from any builder in the set.
- """
-
- title = "Latest Build"
-
- def body(self, req):
- status = self.getStatus(req)
- control = self.getControl(req)
-
- builders = req.args.get("builder", status.getBuilderNames())
- branches = [b for b in req.args.get("branch", []) if b]
-
- data = ""
-
- data += "<h2>Latest builds: %s</h2>\n" % ", ".join(branches)
- data += "<table>\n"
-
- building = False
- online = 0
- base_builders_url = self.path_to_root(req) + "builders/"
- for bn in builders:
- base_builder_url = base_builders_url + urllib.quote(bn, safe='')
- builder = status.getBuilder(bn)
- data += "<tr>\n"
- data += '<td class="box"><a href="%s">%s</a></td>\n' \
- % (base_builder_url, html.escape(bn))
- builds = list(builder.generateFinishedBuilds(map_branches(branches),
- num_builds=1))
- if builds:
- b = builds[0]
- url = (base_builder_url + "/builds/%d" % b.getNumber())
- try:
- label = b.getProperty("got_revision")
- except KeyError:
- label = None
- if not label or len(str(label)) > 20:
- label = "#%d" % b.getNumber()
- text = ['<a href="%s">%s</a>' % (url, label)]
- text.extend(b.getText())
- box = Box(text,
- class_="LastBuild box %s" % build_get_class(b))
- data += box.td(align="center")
- else:
- data += '<td class="LastBuild box" >no build</td>\n'
- current_box = ICurrentBox(builder).getBox(status)
- data += current_box.td(align="center")
-
- builder_status = builder.getState()[0]
- if builder_status == "building":
- building = True
- online += 1
- elif builder_status != "offline":
- online += 1
-
- data += "</table>\n"
-
- if control is not None:
- if building:
- stopURL = "builders/_all/stop"
- data += make_stop_form(stopURL, True, "Builds")
- if online:
- forceURL = "builders/_all/force"
- data += make_force_build_form(forceURL, True)
-
- return data
-
-
-
-HEADER = '''
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-
-<html
- xmlns="http://www.w3.org/1999/xhtml"
- lang="en"
- xml:lang="en">
-'''
-
-HEAD_ELEMENTS = [
- '<title>%(title)s</title>',
- '<link href="%(root)sbuildbot.css" rel="stylesheet" type="text/css" />',
- ]
-BODY_ATTRS = {
- 'vlink': "#800080",
- }
-
-FOOTER = '''
-</html>
-'''
-
-
-class WebStatus(service.MultiService):
- implements(IStatusReceiver)
- # TODO: IStatusReceiver is really about things which subscribe to hear
- # about buildbot events. We need a different interface (perhaps a parent
- # of IStatusReceiver) for status targets that don't subscribe, like the
- # WebStatus class. buildbot.master.BuildMaster.loadConfig:737 asserts
- # that everything in c['status'] provides IStatusReceiver, but really it
- # should check that they provide IStatusTarget instead.
-
- """
- The webserver provided by this class has the following resources:
-
- /waterfall : the big time-oriented 'waterfall' display, with links
- to individual changes, builders, builds, steps, and logs.
- A number of query-arguments can be added to influence
- the display.
- /rss : a rss feed summarizing all failed builds. The same
- query-arguments used by 'waterfall' can be added to
- influence the feed output.
- /atom : an atom feed summarizing all failed builds. The same
- query-arguments used by 'waterfall' can be added to
- influence the feed output.
- /grid : another summary display that shows a grid of builds, with
- sourcestamps on the x axis, and builders on the y. Query
- arguments similar to those for the waterfall can be added.
- /builders/BUILDERNAME: a page summarizing the builder. This includes
- references to the Schedulers that feed it,
- any builds currently in the queue, which
- buildslaves are designated or attached, and a
- summary of the build process it uses.
- /builders/BUILDERNAME/builds/NUM: a page describing a single Build
- /builders/BUILDERNAME/builds/NUM/steps/STEPNAME: describes a single step
- /builders/BUILDERNAME/builds/NUM/steps/STEPNAME/logs/LOGNAME: a StatusLog
- /builders/BUILDERNAME/builds/NUM/tests : summarize test results
- /builders/BUILDERNAME/builds/NUM/tests/TEST.NAME: results of one test
- /builders/_all/{force,stop}: force a build/stop building on all builders.
- /changes : summarize all ChangeSources
- /changes/CHANGENUM: a page describing a single Change
- /schedulers/SCHEDULERNAME: a page describing a Scheduler, including
- a description of its behavior, a list of the
- Builders it triggers, and list of the Changes
- that are queued awaiting the tree-stable
- timer, and controls to accelerate the timer.
- /buildslaves : list all BuildSlaves
- /buildslaves/SLAVENAME : describe a single BuildSlave
- /one_line_per_build : summarize the last few builds, one line each
- /one_line_per_build/BUILDERNAME : same, but only for a single builder
- /one_box_per_builder : show the latest build and current activity
- /about : describe this buildmaster (Buildbot and support library versions)
- /xmlrpc : (not yet implemented) an XMLRPC server with build status
-
-
- All URLs for pages which are not defined here are used to look
- for files in PUBLIC_HTML, which defaults to BASEDIR/public_html.
- This means that /robots.txt or /buildbot.css or /favicon.ico can
- be placed in that directory.
-
- If an index file (index.html, index.htm, or index, in that order) is
- present in PUBLIC_HTML, it will be used for the root resource. If not,
- the default behavior is to put a redirection to the /waterfall page.
-
- All of the resources provided by this service use relative URLs to reach
- each other. The only absolute links are the c['projectURL'] links at the
- top and bottom of the page, and the buildbot home-page link at the
- bottom.
-
- This webserver defines class attributes on elements so they can be styled
- with CSS stylesheets. All pages pull in PUBLIC_HTML/buildbot.css, and you
- can cause additional stylesheets to be loaded by adding a suitable <link>
- to the WebStatus instance's .head_elements attribute.
-
- Buildbot uses some generic classes to identify the type of object, and
- some more specific classes for the various kinds of those types. It does
- this by specifying both in the class attributes where applicable,
- separated by a space. It is important that in your CSS you declare the
- more generic class styles above the more specific ones. For example,
- first define a style for .Event, and below that for .SUCCESS
-
- The following CSS class names are used:
- - Activity, Event, BuildStep, LastBuild: general classes
- - waiting, interlocked, building, offline, idle: Activity states
- - start, running, success, failure, warnings, skipped, exception:
- LastBuild and BuildStep states
- - Change: box with change
- - Builder: box for builder name (at top)
- - Project
- - Time
-
- """
-
- # we are not a ComparableMixin, and therefore the webserver will be
- # rebuilt every time we reconfig. This is because WebStatus.putChild()
- # makes it too difficult to tell whether two instances are the same or
- # not (we'd have to do a recursive traversal of all children to discover
- # all the changes).
-
- def __init__(self, http_port=None, distrib_port=None, allowForce=False,
- public_html="public_html", site=None):
- """Run a web server that provides Buildbot status.
-
- @type http_port: int or L{twisted.application.strports} string
- @param http_port: a strports specification describing which port the
- buildbot should use for its web server, with the
- Waterfall display as the root page. For backwards
- compatibility this can also be an int. Use
- 'tcp:8000' to listen on that port, or
- 'tcp:12345:interface=127.0.0.1' if you only want
- local processes to connect to it (perhaps because
- you are using an HTTP reverse proxy to make the
- buildbot available to the outside world, and do not
- want to make the raw port visible).
-
- @type distrib_port: int or L{twisted.application.strports} string
- @param distrib_port: Use this if you want to publish the Waterfall
- page using web.distrib instead. The most common
- case is to provide a string that is an absolute
- pathname to the unix socket on which the
- publisher should listen
- (C{os.path.expanduser(~/.twistd-web-pb)} will
- match the default settings of a standard
- twisted.web 'personal web server'). Another
- possibility is to pass an integer, which means
- the publisher should listen on a TCP socket,
- allowing the web server to be on a different
- machine entirely. Both forms are provided for
- backwards compatibility; the preferred form is a
- strports specification like
- 'unix:/home/buildbot/.twistd-web-pb'. Providing
- a non-absolute pathname will probably confuse
- the strports parser.
-
- @param allowForce: boolean, if True then the webserver will allow
- visitors to trigger and cancel builds
-
- @param public_html: the path to the public_html directory for this display,
- either absolute or relative to the basedir. The default
- is 'public_html', which selects BASEDIR/public_html.
-
- @type site: None or L{twisted.web.server.Site}
- @param site: Use this if you want to define your own object instead of
- using the default.`
- """
-
- service.MultiService.__init__(self)
- if type(http_port) is int:
- http_port = "tcp:%d" % http_port
- self.http_port = http_port
- if distrib_port is not None:
- if type(distrib_port) is int:
- distrib_port = "tcp:%d" % distrib_port
- if distrib_port[0] in "/~.": # pathnames
- distrib_port = "unix:%s" % distrib_port
- self.distrib_port = distrib_port
- self.allowForce = allowForce
- self.public_html = public_html
-
- # If we were given a site object, go ahead and use it.
- if site:
- self.site = site
- else:
- # this will be replaced once we've been attached to a parent (and
- # thus have a basedir and can reference BASEDIR)
- root = static.Data("placeholder", "text/plain")
- self.site = server.Site(root)
- self.childrenToBeAdded = {}
-
- self.setupUsualPages()
-
- # the following items are accessed by HtmlResource when it renders
- # each page.
- self.site.buildbot_service = self
- self.header = HEADER
- self.head_elements = HEAD_ELEMENTS[:]
- self.body_attrs = BODY_ATTRS.copy()
- self.footer = FOOTER
- self.template_values = {}
-
- # keep track of cached connections so we can break them when we shut
- # down. See ticket #102 for more details.
- self.channels = weakref.WeakKeyDictionary()
-
- if self.http_port is not None:
- s = strports.service(self.http_port, self.site)
- s.setServiceParent(self)
- if self.distrib_port is not None:
- f = pb.PBServerFactory(distrib.ResourcePublisher(self.site))
- s = strports.service(self.distrib_port, f)
- s.setServiceParent(self)
-
- def setupUsualPages(self):
- #self.putChild("", IndexOrWaterfallRedirection())
- self.putChild("waterfall", WaterfallStatusResource())
- self.putChild("grid", GridStatusResource())
- self.putChild("builders", BuildersResource()) # has builds/steps/logs
- self.putChild("changes", ChangesResource())
- self.putChild("buildslaves", BuildSlavesResource())
- #self.putChild("schedulers", SchedulersResource())
- self.putChild("one_line_per_build", OneLinePerBuild())
- self.putChild("one_box_per_builder", OneBoxPerBuilder())
- self.putChild("xmlrpc", XMLRPCServer())
- self.putChild("about", AboutBuildbot())
-
- def __repr__(self):
- if self.http_port is None:
- return "<WebStatus on path %s at %s>" % (self.distrib_port,
- hex(id(self)))
- if self.distrib_port is None:
- return "<WebStatus on port %s at %s>" % (self.http_port,
- hex(id(self)))
- return ("<WebStatus on port %s and path %s at %s>" %
- (self.http_port, self.distrib_port, hex(id(self))))
-
- def setServiceParent(self, parent):
- service.MultiService.setServiceParent(self, parent)
-
- # this class keeps a *separate* link to the buildmaster, rather than
- # just using self.parent, so that when we are "disowned" (and thus
- # parent=None), any remaining HTTP clients of this WebStatus will still
- # be able to get reasonable results.
- self.master = parent
-
- self.setupSite()
-
- def setupSite(self):
- # this is responsible for creating the root resource. It isn't done
- # at __init__ time because we need to reference the parent's basedir.
- htmldir = os.path.abspath(os.path.join(self.master.basedir, self.public_html))
- if os.path.isdir(htmldir):
- log.msg("WebStatus using (%s)" % htmldir)
- else:
- log.msg("WebStatus: warning: %s is missing. Do you need to run"
- " 'buildbot upgrade-master' on this buildmaster?" % htmldir)
- # all static pages will get a 404 until upgrade-master is used to
- # populate this directory. Create the directory, though, since
- # otherwise we get internal server errors instead of 404s.
- os.mkdir(htmldir)
- root = static.File(htmldir)
-
- for name, child_resource in self.childrenToBeAdded.iteritems():
- root.putChild(name, child_resource)
-
- status = self.getStatus()
- root.putChild("rss", Rss20StatusResource(status))
- root.putChild("atom", Atom10StatusResource(status))
-
- self.site.resource = root
-
- def putChild(self, name, child_resource):
- """This behaves a lot like root.putChild() . """
- self.childrenToBeAdded[name] = child_resource
-
- def registerChannel(self, channel):
- self.channels[channel] = 1 # weakrefs
-
- def stopService(self):
- for channel in self.channels:
- try:
- channel.transport.loseConnection()
- except:
- log.msg("WebStatus.stopService: error while disconnecting"
- " leftover clients")
- log.err()
- return service.MultiService.stopService(self)
-
- def getStatus(self):
- return self.master.getStatus()
-
- def getControl(self):
- if self.allowForce:
- return IControl(self.master)
- return None
-
- def getChangeSvc(self):
- return self.master.change_svc
- def getPortnum(self):
- # this is for the benefit of unit tests
- s = list(self)[0]
- return s._port.getHost().port
-
-# resources can get access to the IStatus by calling
-# request.site.buildbot_service.getStatus()
-
-# this is the compatibility class for the old waterfall. It is exactly like a
-# regular WebStatus except that the root resource (e.g. http://buildbot.net/)
-# always redirects to a WaterfallStatusResource, and the old arguments are
-# mapped into the new resource-tree approach. In the normal WebStatus, the
-# root resource either redirects the browser to /waterfall or serves
-# PUBLIC_HTML/index.html, and favicon/robots.txt are provided by
-# having the admin write actual files into PUBLIC_HTML/ .
-
-# note: we don't use a util.Redirect here because HTTP requires that the
-# Location: header provide an absolute URI, and it's non-trivial to figure
-# out our absolute URI from here.
-
-class Waterfall(WebStatus):
-
- if hasattr(sys, "frozen"):
- # all 'data' files are in the directory of our executable
- here = os.path.dirname(sys.executable)
- buildbot_icon = os.path.abspath(os.path.join(here, "buildbot.png"))
- buildbot_css = os.path.abspath(os.path.join(here, "classic.css"))
- else:
- # running from source
- # the icon is sibpath(__file__, "../buildbot.png") . This is for
- # portability.
- up = os.path.dirname
- buildbot_icon = os.path.abspath(os.path.join(up(up(up(__file__))),
- "buildbot.png"))
- buildbot_css = os.path.abspath(os.path.join(up(__file__),
- "classic.css"))
-
- compare_attrs = ["http_port", "distrib_port", "allowForce",
- "categories", "css", "favicon", "robots_txt"]
-
- def __init__(self, http_port=None, distrib_port=None, allowForce=True,
- categories=None, css=buildbot_css, favicon=buildbot_icon,
- robots_txt=None):
- import warnings
- m = ("buildbot.status.html.Waterfall is deprecated as of 0.7.6 "
- "and will be removed from a future release. "
- "Please use html.WebStatus instead.")
- warnings.warn(m, DeprecationWarning)
-
- WebStatus.__init__(self, http_port, distrib_port, allowForce)
- self.css = css
- if css:
- if os.path.exists(os.path.join("public_html", "buildbot.css")):
- # they've upgraded, so defer to that copy instead
- pass
- else:
- data = open(css, "rb").read()
- self.putChild("buildbot.css", static.Data(data, "text/plain"))
- self.favicon = favicon
- self.robots_txt = robots_txt
- if favicon:
- data = open(favicon, "rb").read()
- self.putChild("favicon.ico", static.Data(data, "image/x-icon"))
- if robots_txt:
- data = open(robots_txt, "rb").read()
- self.putChild("robots.txt", static.Data(data, "text/plain"))
- self.putChild("", WaterfallStatusResource(categories))