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
21
22
23class Db2DataMixin:
24
25    def db2data(self, dbdict):
26        data = {
27            'stepid': dbdict['id'],
28            'number': dbdict['number'],
29            'name': dbdict['name'],
30            'buildid': dbdict['buildid'],
31            'started_at': dbdict['started_at'],
32            'complete': dbdict['complete_at'] is not None,
33            'complete_at': dbdict['complete_at'],
34            'state_string': dbdict['state_string'],
35            'results': dbdict['results'],
36            'urls': dbdict['urls'],
37            'hidden': dbdict['hidden'],
38        }
39        return defer.succeed(data)
40
41
42class StepEndpoint(Db2DataMixin, base.BuildNestingMixin, base.Endpoint):
43
44    isCollection = False
45    pathPatterns = """
46        /steps/n:stepid
47        /builds/n:buildid/steps/i:step_name
48        /builds/n:buildid/steps/n:step_number
49        /builders/n:builderid/builds/n:build_number/steps/i:step_name
50        /builders/n:builderid/builds/n:build_number/steps/n:step_number
51        /builders/i:buildername/builds/n:build_number/steps/i:step_name
52        /builders/i:buildername/builds/n:build_number/steps/n:step_number
53        """
54
55    @defer.inlineCallbacks
56    def get(self, resultSpec, kwargs):
57        if 'stepid' in kwargs:
58            dbdict = yield self.master.db.steps.getStep(kwargs['stepid'])
59            return (yield self.db2data(dbdict)) if dbdict else None
60        buildid = yield self.getBuildid(kwargs)
61        if buildid is None:
62            return None
63        dbdict = yield self.master.db.steps.getStep(
64            buildid=buildid,
65            number=kwargs.get('step_number'),
66            name=kwargs.get('step_name'))
67        return (yield self.db2data(dbdict)) if dbdict else None
68
69
70class StepsEndpoint(Db2DataMixin, base.BuildNestingMixin, base.Endpoint):
71
72    isCollection = True
73    pathPatterns = """
74        /builds/n:buildid/steps
75        /builders/n:builderid/builds/n:build_number/steps
76        /builders/i:buildername/builds/n:build_number/steps
77    """
78
79    @defer.inlineCallbacks
80    def get(self, resultSpec, kwargs):
81        if 'buildid' in kwargs:
82            buildid = kwargs['buildid']
83        else:
84            buildid = yield self.getBuildid(kwargs)
85            if buildid is None:
86                return None
87        steps = yield self.master.db.steps.getSteps(buildid=buildid)
88        results = []
89        for dbdict in steps:
90            results.append((yield self.db2data(dbdict)))
91        return results
92
93
94class UrlEntityType(types.Entity):
95    name = types.String()
96    url = types.String()
97
98
99class Step(base.ResourceType):
100
101    name = "step"
102    plural = "steps"
103    endpoints = [StepEndpoint, StepsEndpoint]
104    keyField = 'stepid'
105    eventPathPatterns = """
106        /builds/:buildid/steps/:stepid
107        /steps/:stepid
108    """
109    subresources = ["Log"]
110
111    class EntityType(types.Entity):
112        stepid = types.Integer()
113        number = types.Integer()
114        name = types.Identifier(50)
115        buildid = types.Integer()
116        started_at = types.NoneOk(types.DateTime())
117        complete = types.Boolean()
118        complete_at = types.NoneOk(types.DateTime())
119        results = types.NoneOk(types.Integer())
120        state_string = types.String()
121        urls = types.List(
122            of=UrlEntityType("Url", "Url"))
123        hidden = types.Boolean()
124    entityType = EntityType(name, 'Step')
125
126    @defer.inlineCallbacks
127    def generateEvent(self, stepid, event):
128        step = yield self.master.data.get(('steps', stepid))
129        self.produceEvent(step, event)
130
131    @base.updateMethod
132    @defer.inlineCallbacks
133    def addStep(self, buildid, name):
134        stepid, num, name = yield self.master.db.steps.addStep(
135            buildid=buildid, name=name, state_string='pending')
136        yield self.generateEvent(stepid, 'new')
137        return (stepid, num, name)
138
139    @base.updateMethod
140    @defer.inlineCallbacks
141    def startStep(self, stepid):
142        yield self.master.db.steps.startStep(stepid=stepid)
143        yield self.generateEvent(stepid, 'started')
144
145    @base.updateMethod
146    @defer.inlineCallbacks
147    def setStepStateString(self, stepid, state_string):
148        yield self.master.db.steps.setStepStateString(
149            stepid=stepid, state_string=state_string)
150        yield self.generateEvent(stepid, 'updated')
151
152    @base.updateMethod
153    @defer.inlineCallbacks
154    def addStepURL(self, stepid, name, url):
155        yield self.master.db.steps.addURL(
156            stepid=stepid, name=name, url=url)
157        yield self.generateEvent(stepid, 'updated')
158
159    @base.updateMethod
160    @defer.inlineCallbacks
161    def finishStep(self, stepid, results, hidden):
162        yield self.master.db.steps.finishStep(
163            stepid=stepid, results=results, hidden=hidden)
164        yield self.generateEvent(stepid, 'finished')
165