Package buildbot :: Package status :: Package web :: Module feeds
[frames] | no frames]

Source Code for Module buildbot.status.web.feeds

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16  # This module enables ATOM and RSS feeds from webstatus. 
 17  # 
 18  # It is based on "feeder.py" which was part of the Buildbot 
 19  # configuration for the Subversion project. The original file was 
 20  # created by Lieven Gobaerts and later adjusted by API 
 21  # (apinheiro@igalia.coma) and also here 
 22  # http://code.google.com/p/pybots/source/browse/trunk/master/Feeder.py 
 23  # 
 24  # All subsequent changes to feeder.py where made by Chandan-Dutta 
 25  # Chowdhury <chandan-dutta.chowdhury @ hp.com> and Gareth Armstrong 
 26  # <gareth.armstrong @ hp.com>. 
 27  # 
 28  # Those modifications are as follows: 
 29  # 1) the feeds are usable from baseweb.WebStatus 
 30  # 2) feeds are fully validated ATOM 1.0 and RSS 2.0 feeds, verified 
 31  #    with code from http://feedvalidator.org 
 32  # 3) nicer xml output 
 33  # 4) feeds can be filtered as per the /waterfall display with the 
 34  #    builder and category filters 
 35  # 5) cleaned up white space and imports 
 36  # 
 37  # Finally, the code was directly integrated into these two files, 
 38  # buildbot/status/web/feeds.py (you're reading it, ;-)) and 
 39  # buildbot/status/web/baseweb.py. 
 40   
 41  import os 
 42  import re 
 43  import time 
 44  from twisted.web import resource 
 45  from buildbot.status.builder import FAILURE 
 46   
