diff options
Diffstat (limited to 'buildbot/buildbot/status/web/builder.py')
-rw-r--r-- | buildbot/buildbot/status/web/builder.py | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/buildbot/buildbot/status/web/builder.py b/buildbot/buildbot/status/web/builder.py new file mode 100644 index 0000000..35f65e9 --- /dev/null +++ b/buildbot/buildbot/status/web/builder.py @@ -0,0 +1,312 @@ + +from twisted.web.error import NoResource +from twisted.web import html, static +from twisted.web.util import Redirect + +import re, urllib, time +from twisted.python import log +from buildbot import interfaces +from buildbot.status.web.base import HtmlResource, make_row, \ + make_force_build_form, OneLineMixin, path_to_build, path_to_slave, path_to_builder +from buildbot.process.base import BuildRequest +from buildbot.sourcestamp import SourceStamp + +from buildbot.status.web.build import BuildsResource, StatusResourceBuild + +# /builders/$builder +class StatusResourceBuilder(HtmlResource, OneLineMixin): + addSlash = True + + def __init__(self, builder_status, builder_control): + HtmlResource.__init__(self) + self.builder_status = builder_status + self.builder_control = builder_control + + def getTitle(self, request): + return "Buildbot: %s" % html.escape(self.builder_status.getName()) + + def build_line(self, build, req): + buildnum = build.getNumber() + buildurl = path_to_build(req, build) + data = '<a href="%s">#%d</a> ' % (buildurl, buildnum) + + when = build.getETA() + if when is not None: + when_time = time.strftime("%H:%M:%S", + time.localtime(time.time() + when)) + data += "ETA %ds (%s) " % (when, when_time) + step = build.getCurrentStep() + if step: + data += "[%s]" % step.getName() + else: + data += "[waiting for Lock]" + # TODO: is this necessarily the case? + + if self.builder_control is not None: + stopURL = path_to_build(req, build) + '/stop' + data += ''' +<form action="%s" class="command stopbuild" style="display:inline"> + <input type="submit" value="Stop Build" /> +</form>''' % stopURL + return data + + def body(self, req): + b = self.builder_status + control = self.builder_control + status = self.getStatus(req) + + slaves = b.getSlaves() + connected_slaves = [s for s in slaves if s.isConnected()] + + projectName = status.getProjectName() + + data = '<a href="%s">%s</a>\n' % (self.path_to_root(req), projectName) + + data += "<h1>Builder: %s</h1>\n" % html.escape(b.getName()) + + # the first section shows builds which are currently running, if any. + + current = b.getCurrentBuilds() + if current: + data += "<h2>Currently Building:</h2>\n" + data += "<ul>\n" + for build in current: + data += " <li>" + self.build_line(build, req) + "</li>\n" + data += "</ul>\n" + else: + data += "<h2>no current builds</h2>\n" + + # Then a section with the last 5 builds, with the most recent build + # distinguished from the rest. + + data += "<h2>Recent Builds:</h2>\n" + data += "<ul>\n" + for i,build in enumerate(b.generateFinishedBuilds(num_builds=5)): + data += " <li>" + self.make_line(req, build, False) + "</li>\n" + if i == 0: + data += "<br />\n" # separator + # TODO: or empty list? + data += "</ul>\n" + + + data += "<h2>Buildslaves:</h2>\n" + data += "<ol>\n" + for slave in slaves: + slaveurl = path_to_slave(req, slave) + data += "<li><b><a href=\"%s\">%s</a></b>: " % (html.escape(slaveurl), html.escape(slave.getName())) + if slave.isConnected(): + data += "CONNECTED\n" + if slave.getAdmin(): + data += make_row("Admin:", html.escape(slave.getAdmin())) + if slave.getHost(): + data += "<span class='label'>Host info:</span>\n" + data += html.PRE(slave.getHost()) + else: + data += ("NOT CONNECTED\n") + data += "</li>\n" + data += "</ol>\n" + + if control is not None and connected_slaves: + forceURL = path_to_builder(req, b) + '/force' + data += make_force_build_form(forceURL) + elif control is not None: + data += """ + <p>All buildslaves appear to be offline, so it's not possible + to force this build to execute at this time.</p> + """ + + if control is not None: + pingURL = path_to_builder(req, b) + '/ping' + data += """ + <form action="%s" class='command pingbuilder'> + <p>To ping the buildslave(s), push the 'Ping' button</p> + + <input type="submit" value="Ping Builder" /> + </form> + """ % pingURL + + data += self.footer(status, req) + + return data + + def force(self, req): + """ + + Custom properties can be passed from the web form. To do + this, subclass this class, overriding the force() method. You + can then determine the properties (usually from form values, + by inspecting req.args), then pass them to this superclass + force method. + + """ + name = req.args.get("username", ["<unknown>"])[0] + reason = req.args.get("comments", ["<no reason specified>"])[0] + branch = req.args.get("branch", [""])[0] + revision = req.args.get("revision", [""])[0] + + r = "The web-page 'force build' button was pressed by '%s': %s\n" \ + % (name, reason) + log.msg("web forcebuild of builder '%s', branch='%s', revision='%s'" + % (self.builder_status.getName(), branch, revision)) + + if not self.builder_control: + # TODO: tell the web user that their request was denied + log.msg("but builder control is disabled") + return Redirect("..") + + # keep weird stuff out of the branch and revision strings. TODO: + # centralize this somewhere. + if not re.match(r'^[\w\.\-\/]*$', branch): + log.msg("bad branch '%s'" % branch) + return Redirect("..") + if not re.match(r'^[\w\.\-\/]*$', revision): + log.msg("bad revision '%s'" % revision) + return Redirect("..") + if not branch: + branch = None + if not revision: + revision = None + + # TODO: if we can authenticate that a particular User pushed the + # button, use their name instead of None, so they'll be informed of + # the results. + s = SourceStamp(branch=branch, revision=revision) + req = BuildRequest(r, s, builderName=self.builder_status.getName()) + try: + self.builder_control.requestBuildSoon(req) + except interfaces.NoSlaveError: + # TODO: tell the web user that their request could not be + # honored + pass + # send the user back to the builder page + return Redirect(".") + + def ping(self, req): + log.msg("web ping of builder '%s'" % self.builder_status.getName()) + self.builder_control.ping() # TODO: there ought to be an ISlaveControl + # send the user back to the builder page + return Redirect(".") + + def getChild(self, path, req): + if path == "force": + return self.force(req) + if path == "ping": + return self.ping(req) + if path == "events": + num = req.postpath.pop(0) + req.prepath.append(num) + num = int(num) + # TODO: is this dead code? .statusbag doesn't exist,right? + log.msg("getChild['path']: %s" % req.uri) + return NoResource("events are unavailable until code gets fixed") + filename = req.postpath.pop(0) + req.prepath.append(filename) + e = self.builder_status.getEventNumbered(num) + if not e: + return NoResource("No such event '%d'" % num) + file = e.files.get(filename, None) + if file == None: + return NoResource("No such file '%s'" % filename) + if type(file) == type(""): + if file[:6] in ("<HTML>", "<html>"): + return static.Data(file, "text/html") + return static.Data(file, "text/plain") + return file + if path == "builds": + return BuildsResource(self.builder_status, self.builder_control) + + return HtmlResource.getChild(self, path, req) + + +# /builders/_all +class StatusResourceAllBuilders(HtmlResource, OneLineMixin): + + def __init__(self, status, control): + HtmlResource.__init__(self) + self.status = status + self.control = control + + def getChild(self, path, req): + if path == "force": + return self.force(req) + if path == "stop": + return self.stop(req) + + return HtmlResource.getChild(self, path, req) + + def force(self, req): + for bname in self.status.getBuilderNames(): + builder_status = self.status.getBuilder(bname) + builder_control = None + c = self.getControl(req) + if c: + builder_control = c.getBuilder(bname) + build = StatusResourceBuilder(builder_status, builder_control) + build.force(req) + # back to the welcome page + return Redirect("../..") + + def stop(self, req): + for bname in self.status.getBuilderNames(): + builder_status = self.status.getBuilder(bname) + builder_control = None + c = self.getControl(req) + if c: + builder_control = c.getBuilder(bname) + (state, current_builds) = builder_status.getState() + if state != "building": + continue + for b in current_builds: + build_status = builder_status.getBuild(b.number) + if not build_status: + continue + if builder_control: + build_control = builder_control.getBuild(b.number) + else: + build_control = None + build = StatusResourceBuild(build_status, build_control, + builder_control) + build.stop(req) + # go back to the welcome page + return Redirect("../..") + + +# /builders +class BuildersResource(HtmlResource): + title = "Builders" + addSlash = True + + def body(self, req): + s = self.getStatus(req) + data = "" + data += "<h1>Builders</h1>\n" + + # TODO: this is really basic. It should be expanded to include a + # brief one-line summary of the builder (perhaps with whatever the + # builder is currently doing) + data += "<ol>\n" + for bname in s.getBuilderNames(): + data += (' <li><a href="%s">%s</a></li>\n' % + (req.childLink(urllib.quote(bname, safe='')), + bname)) + data += "</ol>\n" + + data += self.footer(s, req) + + return data + + def getChild(self, path, req): + s = self.getStatus(req) + if path in s.getBuilderNames(): + builder_status = s.getBuilder(path) + builder_control = None + c = self.getControl(req) + if c: + builder_control = c.getBuilder(path) + return StatusResourceBuilder(builder_status, builder_control) + if path == "_all": + return StatusResourceAllBuilders(self.getStatus(req), + self.getControl(req)) + + return HtmlResource.getChild(self, path, req) + |