~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to tools/buildbot/master/Feeder.py

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#
2
 
#
3
 
# Licensed to the Apache Software Foundation (ASF) under one
4
 
# or more contributor license agreements.  See the NOTICE file
5
 
# distributed with this work for additional information
6
 
# regarding copyright ownership.  The ASF licenses this file
7
 
# to you under the Apache License, Version 2.0 (the
8
 
# "License"); you may not use this file except in compliance
9
 
# with the License.  You may obtain a copy of the License at
10
 
#
11
 
#   http://www.apache.org/licenses/LICENSE-2.0
12
 
#
13
 
# Unless required by applicable law or agreed to in writing,
14
 
# software distributed under the License is distributed on an
15
 
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
 
# KIND, either express or implied.  See the License for the
17
 
# specific language governing permissions and limitations
18
 
# under the License.
19
 
#
20
 
#
21
 
# This file is part of the Buildbot configuration for the Subversion project.
22
 
# The original file was created by Lieven Govaerts
23
 
#
24
 
# Minor changes made by API (apinheiro@igalia.com) in order to fit with our
25
 
# configuration and last buildbot changes
26
 
#
27
 
# Minor whitespace clean up, clean up imports, adapted to buildbot 0.7.7,
28
 
# and finally attempt to create valid atom and RSS feeds.
29
 
# Changes by Chandan-Dutta Chowdhury <chandan-dutta chowdhury @ hp com> and
30
 
# Gareth Armstrong <gareth armstrong @ hp com>
31
 
# Also integrate changes from
32
 
# http://code.google.com/p/pybots/source/browse/trunk/master/Feeder.py
33
 
# which adds ability to filter RSS feeds to specific builders.
34
 
# e.g. http://localhost:8012/rss?builder=builder-log4c-rhel-4-i386
35
 
 
36
 
import time
37
 
import os
38
 
import re
39
 
import sys
40
 
 
41
 
from twisted.web.resource import Resource
42
 
 
43
 
from buildbot.status.web import baseweb
44
 
from buildbot.status.builder import FAILURE, SUCCESS, WARNINGS
45
 
 
46
 
class XmlResource(Resource):
47
 
    contentType = "text/xml; charset=UTF-8"
48
 
    def render(self, request):
49
 
        data = self.content(request)
50
 
        request.setHeader("content-type", self.contentType)
51
 
        if request.method == "HEAD":
52
 
            request.setHeader("content-length", len(data))
53
 
            return ''
54
 
        return data
55
 
    docType = ''
56
 
    def header (self, request):
57
 
        data = ('<?xml version="1.0"?>\n')
58
 
        return data
59
 
    def footer(self, request):
60
 
        data = ''
61
 
        return data
62
 
    def content(self, request):
63
 
        data = self.docType
64
 
        data += self.header(request)
65
 
        data += self.body(request)
66
 
        data += self.footer(request)
67
 
        return data
68
 
    def body(self, request):
69
 
        return ''
70
 
 
71
 
class FeedResource(XmlResource):
72
 
    title = 'Dummy'
73
 
    link = 'http://dummylink'
74
 
    language = 'en-us'
75
 
    description = 'Dummy rss'
76
 
    status = None
77
 
 
78
 
    def __init__(self, status, categories=None):
79
 
        self.status = status
80
 
        self.categories = categories
81
 
        self.link = self.status.getBuildbotURL()
82
 
        self.title = 'Build status of ' + status.getProjectName()
83
 
        self.description = 'List of FAILED builds'
84
 
        self.pubdate = time.gmtime(int(time.time()))
85
 
 
86
 
    def getBuilds(self, request):
87
 
        builds = []
88
 
        # THIS is lifted straight from the WaterfallStatusResource Class in
89
 
        # status/web/waterfall.py
90
 
        #
91
 
        # we start with all Builders available to this Waterfall: this is
92
 
        # limited by the config-file -time categories= argument, and defaults
93
 
        # to all defined Builders.
94
 
        allBuilderNames = self.status.getBuilderNames(categories=self.categories)
95
 
        builders = [self.status.getBuilder(name) for name in allBuilderNames]
96
 
 
97
 
        # but if the URL has one or more builder= arguments (or the old show=
98
 
        # argument, which is still accepted for backwards compatibility), we
99
 
        # use that set of builders instead. We still don't show anything
100
 
        # outside the config-file time set limited by categories=.
