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 BuilderEndpoint(base.BuildNestingMixin, base.Endpoint):
24
25    isCollection = False
26    pathPatterns = """
27        /builders/n:builderid
28        /builders/i:buildername
29        /masters/n:masterid/builders/n:builderid
30    """
31
32    @defer.inlineCallbacks
33    def get(self, resultSpec, kwargs):
34        builderid = yield self.getBuilderId(kwargs)
35        if builderid is None:
36            return None
37
38        bdict = yield self.master.db.builders.getBuilder(builderid)
39        if not bdict:
40            return None
41        if 'masterid' in kwargs:
42            if kwargs['masterid'] not in bdict['masterids']:
43                return None
44        return dict(builderid=builderid,
45                    name=bdict['name'],
46                    masterids=bdict['masterids'],
47                    description=bdict['description'],
48                    tags=bdict['tags'])
49
50
51class BuildersEndpoint(base.Endpoint):
52
53    isCollection = True
54    rootLinkName = 'builders'
55    pathPatterns = """
56        /builders
57        /masters/n:masterid/builders
58    """
59
60    @defer.inlineCallbacks
61    def get(self, resultSpec, kwargs):
62        bdicts = yield self.master.db.builders.getBuilders(
63            masterid=kwargs.get('masterid', None))
64        return [dict(builderid=bd['id'],
65                     name=bd['name'],
66                     masterids=bd['masterids'],
67                     description=bd['description'],
68                     tags=bd['tags'])
69               for bd in bdicts]
70
71    def get_kwargs_from_graphql(self, parent, resolve_info, args):
72        if parent is not None:
73            return {'masterid': parent['masterid']}
74        return {}
75
76
77class Builder(base.ResourceType):
78
79    name = "builder"
80    plural = "builders"
81    endpoints = [BuilderEndpoint, BuildersEndpoint]
82    keyField = 'builderid'
83    eventPathPatterns = """
84        /builders/:builderid
85    """
86    subresources = ["Build", "Forcescheduler", "Scheduler", "Buildrequest"]
87
88    class EntityType(types.Entity):
89        builderid = types.Integer()
90        name = types.Identifier(70)
91        masterids = types.List(of=types.Integer())
92        description = types.NoneOk(types.String())
93        tags = types.List(of=types.String())
94    entityType = EntityType(name, 'Builder')
95
96    @defer.inlineCallbacks
97    def generateEvent(self, _id, event):
98        builder = yield self.master.data.get(('builders', str(_id)))
99        self.produceEvent(builder, event)
100
101    @base.updateMethod
102    def findBuilderId(self, name):
103        return self.master.db.builders.findBuilderId(name)
104
105    @base.updateMethod
106    @defer.inlineCallbacks
107    def updateBuilderInfo(self, builderid, description, tags):
108        ret = yield self.master.db.builders.updateBuilderInfo(builderid, description, tags)
109        yield self.generateEvent(builderid, "update")
110        return ret
111
112    @base.updateMethod
113    @defer.inlineCallbacks
114    def updateBuilderList(self, masterid, builderNames):
115        # get the "current" list of builders for this master, so we know what
116        # changes to make.  Race conditions here aren't a great worry, as this
117        # is the only master inserting or deleting these records.
118        builders = yield self.master.db.builders.getBuilders(masterid=masterid)
119
120        # figure out what to remove and remove it
121        builderNames_set = set(builderNames)
122        for bldr in builders:
123            if bldr['name'] not in builderNames_set:
124                builderid = bldr['id']
125                yield self.master.db.builders.removeBuilderMaster(
126                    masterid=masterid, builderid=builderid)
127                self.master.mq.produce(('builders', str(builderid), 'stopped'),
128                                       dict(builderid=builderid, masterid=masterid,
129                                            name=bldr['name']))
130            else:
131                builderNames_set.remove(bldr['name'])
132
133        # now whatever's left in builderNames_set is new
134        for name in builderNames_set:
135            builderid = yield self.master.db.builders.findBuilderId(name)
136            yield self.master.db.builders.addBuilderMaster(
137                masterid=masterid, builderid=builderid)
138            self.master.mq.produce(('builders', str(builderid), 'started'),
139                                   dict(builderid=builderid, masterid=masterid, name=name))
140
141    # returns a Deferred that returns None
142    def _masterDeactivated(self, masterid):
143        # called from the masters rtype to indicate that the given master is
144        # deactivated
145        return self.updateBuilderList(masterid, [])
146