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

Side by Side Diff: third_party/buildbot_7_12/buildbot/status/web/feeds.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 # This module enables ATOM and RSS feeds from webstatus.
2 #
3 # It is based on "feeder.py" which was part of the Buildbot
4 # configuration for the Subversion project. The original file was
5 # created by Lieven Gobaerts and later adjusted by API
6 # (apinheiro@igalia.coma) and also here
7 # http://code.google.com/p/pybots/source/browse/trunk/master/Feeder.py
8 #
9 # All subsequent changes to feeder.py where made by Chandan-Dutta
10 # Chowdhury <chandan-dutta.chowdhury @ hp.com> and Gareth Armstrong
11 # <gareth.armstrong @ hp.com>.
12 #
13 # Those modifications are as follows:
14 # 1) the feeds are usable from baseweb.WebStatus
15 # 2) feeds are fully validated ATOM 1.0 and RSS 2.0 feeds, verified
16 # with code from http://feedvalidator.org
17 # 3) nicer xml output
18 # 4) feeds can be filtered as per the /waterfall display with the
19 # builder and category filters
20 # 5) cleaned up white space and imports
21 #
22 # Finally, the code was directly integrated into these two files,
23 # buildbot/status/web/feeds.py (you're reading it, ;-)) and
24 # buildbot/status/web/baseweb.py.
25
26 import os
27 import re
28 import sys
29 import time
30 from twisted.web import resource, html
31 from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, EXCEPTION
32
33 class XmlResource(resource.Resource):
34 contentType = "text/xml; charset=UTF-8"
35 def render(self, request):
36 data = self.content(request)
37 request.setHeader("content-type", self.contentType)
38 if request.method == "HEAD":
39 request.setHeader("content-length", len(data))
40 return ''
41 return data
42 docType = ''
43 def header (self, request):
44 data = ('<?xml version="1.0"?>\n')
45 return data
46 def footer(self, request):
47 data = ''
48 return data
49 def content(self, request):
50 data = self.docType
51 data += self.header(request)
52 data += self.body(request)
53 data += self.footer(request)
54 return data
55 def body(self, request):
56 return ''
57
58 class FeedResource(XmlResource):
59 title = None
60 link = 'http://dummylink'
61 language = 'en-us'
62 description = 'Dummy rss'
63 status = None
64
65 def __init__(self, status, categories=None, title=None):
66 self.status = status
67 self.categories = categories
68 self.title = title
69 self.projectName = self.status.getProjectName()
70 self.link = self.status.getBuildbotURL()
71 self.description = 'List of FAILED builds'
72 self.pubdate = time.gmtime(int(time.time()))
73 self.user = self.getEnv(['USER', 'USERNAME'], 'buildmaster')
74 self.hostname = self.getEnv(['HOSTNAME', 'COMPUTERNAME'],
75 'buildmaster')
76
77 def getEnv(self, keys, fallback):
78 for key in keys:
79 if key in os.environ:
80 return os.environ[key]
81 return fallback
82
83 def getBuilds(self, request):
84 builds = []
85 # THIS is lifted straight from the WaterfallStatusResource Class in
86 # status/web/waterfall.py
87 #
88 # we start with all Builders available to this Waterfall: this is
89 # limited by the config-file -time categories= argument, and defaults
90 # to all defined Builders.
91 allBuilderNames = self.status.getBuilderNames(categories=self.categories )
92 builders = [self.status.getBuilder(name) for name in allBuilderNames]
93
94 # but if the URL has one or more builder= arguments (or the old show=
95 # argument, which is still accepted for backwards compatibility), we
96 # use that set of builders instead. We still don't show anything
97 # outside the config-file time set limited by categories=.
98 showBuilders = request.args.get("show", [])
99 showBuilders.extend(request.args.get("builder", []))
100 if showBuilders:
101 builders = [b for b in builders if b.name in showBuilders]
102
103 # now, if the URL has one or category= arguments, use them as a
104 # filter: only show those builders which belong to one of the given
105 # categories.
106 showCategories = request.args.get("category", [])
107 if showCategories:
108 builders = [b for b in builders if b.category in showCategories]
109
110 maxFeeds = 25
111
112 # Copy all failed builds in a new list.
113 # This could clearly be implemented much better if we had
114 # access to a global list of builds.
115 for b in builders:
116 lastbuild = b.getLastFinishedBuild()
117 if lastbuild is None:
118 continue
119
120 lastnr = lastbuild.getNumber()
121
122 totalbuilds = 0
123 i = lastnr
124 while i >= 0:
125 build = b.getBuild(i)
126 i -= 1
127 if not build:
128 continue
129
130 results = build.getResults()
131
132 # only add entries for failed builds!
133 if results == FAILURE:
134 totalbuilds += 1
135 builds.append(build)
136
137 # stop for this builder when our total nr. of feeds is reached
138 if totalbuilds >= maxFeeds:
139 break
140
141 # Sort build list by date, youngest first.
142 # To keep compatibility with python < 2.4, use this for sorting instead:
143 # We apply Decorate-Sort-Undecorate
144 deco = [(build.getTimes(), build) for build in builds]
145 deco.sort()
146 deco.reverse()
147 builds = [build for (b1, build) in deco]
148
149 if builds:
150 builds = builds[:min(len(builds), maxFeeds)]
151 return builds
152
153 def body (self, request):
154 data = ''
155 builds = self.getBuilds(request)
156
157 for build in builds:
158 start, finished = build.getTimes()
159 finishedTime = time.gmtime(int(finished))
160 link = re.sub(r'index.html', "", self.status.getURLForThing(build))
161
162 # title: trunk r22191 (plus patch) failed on 'i686-debian-sarge1 sha red gcc-3.3.5'
163 ss = build.getSourceStamp()
164 source = ""
165 if ss.branch:
166 source += "Branch %s " % ss.branch
167 if ss.revision:
168 source += "Revision %s " % str(ss.revision)
169 if ss.patch:
170 source += " (plus patch)"
171 if ss.changes:
172 pass
173 if (ss.branch is None and ss.revision is None and ss.patch is None
174 and not ss.changes):
175 source += "Latest revision "
176 got_revision = None
177 try:
178 got_revision = build.getProperty("got_revision")
179 except KeyError:
180 pass
181 if got_revision:
182 got_revision = str(got_revision)
183 if len(got_revision) > 40:
184 got_revision = "[revision string too long]"
185 source += "(Got Revision: %s)" % got_revision
186 title = ('%s failed on "%s"' %
187 (source, build.getBuilder().getName()))
188
189 description = ''
190 description += ('Date: %s<br/><br/>' %
191 time.strftime("%a, %d %b %Y %H:%M:%S GMT",
192 finishedTime))
193 description += ('Full details available here: <a href="%s">%s</a><br />' %
194 (self.link, self.projectName))
195 builder_summary_link = ('%s/builders/%s' %
196 (re.sub(r'/index.html', '', self.link),
197 build.getBuilder().getName()))
198 description += ('Build summary: <a href="%s">%s</a><br/><br/>' %
199 (builder_summary_link,
200 build.getBuilder().getName()))
201 description += ('Build details: <a href="%s">%s</a><br/><br/>' %
202 (link, link))
203 description += ('Author list: <b>%s</b><br/><br/>' %
204 ",".join(build.getResponsibleUsers()))
205
206 # Add information about the failing steps.
207 lastlog = ''
208 for s in build.getSteps():
209 if s.getResults()[0] == FAILURE:
210 description += ('Failed step: <b>%s</b><br/>' % s.getName())
211
212 # Add the last 30 lines of each log.
213 for log in s.getLogs():
214 lastlog += ('Last lines of build log "%s":<br/>' % log.g etName())
215 try:
216 logdata = log.getText()
217 except IOError:
218 # Probably the log file has been removed
219 logdata ='<b>log file not available</b>'
220
221 lastlines = logdata.split('\n')[-30:]
222 lastlog += '<br/>'.join(lastlines)
223 lastlog += '<br/>'
224 description += '<br/>'
225
226 data += self.item(title, description=description, lastlog=lastlog,
227 link=link, pubDate=finishedTime)
228
229 return data
230
231 def item(self, title='', link='', description='', pubDate=''):
232 """Generates xml for one item in the feed."""
233
234 class Rss20StatusResource(FeedResource):
235 def __init__(self, status, categories=None, title=None):
236 FeedResource.__init__(self, status, categories, title)
237 contentType = 'application/rss+xml'
238
239 def header(self, request):
240 data = FeedResource.header(self, request)
241 data += ('<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">\n ')
242 data += (' <channel>\n')
243 if self.title is None:
244 title = 'Build status of ' + self.projectName
245 else:
246 title = self.title
247 data += (' <title>%s</title>\n' % title)
248 if self.link is not None:
249 data += (' <link>%s</link>\n' % self.link)
250 link = re.sub(r'/index.html', '', self.link)
251 data += (' <atom:link href="%s/rss" rel="self" type="application/rss+ xml"/>\n' % link)
252 if self.language is not None:
253 data += (' <language>%s</language>\n' % self.language)
254 if self.description is not None:
255 data += (' <description>%s</description>\n' % self.description)
256 if self.pubdate is not None:
257 rfc822_pubdate = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
258 self.pubdate)
259 data += (' <pubDate>%s</pubDate>\n' % rfc822_pubdate)
260 return data
261
262 def item(self, title='', link='', description='', lastlog='', pubDate=''):
263 data = (' <item>\n')
264 data += (' <title>%s</title>\n' % title)
265 if link is not None:
266 data += (' <link>%s</link>\n' % link)
267 if (description is not None and lastlog is not None):
268 lastlog = lastlog.replace('<br/>', '\n')
269 lastlog = html.escape(lastlog)
270 lastlog = lastlog.replace('\n', '<br/>')
271 content = '<![CDATA['
272 content += description
273 content += lastlog
274 content += ']]>'
275 data += (' <description>%s</description>\n' % content)
276 if pubDate is not None:
277 rfc822pubDate = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
278 pubDate)
279 data += (' <pubDate>%s</pubDate>\n' % rfc822pubDate)
280 # Every RSS item must have a globally unique ID
281 guid = ('tag:%s@%s,%s:%s' % (self.user, self.hostname,
282 time.strftime("%Y-%m-%d", pubDate),
283 time.strftime("%Y%m%d%H%M%S",
284 pubDate)))
285 data += (' <guid isPermaLink="false">%s</guid>\n' % guid)
286 data += (' </item>\n')
287 return data
288
289 def footer(self, request):
290 data = (' </channel>\n'
291 '</rss>')
292 return data
293
294 class Atom10StatusResource(FeedResource):
295 def __init__(self, status, categories=None, title=None):
296 FeedResource.__init__(self, status, categories, title)
297 contentType = 'application/atom+xml'
298
299 def header(self, request):
300 data = FeedResource.header(self, request)
301 data += '<feed xmlns="http://www.w3.org/2005/Atom">\n'
302 data += (' <id>%s</id>\n' % self.link)
303 if self.title is None:
304 title = 'Build status of ' + self.projectName
305 else:
306 title = self.title
307 data += (' <title>%s</title>\n' % title)
308 if self.link is not None:
309 link = re.sub(r'/index.html', '', self.link)
310 data += (' <link rel="self" href="%s/atom"/>\n' % link)
311 data += (' <link rel="alternate" href="%s/"/>\n' % link)
312 if self.description is not None:
313 data += (' <subtitle>%s</subtitle>\n' % self.description)
314 if self.pubdate is not None:
315 rfc3339_pubdate = time.strftime("%Y-%m-%dT%H:%M:%SZ",
316 self.pubdate)
317 data += (' <updated>%s</updated>\n' % rfc3339_pubdate)
318 data += (' <author>\n')
319 data += (' <name>Build Bot</name>\n')
320 data += (' </author>\n')
321 return data
322
323 def item(self, title='', link='', description='', lastlog='', pubDate=''):
324 data = (' <entry>\n')
325 data += (' <title>%s</title>\n' % title)
326 if link is not None:
327 data += (' <link href="%s"/>\n' % link)
328 if (description is not None and lastlog is not None):
329 lastlog = lastlog.replace('<br/>', '\n')
330 lastlog = html.escape(lastlog)
331 lastlog = lastlog.replace('\n', '<br/>')
332 data += (' <content type="xhtml">\n')
333 data += (' <div xmlns="http://www.w3.org/1999/xhtml">\n')
334 data += (' %s\n' % description)
335 data += (' <pre xml:space="preserve">%s</pre>\n' % lastlog)
336 data += (' </div>\n')
337 data += (' </content>\n')
338 if pubDate is not None:
339 rfc3339pubDate = time.strftime("%Y-%m-%dT%H:%M:%SZ",
340 pubDate)
341 data += (' <updated>%s</updated>\n' % rfc3339pubDate)
342 # Every Atom entry must have a globally unique ID
343 # http://diveintomark.org/archives/2004/05/28/howto-atom-id
344 guid = ('tag:%s@%s,%s:%s' % (self.user, self.hostname,
345 time.strftime("%Y-%m-%d", pubDate),
346 time.strftime("%Y%m%d%H%M%S",
347 pubDate)))
348 data += (' <id>%s</id>\n' % guid)
349 data += (' <author>\n')
350 data += (' <name>Build Bot</name>\n')
351 data += (' </author>\n')
352 data += (' </entry>\n')
353 return data
354
355 def footer(self, request):
356 data = ('</feed>')
357 return data
OLDNEW
« no previous file with comments | « third_party/buildbot_7_12/buildbot/status/web/extended.css ('k') | third_party/buildbot_7_12/buildbot/status/web/grid.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698