101
 
        showBuilders = request.args.get("show", [])
102
 
        showBuilders.extend(request.args.get("builder", []))
103
 
        if showBuilders:
104
 
            builders = [b for b in builders if b.name in showBuilders]
105
 
 
106
 
        # now, if the URL has one or category= arguments, use them as a
107
 
        # filter: only show those builders which belong to one of the given
108
 
        # categories.
109
 
        showCategories = request.args.get("category", [])
110
 
        if showCategories:
111
 
            builders = [b for b in builders if b.category in showCategories]
112
 
 
113
 
        maxFeeds = 25
114
 
 
115
 
        # Copy all failed builds in a new list.
116
 
        # This could clearly be implemented much better if we had
117
 
        # access to a global list of builds.
118
 
        for b in builders:
119
 
            lastbuild = b.getLastFinishedBuild()
120
 
            if lastbuild is None:
121
 
                continue
122
 
 
123
 
            lastnr = lastbuild.getNumber()
124
 
 
125
 
            totalbuilds = 0
126
 
            i = lastnr
127
 
            while i >= 0:
128
 
                build = b.getBuild(i)
129
 
                i -= 1
130
 
                if not build:
131
 
                    continue
132
 
 
133
 
                results = build.getResults()
134
 
 
135
 
                # only add entries for failed builds!
136
 
                if results == FAILURE:
137
 
                    totalbuilds += 1
138
 
                    builds.append(build)
139
 
 
140
 
                # stop for this builder when our total nr. of feeds is reached
141
 
                if totalbuilds >= maxFeeds:
142
 
                    break
143
 
 
144
 
        # Sort build list by date, youngest first.
145
 
        if sys.version_info[:3] >= (2,4,0):
146
 
            builds.sort(key=lambda build: build.getTimes(), reverse=True)
147
 
        else:
148
 
            # If you need compatibility with python < 2.4, use this for
149
 
            # sorting instead:
150
 
            # We apply Decorate-Sort-Undecorate
151
 
            deco = [(build.getTimes(), build) for build in builds]
152
 
            deco.sort()
153
 
            deco.reverse()
154
 
            builds = [build for (b1, build) in deco]
155
 
 
156
 
        if builds:
157
 
            builds = builds[:min(len(builds), maxFeeds)]
158
 
        return builds
159
 
 
160
 
    def body (self, request):
161
 
        data = ''
162
 
        builds = self.getBuilds(request)
163
 
 
164
 
        for build in builds:
165
 
            start, finished = build.getTimes()
166
 
            finishedTime = time.gmtime(int(finished))
167
 
            projectName = self.status.getProjectName()
168
 
            link = re.sub(r'index.html', "", self.status.getURLForThing(build))
169
 
 
170
 
            # title: trunk r862265 (plus patch) failed on '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
 
            title = ('%s failed on "%s"' %
195
 
                     (source, build.getBuilder().getName()))
196
 
 
197
 
            # get name of the failed step and the last 30 lines of its log.
198
 
            if build.getLogs():
199
 
                log = build.getLogs()[-1]
200
 
                laststep = log.getStep().getName()
201
 
                try:
202
 
                    lastlog = log.getText()
203
 
                except IOError:
204
 
                    # Probably the log file has been removed
205
 
                    lastlog='<b>log file not available</b>'
206
 
 
207
 
            lines = re.split('\n', lastlog)
208
 
            lastlog = ''
209
 
            for logline in lines[max(0, len(lines)-30):]:
210
 
                lastlog = lastlog + logline + '<br/>'
211
 
            lastlog = lastlog.replace('\n', '<br/>')
212
 
 
213
 
            description = ''
214
 
            description += ('Date: %s<br/><br/>' %
215
 
                            time.strftime("%a, %d %b %Y %H:%M:%S GMT",
216
 
                                          finishedTime))
217
 
            description += ('Full details available here: <a href="%s">%s</a><br/>' % (self.link, projectName))
218
 
            builder_summary_link = ('%s/builders/%s' %
219
 
                                    (re.sub(r'/index.html', '', self.link),
220
 
                                     build.getBuilder().getName()))
221
 
            description += ('Build summary: <a href="%s">%s</a><br/><br/>' %
222
 
                            (builder_summary_link,
223
 
                             build.getBuilder().getName()))
224
 
            description += ('Build details: <a href="%s">%s</a><br/><br/>' %
225
 
                            (link, self.link + link[1:]))
