Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(321)

Side by Side Diff: third_party/buildbot_7_12/buildbot/status/web/base.py

Issue 12207158: Bye bye buildbot 0.7.12. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1
2 import urlparse, urllib, time, re
3 from zope.interface import Interface
4 from twisted.python import log
5 from twisted.web import html, resource
6 from buildbot.status import builder
7 from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTI ON
8 from buildbot import version, util
9 from buildbot.process.properties import Properties
10
11 import datetime
12
13 class ITopBox(Interface):
14 """I represent a box in the top row of the waterfall display: the one
15 which shows the status of the last build for each builder."""
16 def getBox(self, request):
17 """Return a Box instance, which can produce a <td> cell.
18 """
19
20 class ICurrentBox(Interface):
21 """I represent the 'current activity' box, just above the builder name."""
22 def getBox(self, status):
23 """Return a Box instance, which can produce a <td> cell.
24 """
25
26 class IBox(Interface):
27 """I represent a box in the waterfall display."""
28 def getBox(self, request):
29 """Return a Box instance, which wraps an Event and can produce a <td>
30 cell.
31 """
32
33 class IHTMLLog(Interface):
34 pass
35
36 css_classes = {SUCCESS: "success",
37 WARNINGS: "warnings",
38 FAILURE: "failure",
39 SKIPPED: "skipped",
40 EXCEPTION: "exception",
41 None: "",
42 }
43
44 ROW_TEMPLATE = '''
45 <div class="row">
46 <span class="label">%(label)s</span>
47 <span class="field">%(field)s</span>
48 </div>
49 '''
50
51 def make_row(label, field):
52 """Create a name/value row for the HTML.
53
54 `label` is plain text; it will be HTML-encoded.
55
56 `field` is a bit of HTML structure; it will not be encoded in
57 any way.
58 """
59 label = html.escape(label)
60 return ROW_TEMPLATE % {"label": label, "field": field}
61
62 def make_name_user_passwd_form(useUserPasswd):
63 """helper function to create HTML prompt for 'name' when
64 C{useUserPasswd} is C{False} or 'username' / 'password' prompt
65 when C{True}."""
66
67 if useUserPasswd:
68 label = "Your username:"
69 else:
70 label = "Your name:"
71 data = make_row(label, '<input type="text" name="username" />')
72 if useUserPasswd:
73 data += make_row("Your password:",
74 '<input type="password" name="passwd" />')
75 return data
76
77 def make_stop_form(stopURL, useUserPasswd, on_all=False, label="Build"):
78 if on_all:
79 data = """<form method="post" action="%s" class='command stopbuild'>
80 <p>To stop all builds, fill out the following fields and
81 push the 'Stop' button</p>\n""" % stopURL
82 else:
83 data = """<form method="post" action="%s" class='command stopbuild'>
84 <p>To stop this build, fill out the following fields and
85 push the 'Stop' button</p>\n""" % stopURL
86 data += make_name_user_passwd_form(useUserPasswd)
87 data += make_row("Reason for stopping build:",
88 "<input type='text' name='comments' />")
89 data += '<input type="submit" value="Stop %s" /></form>\n' % label
90 return data
91
92 def make_extra_property_row(N):
93 """helper function to create the html for adding extra build
94 properties to a forced (or resubmitted) build. "N" is an integer
95 inserted into the form names so that more than one property can be
96 used in the form.
97 """
98 prop_html = '''
99 <div class="row">Property %(N)i
100 <span class="label">Name:</span>
101 <span class="field"><input type="text" name="property%(N)iname" /></span>
102 <span class="label">Value:</span>
103 <span class="field"><input type="text" name="property%(N)ivalue" /></span>
104 </div>
105 ''' % {"N": N}
106 return prop_html
107
108 def make_force_build_form(forceURL, useUserPasswd, on_all=False):
109 if on_all:
110 data = """<form method="post" action="%s" class="command forcebuild">
111 <p>To force a build on all Builders, fill out the following fields
112 and push the 'Force Build' button</p>""" % forceURL
113 else:
114 data = """<form method="post" action="%s" class="command forcebuild">
115 <p>To force a build, fill out the following fields and
116 push the 'Force Build' button</p>""" % forceURL
117 return (data
118 + make_name_user_passwd_form(useUserPasswd)
119 + make_row("Reason for build:",
120 "<input type='text' name='comments' />")
121 + make_row("Branch to build:",
122 "<input type='text' name='branch' />")
123 + make_row("Revision to build:",
124 "<input type='text' name='revision' />")
125 + make_extra_property_row(1)
126 + make_extra_property_row(2)
127 + make_extra_property_row(3)
128 + '<input type="submit" value="Force Build" /></form>\n')
129
130 def getAndCheckProperties(req):
131 """
132 Fetch custom build properties from the HTTP request of a "Force build" or
133 "Resubmit build" HTML form.
134 Check the names for valid strings, and return None if a problem is found.
135 Return a new Properties object containing each property found in req.
136 """
137 properties = Properties()
138 for i in (1,2,3):
139 pname = req.args.get("property%dname" % i, [""])[0]
140 pvalue = req.args.get("property%dvalue" % i, [""])[0]
141 if pname and pvalue:
142 if not re.match(r'^[\w\.\-\/\~:]*$', pname) \
143 or not re.match(r'^[\w\.\-\/\~:]*$', pvalue):
144 log.msg("bad property name='%s', value='%s'" % (pname, pvalue))
145 return None
146 properties.setProperty(pname, pvalue, "Force Build Form")
147 return properties
148
149 def td(text="", parms={}, **props):
150 data = ""
151 data += " "
152 #if not props.has_key("border"):
153 # props["border"] = 1
154 props.update(parms)
155 comment = props.get("comment", None)
156 if comment:
157 data += "<!-- %s -->" % comment
158 data += "<td"
159 class_ = props.get('class_', None)
160 if class_:
161 props["class"] = class_
162 for prop in ("align", "colspan", "rowspan", "border",
163 "valign", "halign", "class"):
164 p = props.get(prop, None)
165 if p != None:
166 data += " %s=\"%s\"" % (prop, p)
167 data += ">"
168 if not text:
169 text = "&nbsp;"
170 if isinstance(text, list):
171 data += "<br />".join(text)
172 else:
173 data += text
174 data += "</td>\n"
175 return data
176
177 def build_get_class(b):
178 """
179 Return the class to use for a finished build or buildstep,
180 based on the result.
181 """
182 # FIXME: this getResults duplicity might need to be fixed
183 result = b.getResults()
184 #print "THOMAS: result for b %r: %r" % (b, result)
185 if isinstance(b, builder.BuildStatus):
186 result = b.getResults()
187 elif isinstance(b, builder.BuildStepStatus):
188 result = b.getResults()[0]
189 # after forcing a build, b.getResults() returns ((None, []), []), ugh
190 if isinstance(result, tuple):
191 result = result[0]
192 else:
193 raise TypeError, "%r is not a BuildStatus or BuildStepStatus" % b
194
195 if result == None:
196 # FIXME: this happens when a buildstep is running ?
197 return "running"
198 return builder.Results[result]
199
200 def path_to_root(request):
201 # /waterfall : ['waterfall'] -> ''
202 # /somewhere/lower : ['somewhere', 'lower'] -> '../'
203 # /somewhere/indexy/ : ['somewhere', 'indexy', ''] -> '../../'
204 # / : [] -> ''
205 if request.prepath:
206 segs = len(request.prepath) - 1
207 else:
208 segs = 0
209 root = "../" * segs
210 return root
211
212 def path_to_builder(request, builderstatus):
213 return (path_to_root(request) +
214 "builders/" +
215 urllib.quote(builderstatus.getName(), safe=''))
216
217 def path_to_build(request, buildstatus):
218 return (path_to_builder(request, buildstatus.getBuilder()) +
219 "/builds/%d" % buildstatus.getNumber())
220
221 def path_to_step(request, stepstatus):
222 return (path_to_build(request, stepstatus.getBuild()) +
223 "/steps/%s" % urllib.quote(stepstatus.getName(), safe=''))
224
225 def path_to_slave(request, slave):
226 return (path_to_root(request) +
227 "buildslaves/" +
228 urllib.quote(slave.getName(), safe=''))
229
230 def path_to_change(request, change):
231 return (path_to_root(request) +
232 "changes/%s" % change.number)
233
234 class Box:
235 # a Box wraps an Event. The Box has HTML <td> parameters that Events
236 # lack, and it has a base URL to which each File's name is relative.
237 # Events don't know about HTML.
238 spacer = False
239 def __init__(self, text=[], class_=None, urlbase=None,
240 **parms):
241 self.text = text
242 self.class_ = class_
243 self.urlbase = urlbase
244 self.show_idle = 0
245 if parms.has_key('show_idle'):
246 del parms['show_idle']
247 self.show_idle = 1
248
249 self.parms = parms
250 # parms is a dict of HTML parameters for the <td> element that will
251 # represent this Event in the waterfall display.
252
253 def td(self, **props):
254 props.update(self.parms)
255 text = self.text
256 if not text and self.show_idle:
257 text = ["[idle]"]
258 return td(text, props, class_=self.class_)
259
260
261 class HtmlResource(resource.Resource):
262 # this is a cheap sort of template thingy
263 contentType = "text/html; charset=UTF-8"
264 title = "Buildbot"
265 addSlash = False # adapted from Nevow
266
267 def getChild(self, path, request):
268 if self.addSlash and path == "" and len(request.postpath) == 0:
269 return self
270 return resource.Resource.getChild(self, path, request)
271
272 def render(self, request):
273 # tell the WebStatus about the HTTPChannel that got opened, so they
274 # can close it if we get reconfigured and the WebStatus goes away.
275 # They keep a weakref to this, since chances are good that it will be
276 # closed by the browser or by us before we get reconfigured. See
277 # ticket #102 for details.
278 if hasattr(request, "channel"):
279 # web.distrib.Request has no .channel
280 request.site.buildbot_service.registerChannel(request.channel)
281
282 # Our pages no longer require that their URL end in a slash. Instead,
283 # they all use request.childLink() or some equivalent which takes the
284 # last path component into account. This clause is left here for
285 # historical and educational purposes.
286 if False and self.addSlash and request.prepath[-1] != '':
287 # this is intended to behave like request.URLPath().child('')
288 # but we need a relative URL, since we might be living behind a
289 # reverse proxy
290 #
291 # note that the Location: header (as used in redirects) are
292 # required to have absolute URIs, and my attempt to handle
293 # reverse-proxies gracefully violates rfc2616. This frequently
294 # works, but single-component paths sometimes break. The best
295 # strategy is to avoid these redirects whenever possible by using
296 # HREFs with trailing slashes, and only use the redirects for
297 # manually entered URLs.
298 url = request.prePathURL()
299 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
300 new_url = request.prepath[-1] + "/"
301 if query:
302 new_url += "?" + query
303 request.redirect(new_url)
304 return ''
305
306 data = self.content(request)
307 if isinstance(data, unicode):
308 data = data.encode("utf-8")
309 request.setHeader("content-type", self.contentType)
310 if request.method == "HEAD":
311 request.setHeader("content-length", len(data))
312 return ''
313
314 # Make sure we get fresh pages.
315 now = datetime.datetime.utcnow()
316 expires = now + datetime.timedelta(seconds=60)
317 request.setHeader("Expires", expires.strftime("%a, %d %b %Y %H:%M:%S GMT "))
318 request.setHeader("Pragma", "no-cache")
319
320 return data
321
322 def getStatus(self, request):
323 return request.site.buildbot_service.getStatus()
324
325 def getControl(self, request):
326 return request.site.buildbot_service.getControl()
327
328 def isUsingUserPasswd(self, request):
329 return request.site.buildbot_service.isUsingUserPasswd()
330
331 def authUser(self, request):
332 user = request.args.get("username", ["<unknown>"])[0]
333 passwd = request.args.get("passwd", ["<no-password>"])[0]
334 if user == "<unknown>" or passwd == "<no-password>":
335 return False
336 return request.site.buildbot_service.authUser(user, passwd)
337
338 def getChangemaster(self, request):
339 return request.site.buildbot_service.getChangeSvc()
340
341 def path_to_root(self, request):
342 return path_to_root(request)
343
344 def footer(self, status, req):
345 # TODO: this stuff should be generated by a template of some sort
346 projectURL = status.getProjectURL()
347 projectName = status.getProjectName()
348 data = '<hr /><div class="footer">\n'
349
350 welcomeurl = self.path_to_root(req) + "index.html"
351 data += '[<a href="%s">welcome</a>]\n' % welcomeurl
352 data += "<br />\n"
353
354 data += '<a href="http://buildbot.sourceforge.net/">Buildbot</a>'
355 data += "-%s " % version
356 if projectName:
357 data += "working for the "
358 if projectURL:
359 data += "<a href=\"%s\">%s</a> project." % (projectURL,
360 projectName)
361 else:
362 data += "%s project." % projectName
363 data += "<br />\n"
364 data += ("Page built: " +
365 time.strftime("%a %d %b %Y %H:%M:%S",
366 time.localtime(util.now()))
367 + "\n")
368 data += '</div>\n'
369
370 return data
371
372 def getTitle(self, request):
373 return self.title
374
375 def fillTemplate(self, template, request):
376 s = request.site.buildbot_service
377 values = s.template_values.copy()
378 values['root'] = self.path_to_root(request)
379 # e.g. to reference the top-level 'buildbot.css' page, use
380 # "%(root)sbuildbot.css"
381 values['title'] = self.getTitle(request)
382 return template % values
383
384 def content(self, request):
385 s = request.site.buildbot_service
386 data = ""
387 data += self.fillTemplate(s.header, request)
388 data += "<head>\n"
389 for he in s.head_elements:
390 data += " " + self.fillTemplate(he, request) + "\n"
391 data += self.head(request)
392 data += "</head>\n\n"
393
394 data += '<body %s>\n' % " ".join(['%s="%s"' % (k,v)
395 for (k,v) in s.body_attrs.items()])
396 data += self.body(request)
397 data += "</body>\n"
398 data += self.fillTemplate(s.footer, request)
399 return data
400
401 def head(self, request):
402 return ""
403
404 def body(self, request):
405 return "Dummy\n"
406
407 class StaticHTML(HtmlResource):
408 def __init__(self, body, title):
409 HtmlResource.__init__(self)
410 self.bodyHTML = body
411 self.title = title
412 def body(self, request):
413 return self.bodyHTML
414
415 MINUTE = 60
416 HOUR = 60*MINUTE
417 DAY = 24*HOUR
418 WEEK = 7*DAY
419 MONTH = 30*DAY
420
421 def plural(word, words, num):
422 if int(num) == 1:
423 return "%d %s" % (num, word)
424 else:
425 return "%d %s" % (num, words)
426
427 def abbreviate_age(age):
428 if age <= 90:
429 return "%s ago" % plural("second", "seconds", age)
430 if age < 90*MINUTE:
431 return "about %s ago" % plural("minute", "minutes", age / MINUTE)
432 if age < DAY:
433 return "about %s ago" % plural("hour", "hours", age / HOUR)
434 if age < 2*WEEK:
435 return "about %s ago" % plural("day", "days", age / DAY)
436 if age < 2*MONTH:
437 return "about %s ago" % plural("week", "weeks", age / WEEK)
438 return "a long time ago"
439
440
441 class OneLineMixin:
442 LINE_TIME_FORMAT = "%b %d %H:%M"
443
444 def get_line_values(self, req, build):
445 '''
446 Collect the data needed for each line display
447 '''
448 builder_name = build.getBuilder().getName()
449 results = build.getResults()
450 text = build.getText()
451 try:
452 rev = build.getProperty("got_revision")
453 if rev is None:
454 rev = "??"
455 except KeyError:
456 rev = "??"
457 rev = str(rev)
458 if len(rev) > 40:
459 rev = "version is too-long"
460 root = self.path_to_root(req)
461 css_class = css_classes.get(results, "")
462 values = {'class': css_class,
463 'builder_name': builder_name,
464 'buildnum': build.getNumber(),
465 'results': css_class,
466 'text': " ".join(build.getText()),
467 'buildurl': path_to_build(req, build),
468 'builderurl': path_to_builder(req, build.getBuilder()),
469 'rev': rev,
470 'time': time.strftime(self.LINE_TIME_FORMAT,
471 time.localtime(build.getTimes()[0])),
472 }
473 return values
474
475 def make_line(self, req, build, include_builder=True):
476 '''
477 Format and render a single line into HTML
478 '''
479 values = self.get_line_values(req, build)
480 fmt_pieces = ['<font size="-1">(%(time)s)</font>',
481 'rev=[%(rev)s]',
482 '<span class="%(class)s">%(results)s</span>',
483 ]
484 if include_builder:
485 fmt_pieces.append('<a href="%(builderurl)s">%(builder_name)s</a>')
486 fmt_pieces.append('<a href="%(buildurl)s">#%(buildnum)d</a>:')
487 fmt_pieces.append('%(text)s')
488 data = " ".join(fmt_pieces) % values
489 return data
490
491 def map_branches(branches):
492 # when the query args say "trunk", present that to things like
493 # IBuilderStatus.generateFinishedBuilds as None, since that's the
494 # convention in use. But also include 'trunk', because some VC systems
495 # refer to it that way. In the long run we should clean this up better,
496 # maybe with Branch objects or something.
497 if "trunk" in branches:
498 return branches + [None]
499 return branches
OLDNEW
« no previous file with comments | « third_party/buildbot_7_12/buildbot/status/web/auth.py ('k') | third_party/buildbot_7_12/buildbot/status/web/baseweb.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698