OLD | NEW |
| (Empty) |
1 | |
2 from twisted.web.error import NoResource | |
3 from twisted.web import html, static | |
4 from twisted.web.util import Redirect | |
5 | |
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, \ | |
10 make_force_build_form, OneLineMixin, path_to_build, path_to_slave, \ | |
11 path_to_builder, path_to_change, getAndCheckProperties | |
12 from buildbot.process.base import BuildRequest | |
13 from buildbot.process.properties import Properties | |
14 from buildbot.sourcestamp import SourceStamp | |
15 | |
16 from buildbot.status.web.build import BuildsResource, StatusResourceBuild | |
17 from buildbot import util | |
18 | |
19 # /builders/$builder | |
20 class StatusResourceBuilder(HtmlResource, OneLineMixin): | |
21 addSlash = True | |
22 | |
23 def __init__(self, builder_status, builder_control): | |
24 HtmlResource.__init__(self) | |
25 self.builder_status = builder_status | |
26 self.builder_control = builder_control | |
27 | |
28 def getTitle(self, request): | |
29 return "Buildbot: %s" % html.escape(self.builder_status.getName()) | |
30 | |
31 def build_line(self, build, req): | |
32 buildnum = build.getNumber() | |
33 buildurl = path_to_build(req, build) | |
34 data = '<a href="%s">#%d</a> ' % (buildurl, buildnum) | |
35 | |
36 when = build.getETA() | |
37 if when is not None: | |
38 when_time = time.strftime("%H:%M:%S", | |
39 time.localtime(time.time() + when)) | |
40 data += "ETA %ds (%s) " % (when, when_time) | |
41 step = build.getCurrentStep() | |
42 if step: | |
43 data += "[%s]" % step.getName() | |
44 else: | |
45 data += "[waiting for Lock]" | |
46 # TODO: is this necessarily the case? | |
47 | |
48 if self.builder_control is not None: | |
49 stopURL = path_to_build(req, build) + '/stop' | |
50 data += ''' | |
51 <form method="post" action="%s" class="command stopbuild" style="display:inline"
> | |
52 <input type="submit" value="Stop Build" /> | |
53 </form>''' % stopURL | |
54 return data | |
55 | |
56 def request_line(self, build_request, req): | |
57 when = time.strftime("%b %d %H:%M:%S", time.localtime(build_request.getS
ubmitTime())) | |
58 delay = util.formatInterval(util.now() - build_request.getSubmitTime()) | |
59 changes = build_request.source.changes | |
60 if changes: | |
61 change_strings = [] | |
62 for c in changes: | |
63 change_strings.append("<a href=\"%s\">%s</a>" % (path_to_change(
req, c), c.who)) | |
64 if len(change_strings) == 1: | |
65 reason = "change by %s" % change_strings[0] | |
66 else: | |
67 reason = "changes by %s" % ", ".join(change_strings) | |
68 elif build_request.source.revision: | |
69 reason = build_request.source.revision | |
70 else: | |
71 reason = "no changes specified" | |
72 | |
73 if self.builder_control is not None: | |
74 cancelURL = path_to_builder(req, self.builder_status) + '/cancelbuil
d' | |
75 cancelButton = ''' | |
76 <form action="%s" class="command cancelbuild" style="display:inline" method="pos
t"> | |
77 <input type="hidden" name="id" value="%s" /> | |
78 <input type="submit" value="Cancel Build" /> | |
79 </form>''' % (cancelURL, id(build_request)) | |
80 else: | |
81 cancelButton = "" | |
82 return "<font size=\"-1\">(%s, waiting %s)</font>%s%s" % (when, delay, c
ancelButton, reason) | |
83 | |
84 def body(self, req): | |
85 b = self.builder_status | |
86 control = self.builder_control | |
87 status = self.getStatus(req) | |
88 | |
89 slaves = b.getSlaves() | |
90 connected_slaves = [s for s in slaves if s.isConnected()] | |
91 | |
92 projectName = status.getProjectName() | |
93 | |
94 data = '<a href="%s">%s</a>\n' % (self.path_to_root(req), projectName) | |
95 | |
96 data += "<h1>Builder: %s</h1>\n" % html.escape(b.getName()) | |
97 | |
98 # the first section shows builds which are currently running, if any. | |
99 | |
100 current = b.getCurrentBuilds() | |
101 if current: | |
102 data += "<h2>Currently Building:</h2>\n" | |
103 data += "<ul>\n" | |
104 for build in current: | |
105 data += " <li>" + self.build_line(build, req) + "</li>\n" | |
106 data += "</ul>\n" | |
107 else: | |
108 data += "<h2>no current builds</h2>\n" | |
109 | |
110 pending = b.getPendingBuilds() | |
111 if pending: | |
112 data += "<h2>Pending Builds:</h2>\n" | |
113 data += "<ul>\n" | |
114 for request in pending: | |
115 data += " <li>" + self.request_line(request, req) + "</li>\n" | |
116 data += "</ul>\n" | |
117 | |
118 cancelURL = path_to_builder(req, self.builder_status) + '/cancelbuil
d' | |
119 if self.builder_control is not None: | |
120 data += ''' | |
121 <form action="%s" class="command cancelbuild" style="display:inline" method="pos
t"> | |
122 <input type="hidden" name="id" value="all" /> | |
123 <input type="submit" value="Cancel All" /> | |
124 </form>''' % cancelURL | |
125 else: | |
126 data += "<h2>no pending builds</h2>\n" | |
127 | |
128 # Then a section with the last 5 builds, with the most recent build | |
129 # distinguished from the rest. | |
130 | |
131 data += "<h2>Recent Builds:</h2>\n" | |
132 data += "(<a href=\"%s\">view in waterfall</a>)\n" % (self.path_to_root(
req)+"waterfall?show="+html.escape(b.getName())) | |
133 data += "<ul>\n" | |
134 numbuilds = int(req.args.get('numbuilds', ['5'])[0]) | |
135 for i,build in enumerate(b.generateFinishedBuilds(num_builds=int(numbuil
ds))): | |
136 data += " <li>" + self.make_line(req, build, False) + "</li>\n" | |
137 if i == 0: | |
138 data += "<br />\n" # separator | |
139 # TODO: or empty list? | |
140 data += "</ul>\n" | |
141 | |
142 | |
143 data += "<h2>Buildslaves:</h2>\n" | |
144 data += "<ol>\n" | |
145 for slave in slaves: | |
146 slaveurl = path_to_slave(req, slave) | |
147 data += "<li><b><a href=\"%s\">%s</a></b>: " % (html.escape(slaveurl
), html.escape(slave.getName())) | |
148 if slave.isConnected(): | |
149 data += "CONNECTED\n" | |
150 if slave.getAdmin(): | |
151 data += make_row("Admin:", html.escape(slave.getAdmin())) | |
152 if slave.getHost(): | |
153 data += "<span class='label'>Host info:</span>\n" | |
154 data += html.PRE(html.escape(slave.getHost())) | |
155 else: | |
156 data += ("NOT CONNECTED\n") | |
157 data += "</li>\n" | |
158 data += "</ol>\n" | |
159 | |
160 if control is not None and connected_slaves: | |
161 forceURL = path_to_builder(req, b) + '/force' | |
162 data += make_force_build_form(forceURL, self.isUsingUserPasswd(req)) | |
163 elif control is not None: | |
164 data += """ | |
165 <p>All buildslaves appear to be offline, so it's not possible | |
166 to force this build to execute at this time.</p> | |
167 """ | |
168 | |
169 if control is not None: | |
170 pingURL = path_to_builder(req, b) + '/ping' | |
171 data += """ | |
172 <form method="post" action="%s" class='command pingbuilder'> | |
173 <p>To ping the buildslave(s), push the 'Ping' button</p> | |
174 | |
175 <input type="submit" value="Ping Builder" /> | |
176 </form> | |
177 """ % pingURL | |
178 | |
179 data += self.footer(status, req) | |
180 | |
181 return data | |
182 | |
183 def force(self, req): | |
184 """ | |
185 | |
186 Custom properties can be passed from the web form. To do | |
187 this, subclass this class, overriding the force() method. You | |
188 can then determine the properties (usually from form values, | |
189 by inspecting req.args), then pass them to this superclass | |
190 force method. | |
191 | |
192 """ | |
193 name = req.args.get("username", ["<unknown>"])[0] | |
194 reason = req.args.get("comments", ["<no reason specified>"])[0] | |
195 branch = req.args.get("branch", [""])[0] | |
196 revision = req.args.get("revision", [""])[0] | |
197 | |
198 r = "The web-page 'force build' button was pressed by '%s': %s\n" \ | |
199 % (html.escape(name), html.escape(reason)) | |
200 log.msg("web forcebuild of builder '%s', branch='%s', revision='%s'" | |
201 " by user '%s'" % (self.builder_status.getName(), branch, | |
202 revision, name)) | |
203 | |
204 if not self.builder_control: | |
205 # TODO: tell the web user that their request was denied | |
206 log.msg("but builder control is disabled") | |
207 return Redirect("..") | |
208 | |
209 if self.isUsingUserPasswd(req): | |
210 if not self.authUser(req): | |
211 return Redirect("../../authfail") | |
212 | |
213 # keep weird stuff out of the branch revision, and property strings. | |
214 # TODO: centralize this somewhere. | |
215 if not re.match(r'^[\w\.\-\/]*$', branch): | |
216 log.msg("bad branch '%s'" % branch) | |
217 return Redirect("..") | |
218 if not re.match(r'^[\w\.\-\/]*$', revision): | |
219 log.msg("bad revision '%s'" % revision) | |
220 return Redirect("..") | |
221 properties = getAndCheckProperties(req) | |
222 if properties is None: | |
223 return Redirect("..") | |
224 if not branch: | |
225 branch = None | |
226 if not revision: | |
227 revision = None | |
228 | |
229 # TODO: if we can authenticate that a particular User pushed the | |
230 # button, use their name instead of None, so they'll be informed of | |
231 # the results. | |
232 # TODO2: we can authenticate that a particular User pushed the button | |
233 # now, so someone can write this support. but it requires a | |
234 # buildbot.changes.changes.Change instance which is tedious at this | |
235 # stage to compute | |
236 s = SourceStamp(branch=branch, revision=revision) | |
237 req = BuildRequest(r, s, builderName=self.builder_status.getName(), | |
238 properties=properties) | |
239 try: | |
240 self.builder_control.requestBuildSoon(req) | |
241 except interfaces.NoSlaveError: | |
242 # TODO: tell the web user that their request could not be | |
243 # honored | |
244 pass | |
245 # send the user back to the builder page | |
246 return Redirect(".") | |
247 | |
248 def ping(self, req): | |
249 log.msg("web ping of builder '%s'" % self.builder_status.getName()) | |
250 self.builder_control.ping() # TODO: there ought to be an ISlaveControl | |
251 # send the user back to the builder page | |
252 return Redirect(".") | |
253 | |
254 def cancel(self, req): | |
255 try: | |
256 request_id = req.args.get("id", [None])[0] | |
257 if request_id == "all": | |
258 cancel_all = True | |
259 else: | |
260 cancel_all = False | |
261 request_id = int(request_id) | |
262 except: | |
263 request_id = None | |
264 if request_id: | |
265 for build_req in self.builder_control.getPendingBuilds(): | |
266 if cancel_all or id(build_req.original_request.status) == reques
t_id: | |
267 log.msg("Cancelling %s" % build_req) | |
268 build_req.cancel() | |
269 if not cancel_all: | |
270 break | |
271 return Redirect(".") | |
272 | |
273 def getChild(self, path, req): | |
274 if path == "force": | |
275 return self.force(req) | |
276 if path == "ping": | |
277 return self.ping(req) | |
278 if path == "events": | |
279 num = req.postpath.pop(0) | |
280 req.prepath.append(num) | |
281 num = int(num) | |
282 # TODO: is this dead code? .statusbag doesn't exist,right? | |
283 log.msg("getChild['path']: %s" % req.uri) | |
284 return NoResource("events are unavailable until code gets fixed") | |
285 filename = req.postpath.pop(0) | |
286 req.prepath.append(filename) | |
287 e = self.builder_status.getEventNumbered(num) | |
288 if not e: | |
289 return NoResource("No such event '%d'" % num) | |
290 file = e.files.get(filename, None) | |
291 if file == None: | |
292 return NoResource("No such file '%s'" % filename) | |
293 if type(file) == type(""): | |
294 if file[:6] in ("<HTML>", "<html>"): | |
295 return static.Data(file, "text/html") | |
296 return static.Data(file, "text/plain") | |
297 return file | |
298 if path == "cancelbuild": | |
299 return self.cancel(req) | |
300 if path == "builds": | |
301 return BuildsResource(self.builder_status, self.builder_control) | |
302 | |
303 return HtmlResource.getChild(self, path, req) | |
304 | |
305 | |
306 # /builders/_all | |
307 class StatusResourceAllBuilders(HtmlResource, OneLineMixin): | |
308 | |
309 def __init__(self, status, control): | |
310 HtmlResource.__init__(self) | |
311 self.status = status | |
312 self.control = control | |
313 | |
314 def getChild(self, path, req): | |
315 if path == "force": | |
316 return self.force(req) | |
317 if path == "stop": | |
318 return self.stop(req) | |
319 | |
320 return HtmlResource.getChild(self, path, req) | |
321 | |
322 def force(self, req): | |
323 for bname in self.status.getBuilderNames(): | |
324 builder_status = self.status.getBuilder(bname) | |
325 builder_control = None | |
326 c = self.getControl(req) | |
327 if c: | |
328 builder_control = c.getBuilder(bname) | |
329 build = StatusResourceBuilder(builder_status, builder_control) | |
330 build.force(req) | |
331 # back to the welcome page | |
332 return Redirect("../..") | |
333 | |
334 def stop(self, req): | |
335 for bname in self.status.getBuilderNames(): | |
336 builder_status = self.status.getBuilder(bname) | |
337 builder_control = None | |
338 c = self.getControl(req) | |
339 if c: | |
340 builder_control = c.getBuilder(bname) | |
341 (state, current_builds) = builder_status.getState() | |
342 if state != "building": | |
343 continue | |
344 for b in current_builds: | |
345 build_status = builder_status.getBuild(b.number) | |
346 if not build_status: | |
347 continue | |
348 if builder_control: | |
349 build_control = builder_control.getBuild(b.number) | |
350 else: | |
351 build_control = None | |
352 build = StatusResourceBuild(build_status, build_control, | |
353 builder_control) | |
354 build.stop(req) | |
355 # go back to the welcome page | |
356 return Redirect("../..") | |
357 | |
358 | |
359 # /builders | |
360 class BuildersResource(HtmlResource): | |
361 title = "Builders" | |
362 addSlash = True | |
363 | |
364 def body(self, req): | |
365 s = self.getStatus(req) | |
366 data = "" | |
367 data += "<h1>Builders</h1>\n" | |
368 | |
369 # TODO: this is really basic. It should be expanded to include a | |
370 # brief one-line summary of the builder (perhaps with whatever the | |
371 # builder is currently doing) | |
372 data += "<ol>\n" | |
373 for bname in s.getBuilderNames(): | |
374 data += (' <li><a href="%s">%s</a></li>\n' % | |
375 (req.childLink(urllib.quote(bname, safe='')), | |
376 bname)) | |
377 data += "</ol>\n" | |
378 | |
379 data += self.footer(s, req) | |
380 | |
381 return data | |
382 | |
383 def getChild(self, path, req): | |
384 s = self.getStatus(req) | |
385 if path in s.getBuilderNames(): | |
386 builder_status = s.getBuilder(path) | |
387 builder_control = None | |
388 c = self.getControl(req) | |
389 if c: | |
390 builder_control = c.getBuilder(path) | |
391 return StatusResourceBuilder(builder_status, builder_control) | |
392 if path == "_all": | |
393 return StatusResourceAllBuilders(self.getStatus(req), | |
394 self.getControl(req)) | |
395 | |
396 return HtmlResource.getChild(self, path, req) | |
397 | |
OLD | NEW |