OLD | NEW |
| (Empty) |
1 | |
2 from twisted.web import html | |
3 from twisted.web.util import Redirect, DeferredResource | |
4 from twisted.internet import defer, reactor | |
5 | |
6 import urllib, time | |
7 from twisted.python import log | |
8 from buildbot.status.web.base import HtmlResource, make_row, make_stop_form, \ | |
9 make_extra_property_row, css_classes, path_to_builder, path_to_slave, \ | |
10 make_name_user_passwd_form, getAndCheckProperties | |
11 | |
12 | |
13 from buildbot.status.web.tests import TestsResource | |
14 from buildbot.status.web.step import StepsResource | |
15 from buildbot import version, util | |
16 | |
17 # /builders/$builder/builds/$buildnum | |
18 class StatusResourceBuild(HtmlResource): | |
19 addSlash = True | |
20 | |
21 def __init__(self, build_status, build_control, builder_control): | |
22 HtmlResource.__init__(self) | |
23 self.build_status = build_status | |
24 self.build_control = build_control | |
25 self.builder_control = builder_control | |
26 | |
27 def getTitle(self, request): | |
28 return ("Buildbot: %s Build #%d" % | |
29 (html.escape(self.build_status.getBuilder().getName()), | |
30 self.build_status.getNumber())) | |
31 | |
32 def body(self, req): | |
33 b = self.build_status | |
34 status = self.getStatus(req) | |
35 projectName = status.getProjectName() | |
36 projectURL = status.getProjectURL() | |
37 data = ('<div class="title"><a href="%s">%s</a></div>\n' | |
38 % (self.path_to_root(req), projectName)) | |
39 builder_name = b.getBuilder().getName() | |
40 data += ("<h1><a href=\"%s\">Builder %s</a>: Build #%d</h1>\n" | |
41 % (path_to_builder(req, b.getBuilder()), | |
42 builder_name, b.getNumber())) | |
43 | |
44 if not b.isFinished(): | |
45 data += "<h2>Build In Progress</h2>" | |
46 when = b.getETA() | |
47 if when is not None: | |
48 when_time = time.strftime("%H:%M:%S", | |
49 time.localtime(time.time() + when)) | |
50 data += "<div>ETA %ds (%s)</div>\n" % (when, when_time) | |
51 | |
52 if self.build_control is not None: | |
53 stopURL = urllib.quote(req.childLink("stop")) | |
54 data += make_stop_form(stopURL, self.isUsingUserPasswd(req)) | |
55 | |
56 if b.isFinished(): | |
57 # Results map loosely to css_classes | |
58 results = b.getResults() | |
59 data += "<h2>Results:</h2>\n" | |
60 text = " ".join(b.getText()) | |
61 data += '<span class="%s">%s</span>\n' % (css_classes[results], | |
62 text) | |
63 if b.getTestResults(): | |
64 url = req.childLink("tests") | |
65 data += "<h3><a href=\"%s\">test results</a></h3>\n" % url | |
66 | |
67 ss = b.getSourceStamp() | |
68 data += "<h2>SourceStamp:</h2>\n" | |
69 data += " <ul>\n" | |
70 if ss.branch: | |
71 data += " <li>Branch: %s</li>\n" % html.escape(ss.branch) | |
72 if ss.revision: | |
73 data += " <li>Revision: %s</li>\n" % html.escape(str(ss.revision)) | |
74 if ss.patch: | |
75 data += " <li>Patch: YES</li>\n" # TODO: provide link to .diff | |
76 if ss.changes: | |
77 data += " <li>Changes: see below</li>\n" | |
78 if (ss.branch is None and ss.revision is None and ss.patch is None | |
79 and not ss.changes): | |
80 data += " <li>build of most recent revision</li>\n" | |
81 got_revision = None | |
82 try: | |
83 got_revision = b.getProperty("got_revision") | |
84 except KeyError: | |
85 pass | |
86 if got_revision: | |
87 got_revision = str(got_revision) | |
88 if len(got_revision) > 40: | |
89 got_revision = "[revision string too long]" | |
90 data += " <li>Got Revision: %s</li>\n" % got_revision | |
91 data += " </ul>\n" | |
92 | |
93 # TODO: turn this into a table, or some other sort of definition-list | |
94 # that doesn't take up quite so much vertical space | |
95 try: | |
96 slaveurl = path_to_slave(req, status.getSlave(b.getSlavename())) | |
97 data += "<h2>Buildslave:</h2>\n <a href=\"%s\">%s</a>\n" % (html.esc
ape(slaveurl), html.escape(b.getSlavename())) | |
98 except KeyError: | |
99 data += "<h2>Buildslave:</h2>\n %s\n" % html.escape(b.getSlavename()
) | |
100 data += "<h2>Reason:</h2>\n%s\n" % html.escape(b.getReason()) | |
101 | |
102 data += "<h2>Steps and Logfiles:</h2>\n" | |
103 # TODO: | |
104 # urls = self.original.getURLs() | |
105 # ex_url_class = "BuildStep external" | |
106 # for name, target in urls.items(): | |
107 # text.append('[<a href="%s" class="%s">%s</a>]' % | |
108 # (target, ex_url_class, html.escape(name))) | |
109 data += "<ol>\n" | |
110 for s in b.getSteps(): | |
111 name = s.getName() | |
112 time_to_run = 0 | |
113 (start, end) = s.getTimes() | |
114 if start and end: | |
115 time_to_run = end - start | |
116 if s.isFinished(): | |
117 css_class = css_classes[s.getResults()[0]] | |
118 elif s.isStarted(): | |
119 css_class = "running" | |
120 else: | |
121 css_class = "" | |
122 data += (' <li><span class="%s"><a href=\"%s\">%s</a> [%s] [%d secon
ds]</span>\n' | |
123 % (css_class, | |
124 req.childLink("steps/%s" % urllib.quote(name)), | |
125 name, | |
126 " ".join(s.getText()), | |
127 time_to_run)) | |
128 data += " <ol>\n" | |
129 if s.getLogs(): | |
130 for logfile in s.getLogs(): | |
131 logname = logfile.getName() | |
132 logurl = req.childLink("steps/%s/logs/%s" % | |
133 (urllib.quote(name), | |
134 urllib.quote(logname))) | |
135 data += (" <li><a href=\"%s\">%s</a></li>\n" % | |
136 (logurl, logfile.getName())) | |
137 if s.getURLs(): | |
138 for url in s.getURLs().items(): | |
139 logname = url[0] | |
140 logurl = url[1] | |
141 data += (' <li><a href="%s">%s</a></li>\n' % | |
142 (logurl, html.escape(logname))) | |
143 data += "</ol>\n" | |
144 data += " </li>\n" | |
145 | |
146 data += "</ol>\n" | |
147 | |
148 data += "<h2>Build Properties:</h2>\n" | |
149 data += "<table><tr><th valign=\"left\">Name</th><th valign=\"left\">Val
ue</th><th valign=\"left\">Source</th></tr>\n" | |
150 for name, value, source in b.getProperties().asList(): | |
151 value = str(value) | |
152 if len(value) > 500: | |
153 value = value[:500] + " .. [property value too long]" | |
154 data += "<tr>" | |
155 data += "<td>%s</td>" % html.escape(name) | |
156 data += "<td>%s</td>" % html.escape(value) | |
157 data += "<td>%s</td>" % html.escape(source) | |
158 data += "</tr>\n" | |
159 data += "</table>" | |
160 | |
161 data += "<h2>Blamelist:</h2>\n" | |
162 if list(b.getResponsibleUsers()): | |
163 data += " <ol>\n" | |
164 for who in b.getResponsibleUsers(): | |
165 data += " <li>%s</li>\n" % html.escape(who) | |
166 data += " </ol>\n" | |
167 else: | |
168 data += "<div>no responsible users</div>\n" | |
169 | |
170 | |
171 (start, end) = b.getTimes() | |
172 data += "<h2>Timing</h2>\n" | |
173 data += "<table>\n" | |
174 data += "<tr><td>Start</td><td>%s</td></tr>\n" % time.ctime(start) | |
175 if end: | |
176 data += "<tr><td>End</td><td>%s</td></tr>\n" % time.ctime(end) | |
177 data += "<tr><td>Elapsed</td><td>%s</td></tr>\n" % util.formatInterv
al(end - start) | |
178 else: | |
179 now = util.now() | |
180 data += "<tr><td>Elapsed</td><td>%s</td></tr>\n" % util.formatInterv
al(now - start) | |
181 data += "</table>\n" | |
182 | |
183 if ss.changes: | |
184 data += "<h2>All Changes</h2>\n" | |
185 data += "<ol>\n" | |
186 for c in ss.changes: | |
187 data += "<li>" + c.asHTML() + "</li>\n" | |
188 data += "</ol>\n" | |
189 #data += html.PRE(b.changesText()) # TODO | |
190 | |
191 if b.isFinished() and self.builder_control is not None: | |
192 data += "<h3>Resubmit Build:</h3>\n" | |
193 # can we rebuild it exactly? | |
194 exactly = (ss.revision is not None) or b.getChanges() | |
195 if exactly: | |
196 data += ("<p>This tree was built from a specific set of \n" | |
197 "source files, and can be rebuilt exactly</p>\n") | |
198 else: | |
199 data += ("<p>This tree was built from the most recent " | |
200 "revision") | |
201 if ss.branch: | |
202 data += " (along some branch)" | |
203 data += (" and thus it might not be possible to rebuild it \n" | |
204 "exactly. Any changes that have been committed \n" | |
205 "after this build was started <b>will</b> be \n" | |
206 "included in a rebuild.</p>\n") | |
207 rebuildURL = urllib.quote(req.childLink("rebuild")) | |
208 data += ('<form method="post" action="%s" class="command rebuild">\n
' | |
209 % rebuildURL) | |
210 data += make_name_user_passwd_form(self.isUsingUserPasswd(req)) | |
211 data += make_extra_property_row(1) | |
212 data += make_extra_property_row(2) | |
213 data += make_extra_property_row(3) | |
214 data += make_row("Reason for re-running build:", | |
215 "<input type='text' name='comments' />") | |
216 data += '<input type="submit" value="Rebuild" />\n' | |
217 data += '</form>\n' | |
218 | |
219 data += self.footer(status, req) | |
220 | |
221 return data | |
222 | |
223 def stop(self, req): | |
224 if self.isUsingUserPasswd(req): | |
225 if not self.authUser(req): | |
226 return Redirect("../../../authfailed") | |
227 b = self.build_status | |
228 c = self.build_control | |
229 log.msg("web stopBuild of build %s:%s" % \ | |
230 (b.getBuilder().getName(), b.getNumber())) | |
231 name = req.args.get("username", ["<unknown>"])[0] | |
232 comments = req.args.get("comments", ["<no reason specified>"])[0] | |
233 # html-quote both the username and comments, just to be safe | |
234 reason = ("The web-page 'stop build' button was pressed by " | |
235 "'%s': %s\n" % (html.escape(name), html.escape(comments))) | |
236 if c: | |
237 c.stopBuild(reason) | |
238 # we're at http://localhost:8080/svn-hello/builds/5/stop?[args] and | |
239 # we want to go to: http://localhost:8080/svn-hello | |
240 r = Redirect("../..") | |
241 d = defer.Deferred() | |
242 reactor.callLater(1, d.callback, r) | |
243 return DeferredResource(d) | |
244 | |
245 def rebuild(self, req): | |
246 if self.isUsingUserPasswd(req): | |
247 if not self.authUser(req): | |
248 return Redirect("../../../authfailed") | |
249 b = self.build_status | |
250 bc = self.builder_control | |
251 builder_name = b.getBuilder().getName() | |
252 log.msg("web rebuild of build %s:%s" % (builder_name, b.getNumber())) | |
253 name = req.args.get("username", ["<unknown>"])[0] | |
254 comments = req.args.get("comments", ["<no reason specified>"])[0] | |
255 reason = ("The web-page 'rebuild' button was pressed by " | |
256 "'%s': %s\n" % (html.escape(name), html.escape(comments))) | |
257 extraProperties = getAndCheckProperties(req) | |
258 if not bc or not b.isFinished() or extraProperties is None: | |
259 log.msg("could not rebuild: bc=%s, isFinished=%s" | |
260 % (bc, b.isFinished())) | |
261 # TODO: indicate an error | |
262 else: | |
263 bc.resubmitBuild(b, reason, extraProperties) | |
264 # we're at | |
265 # http://localhost:8080/builders/NAME/builds/5/rebuild?[args] | |
266 # Where should we send them? | |
267 # | |
268 # Ideally it would be to the per-build page that they just started, | |
269 # but we don't know the build number for it yet (besides, it might | |
270 # have to wait for a current build to finish). The next-most | |
271 # preferred place is somewhere that the user can see tangible | |
272 # evidence of their build starting (or to see the reason that it | |
273 # didn't start). This should be the Builder page. | |
274 r = Redirect("../..") # the Builder's page | |
275 d = defer.Deferred() | |
276 reactor.callLater(1, d.callback, r) | |
277 return DeferredResource(d) | |
278 | |
279 def getChild(self, path, req): | |
280 if path == "stop": | |
281 return self.stop(req) | |
282 if path == "rebuild": | |
283 return self.rebuild(req) | |
284 if path == "steps": | |
285 return StepsResource(self.build_status) | |
286 if path == "tests": | |
287 return TestsResource(self.build_status) | |
288 | |
289 return HtmlResource.getChild(self, path, req) | |
290 | |
291 # /builders/$builder/builds | |
292 class BuildsResource(HtmlResource): | |
293 addSlash = True | |
294 | |
295 def __init__(self, builder_status, builder_control): | |
296 HtmlResource.__init__(self) | |
297 self.builder_status = builder_status | |
298 self.builder_control = builder_control | |
299 | |
300 def getChild(self, path, req): | |
301 try: | |
302 num = int(path) | |
303 except ValueError: | |
304 num = None | |
305 if num is not None: | |
306 build_status = self.builder_status.getBuild(num) | |
307 if build_status: | |
308 if self.builder_control: | |
309 build_control = self.builder_control.getBuild(num) | |
310 else: | |
311 build_control = None | |
312 return StatusResourceBuild(build_status, build_control, | |
313 self.builder_control) | |
314 | |
315 return HtmlResource.getChild(self, path, req) | |
316 | |
OLD | NEW |