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 -class FeedResource(XmlResource):
63 title = None 64 link = 'http://dummylink' 65 language = 'en-us' 66 description = 'Dummy rss' 67 status = None 68
69 - def __init__(self, status, categories=None, title=None):
70 self.status = status 71 self.categories = categories 72 self.title = title 73 self.projectName = self.status.getProjectName() 74 self.link = self.status.getBuildbotURL() 75 self.description = 'List of builds' 76 self.pubdate = time.gmtime(int(time.time())) 77 self.user = self.getEnv(['USER', 'USERNAME'], 'buildmaster') 78 self.hostname = self.getEnv(['HOSTNAME', 'COMPUTERNAME'], 79 'buildmaster') 80 self.children = {}
81
82 - def getEnv(self, keys, fallback):
83 for key in keys: 84 if key in os.environ: 85 return os.environ[key] 86 return fallback
87
88 - def getBuilds(self, request):
89 builds = [] 90 # THIS is lifted straight from the WaterfallStatusResource Class in 91 # status/web/waterfall.py 92 # 93 # we start with all Builders available to this Waterfall: this is 94 # limited by the config-file -time categories= argument, and defaults 95 # to all defined Builders. 96 allBuilderNames = self.status.getBuilderNames(categories=self.categories) 97 builders = [self.status.getBuilder(name) for name in allBuilderNames] 98 99 # but if the URL has one or more builder= arguments (or the old show= 100 # argument, which is still accepted for backwards compatibility), we 101 # use that set of builders instead. We still don't show anything 102 # outside the config-file time set limited by categories=. 103 showBuilders = request.args.get("show", []) 104 showBuilders.extend(request.args.get("builder", [])) 105 if showBuilders: 106 builders = [b for b in builders if b.name in showBuilders] 107 108 # now, if the URL has one or category= arguments, use them as a 109 # filter: only show those builders which belong to one of the given 110 # categories. 111 showCategories = request.args.get("category", []) 112 if showCategories: 113 builders = [b for b in builders if b.category in showCategories] 114 115 failures_only = request.args.get("failures_only", "false") 116 117 maxFeeds = 25 118 119 # Copy all failed builds in a new list. 120 # This could clearly be implemented much better if we had 121 # access to a global list of builds. 122 for b in builders: 123 lastbuild = b.getLastFinishedBuild() 124 if lastbuild is None: 125 continue 126 127 lastnr = lastbuild.getNumber() 128 129 totalbuilds = 0 130 i = lastnr 131 while i >= 0: 132 build = b.getBuild(i) 133 i -= 1 134 if not build: 135 continue 136 137 results = build.getResults() 138 139 if failures_only == "false" or results == FAILURE: 140 totalbuilds += 1 141 builds.append(build) 142 143 # stop for this builder when our total nr. of feeds is reached 144 if totalbuilds >= maxFeeds: 145 break 146 147 # Sort build list by date, youngest first. 148 # To keep compatibility with python < 2.4, use this for sorting instead: 149 # We apply Decorate-Sort-Undecorate 150 deco = [(build.getTimes(), build) for build in builds] 151 deco.sort() 152 deco.reverse() 153 builds = [build for (b1, build) in deco] 154 155 if builds: 156 builds = builds[:min(len(builds), maxFeeds)] 157 return builds
158
159 - def content(self, request):
160 builds = self.getBuilds(request) 161 162 build_cxts = [] 163 164 for build in builds: 165 start, finished = build.getTimes() 166 finishedTime = time.gmtime(int(finished)) 167 link = re.sub(r'index.html', "", self.status.getURLForThing(build)) 168 169 # title: trunk r22191 (plus patch) failed on 170 # 'i686-debian-sarge1 shared gcc-3.3.5' 171 ss = build.getSourceStamp() 172 source = "" 173 if ss.branch: 174 source += "Branch %s " % ss.branch 175 if ss.revision: 176 source += "Revision %s " % str(ss.revision) 177 if ss.patch: 178 source += " (plus patch)" 179 if ss.changes: 180 pass 181 if (ss.branch is None and ss.revision is None and ss.patch is None 182 and not ss.changes): 183 source += "Latest revision " 184 got_revision = None 185 try: 186 got_revision = build.getProperty("got_revision") 187 except KeyError: 188 pass 189 if got_revision: 190 got_revision = str(got_revision) 191 if len(got_revision) > 40: 192 got_revision = "[revision string too long]" 193 source += "(Got Revision: %s)" % got_revision 194 failflag = (build.getResults() != FAILURE) 195 title = ('%s %s on "%s"' % 196 (source, ["failed","succeeded"][failflag], 197 build.getBuilder().getName())) 198 199 # Add information about the failing steps. 200 failed_steps = [] 201 log_lines = [] 202 for s in build.getSteps(): 203 if s.getResults()[0] == FAILURE: 204 failed_steps.append(s.getName()) 205 206 # Add the last 30 lines of each log. 207 for log in s.getLogs(): 208 log_lines.append('Last lines of build log "%s":' % 209 log.getName()) 210 log_lines.append([]) 211 try: 212 logdata = log.getText() 213 except IOError: 214 # Probably the log file has been removed 215 logdata ='** log file not available **' 216 unilist = list() 217 for line in logdata.split('\n')[-30:]: 218 unilist.append(unicode(line,'utf-8')) 219 log_lines.extend(unilist) 220 221 bc = {} 222 bc['date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", 223 finishedTime) 224 bc['summary_link'] = ('%sbuilders/%s' % 225 (self.link, 226 build.getBuilder().getName())) 227 bc['name'] = build.getBuilder().getName() 228 bc['number'] = build.getNumber() 229 bc['responsible_users'] = build.getResponsibleUsers() 230 bc['failed_steps'] = failed_steps 231 bc['title'] = title 232 bc['link'] = link 233 bc['log_lines'] = log_lines 234 235 if finishedTime is not None: 236 bc['rfc822_pubdate'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", 237 finishedTime) 238 bc['rfc3339_pubdate'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", 239 finishedTime) 240 241 # Every RSS/Atom item must have a globally unique ID 242 guid = ('tag:%s@%s,%s:%s' % 243 (self.user, self.hostname, 244 time.strftime("%Y-%m-%d", finishedTime), 245 time.strftime("%Y%m%d%H%M%S", finishedTime))) 246 bc['guid'] = guid 247 248 build_cxts.append(bc) 249 250 title = self.title 251 if not title: 252 title = 'Build status of %s' % self.projectName 253 254 cxt = {} 255 cxt['title'] = title 256 cxt['project_url'] = self.link 257 cxt['project_name'] = self.projectName 258 cxt['language'] = self.language 259 cxt['description'] = self.description 260 if self.pubdate is not None: 261 cxt['rfc822_pubdate'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", 262 self.pubdate) 263 cxt['rfc3339_pubdate'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", 264 self.pubdate) 265 266 cxt['builds'] = build_cxts 267 template = request.site.buildbot_service.templates.get_template(self.template_file) 268 return template.render(**cxt).encode('utf-8').strip()
269
270 -class Rss20StatusResource(FeedResource):
271 # contentType = 'application/rss+xml' (browser dependent) 272 template_file = 'feed_rss20.xml' 273
274 - def __init__(self, status, categories=None, title=None):
275 FeedResource.__init__(self, status, categories, title)
276
277 -class Atom10StatusResource(FeedResource):
278 # contentType = 'application/atom+xml' (browser dependent) 279 template_file = 'feed_atom10.xml' 280
281 - def __init__(self, status, categories=None, title=None):
282 FeedResource.__init__(self, status, categories, title)
283