226
 
            description += ('Author list: <b>%s</b><br/><br/>' %
227
 
                            ",".join(build.getResponsibleUsers()))
228
 
            description += ('Failed step: <b>%s</b><br/><br/>' % laststep)
229
 
            description += 'Last lines of the build log:<br/>'
230
 
 
231
 
            data += self.item(title, description=description, lastlog=lastlog,
232
 
                              link=link, pubDate=finishedTime)
233
 
 
234
 
        return data
235
 
 
236
 
    def item(self, title='', link='', description='', pubDate=''):
237
 
        """Generates xml for one item in the feed."""
238
 
 
239
 
class Rss20StatusResource(FeedResource):
240
 
    def __init__(self, status, categories=None):
241
 
        FeedResource.__init__(self, status, categories)
242
 
        contentType = 'application/rss+xml'
243
 
 
244
 
    def header(self, request):
245
 
        data = FeedResource.header(self, request)
246
 
        data += ('<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">\n')
247
 
        data += ('  <channel>\n')
248
 
        if self.title is not None:
249
 
            data += ('    <title>%s</title>\n' % self.title)
250
 
        if self.link is not None:
251
 
            data += ('    <link>%s</link>\n' % self.link)
252
 
        link = re.sub(r'/index.html', '', self.link)
253
 
        data += ('    <atom:link href="%s/rss" rel="self" type="application/rss+xml"/>\n' % link)
254
 
        if self.language is not None:
255
 
            data += ('    <language>%s</language>\n' % self.language)
256
 
        if self.description is not None:
257
 
            data += ('    <description>%s</description>\n' % self.description)
258
 
        if self.pubdate is not None:
259
 
            rfc822_pubdate = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
260
 
                                           self.pubdate)
261
 
            data += ('    <pubDate>%s</pubDate>\n' % rfc822_pubdate)
262
 
        return data
263
 
 
264
 
    def item(self, title='', link='', description='', lastlog='', pubDate=''):
265
 
        data = ('      <item>\n')
266
 
        data += ('        <title>%s</title>\n' % title)
267
 
        if link is not None:
268
 
            data += ('        <link>%s</link>\n' % link)
269
 
        if (description is not None and lastlog is not None):
270
 
            lastlog = re.sub(r'<br/>', "\n", lastlog)
271
 
            lastlog = re.sub(r'&', "&amp;", lastlog)
272
 
            lastlog = re.sub(r"'", "&apos;", lastlog)
273
 
            lastlog = re.sub(r'"', "&quot;", lastlog)
274
 
            lastlog = re.sub(r'<', '&lt;', lastlog)
275
 
            lastlog = re.sub(r'>', '&gt;', lastlog)
276
 
            lastlog = lastlog.replace('\n', '<br/>')
277
 
            content = '<![CDATA['
278
 
            content += description
279
 
            content += lastlog
280
 
            content += ']]>'
281
 
            data += ('        <description>%s</description>\n' % content)
282
 
        if pubDate is not None:
283
 
            rfc822pubDate = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
284
 
                                          pubDate)
285
 
            data += ('        <pubDate>%s</pubDate>\n' % rfc822pubDate)
286
 
            # Every RSS item must have a globally unique ID
287
 
            guid = ('tag:%s@%s,%s:%s' % (os.environ['USER'],
288
 
                                         os.environ['HOSTNAME'],
289
 
                                         time.strftime("%Y-%m-%d", pubDate),
290
 
                                         time.strftime("%Y%m%d%H%M%S",
291
 
                                                       pubDate)))
292
 
            data += ('    <guid isPermaLink="false">%s</guid>\n' % guid)
293
 
        data += ('      </item>\n')
294
 
        return data
295
 
 
296
 
    def footer(self, request):
297
 
        data = ('  </channel>\n'
298
 
                '</rss>')
299
 
        return data
300
 
 
301
 
class Atom10StatusResource(FeedResource):
302
 
    def __init__(self, status, categories=None):
303
 
        FeedResource.__init__(self, status, categories)
304
 
        contentType = 'application/atom+xml'
305
 
 
306
 
    def header(self, request):
307
 
        data = FeedResource.header(self, request)
308
 
        data += '<feed xmlns="http://www.w3.org/2005/Atom">\n'
309
 
        data += ('  <id>%s</id>\n' % self.status.getBuildbotURL())
