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

Side by Side Diff: third_party/buildbot_7_12/buildbot/status/web/status_json.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 # -*- test-case-name: buildbot.test.test_web_status_json -*-
2 # Original Copyright (c) 2010 The Chromium Authors.
3
4 """Simple JSON exporter."""
5
6 import datetime
7 import os
8 import re
9
10 try:
11 import simplejson as json
12 except ImportError:
13 import json
14
15 from buildbot.status.web.base import HtmlResource, StaticHTML
16 from twisted.web import error, html, resource
17
18
19 _IS_INT = re.compile('^[-+]?\d+$')
20
21
22 FLAGS = """Flags:
23 - as_text
24 - By default, application/json is used. Setting as_text=1 change the type
25 to text/plain and implicitly sets compact=0 and filter=1. Mainly useful to
26 look at the result in a web browser.
27 - compact
28 - By default, the json data is compact and defaults to 1. For easier to read
29 indented output, set compact=0.
30 - select
31 - By default, most children data is listed. You can do a random selection
32 of data by using select=<sub-url> multiple times to coagulate data.
33 "select=" includes the actual url otherwise it is skipped.
34 - filter
35 - Filters out null, false, and empty string, list and dict. This reduce the
36 amount of useless data sent.
37 - callback
38 - Enable uses of JSONP as described in
39 http://en.wikipedia.org/wiki/JSON#JSONP. Note that
40 Access-Control-Allow-Origin:* is set in the HTTP response header so you
41 can use this in compatible browsers.
42 """
43
44 EXAMPLES = """Examples:
45 - /json
46 - Root node, that *doesn't* mean all the data. Many things (like logs) must
47 be explicitly queried for performance reasons.
48 - /json/builders/
49 - All builders.
50 - /json/builders/<A_BUILDER>
51 - A specific builder as compact text.
52 - /json/builders/<A_BUILDER>/builds
53 - All *cached* builds.
54 - /json/builders/<A_BUILDER>/builds/_all
55 - All builds. Warning, reads all previous build data.
56 - /json/builders/<A_BUILDER>/builds/<A_BUILD>
57 - Where <A_BUILD> is either positive, a build number, or negative, a past
58 build.
59 - /json/builders/<A_BUILDER>/builds/-1/source_stamp/changes
60 - Build changes
61 - /json/builders/<A_BUILDER>/builds?select=-1&select=-2
62 - Two last builds on '<A_BUILDER>' builder.
63 - /json/builders/<A_BUILDER>/builds?select=-1/source_stamp/changes&select=-2/s ource_stamp/changes
64 - Changes of the two last builds on '<A_BUILDER>' builder.
65 - /json/builders/<A_BUILDER>/slaves
66 - Slaves associated to this builder.
67 - /json/builders/<A_BUILDER>?select=&select=slaves
68 - Builder information plus details information about its slaves. Neat eh?
69 - /json/slaves/<A_SLAVE>
70 - A specific slave.
71 - /json?select=slaves/<A_SLAVE>/&select=project&select=builders/<A_BUILDER>/bu ilds/<A_BUILD>
72 - A selection of random unrelated stuff as an random example. :)
73 """
74
75
76 def RequestArg(request, arg, default):
77 return request.args.get(arg, [default])[0]
78
79
80 def RequestArgToBool(request, arg, default):
81 value = RequestArg(request, arg, default)
82 if value in (False, True):
83 return value
84 value = value.lower()
85 if value in ('1', 'true'):
86 return True
87 if value in ('0', 'false'):
88 return False
89 # Ignore value.
90 return default
91
92
93 def TwistedWebErrorAsDict(self, request):
94 """Additional method for twisted.web.error.Error."""
95 result = {}
96 result['http_error'] = self.status
97 result['response'] = self.response
98 return result
99
100
101 def TwistedWebErrorPageAsDict(self, request):
102 """Additional method for twisted.web.error.Error."""
103 result = {}
104 result['http_error'] = self.code
105 result['response'] = self.brief
106 result['detail'] = self.detail
107 return result
108
109
110 # Add .asDict() method to twisted.web.error.Error to simplify the code below.
111 error.Error.asDict = TwistedWebErrorAsDict
112 error.PageRedirect.asDict = TwistedWebErrorAsDict
113 error.ErrorPage.asDict = TwistedWebErrorPageAsDict
114 error.NoResource.asDict = TwistedWebErrorPageAsDict
115 error.ForbiddenResource.asDict = TwistedWebErrorPageAsDict
116
117
118 def FilterOut(data):
119 """Returns a copy with None, False, "", [], () and {} removed.
120 Warning: converts tuple to list."""
121 if isinstance(data, (list, tuple)):
122 # Recurse in every items and filter them out.
123 items = map(FilterOut, data)
124 if not filter(lambda x: not x in ('', False, None, [], {}, ()), items):
125 return None
126 return items
127 elif isinstance(data, dict):
128 return dict(filter(lambda x: not x[1] in ('', False, None, [], {}, ()),
129 [(k, FilterOut(v)) for (k, v) in data.iteritems()]))
130 else:
131 return data
132
133
134 class JsonResource(resource.Resource):
135 """Base class for json data."""
136
137 contentType = "application/json"
138 cache_seconds = 60
139 help = None
140 title = None
141 level = 0
142
143 def __init__(self, status):
144 """Adds transparent lazy-child initialization."""
145 resource.Resource.__init__(self)
146 # buildbot.status.builder.Status
147 self.status = status
148 if self.help:
149 title = ''
150 if self.title:
151 title = self.title + ' help'
152 self.putChild('help',
153 HelpResource(self.help, title=title, parent_node=self) )
154
155 def getChildWithDefault(self, path, request):
156 """Adds transparent support for url ending with /"""
157 if path == "" and len(request.postpath) == 0:
158 return self
159 # Equivalent to resource.Resource.getChildWithDefault()
160 if self.children.has_key(path):
161 return self.children[path]
162 return self.getChild(path, request)
163
164 def putChild(self, name, res):
165 """Adds the resource's level for help links generation."""
166
167 def RecurseFix(res, level):
168 res.level = level + 1
169 for c in res.children.itervalues():
170 RecurseFix(c, res.level)
171
172 RecurseFix(res, self.level)
173 resource.Resource.putChild(self, name, res)
174
175 def render_GET(self, request):
176 """Renders a HTTP GET at the http request level."""
177 data = self.content(request)
178 if isinstance(data, unicode):
179 data = data.encode("utf-8")
180 request.setHeader("Access-Control-Allow-Origin", "*")
181 if RequestArgToBool(request, 'as_text', False):
182 request.setHeader("content-type", 'text/plain')
183 else:
184 request.setHeader("content-type", self.contentType)
185 request.setHeader("content-disposition",
186 "attachment; filename=\"%s.json\"" % request.path)
187 # Make sure we get fresh pages.
188 if self.cache_seconds:
189 now = datetime.datetime.utcnow()
190 expires = now + datetime.timedelta(seconds=self.cache_seconds)
191 request.setHeader("Expires",
192 expires.strftime("%a, %d %b %Y %H:%M:%S GMT"))
193 request.setHeader("Pragma", "no-cache")
194 return data
195
196 def content(self, request):
197 """Renders the json dictionaries."""
198 # Supported flags.
199 select = request.args.get('select')
200 as_text = RequestArgToBool(request, 'as_text', False)
201 filter_out = RequestArgToBool(request, 'filter', as_text)
202 compact = RequestArgToBool(request, 'compact', not as_text)
203 callback = request.args.get('callback')
204
205 # Implement filtering at global level and every child.
206 if select is not None:
207 del request.args['select']
208 # Do not render self.asDict()!
209 data = {}
210 # Remove superfluous /
211 select = [s.strip('/') for s in select]
212 select.sort(cmp=lambda x,y: cmp(x.count('/'), y.count('/')),
213 reverse=True)
214 for item in select:
215 # Start back at root.
216 node = data
217 # Implementation similar to twisted.web.resource.getChildForRequ est
218 # but with a hacked up request.
219 child = self
220 prepath = request.prepath[:]
221 postpath = request.postpath[:]
222 request.postpath = filter(None, item.split('/'))
223 while request.postpath and not child.isLeaf:
224 pathElement = request.postpath.pop(0)
225 node[pathElement] = {}
226 node = node[pathElement]
227 request.prepath.append(pathElement)
228 child = child.getChildWithDefault(pathElement, request)
229 node.update(child.asDict(request))
230 request.prepath = prepath
231 request.postpath = postpath
232 else:
233 data = self.asDict(request)
234 if filter_out:
235 data = FilterOut(data)
236 if compact:
237 data = json.dumps(data, sort_keys=True, separators=(',',':'))
238 else:
239 data = json.dumps(data, sort_keys=True, indent=2)
240 if callback:
241 callback = callback[0]
242 if re.match(r'^[a-zA-Z$][a-zA-Z$0-9.]*$', callback):
243 data = '%s(%s);' % (callback, data)
244 return data
245
246 def asDict(self, request):
247 """Generates the json dictionary.
248
249 By default, renders every childs."""
250 if self.children:
251 data = {}
252 for name in self.children:
253 child = self.getChildWithDefault(name, request)
254 if isinstance(child, JsonResource):
255 data[name] = child.asDict(request)
256 # else silently pass over non-json resources.
257 return data
258 else:
259 raise NotImplementedError()
260
261
262 def ToHtml(text):
263 """Convert a string in a wiki-style format into HTML."""
264 indent = 0
265 in_item = False
266 output = []
267 for line in text.splitlines(False):
268 match = re.match(r'^( +)\- (.*)$', line)
269 if match:
270 if indent < len(match.group(1)):
271 output.append('<ul>')
272 indent = len(match.group(1))
273 elif indent > len(match.group(1)):
274 while indent > len(match.group(1)):
275 output.append('</ul>')
276 indent -= 2
277 if in_item:
278 # Close previous item
279 output.append('</li>')
280 output.append('<li>')
281 in_item = True
282 line = match.group(2)
283 elif indent:
284 if line.startswith((' ' * indent) + ' '):
285 # List continuation
286 line = line.strip()
287 else:
288 # List is done
289 if in_item:
290 output.append('</li>')
291 in_item = False
292 while indent > 0:
293 output.append('</ul>')
294 indent -= 2
295
296 if line.startswith('/'):
297 if not '?' in line:
298 line_full = line + '?as_text=1'
299 else:
300 line_full = line + '&as_text=1'
301 output.append('<a href="' + html.escape(line_full) + '">' +
302 html.escape(line) + '</a>')
303 else:
304 output.append(html.escape(line).replace(' ', '&nbsp;&nbsp;'))
305 if not in_item:
306 output.append('<br>')
307
308 if in_item:
309 output.append('</li>')
310 while indent > 0:
311 output.append('</ul>')
312 indent -= 2
313 return '\n'.join(output)
314
315
316 class HelpResource(HtmlResource):
317 def __init__(self, text, title, parent_node):
318 HtmlResource.__init__(self)
319 self.text = text
320 self.title = title
321 self.parent_node = parent_node
322 self.rendered = False
323
324 def body(self, request):
325 if not self.rendered:
326 self.text = ToHtml(self.text) + '<p>\n'
327 more_text = '<a href="../help">Parent\'s help</a><p>'
328 if len(self.parent_node.children) > 1:
329 more_text += '<p>Child nodes are:<ul>\n'
330 for (name, child) in self.parent_node.children.iteritems():
331 if name == 'help':
332 continue
333 name = html.escape(name)
334 more_text += (
335 '<li><a href="%s">%s</a> (<a href="%s/help">%s/help</a>) </li>\n' %
336 (name + '?as_text=1', name, name, name))
337 more_text += '</ul>\n'
338 self.text += more_text
339 examples = ToHtml(EXAMPLES).replace(
340 'href="/json',
341 'href="%sjson' % (self.level * '../'))
342 self.text += ToHtml(FLAGS) + '<p>' + examples
343 self.rendered = True
344 return self.text
345
346
347 class BuilderPendingBuildsJsonResource(JsonResource):
348 help = """Describe pending builds for a builder.
349 """
350 title = 'Builder'
351
352 def __init__(self, status, builder_status):
353 JsonResource.__init__(self, status)
354 self.builder_status = builder_status
355
356 def asDict(self, request):
357 # buildbot.status.builder.BuilderStatus
358 return [b.asDict() for b in self.builder_status.getPendingBuilds()]
359
360
361 class BuilderJsonResource(JsonResource):
362 help = """Describe a single builder.
363 """
364 title = 'Builder'
365
366 def __init__(self, status, builder_status):
367 JsonResource.__init__(self, status)
368 self.builder_status = builder_status
369 self.putChild('builds', BuildsJsonResource(status, builder_status))
370 self.putChild('slaves', BuilderSlavesJsonResources(status,
371 builder_status))
372 self.putChild(
373 'pendingBuilds',
374 BuilderPendingBuildsJsonResource(status, builder_status))
375
376 def asDict(self, request):
377 # buildbot.status.builder.BuilderStatus
378 return self.builder_status.asDict()
379
380
381 class BuildersJsonResource(JsonResource):
382 help = """List of all the builders defined on a master.
383 """
384 title = 'Builders'
385
386 def __init__(self, status):
387 JsonResource.__init__(self, status)
388 for builder_name in self.status.getBuilderNames():
389 self.putChild(builder_name,
390 BuilderJsonResource(status,
391 status.getBuilder(builder_name)))
392
393
394 class BuilderSlavesJsonResources(JsonResource):
395 help = """Describe the slaves attached to a single builder.
396 """
397 title = 'BuilderSlaves'
398
399 def __init__(self, status, builder_status):
400 JsonResource.__init__(self, status)
401 self.builder_status = builder_status
402 for slave_name in self.builder_status.slavenames:
403 self.putChild(slave_name,
404 SlaveJsonResource(status,
405 self.status.getSlave(slave_name)))
406
407
408 class BuildJsonResource(JsonResource):
409 help = """Describe a single build.
410 """
411 title = 'Build'
412
413 def __init__(self, status, build_status):
414 JsonResource.__init__(self, status)
415 self.build_status = build_status
416 self.putChild('source_stamp',
417 SourceStampJsonResource(status,
418 build_status.getSourceStamp()))
419 self.putChild('steps', BuildStepsJsonResource(status, build_status))
420
421 def asDict(self, request):
422 return self.build_status.asDict()
423
424
425 class AllBuildsJsonResource(JsonResource):
426 help = """All the builds that were run on a builder.
427 """
428 title = 'AllBuilds'
429
430 def __init__(self, status, builder_status):
431 JsonResource.__init__(self, status)
432 self.builder_status = builder_status
433
434 def getChild(self, path, request):
435 # Dynamic childs.
436 if isinstance(path, int) or _IS_INT.match(path):
437 build_status = self.builder_status.getBuild(int(path))
438 if build_status:
439 build_status_number = str(build_status.getNumber())
440 # Happens with negative numbers.
441 child = self.children.get(build_status_number)
442 if child:
443 return child
444 # Create it on-demand.
445 child = BuildJsonResource(self.status, build_status)
446 # Cache it. Never cache negative numbers.
447 # TODO(maruel): Cleanup the cache once it's too heavy!
448 self.putChild(build_status_number, child)
449 return child
450 return JsonResource.getChild(self, path, request)
451
452 def asDict(self, request):
453 results = {}
454 # If max > buildCacheSize, it'll trash the cache...
455 max = int(RequestArg(request, 'max',
456 self.builder_status.buildCacheSize))
457 for i in range(0, max):
458 child = self.getChildWithDefault(-i, request)
459 if not isinstance(child, BuildJsonResource):
460 continue
461 results[child.build_status.getNumber()] = child.asDict(request)
462 return results
463
464
465 class BuildsJsonResource(AllBuildsJsonResource):
466 help = """Builds that were run on a builder.
467 """
468 title = 'Builds'
469
470 def __init__(self, status, builder_status):
471 AllBuildsJsonResource.__init__(self, status, builder_status)
472 self.putChild('_all', AllBuildsJsonResource(status, builder_status))
473
474 def getChild(self, path, request):
475 # Transparently redirects to _all if path is not ''.
476 return self.children['_all'].getChildWithDefault(path, request)
477
478 def asDict(self, request):
479 # This would load all the pickles and is way too heavy, especially that
480 # it would trash the cache:
481 # self.children['builds'].asDict(request)
482 # TODO(maruel) This list should also need to be cached but how?
483 builds = dict([
484 (int(file), None)
485 for file in os.listdir(self.builder_status.basedir)
486 if _IS_INT.match(file)
487 ])
488 return builds
489
490
491 class BuildStepJsonResource(JsonResource):
492 help = """A single build step.
493 """
494 title = 'BuildStep'
495
496 def __init__(self, status, build_step_status):
497 # buildbot.status.builder.BuildStepStatus
498 JsonResource.__init__(self, status)
499 self.build_step_status = build_step_status
500 # TODO self.putChild('logs', LogsJsonResource())
501
502 def asDict(self, request):
503 return self.build_step_status.asDict()
504
505
506 class BuildStepsJsonResource(JsonResource):
507 help = """A list of build steps that occurred during a build.
508 """
509 title = 'BuildSteps'
510
511 def __init__(self, status, build_status):
512 JsonResource.__init__(self, status)
513 self.build_status = build_status
514 # The build steps are constantly changing until the build is done so
515 # keep a reference to build_status instead
516
517 def getChild(self, path, request):
518 # Dynamic childs.
519 build_set_status = None
520 if isinstance(path, int) or _IS_INT.match(path):
521 build_set_status = self.build_status.getSteps[int(path)]
522 else:
523 steps_dict = dict([(step.getName(), step)
524 for step in self.build_status.getStep()])
525 build_set_status = steps_dict.get(path)
526 if build_set_status:
527 # Create it on-demand.
528 child = BuildStepJsonResource(status, build_step_status)
529 # Cache it.
530 index = self.build_status.getSteps().index(build_step_status)
531 self.putChild(str(index), child)
532 self.putChild(build_set_status.getName(), child)
533 return child
534 return JsonResource.getChild(self, path, request)
535
536 def asDict(self, request):
537 # Only use the number and not the names!
538 results = {}
539 index = 0
540 for step in self.build_status.getStep():
541 results[index] = step
542 index += 1
543 return results
544
545
546 class ChangeJsonResource(JsonResource):
547 help = """Describe a single change that originates from a change source.
548 """
549 title = 'Change'
550
551 def __init__(self, status, change):
552 # buildbot.changes.changes.Change
553 JsonResource.__init__(self, status)
554 self.change = change
555
556 def asDict(self, request):
557 return self.change.asDict()
558
559
560 class ChangesJsonResource(JsonResource):
561 help = """List of changes.
562 """
563 title = 'Changes'
564
565 def __init__(self, status, changes):
566 JsonResource.__init__(self, status)
567 for c in changes:
568 # c.number can be None or clash another change if the change was
569 # generated inside buildbot or if using multiple pollers.
570 if c.number is not None and str(c.number) not in self.children:
571 self.putChild(str(c.number), ChangeJsonResource(status, c))
572 else:
573 # Temporary hack since it creates information exposure.
574 self.putChild(str(id(c)), ChangeJsonResource(status, c))
575
576 def asDict(self, request):
577 """Don't throw an exception when there is no child."""
578 if not self.children:
579 return {}
580 return JsonResource.asDict(self, request)
581
582
583 class ChangeSourcesJsonResource(JsonResource):
584 help = """Describe a change source.
585 """
586 title = 'ChangeSources'
587
588 def asDict(self, request):
589 result = {}
590 n = 0
591 for c in self.status.getChangeSources():
592 # buildbot.changes.changes.ChangeMaster
593 change = {}
594 change['description'] = c.describe()
595 result[n] = change
596 n += 1
597 return result
598
599
600 class ProjectJsonResource(JsonResource):
601 help = """Project-wide settings.
602 """
603 title = 'Project'
604
605 def asDict(self, request):
606 return self.status.asDict()
607
608
609 class SlaveJsonResource(JsonResource):
610 help = """Describe a slave.
611 """
612 title = 'Slave'
613
614 def __init__(self, status, slave_status):
615 JsonResource.__init__(self, status)
616 self.slave_status = slave_status
617 self.name = self.slave_status.getName()
618 self.builders = None
619
620 def getBuilders(self):
621 if self.builders is None:
622 # Figure out all the builders to which it's attached
623 self.builders = []
624 for builderName in self.status.getBuilderNames():
625 if self.name in self.status.getBuilder(builderName).slavenames:
626 self.builders.append(builderName)
627 return self.builders
628
629 def asDict(self, request):
630 results = self.slave_status.asDict()
631 # Enhance it by adding more informations.
632 results['builders'] = {}
633 for builderName in self.getBuilders():
634 builds = []
635 builder_status = self.status.getBuilder(builderName)
636 for i in range(1, builder_status.buildCacheSize - 1):
637 build_status = builder_status.getBuild(-i)
638 if not build_status or not build_status.isFinished():
639 # If not finished, it will appear in runningBuilds.
640 break
641 if build_status.getSlavename() == self.name:
642 builds.append(build_status.getNumber())
643 results['builders'][builderName] = builds
644 return results
645
646
647 class SlavesJsonResource(JsonResource):
648 help = """List the registered slaves.
649 """
650 title = 'Slaves'
651
652 def __init__(self, status):
653 JsonResource.__init__(self, status)
654 for slave_name in status.getSlaveNames():
655 self.putChild(slave_name,
656 SlaveJsonResource(status,
657 status.getSlave(slave_name)))
658
659
660 class SourceStampJsonResource(JsonResource):
661 help = """Describe the sources for a BuildRequest.
662 """
663 title = 'SourceStamp'
664
665 def __init__(self, status, source_stamp):
666 # buildbot.sourcestamp.SourceStamp
667 JsonResource.__init__(self, status)
668 self.source_stamp = source_stamp
669 self.putChild('changes',
670 ChangesJsonResource(status, source_stamp.changes))
671 # TODO(maruel): Should redirect to the patch's url instead.
672 #if source_stamp.patch:
673 # self.putChild('patch', StaticHTML(source_stamp.path))
674
675 def asDict(self, request):
676 return self.source_stamp.asDict()
677
678
679 class JsonStatusResource(JsonResource):
680 """Retrieves all json data."""
681 help = """JSON status
682
683 Root page to give a fair amount of information in the current buildbot master
684 status. You may want to use a child instead to reduce the load on the server.
685
686 For help on any sub directory, use url /child/help
687 """
688 title = 'Buildbot JSON'
689
690 def __init__(self, status):
691 JsonResource.__init__(self, status)
692 self.level = 1
693 self.putChild('builders', BuildersJsonResource(status))
694 self.putChild('change_sources', ChangeSourcesJsonResource(status))
695 self.putChild('project', ProjectJsonResource(status))
696 self.putChild('slaves', SlavesJsonResource(status))
697 # This needs to be called before the first HelpResource().body call.
698 self.HackExamples()
699
700 def content(self, request):
701 result = JsonResource.content(self, request)
702 # This is done to hook the downloaded filename.
703 request.path = 'buildbot'
704 return result
705
706 def HackExamples(self):
707 global EXAMPLES
708 # Find the first builder with a previous build or select the last one.
709 builder = None
710 for b in self.status.getBuilderNames():
711 builder = self.status.getBuilder(b)
712 if builder.getBuild(-1):
713 break
714 if not builder:
715 return
716 EXAMPLES = EXAMPLES.replace('<A_BUILDER>', builder.getName())
717 build = builder.getBuild(-1)
718 if build:
719 EXAMPLES = EXAMPLES.replace('<A_BUILD>', str(build.getNumber()))
720 if builder.slavenames:
721 EXAMPLES = EXAMPLES.replace('<A_SLAVE>', builder.slavenames[0])
722
723 # vim: set ts=4 sts=4 sw=4 et:
OLDNEW
« no previous file with comments | « third_party/buildbot_7_12/buildbot/status/web/slaves.py ('k') | third_party/buildbot_7_12/buildbot/status/web/step.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698