/
triggerable.py
161 lines (136 loc) · 6.79 KB
/
triggerable.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# This file is part of Buildbot. Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members
from twisted.python import failure
from twisted.internet import defer
from buildbot.schedulers import base
from buildbot.process.properties import Properties
from buildbot import config
class Triggerable(base.BaseScheduler):
compare_attrs = base.BaseScheduler.compare_attrs
def __init__(self, name, builderNames, properties={}, codebases = {'':{}}):
base.BaseScheduler.__init__(self, name, builderNames, properties,
codebases = codebases)
self._waiters = {}
self._bsc_subscription = None
self.reason = "Triggerable(%s)" % name
def trigger(self, ss_setid, sourcestamps = None, got_revision = None,
set_props=None):
"""Trigger this scheduler with the given sourcestampset ID, optionally
a set of sourcestamps and a dictionary with got_revision entries.
Returns a deferred that will fire when the buildset is finished."""
# properties for this buildset are composed of our own properties,
# potentially overridden by anything from the triggering build
props = Properties()
props.updateFromProperties(self.properties)
if set_props:
props.updateFromProperties(set_props)
# note that this does not use the buildset subscriptions mechanism, as
# the duration of interest to the caller is bounded by the lifetime of
# this process.
if ss_setid or sourcestamps or got_revision:
d = self._addBuildsetForTrigger(self.reason, ss_setid, sourcestamps,
got_revision, props)
else:
d = self.addBuildsetForLatest(reason=self.reason, properties=props)
def setup_waiter((bsid,brids)):
d = defer.Deferred()
self._waiters[bsid] = (d, brids)
self._updateWaiters()
return d
d.addCallback(setup_waiter)
return d
def stopService(self):
# cancel any outstanding subscription
if self._bsc_subscription:
self._bsc_subscription.unsubscribe()
self._bsc_subscription = None
# and errback any outstanding deferreds
if self._waiters:
msg = 'Triggerable scheduler stopped before build was complete'
for d, brids in self._waiters.values():
d.errback(failure.Failure(RuntimeError(msg)))
self._waiters = {}
return base.BaseScheduler.stopService(self)
@defer.inlineCallbacks
def _addBuildsetForTrigger(self, reason, setid, sourcestamps,
got_revision, properties):
def createLookup(input_list, key):
output_dict = {}
for item in input_list:
output_dict[item[key]] = item
return output_dict
if got_revision is None:
got_revision = {}
if sourcestamps is None:
sourcestamps = []
# Create lookup for sourcestamps that exist in database
existing_lookup = {}
if setid:
existing_list = yield self.master.db.sourcestamps.getSourceStamps(setid)
existing_lookup = createLookup(existing_list, 'codebase')
# Create lookup for sourcestamps that where passed
passed_lookup = createLookup(sourcestamps, 'codebase')
# Define new setid for this set of triggering sourcestamps
new_setid = yield self.master.db.sourcestampsets.addSourceStampSet()
# Merge codebases with the passed setid, sourcestamps and got_revision
# This results in a new sourcestamp for each codebase
for codebase in self.codebases:
ss = self.codebases[codebase].copy()
# apply info from setid
ss.update(existing_lookup.get(codebase,{}))
# apply info from got_revision
ss['revision'] = got_revision.get(codebase, ss.get('revision', None))
# apply info from passed sourcestamp
ss.update(passed_lookup.get(codebase,{}))
# at least repository must be set, this is normaly forced except when
# codebases is not explicitly set in configuration file.
ss_repository = ss.get('repository')
if not ss_repository:
config.error("The codebases argument is not set but still receiving " +
"non empty codebase values")
# add sourcestamp to the new setid
yield self.master.db.sourcestamps.addSourceStamp(
codebase=codebase,
repository=ss_repository,
branch=ss.get('branch', None),
revision=ss.get('revision', None),
project=ss.get('project', ''),
changeids=[c['number'] for c in getattr(ss, 'changes', [])],
patch_body=ss.get('patch_body', None),
patch_level=ss.get('patch_level', None),
patch_author=ss.get('patch_author', None),
patch_comment=ss.get('patch_comment', None),
sourcestampsetid=new_setid)
bsid,brids = yield self.addBuildsetForSourceStamp(
setid=new_setid, reason=reason,
properties=properties)
defer.returnValue((bsid,brids))
def _updateWaiters(self):
if self._waiters and not self._bsc_subscription:
self._bsc_subscription = \
self.master.subscribeToBuildsetCompletions(
self._buildsetComplete)
elif not self._waiters and self._bsc_subscription:
self._bsc_subscription.unsubscribe()
self._bsc_subscription = None
def _buildsetComplete(self, bsid, result):
if bsid not in self._waiters:
return
# pop this bsid from the waiters list, and potentially unsubscribe
# from completion notifications
d, brids = self._waiters.pop(bsid)
self._updateWaiters()
# fire the callback to indicate that the triggered build is complete
d.callback((result, brids))