2 from twisted
.web
.error
import NoResource
3 from twisted
.web
import html
, static
4 from twisted
.web
.util
import Redirect
6 import re
, urllib
, time
7 from twisted
.python
import log
8 from buildbot
import interfaces
9 from buildbot
.status
.web
.base
import HtmlResource
, make_row
, css_classes
10 from buildbot
.process
.base
import BuildRequest
11 from buildbot
.sourcestamp
import SourceStamp
13 from buildbot
.status
.web
.build
import BuildsResource
16 class StatusResourceBuilder(HtmlResource
):
19 def __init__(self
, builder_status
, builder_control
):
20 HtmlResource
.__init
__(self
)
21 self
.builder_status
= builder_status
22 self
.builder_control
= builder_control
24 def getTitle(self
, request
):
25 return "Buildbot: %s" % html
.escape(self
.builder_status
.getName())
27 def build_line(self
, build
, req
):
28 buildnum
= build
.getNumber()
29 buildurl
= "builds/%d" % buildnum
30 data
= '<a href="%s">#%d</a> ' % (buildurl
, buildnum
)
33 when_time
= time
.strftime("%H:%M:%S",
34 time
.localtime(time
.time() + when
))
35 data
+= "ETA %ds (%s) " % (when
, when_time
)
36 data
+= "[%s]" % build
.getCurrentStep().getName()
39 def build_finished_line(self
, build
, req
):
40 buildnum
= build
.getNumber()
41 buildurl
= "builds/%d" % buildnum
42 results
= build
.getResults()
43 text
= " ".join(build
.getText())
44 data
= '<a href="%s">#%d</a> ' % (buildurl
, buildnum
)
45 data
+= '<span class="%s">%s</span>' % (css_classes
[results
], text
)
49 b
= self
.builder_status
50 control
= self
.builder_control
51 status
= self
.getStatus(req
)
53 slaves
= b
.getSlaves()
54 connected_slaves
= [s
for s
in slaves
if s
.isConnected()]
56 projectName
= status
.getProjectName()
58 data
= '<a href="../..">%s</a>\n' % projectName
60 data
+= "<h1>Builder: %s</h1>\n" % html
.escape(b
.getName())
62 # the first section shows builds which are currently running, if any.
64 current
= b
.getCurrentBuilds()
66 data
+= "<h2>Currently Building:</h2>\n"
69 data
+= " <li>" + self
.build_line(build
, req
) + "</li>\n"
72 data
+= "<h2>no current builds</h2>\n"
74 # Then a section with the last 5 builds, with the most recent build
75 # distinguished from the rest.
77 data
+= "<h2>Recent Builds:</h2>\n"
79 for i
,build
in enumerate(b
.generateFinishedBuilds(num_builds
=5)):
80 data
+= " <li>" + self
.build_finished_line(build
, req
) + "</li>\n"
82 data
+= "<br />\n" # separator
83 # TODO: or empty list?
87 data
+= "<h2>Buildslaves:</h2>\n"
90 data
+= "<li><b>%s</b>: " % html
.escape(slave
.getName())
91 if slave
.isConnected():
94 data
+= make_row("Admin:", html
.escape(slave
.getAdmin()))
96 data
+= "<span class='label'>Host info:</span>\n"
97 data
+= html
.PRE(slave
.getHost())
99 data
+= ("NOT CONNECTED\n")
103 if control
is not None and connected_slaves
:
104 forceURL
= urllib
.quote(req
.childLink("force"))
107 <form action='%(forceURL)s' class='command forcebuild'>
108 <p>To force a build, fill out the following fields and
109 push the 'Force Build' button</p>"""
110 + make_row("Your name:",
111 "<input type='text' name='username' />")
112 + make_row("Reason for build:",
113 "<input type='text' name='comments' />")
114 + make_row("Branch to build:",
115 "<input type='text' name='branch' />")
116 + make_row("Revision to build:",
117 "<input type='text' name='revision' />")
119 <input type='submit' value='Force Build' />
121 """) % {"forceURL": forceURL
}
122 elif control
is not None:
124 <p>All buildslaves appear to be offline, so it's not possible
125 to force this build to execute at this time.</p>
128 if control
is not None:
129 pingURL
= urllib
.quote(req
.childLink("ping"))
131 <form action="%s" class='command pingbuilder'>
132 <p>To ping the buildslave(s), push the 'Ping' button</p>
134 <input type="submit" value="Ping Builder" />
140 def force(self
, req
):
141 name
= req
.args
.get("username", ["<unknown>"])[0]
142 reason
= req
.args
.get("comments", ["<no reason specified>"])[0]
143 branch
= req
.args
.get("branch", [""])[0]
144 revision
= req
.args
.get("revision", [""])[0]
146 r
= "The web-page 'force build' button was pressed by '%s': %s\n" \
148 log
.msg("web forcebuild of builder '%s', branch='%s', revision='%s'"
149 % (self
.builder_status
.getName(), branch
, revision
))
151 if not self
.builder_control
:
152 # TODO: tell the web user that their request was denied
153 log
.msg("but builder control is disabled")
154 return Redirect("..")
156 # keep weird stuff out of the branch and revision strings. TODO:
157 # centralize this somewhere.
158 if not re
.match(r
'^[\w\.\-\/]*$', branch
):
159 log
.msg("bad branch '%s'" % branch
)
160 return Redirect("..")
161 if not re
.match(r
'^[\w\.\-\/]*$', revision
):
162 log
.msg("bad revision '%s'" % revision
)
163 return Redirect("..")
169 # TODO: if we can authenticate that a particular User pushed the
170 # button, use their name instead of None, so they'll be informed of
172 s
= SourceStamp(branch
=branch
, revision
=revision
)
173 req
= BuildRequest(r
, s
, self
.builder_status
.getName())
175 self
.builder_control
.requestBuildSoon(req
)
176 except interfaces
.NoSlaveError
:
177 # TODO: tell the web user that their request could not be
180 return Redirect("../..")
183 log
.msg("web ping of builder '%s'" % self
.builder_status
.getName())
184 self
.builder_control
.ping() # TODO: there ought to be an ISlaveControl
185 return Redirect("../..")
187 def getChild(self
, path
, req
):
189 return self
.force(req
)
191 return self
.ping(req
)
193 num
= req
.postpath
.pop(0)
194 req
.prepath
.append(num
)
196 # TODO: is this dead code? .statusbag doesn't exist,right?
197 log
.msg("getChild['path']: %s" % req
.uri
)
198 return NoResource("events are unavailable until code gets fixed")
199 filename
= req
.postpath
.pop(0)
200 req
.prepath
.append(filename
)
201 e
= self
.builder_status
.getEventNumbered(num
)
203 return NoResource("No such event '%d'" % num
)
204 file = e
.files
.get(filename
, None)
206 return NoResource("No such file '%s'" % filename
)
207 if type(file) == type(""):
208 if file[:6] in ("<HTML>", "<html>"):
209 return static
.Data(file, "text/html")
210 return static
.Data(file, "text/plain")
213 return BuildsResource(self
.builder_status
, self
.builder_control
)
215 return HtmlResource
.getChild(self
, path
, req
)
218 class BuildersResource(HtmlResource
):
221 def getChild(self
, path
, req
):
222 s
= self
.getStatus(req
)
223 if path
in s
.getBuilderNames():
224 builder_status
= s
.getBuilder(path
)
225 builder_control
= None
226 c
= self
.getControl(req
)
228 builder_control
= c
.getBuilder(path
)
229 return StatusResourceBuilder(builder_status
, builder_control
)
231 return HtmlResource
.getChild(self
, path
, req
)