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 import results 
 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 failures_only = failures_only[0] not in ('false', '0', 'no', 'off') 127 128 maxFeeds = 25 129 130 # Copy all failed builds in a new list. 131 # This could clearly be implemented much better if we had 132 # access to a global list of builds. 133 for b in builders: 134 if failures_only: 135 res = (results.FAILURE,) 136 else: 137 res = None 138 builds.extend(b.generateFinishedBuilds(results=res, max_search=maxFeeds)) 139 140 # Sort build list by date, youngest first. 141 # To keep compatibility with python < 2.4, use this for sorting instead: 142 # We apply Decorate-Sort-Undecorate 143 deco = [(build.getTimes(), build) for build in builds] 144 deco.sort() 145 deco.reverse() 146 builds = [build for (b1, build) in deco] 147 148 if builds: 149 builds = builds[:min(len(builds), maxFeeds)] 150 return builds
151
152 - def content(self, request):
153 builds = self.getBuilds(request) 154 155 build_cxts = [] 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 163 # 'i686-debian-sarge1 shared gcc-3.3.5' 164 ss_list = build.getSourceStamps() 165 all_got_revisions = build.getAllGotRevisions() 166 src_cxts = [] 167 for ss in ss_list: 168 sc = {} 169 sc['codebase'] = ss.codebase 170 if (ss.branch is None and ss.revision is None and ss.patch is None 171 and not ss.changes): 172 sc['repository'] = None 173 sc['branch'] = None 174 sc['revision'] = "Latest revision" 175 else: 176 sc['repository'] = ss.repository 177 sc['branch'] = ss.branch 178 got_revision = all_got_revisions.get(ss.codebase, None) 179 if got_revision: 180 sc['revision'] = got_revision 181 else: 182 sc['revision'] = str(ss.revision) 183 if ss.patch: 184 sc['revision'] += " (plus patch)" 185 if ss.changes: 186 pass 187 src_cxts.append(sc) 188 res = build.getResults() 189 pageTitle = ('Builder "%s": %s' % 190 (build.getBuilder().getName(), results.Results[res])) 191 192 # Add information about the failing steps. 193 failed_steps = [] 194 log_lines = [] 195 for s in build.getSteps(): 196 res = s.getResults()[0] 197 if res not in (results.SUCCESS, results.WARNINGS, 198 results.SKIPPED): 199 failed_steps.append(s.getName()) 200 201 # Add the last 30 lines of each log. 202 for log in s.getLogs(): 203 log_lines.append('Last lines of build log "%s":' % 204 log.getName()) 205 log_lines.append([]) 206 try: 207 logdata = log.getText() 208 except IOError: 209 # Probably the log file has been removed 210 logdata ='** log file not available **' 211 unilist = list() 212 for line in logdata.split('\n')[-30:]: 213 unilist.append(unicode(line,'utf-8')) 214 log_lines.extend(unilist) 215 216 bc = {} 217 bc['sources'] = src_cxts 218 bc['date'] = rfc822_time(finishedTime) 219 bc['summary_link'] = ('%sbuilders/%s' % 220 (self.link, 221 build.getBuilder().getName())) 222 bc['name'] = build.getBuilder().getName() 223 bc['number'] = build.getNumber() 224 bc['responsible_users'] = build.getResponsibleUsers() 225 bc['failed_steps'] = failed_steps 226 bc['pageTitle'] = pageTitle 227 bc['link'] = link 228 bc['log_lines'] = log_lines 229 230 if finishedTime is not None: 231 bc['rfc822_pubdate'] = rfc822_time(finishedTime) 232 bc['rfc3339_pubdate'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", 233 finishedTime) 234 235 # Every RSS/Atom item must have a globally unique ID 236 guid = ('tag:%s@%s,%s:%s' % 237 (self.user, self.hostname, 238 time.strftime("%Y-%m-%d", finishedTime), 239 time.strftime("%Y%m%d%H%M%S", finishedTime))) 240 bc['guid'] = guid 241 242 build_cxts.append(bc) 243 244 pageTitle = self.pageTitle 245 if not pageTitle: 246 pageTitle = 'Build status of %s' % self.title 247 248 cxt = {} 249 cxt['pageTitle'] = pageTitle 250 cxt['title_url'] = self.link 251 cxt['title'] = self.title 252 cxt['language'] = self.language 253 cxt['description'] = self.description 254 if self.pubdate is not None: 255 cxt['rfc822_pubdate'] = rfc822_time( self.pubdate) 256 cxt['rfc3339_pubdate'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", 257 self.pubdate) 258 259 cxt['builds'] = build_cxts 260 template = request.site.buildbot_service.templates.get_template(self.template_file) 261 return template.render(**cxt).encode('utf-8').strip()
262
263 -class Rss20StatusResource(FeedResource):
264 # contentType = 'application/rss+xml' (browser dependent) 265 template_file = 'feed_rss20.xml' 266
267 - def __init__(self, status, categories=None, pageTitle=None):
268 FeedResource.__init__(self, status, categories, pageTitle)
269
270 -class Atom10StatusResource(FeedResource):
271 # contentType = 'application/atom+xml' (browser dependent) 272 template_file = 'feed_atom10.xml' 273
274 - def __init__(self, status, categories=None, pageTitle=None):
275 FeedResource.__init__(self, status, categories, pageTitle)
276