-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
builder.py
325 lines (268 loc) · 11.8 KB
/
builder.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
from twisted.web import html
from twisted.web.util import Redirect
import re, urllib, time
from twisted.python import log
from buildbot import interfaces
from buildbot.status.web.base import HtmlResource, BuildLineMixin, \
path_to_build, path_to_slave, path_to_builder, path_to_change, \
path_to_root, getAndCheckProperties, ICurrentBox, build_get_class, \
map_branches, path_to_authfail
from buildbot.sourcestamp import SourceStamp
from buildbot.status.web.build import BuildsResource, StatusResourceBuild
from buildbot import util
# /builders/$builder
class StatusResourceBuilder(HtmlResource, BuildLineMixin):
addSlash = True
def __init__(self, builder_status):
HtmlResource.__init__(self)
self.builder_status = builder_status
def getTitle(self, request):
return "Buildbot: %s" % self.builder_status.getName()
def builder(self, build, req):
b = {}
b['num'] = build.getNumber()
b['link'] = path_to_build(req, build)
when = build.getETA()
if when is not None:
b['when'] = util.formatInterval(when)
b['when_time'] = time.strftime("%H:%M:%S",
time.localtime(time.time() + when))
step = build.getCurrentStep()
# TODO: is this necessarily the case?
if not step:
b['current_step'] = "[waiting for Lock]"
else:
if step.isWaitingForLocks():
b['current_step'] = "%s [waiting for Lock]" % step.getName()
else:
b['current_step'] = step.getName()
b['stop_url'] = path_to_build(req, build) + '/stop'
return b
def content(self, req, cxt):
b = self.builder_status
cxt['name'] = b.getName()
slaves = b.getSlaves()
connected_slaves = [s for s in slaves if s.isConnected()]
cxt['current'] = [self.builder(x, req) for x in b.getCurrentBuilds()]
cxt['pending'] = []
for pb in b.getPendingBuilds():
source = pb.getSourceStamp()
changes = []
if source.changes:
for c in source.changes:
changes.append({ 'url' : path_to_change(req, c),
'who' : c.who})
if source.revision:
reason = source.revision
else:
reason = "no changes specified"
cxt['pending'].append({
'when': time.strftime("%b %d %H:%M:%S", time.localtime(pb.getSubmitTime())),
'delay': util.formatInterval(util.now() - pb.getSubmitTime()),
'reason': reason,
'id': pb.brid,
'changes' : changes
})
numbuilds = int(req.args.get('numbuilds', ['5'])[0])
recent = cxt['recent'] = []
for build in b.generateFinishedBuilds(num_builds=int(numbuilds)):
recent.append(self.get_line_values(req, build, False))
sl = cxt['slaves'] = []
connected_slaves = 0
for slave in slaves:
s = {}
sl.append(s)
s['link'] = path_to_slave(req, slave)
s['name'] = slave.getName()
c = s['connected'] = slave.isConnected()
if c:
s['admin'] = slave.getAdmin()
connected_slaves += 1
cxt['connected_slaves'] = connected_slaves
cxt['authz'] = self.getAuthz(req)
cxt['builder_url'] = path_to_builder(req, b)
template = req.site.buildbot_service.templates.get_template("builder.html")
return template.render(**cxt)
def force(self, req, auth_ok=False):
name = req.args.get("username", ["<unknown>"])[0]
reason = req.args.get("comments", ["<no reason specified>"])[0]
branch = req.args.get("branch", [""])[0]
revision = req.args.get("revision", [""])[0]
r = "The web-page 'force build' button was pressed by '%s': %s\n" \
% (html.escape(name), html.escape(reason))
log.msg("web forcebuild of builder '%s', branch='%s', revision='%s'"
" by user '%s'" % (self.builder_status.getName(), branch,
revision, name))
# check if this is allowed
if not auth_ok:
if not self.getAuthz(req).actionAllowed('forceBuild', req, self.builder_status):
log.msg("..but not authorized")
return Redirect(path_to_authfail(req))
# keep weird stuff out of the branch revision, and property strings.
# TODO: centralize this somewhere.
if not re.match(r'^[\w\.\-\/]*$', branch):
log.msg("bad branch '%s'" % branch)
return Redirect(path_to_builder(req, self.builder_status))
if not re.match(r'^[ \w\.\-\/]*$', revision):
log.msg("bad revision '%s'" % revision)
return Redirect(path_to_builder(req, self.builder_status))
properties = getAndCheckProperties(req)
if properties is None:
return Redirect(path_to_builder(req, self.builder_status))
if not branch:
branch = None
if not revision:
revision = None
# TODO: if we can authenticate that a particular User pushed the
# button, use their name instead of None, so they'll be informed of
# the results.
# TODO2: we can authenticate that a particular User pushed the button
# now, so someone can write this support. but it requires a
# buildbot.changes.changes.Change instance which is tedious at this
# stage to compute
s = SourceStamp(branch=branch, revision=revision)
try:
c = interfaces.IControl(self.getBuildmaster(req))
bc = c.getBuilder(self.builder_status.getName())
bc.submitBuildRequest(s, r, properties, now=True)
except interfaces.NoSlaveError:
# TODO: tell the web user that their request could not be
# honored
pass
# send the user back to the builder page
return Redirect(path_to_builder(req, self.builder_status))
def ping(self, req):
log.msg("web ping of builder '%s'" % self.builder_status.getName())
if not self.getAuthz(req).actionAllowed('pingBuilder', req, self.builder_status):
log.msg("..but not authorized")
return Redirect(path_to_authfail(req))
c = interfaces.IControl(self.getBuildmaster(req))
bc = c.getBuilder(self.builder_status.getName())
bc.ping()
# send the user back to the builder page
return Redirect(path_to_builder(req, self.builder_status))
def cancelbuild(self, req):
try:
request_id = req.args.get("id", [None])[0]
if request_id == "all":
cancel_all = True
else:
cancel_all = False
request_id = int(request_id)
except:
request_id = None
authz = self.getAuthz(req)
if request_id:
c = interfaces.IControl(self.getBuildmaster(req))
bc = c.getBuilder(self.builder_status.getName())
for build_req in bc.getPendingBuilds():
if cancel_all or (build_req.brid == request_id):
log.msg("Cancelling %s" % build_req)
if authz.actionAllowed('cancelPendingBuild', req, build_req):
build_req.cancel()
else:
return Redirect(path_to_authfail(req))
if not cancel_all:
break
return Redirect(path_to_builder(req, self.builder_status))
def getChild(self, path, req):
if path == "force":
return self.force(req)
if path == "ping":
return self.ping(req)
if path == "cancelbuild":
return self.cancelbuild(req)
if path == "builds":
return BuildsResource(self.builder_status)
return HtmlResource.getChild(self, path, req)
# /builders/_all
class StatusResourceAllBuilders(HtmlResource, BuildLineMixin):
def __init__(self, status):
HtmlResource.__init__(self)
self.status = status
def getChild(self, path, req):
if path == "forceall":
return self.forceall(req)
if path == "stopall":
return self.stopall(req)
return HtmlResource.getChild(self, path, req)
def forceall(self, req):
authz = self.getAuthz(req)
if not authz.actionAllowed('forceAllBuilds', req):
return Redirect(path_to_authfail(req))
for bname in self.status.getBuilderNames():
builder_status = self.status.getBuilder(bname)
build = StatusResourceBuilder(builder_status)
build.force(req, auth_ok=True) # auth_ok because we already checked
# back to the welcome page
return Redirect(path_to_root(req))
def stopall(self, req):
authz = self.getAuthz(req)
if not authz.actionAllowed('stopAllBuilds', req):
return Redirect(path_to_authfail(req))
for bname in self.status.getBuilderNames():
builder_status = self.status.getBuilder(bname)
(state, current_builds) = builder_status.getState()
if state != "building":
continue
for b in current_builds:
build_status = builder_status.getBuild(b.number)
if not build_status:
continue
build = StatusResourceBuild(build_status)
build.stop(req, auth_ok=True)
# go back to the welcome page
return Redirect(path_to_root(req))
# /builders
class BuildersResource(HtmlResource):
title = "Builders"
addSlash = True
def content(self, req, cxt):
status = self.getStatus(req)
builders = req.args.get("builder", status.getBuilderNames())
branches = [b for b in req.args.get("branch", []) if b]
cxt['branches'] = branches
bs = cxt['builders'] = []
building = 0
online = 0
base_builders_url = path_to_root(req) + "builders/"
for bn in builders:
bld = { 'link': base_builders_url + urllib.quote(bn, safe=''),
'name': bn }
bs.append(bld)
builder = status.getBuilder(bn)
builds = list(builder.generateFinishedBuilds(map_branches(branches),
num_builds=1))
if builds:
b = builds[0]
bld['build_url'] = (bld['link'] + "/builds/%d" % b.getNumber())
try:
label = b.getProperty("got_revision")
except KeyError:
label = None
if not label or len(str(label)) > 20:
label = "#%d" % b.getNumber()
bld['build_label'] = label
bld['build_text'] = " ".join(b.getText())
bld['build_css_class'] = build_get_class(b)
current_box = ICurrentBox(builder).getBox(status)
bld['current_box'] = current_box.td()
builder_status = builder.getState()[0]
if builder_status == "building":
building += 1
online += 1
elif builder_status != "offline":
online += 1
cxt['authz'] = self.getAuthz(req)
cxt['num_building'] = building
cxt['num_online'] = online
template = req.site.buildbot_service.templates.get_template("builders.html")
return template.render(**cxt)
def getChild(self, path, req):
s = self.getStatus(req)
if path in s.getBuilderNames():
builder_status = s.getBuilder(path)
return StatusResourceBuilder(builder_status)
if path == "_all":
return StatusResourceAllBuilders(self.getStatus(req))
return HtmlResource.getChild(self, path, req)