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

Source Code for Module buildbot.status.web.feeds

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