310
 
        if self.title is not None:
311
 
            data += ('  <title>%s</title>\n' % self.title)
312
 
        if self.link is not None:
313
 
            link = re.sub(r'/index.html', '', self.link)
314
 
            data += ('  <link rel="self" href="%s/atom"/>\n' % link)
315
 
            data += ('  <link rel="alternate" href="%s/"/>\n' % link)
316
 
        if self.description is not None:
317
 
            data += ('  <subtitle>%s</subtitle>\n' % self.description)
318
 
        if self.pubdate is not None:
319
 
            rfc3339_pubdate = time.strftime("%Y-%m-%dT%H:%M:%SZ",
320
 
                                            self.pubdate)
321
 
            data += ('  <updated>%s</updated>\n' % rfc3339_pubdate)
322
 
        data += ('  <author>\n')
323
 
        data += ('    <name>Build Bot</name>\n')
324
 
        data += ('  </author>\n')
325
 
        return data
326
 
 
327
 
    def item(self, title='', link='', description='', lastlog='', pubDate=''):
328
 
        data = ('  <entry>\n')
329
 
        data += ('    <title>%s</title>\n' % title)
330
 
        if link is not None:
331
 
            data += ('    <link href="%s"/>\n' % link)
332
 
        if (description is not None and lastlog is not None):
333
 
            lastlog = re.sub(r'<br/>', "\n", lastlog)
334
 
            lastlog = re.sub(r'&', "&amp;", lastlog)
335
 
            lastlog = re.sub(r"'", "&apos;", lastlog)
336
 
            lastlog = re.sub(r'"', "&quot;", lastlog)
337
 
            lastlog = re.sub(r'<', '&lt;', lastlog)
338
 
            lastlog = re.sub(r'>', '&gt;', lastlog)
339
 
            data += ('    <content type="xhtml">\n')
340
 
            data += ('      <div xmlns="http://www.w3.org/1999/xhtml">\n')
341
 
            data += ('        %s\n' % description)
342
 
            data += ('        <pre xml:space="preserve">%s</pre>\n' % lastlog)
343
 
            data += ('      </div>\n')
344
 
            data += ('    </content>\n')
345
 
        if pubDate is not None:
346
 
            rfc3339pubDate = time.strftime("%Y-%m-%dT%H:%M:%SZ",
347
 
                                           pubDate)
348
 
            data += ('    <updated>%s</updated>\n' % rfc3339pubDate)
349
 
            # Every Atom entry must have a globally unique ID
350
 
            # http://diveintomark.org/archives/2004/05/28/howto-atom-id
351
 
            guid = ('tag:%s@%s,%s:%s' % (os.environ['USER'],
352
 
                                         os.environ['HOSTNAME'],
353
 
                                         time.strftime("%Y-%m-%d", pubDate),
354
 
                                         time.strftime("%Y%m%d%H%M%S",
355
 
                                                       pubDate)))
356
 
            data += ('    <id>%s</id>\n' % guid)
357
 
        data += ('    <author>\n')
358
 
        data += ('      <name>Build Bot</name>\n')
359
 
        data += ('    </author>\n')
360
 
        data += ('  </entry>\n')
361
 
        return data
362
 
 
363
 
    def footer(self, request):
364
 
        data = ('</feed>')
365
 
        return data
366
 
 
367
 
class WebStatusWithFeeds(baseweb.WebStatus):
368
 
    """Override the standard WebStatus class to add RSS and Atom feeds.
369
 
 
370
 
       This adds the following web resources in addition to /waterfall:
371
 
       /rss
372
 
       /atom
373
 
 
374
 
       The same "branch" and "category" query arguments can be passed
375
 
       as with /waterfall
376
 
       e.g. http://mybot.buildbot.com:8012/rss?branch=&builder=builder-log4c-rhel-4-i386
377
 
       or
378
 
       http://mybot.buildbot.com:8012/rss?branch=&category=log4c
379
 
    """
380
 
 
381
 
    def setupSite(self):
382
 
        baseweb.WebStatus.setupSite(self)
383
 
 
384
 
        status = self.parent.getStatus()
385
 
        sr = self.site.resource
386
 
 
387
 
        rss = Rss20StatusResource(status, categories=None)
388
 
        sr.putChild("rss", rss)
389
 
        atom = Atom10StatusResource(status, categories=None)
390
 
        sr.putChild("atom", atom)
391