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
12 from buildbot.process.base import BuildRequest
13 from buildbot.sourcestamp import SourceStamp
14
15 from buildbot.status.web.build import BuildsResource, StatusResourceBuild
16 from buildbot import util
17
18
20 addSlash = True
21
22 - def __init__(self, builder_status, builder_control):
23 HtmlResource.__init__(self)
24 self.builder_status = builder_status
25 self.builder_control = builder_control
26
28 return "Buildbot: %s" % html.escape(self.builder_status.getName())
29
31 buildnum = build.getNumber()
32 buildurl = path_to_build(req, build)
33 data = '<a href="%s">#%d</a> ' % (buildurl, buildnum)
34
35 when = build.getETA()
36 if when is not None:
37 when_time = time.strftime("%H:%M:%S",
38 time.localtime(time.time() + when))
39 data += "ETA %ds (%s) " % (when, when_time)
40 step = build.getCurrentStep()
41 if step:
42 data += "[%s]" % step.getName()
43 else:
44 data += "[waiting for Lock]"
45
46
47 if self.builder_control is not None:
48 stopURL = path_to_build(req, build) + '/stop'
49 data += '''
50 <form method="post" action="%s" class="command stopbuild" style="display:inline">
51 <input type="submit" value="Stop Build" />
52 </form>''' % stopURL
53 return data
54
56 when = time.strftime("%b %d %H:%M:%S", time.localtime(build_request.getSubmitTime()))
57 delay = util.formatInterval(util.now() - build_request.getSubmitTime())
58 changes = build_request.source.changes
59 if changes:
60 change_strings = []
61 for c in changes:
62 change_strings.append("<a href=\"%s\">%s</a>" % (path_to_change(req, c), c.who))
63 if len(change_strings) == 1:
64 reason = "change by %s" % change_strings[0]
65 else:
66 reason = "changes by %s" % ", ".join(change_strings)
67 elif build_request.source.revision:
68 reason = build_request.source.revision
69 else:
70 reason = "no changes specified"
71
72 if self.builder_control is not None:
73 cancelURL = path_to_builder(req, self.builder_status) + '/cancelbuild'
74 cancelButton = '''
75 <form action="%s" class="command cancelbuild" style="display:inline" method="post">
76 <input type="hidden" name="id" value="%s" />
77 <input type="submit" value="Cancel Build" />
78 </form>''' % (cancelURL, id(build_request))
79 else:
80 cancelButton = ""
81 return "<font size=\"-1\">(%s, waiting %s)</font>%s%s" % (when, delay, cancelButton, reason)
82
83 - def body(self, req):
84 b = self.builder_status
85 control = self.builder_control
86 status = self.getStatus(req)
87
88 slaves = b.getSlaves()
89 connected_slaves = [s for s in slaves if s.isConnected()]
90
91 projectName = status.getProjectName()
92
93 data = '<a href="%s">%s</a>\n' % (self.path_to_root(req), projectName)
94
95 data += "<h1>Builder: %s</h1>\n" % html.escape(b.getName())
96
97
98
99 current = b.getCurrentBuilds()
100 if current:
101 data += "<h2>Currently Building:</h2>\n"
102 data += "<ul>\n"
103 for build in current:
104 data += " <li>" + self.build_line(build, req) + "</li>\n"
105 data += "</ul>\n"
106 else:
107 data += "<h2>no current builds</h2>\n"
108
109 pending = b.getPendingBuilds()
110 if pending:
111 data += "<h2>Pending Builds:</h2>\n"
112 data += "<ul>\n"
113 for request in pending:
114 data += " <li>" + self.request_line(request, req) + "</li>\n"
115 data += "</ul>\n"
116
117 cancelURL = path_to_builder(req, self.builder_status) + '/cancelbuild'
118 data += '''
119 <form action="%s" class="command cancelbuild" style="display:inline" method="post">
120 <input type="hidden" name="id" value="all" />
121 <input type="submit" value="Cancel All" />
122 </form>''' % cancelURL
123 else:
124 data += "<h2>no pending builds</h2>\n"
125
126
127
128
129 data += "<h2>Recent Builds:</h2>\n"
130 data += "(<a href=\"%s\">view in waterfall</a>)\n" % (self.path_to_root(req)+"waterfall?show="+html.escape(b.getName()))
131 data += "<ul>\n"
132 numbuilds = req.args.get('numbuilds', ['5'])[0]
133 for i,build in enumerate(b.generateFinishedBuilds(num_builds=int(numbuilds))):
134 data += " <li>" + self.make_line(req, build, False) + "</li>\n"
135 if i == 0:
136 data += "<br />\n"
137
138 data += "</ul>\n"
139
140
141 data += "<h2>Buildslaves:</h2>\n"
142 data += "<ol>\n"
143 for slave in slaves:
144 slaveurl = path_to_slave(req, slave)
145 data += "<li><b><a href=\"%s\">%s</a></b>: " % (html.escape(slaveurl), html.escape(slave.getName()))
146 if slave.isConnected():
147 data += "CONNECTED\n"
148 if slave.getAdmin():
149 data += make_row("Admin:", html.escape(slave.getAdmin()))
150 if slave.getHost():
151 data += "<span class='label'>Host info:</span>\n"
152 data += html.PRE(slave.getHost())
153 else:
154 data += ("NOT CONNECTED\n")
155 data += "</li>\n"
156 data += "</ol>\n"
157
158 if control is not None and connected_slaves:
159 forceURL = path_to_builder(req, b) + '/force'
160 data += make_force_build_form(forceURL, self.isUsingUserPasswd(req))
161 elif control is not None:
162 data += """
163 <p>All buildslaves appear to be offline, so it's not possible
164 to force this build to execute at this time.</p>
165 """
166
167 if control is not None:
168 pingURL = path_to_builder(req, b) + '/ping'
169 data += """
170 <form method="post" action="%s" class='command pingbuilder'>
171 <p>To ping the buildslave(s), push the 'Ping' button</p>
172
173 <input type="submit" value="Ping Builder" />
174 </form>
175 """ % pingURL
176
177 data += self.footer(status, req)
178
179 return data
180
182 """
183
184 Custom properties can be passed from the web form. To do
185 this, subclass this class, overriding the force() method. You
186 can then determine the properties (usually from form values,
187 by inspecting req.args), then pass them to this superclass
188 force method.
189
190 """
191 name = req.args.get("username", ["<unknown>"])[0]
192 reason = req.args.get("comments", ["<no reason specified>"])[0]
193 branch = req.args.get("branch", [""])[0]
194 revision = req.args.get("revision", [""])[0]
195
196 r = "The web-page 'force build' button was pressed by '%s': %s\n" \
197 % (name, reason)
198 log.msg("web forcebuild of builder '%s', branch='%s', revision='%s'"
199 " by user '%s'" % (self.builder_status.getName(), branch,
200 revision, name))
201
202 if not self.builder_control:
203
204 log.msg("but builder control is disabled")
205 return Redirect("..")
206
207 if self.isUsingUserPasswd(req):
208 if not self.authUser(req):
209 return Redirect("../../authfail")
210
211
212
213 if not re.match(r'^[\w\.\-\/]*$', branch):
214 log.msg("bad branch '%s'" % branch)
215 return Redirect("..")
216 if not re.match(r'^[\w\.\-\/]*$', revision):
217 log.msg("bad revision '%s'" % revision)
218 return Redirect("..")
219 if not branch:
220 branch = None
221 if not revision:
222 revision = None
223
224
225
226
227
228
229
230
231 s = SourceStamp(branch=branch, revision=revision)
232 req = BuildRequest(r, s, builderName=self.builder_status.getName())
233 try:
234 self.builder_control.requestBuildSoon(req)
235 except interfaces.NoSlaveError:
236
237
238 pass
239
240 return Redirect(".")
241
242 - def ping(self, req):
243 log.msg("web ping of builder '%s'" % self.builder_status.getName())
244 self.builder_control.ping()
245
246 return Redirect(".")
247
249 try:
250 request_id = req.args.get("id", [None])[0]
251 if request_id == "all":
252 cancel_all = True
253 else:
254 cancel_all = False
255 request_id = int(request_id)
256 except:
257 request_id = None
258 if request_id:
259 for build_req in self.builder_control.getPendingBuilds():
260 if cancel_all or id(build_req.original_request.status) == request_id:
261 log.msg("Cancelling %s" % build_req)
262 build_req.cancel()
263 if not cancel_all:
264 break
265 return Redirect(".")
266
268 if path == "force":
269 return self.force(req)
270 if path == "ping":
271 return self.ping(req)
272 if path == "events":
273 num = req.postpath.pop(0)
274 req.prepath.append(num)
275 num = int(num)
276
277 log.msg("getChild['path']: %s" % req.uri)
278 return NoResource("events are unavailable until code gets fixed")
279 filename = req.postpath.pop(0)
280 req.prepath.append(filename)
281 e = self.builder_status.getEventNumbered(num)
282 if not e:
283 return NoResource("No such event '%d'" % num)
284 file = e.files.get(filename, None)
285 if file == None:
286 return NoResource("No such file '%s'" % filename)
287 if type(file) == type(""):
288 if file[:6] in ("<HTML>", "<html>"):
289 return static.Data(file, "text/html")
290 return static.Data(file, "text/plain")
291 return file
292 if path == "cancelbuild":
293 return self.cancel(req)
294 if path == "builds":
295 return BuildsResource(self.builder_status, self.builder_control)
296
297 return HtmlResource.getChild(self, path, req)
298
299
300
302
307
309 if path == "force":
310 return self.force(req)
311 if path == "stop":
312 return self.stop(req)
313
314 return HtmlResource.getChild(self, path, req)
315
327
328 - def stop(self, req):
351
352
353
355 title = "Builders"
356 addSlash = True
357
358 - def body(self, req):
359 s = self.getStatus(req)
360 data = ""
361 data += "<h1>Builders</h1>\n"
362
363
364
365
366 data += "<ol>\n"
367 for bname in s.getBuilderNames():
368 data += (' <li><a href="%s">%s</a></li>\n' %
369 (req.childLink(urllib.quote(bname, safe='')),
370 bname))
371 data += "</ol>\n"
372
373 data += self.footer(s, req)
374
375 return data
376
391