47 -class XmlResource(resource.Resource):
48 contentType = "text/xml; charset=UTF-8" 49 docType = '' 50
51 - def getChild(self, name, request):
52 return self
53
54 - def render(self, request):
55 data = self.content(request) 56 request.setHeader("content-type", self.contentType) 57 if request.method == "HEAD": 58 request.setHeader("content-length", len(data)) 59 return '' 60 return data
61 62 _abbr_day = [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 63 _abbr_mon = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 64 'Sep', 'Oct', 'Nov', 'Dec'] 65
66 -def rfc822_time(tstamp):
67 res = time.strftime("%%s, %d %%s %Y %H:%M:%S GMT", 68 tstamp) 69 res = res % (tstamp.tm_wday, tstamp.tm_mon) 70 return res
71
72 -class FeedResource(XmlResource):
73 pageTitle = None 74 link = 'http://dummylink' 75 language = 'en-us' 76 description = 'Dummy rss' 77 status = None 78
79 - def __init__(self, status, categories=None, pageTitle=None):
80 self.status = status 81 self.categories = categories 82 self.pageTitle = pageTitle 83 self.title = self.status.getTitle() 84 self.link = self.status.getBuildbotURL() 85 self.description = 'List of builds' 86 self.pubdate = time.gmtime(int(time.time())) 87 self.user = self.getEnv(['USER', 'USERNAME'], 'buildmaster') 88 self.hostname = self.getEnv(['HOSTNAME', 'COMPUTERNAME'], 89 'buildmaster') 90 self.children = {}
91
92 - def getEnv(self, keys, fallback):
93 for key in keys: 94 if key in os.environ: 95 return os.environ[key] 96 return fallback
97
98 - def getBuilds(self, request):
99 builds = [] 100 # THIS is lifted straight from the WaterfallStatusResource Class in 101 # status/web/waterfall.py 102 # 103 # we start with all Builders available to this Waterfall: this is 104 # limited by the config-file -time categories= argument, and defaults 105 # to all defined Builders. 106 allBuilderNames = self.status.getBuilderNames(categories=self.categories) 107 builders = [self.status.getBuilder(name) for name in allBuilderNames] 108 109 # but if the URL has one or more builder= arguments (or the old show= 110 # argument, which is still accepted for backwards compatibility), we 111 # use that set of builders instead. We still don't show anything 112 # outside the config-file time set limited by categories=. 113 showBuilders = request.args.get("show", []) 114 showBuilders.extend(request.args.get("builder", [])) 115 if showBuilders: 116 builders = [b for b in builders if b.name in showBuilders] 117 118 # now, if the URL has one or category= arguments, use them as a 119 # filter: only show those builders which belong to one of the given 120 # categories. 121 showCategories = request.args.get("category", []) 122 if showCategories: 123 builders = [b for b in builders if b.category in showCategories] 124 125 failures_only = request.args.get("failures_only", "false") 126 127 maxFeeds = 25 128 129 # Copy all failed builds in a new list. 130 # This could clearly be implemented much better if we had 131 # access to a global list of builds. 132 for b in builders: 133 lastbuild = b.getLastFinishedBuild() 134 if lastbuild is None: 135 continue 136 137 lastnr = lastbuild.getNumber() 138 139 totalbuilds = 0 140 i = lastnr 141 while i >= 0: 142 build = b.getBuild(i) 143 i -= 1 144 if not build: 145 continue 146 147 results = build.getResults() 148 149 if failures_only == "false" or results == FAILURE: 150 totalbuilds += 1 151 builds.append(build) 152 153 # stop for this builder when our total nr. of feeds is reached 154 if totalbuilds >= maxFeeds: 155 break 156 157 # Sort build list by date, youngest first. 158 # To keep compatibility with python < 2.4, use this for sorting instead: 159 # We apply Decorate-Sort-Undecorate 160 deco = [(build.getTimes(), build) for build in builds] 161 deco.sort() 162 deco.reverse() 163 builds = [build for (b1, build) in deco] 164 165 if builds: 166 builds = builds[:min(len(builds), maxFeeds)] 167 return builds
168
169 - def content(self, request):
170 builds = self.getBuilds(request) 171 172 build_cxts = [] 173 174 for build in builds: 175 start, finished = build.getTimes() 176 finishedTime = time.gmtime(int(finished)) 177 link = re.sub(r'index.html', "", self.status.getURLForThing(build)) 178 179 # title: trunk r22191 (plus patch) failed on 180 # 'i686-debian-sarge1 shared gcc-3.3.5' 181 ss = build.getSourceStamp() 182 source = "" 183 if ss.branch: 184 source += "Branch %s " % ss.branch 185 if ss.revision: 186 source += "Revision %s " % str(ss.revision) 187 if ss.patch: 188 source += " (plus patch)" 189 if ss.changes: 190 pass 191 if (ss.branch is None and ss.revision is None and ss.patch is None 192 and not ss.changes): 193 source += "Latest revision " 194 got_revision = None 195 try: 196 got_revision = build.getProperty("got_revision") 197 except KeyError: 198 pass 199 if got_revision: 200 got_revision = str(got_revision) 201 if len(got_revision) > 40: 202 got_revision = "[revision string too long]" 203 source += "(Got Revision: %s)" % got_revision 204 failflag = (build.getResults() != FAILURE) 205 pageTitle = ('%s %s on "%s"' % 206 (source, ["failed","succeeded"][failflag], 207 build.getBuilder().getName())) 208 209 # Add information about the failing steps. 210 failed_steps = [] 211 log_lines = [] 212 for s in build.getSteps(): 213 if s.getResults()[0] == FAILURE: 214 failed_steps.append(s.getName()) 215 216 # Add the last 30 lines of each log. 217 for log in s.getLogs(): 218 log_lines.append('Last lines of build log "%s":' % 219 log.getName()) 220 log_lines.append([]) 221 try: 222 logdata = log.getText() 223 except IOError: 224 # Probably the log file has been removed 225 logdata ='** log file not available **' 226 unilist = list() 227 for line in logdata.split('\n')[-30:]: 228 unilist.append(unicode(line,'utf-8')) 229 log_lines.extend(unilist) 230 231 bc = {} 232 bc['date'] = rfc822_time(finishedTime) 233 bc['summary_link'] = ('%sbuilders/%s' % 234 (self.link, 235 build.getBuilder().getName())) 236 bc['name'] = build.getBuilder().getName() 237 bc['number'] = build.getNumber() 238 bc['responsible_users'] = build.getResponsibleUsers() 239 bc['failed_steps'] = failed_steps 240 bc['pageTitle'] = pageTitle 241 bc['link'] = link 242 bc['log_lines'] = log_lines 243 244 if finishedTime is not None: 245 bc['rfc822_pubdate'] = rfc822_time(finishedTime) 246 bc['rfc3339_pubdate'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", 247 finishedTime) 248 249 # Every RSS/Atom item must have a globally unique ID 250 guid = ('tag:%s@%s,%s:%s' % 251 (self.user, self.hostname, 252 time.strftime("%Y-%m-%d", finishedTime), 253 time.strftime("%Y%m%d%H%M%S", finishedTime))) 254 bc['guid'] = guid 255 256 build_cxts.append(bc) 257 258 pageTitle = self.pageTitle 259 if not pageTitle: 260 pageTitle = 'Build status of %s' % self.title 261 262 cxt = {} 263 cxt['pageTitle'] = pageTitle 264 cxt['title_url'] = self.link 265 cxt['title'] = self.title 266 cxt['language'] = self.language 267 cxt['description'] = self.description 268 if self.pubdate is not None: 269 cxt['rfc822_pubdate'] = rfc822_time( self.pubdate) 270 cxt['rfc3339_pubdate'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", 271 self.pubdate) 272 273 cxt['builds'] = build_cxts 274 template = request.site.buildbot_service.templates.get_template(self.template_file) 275 return template.render(**cxt).encode('utf-8').strip()
276
277 -class Rss20StatusResource(FeedResource):
278 # contentType = 'application/rss+xml' (browser dependent) 279 template_file = 'feed_rss20.xml' 280
281 - def __init__(self, status, categories=None, pageTitle=None):
282 FeedResource.__init__(self, status, categories, pageTitle)
283
284 -class Atom10StatusResource(FeedResource):
285 # contentType = 'application/atom+xml' (browser dependent) 286 template_file = 'feed_atom10.xml' 287
288 - def __init__(self, status, categories=None, pageTitle=None):
289 FeedResource.__init__(self, status, categories, pageTitle)
290