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
17from twisted.internet import defer
18
19from buildbot.data import base
20from buildbot.data import types
21from buildbot.util import identifiers
22
23
24class EndpointMixin:
25
26    def db2data(self, dbdict):
27        data = {
28            'logid': dbdict['id'],
29            'name': dbdict['name'],
30            'slug': dbdict['slug'],
31            'stepid': dbdict['stepid'],
32            'complete': dbdict['complete'],
33            'num_lines': dbdict['num_lines'],
34            'type': dbdict['type'],
35        }
36        return defer.succeed(data)
37
38
39class LogEndpoint(EndpointMixin, base.BuildNestingMixin, base.Endpoint):
40
41    isCollection = False
42    pathPatterns = """
43        /logs/n:logid
44        /steps/n:stepid/logs/i:log_slug
45        /builds/n:buildid/steps/i:step_name/logs/i:log_slug
46        /builds/n:buildid/steps/n:step_number/logs/i:log_slug
47        /builders/n:builderid/builds/n:build_number/steps/i:step_name/logs/i:log_slug
48        /builders/n:builderid/builds/n:build_number/steps/n:step_number/logs/i:log_slug
49        /builders/i:buildername/builds/n:build_number/steps/i:step_name/logs/i:log_slug
50        /builders/i:buildername/builds/n:build_number/steps/n:step_number/logs/i:log_slug
51    """
52
53    @defer.inlineCallbacks
54    def get(self, resultSpec, kwargs):
55        if 'logid' in kwargs:
56            dbdict = yield self.master.db.logs.getLog(kwargs['logid'])
57            return (yield self.db2data(dbdict)) if dbdict else None
58
59        stepid = yield self.getStepid(kwargs)
60        if stepid is None:
61            return None
62
63        dbdict = yield self.master.db.logs.getLogBySlug(stepid,
64                                                        kwargs.get('log_slug'))
65        return (yield self.db2data(dbdict)) if dbdict else None
66
67
68class LogsEndpoint(EndpointMixin, base.BuildNestingMixin, base.Endpoint):
69
70    isCollection = True
71    pathPatterns = """
72        /steps/n:stepid/logs
73        /builds/n:buildid/steps/i:step_name/logs
74        /builds/n:buildid/steps/n:step_number/logs
75        /builders/n:builderid/builds/n:build_number/steps/i:step_name/logs
76        /builders/n:builderid/builds/n:build_number/steps/n:step_number/logs
77        /builders/i:buildername/builds/n:build_number/steps/i:step_name/logs
78        /builders/i:buildername/builds/n:build_number/steps/n:step_number/logs
79    """
80
81    @defer.inlineCallbacks
82    def get(self, resultSpec, kwargs):
83        stepid = yield self.getStepid(kwargs)
84        if not stepid:
85            return []
86        logs = yield self.master.db.logs.getLogs(stepid=stepid)
87        results = []
88        for dbdict in logs:
89            results.append((yield self.db2data(dbdict)))
90        return results
91
92
93class Log(base.ResourceType):
94
95    name = "log"
96    plural = "logs"
97    endpoints = [LogEndpoint, LogsEndpoint]
98    keyField = "logid"
99    eventPathPatterns = """
100        /logs/:logid
101        /steps/:stepid/logs/:slug
102    """
103    subresources = ["LogChunk"]
104
105    class EntityType(types.Entity):
106        logid = types.Integer()
107        name = types.String()
108        slug = types.Identifier(50)
109        stepid = types.Integer()
110        complete = types.Boolean()
111        num_lines = types.Integer()
112        type = types.Identifier(1)
113
114    entityType = EntityType(name, 'Log')
115
116    @defer.inlineCallbacks
117    def generateEvent(self, _id, event):
118        # get the build and munge the result for the notification
119        build = yield self.master.data.get(('logs', str(_id)))
120        self.produceEvent(build, event)
121
122    @base.updateMethod
123    @defer.inlineCallbacks
124    def addLog(self, stepid, name, type):
125        slug = identifiers.forceIdentifier(50, name)
126        while True:
127            try:
128                logid = yield self.master.db.logs.addLog(
129                    stepid=stepid, name=name, slug=slug, type=type)
130            except KeyError:
131                slug = identifiers.incrementIdentifier(50, slug)
132                continue
133            self.generateEvent(logid, "new")
134            return logid
135
136    @base.updateMethod
137    @defer.inlineCallbacks
138    def appendLog(self, logid, content):
139        res = yield self.master.db.logs.appendLog(logid=logid, content=content)
140        self.generateEvent(logid, "append")
141        return res
142
143    @base.updateMethod
144    @defer.inlineCallbacks
145    def finishLog(self, logid):
146        res = yield self.master.db.logs.finishLog(logid=logid)
147        self.generateEvent(logid, "finished")
148        return res
149
150    @base.updateMethod
151    def compressLog(self, logid):
152        return self.master.db.logs.compressLog(logid=logid)
153