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
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
29 return "Buildbot: %s" % html.escape(self.builder_status.getName())
30
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
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
57 when = time.strftime("%b %d %H:%M:%S", time.localtime(build_request.getSubmitTime()))
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) + '/cancelbuild'
75 cancelButton = '''
76 <form action="%s" class="command cancelbuild" style="display:inline" method="post">
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, cancelButton, 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
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) + '/cancelbuild'
119 if self.builder_control is not None:
120 data += '''
121 <form action="%s" class="command cancelbuild" style="display:inline" method="post">
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
129
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(numbuilds))):
136 data += " <li>" + self.make_line(req, build, False) + "</li>\n"
137 if i == 0:
138 data += "<br />\n"
139
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
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
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
214
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
230
231
232
233
234
235
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
243
244 pass
245
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()
251
252 return Redirect(".")
253
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) == request_id:
267 log.msg("Cancelling %s" % build_req)
268 build_req.cancel()
269 if not cancel_all:
270 break
271 return Redirect(".")
272
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
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
308
313
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
333
334 - def stop(self, req):
357
358
359
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
370
371
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
397