Package buildbot :: Package status :: Package web :: Module builder
[frames] | no frames]

Source Code for Module buildbot.status.web.builder

  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   
 17  from twisted.web import html 
 18  from twisted.web.util import Redirect 
 19   
 20  import re, urllib, time 
 21  from twisted.python import log 
 22  from buildbot import interfaces 
 23  from buildbot.status.web.base import HtmlResource, BuildLineMixin, \ 
 24      path_to_build, path_to_slave, path_to_builder, path_to_change, \ 
 25      path_to_root, getAndCheckProperties, ICurrentBox, build_get_class, \ 
 26      map_branches, path_to_authfail 
 27  from buildbot.sourcestamp import SourceStamp 
 28   
 29  from buildbot.status.web.build import BuildsResource, StatusResourceBuild 
 30  from buildbot import util 
 31   
 32  # /builders/$builder 
33 -class StatusResourceBuilder(HtmlResource, BuildLineMixin):
34 addSlash = True 35
36 - def __init__(self, builder_status):
37 HtmlResource.__init__(self) 38 self.builder_status = builder_status
39
40 - def getTitle(self, request):
41 return "Buildbot: %s" % self.builder_status.getName()
42
43 - def builder(self, build, req):
44 b = {} 45 46 b['num'] = build.getNumber() 47 b['link'] = path_to_build(req, build) 48 49 when = build.getETA() 50 if when is not None: 51 b['when'] = util.formatInterval(when) 52 b['when_time'] = time.strftime("%H:%M:%S", 53 time.localtime(time.time() + when)) 54 55 step = build.getCurrentStep() 56 # TODO: is this necessarily the case? 57 if not step: 58 b['current_step'] = "[waiting for Lock]" 59 else: 60 if step.isWaitingForLocks(): 61 b['current_step'] = "%s [waiting for Lock]" % step.getName() 62 else: 63 b['current_step'] = step.getName() 64 65 b['stop_url'] = path_to_build(req, build) + '/stop' 66 67 return b
68
69 - def content(self, req, cxt):
70 b = self.builder_status 71 72 cxt['name'] = b.getName() 73 req.setHeader('Cache-Control', 'no-cache') 74 slaves = b.getSlaves() 75 connected_slaves = [s for s in slaves if s.isConnected()] 76 77 cxt['current'] = [self.builder(x, req) for x in b.getCurrentBuilds()] 78 79 cxt['pending'] = [] 80 for pb in b.getPendingBuilds(): 81 source = pb.getSourceStamp() 82 changes = [] 83 84 if source.changes: 85 for c in source.changes: 86 changes.append({ 'url' : path_to_change(req, c), 87 'who' : c.who, 88 'revision' : c.revision, 89 'repo' : c.repository }) 90 91 cxt['pending'].append({ 92 'when': time.strftime("%b %d %H:%M:%S", time.localtime(pb.getSubmitTime())), 93 'delay': util.formatInterval(util.now() - pb.getSubmitTime()), 94 'id': pb.brid, 95 'changes' : changes, 96 'num_changes' : len(changes), 97 }) 98 99 numbuilds = int(req.args.get('numbuilds', ['5'])[0]) 100 recent = cxt['recent'] = [] 101 for build in b.generateFinishedBuilds(num_builds=int(numbuilds)): 102 recent.append(self.get_line_values(req, build, False)) 103 104 sl = cxt['slaves'] = [] 105 connected_slaves = 0 106 for slave in slaves: 107 s = {} 108 sl.append(s) 109 s['link'] = path_to_slave(req, slave) 110 s['name'] = slave.getName() 111 c = s['connected'] = slave.isConnected() 112 if c: 113 s['admin'] = unicode(slave.getAdmin() or '', 'utf-8') 114 connected_slaves += 1 115 cxt['connected_slaves'] = connected_slaves 116 117 cxt['authz'] = self.getAuthz(req) 118 cxt['builder_url'] = path_to_builder(req, b) 119 120 template = req.site.buildbot_service.templates.get_template("builder.html") 121 return template.render(**cxt)
122
123 - def force(self, req, auth_ok=False):
124 name = req.args.get("username", ["<unknown>"])[0] 125 reason = req.args.get("comments", ["<no reason specified>"])[0] 126 branch = req.args.get("branch", [""])[0] 127 revision = req.args.get("revision", [""])[0] 128 repository = req.args.get("repository", [""])[0] 129 project = req.args.get("project", [""])[0] 130 131 r = "The web-page 'force build' button was pressed by '%s': %s\n" \ 132 % (html.escape(name), html.escape(reason)) 133 log.msg("web forcebuild of builder '%s', branch='%s', revision='%s'," 134 " repository='%s', project='%s' by user '%s'" % ( 135 self.builder_status.getName(), branch, revision, repository, 136 project, name)) 137 138 # check if this is allowed 139 if not auth_ok: 140 if not self.getAuthz(req).actionAllowed('forceBuild', req, self.builder_status): 141 log.msg("..but not authorized") 142 return Redirect(path_to_authfail(req)) 143 144 # keep weird stuff out of the branch revision, and property strings. 145 # TODO: centralize this somewhere. 146 if not re.match(r'^[\w.+/~-]*$', branch): 147 log.msg("bad branch '%s'" % branch) 148 return Redirect(path_to_builder(req, self.builder_status)) 149 if not re.match(r'^[ \w\.\-\/]*$', revision): 150 log.msg("bad revision '%s'" % revision) 151 return Redirect(path_to_builder(req, self.builder_status)) 152 properties = getAndCheckProperties(req) 153 if properties is None: 154 return Redirect(path_to_builder(req, self.builder_status)) 155 if not branch: 156 branch = None 157 if not revision: 158 revision = None 159 160 # TODO: if we can authenticate that a particular User pushed the 161 # button, use their name instead of None, so they'll be informed of 162 # the results. 163 # TODO2: we can authenticate that a particular User pushed the button 164 # now, so someone can write this support. but it requires a 165 # buildbot.changes.changes.Change instance which is tedious at this 166 # stage to compute 167 s = SourceStamp(branch=branch, revision=revision, project=project, repository=repository) 168 try: 169 c = interfaces.IControl(self.getBuildmaster(req)) 170 bc = c.getBuilder(self.builder_status.getName()) 171 bc.submitBuildRequest(s, r, properties) 172 except interfaces.NoSlaveError: 173 # TODO: tell the web user that their request could not be 174 # honored 175 pass 176 # send the user back to the builder page 177 return Redirect(path_to_builder(req, self.builder_status))
178
179 - def ping(self, req):
180 log.msg("web ping of builder '%s'" % self.builder_status.getName()) 181 if not self.getAuthz(req).actionAllowed('pingBuilder', req, self.builder_status): 182 log.msg("..but not authorized") 183 return Redirect(path_to_authfail(req)) 184 c = interfaces.IControl(self.getBuildmaster(req)) 185 bc = c.getBuilder(self.builder_status.getName()) 186 bc.ping() 187 # send the user back to the builder page 188 return Redirect(path_to_builder(req, self.builder_status))
189
190 - def cancelbuild(self, req):
191 try: 192 request_id = req.args.get("id", [None])[0] 193 if request_id == "all": 194 cancel_all = True 195 else: 196 cancel_all = False 197 request_id = int(request_id) 198 except: 199 request_id = None 200 201 authz = self.getAuthz(req) 202 if request_id: 203 c = interfaces.IControl(self.getBuildmaster(req)) 204 bc = c.getBuilder(self.builder_status.getName()) 205 for build_req in bc.getPendingBuilds(): 206 if cancel_all or (build_req.brid == request_id): 207 log.msg("Cancelling %s" % build_req) 208 if authz.actionAllowed('cancelPendingBuild', req, build_req): 209 build_req.cancel() 210 else: 211 return Redirect(path_to_authfail(req)) 212 if not cancel_all: 213 break 214 return Redirect(path_to_builder(req, self.builder_status))
215
216 - def stopchange(self, req, auth_ok=False):
217 """Cancel all pending builds that include a given numbered change.""" 218 try: 219 request_change = req.args.get("change", [None])[0] 220 request_change = int(request_change) 221 except: 222 request_change = None 223 224 authz = self.getAuthz(req) 225 if request_change: 226 # FIXME: Please, for the love of god one day make there only be 227 # one getPendingBuilds() with combined status info/controls 228 c = interfaces.IControl(self.getBuildmaster(req)) 229 builder_control = c.getBuilder(self.builder_status.getName()) 230 build_controls = dict((x.brid, x) for x in builder_control.getPendingBuilds()) 231 for build_req in self.builder_status.getPendingBuilds(): 232 ss = build_req.getSourceStamp() 233 if not ss.changes: 234 continue 235 for change in ss.changes: 236 if change.number == request_change: 237 control = build_controls[build_req.brid] 238 log.msg("Cancelling %s" % control) 239 if auth_ok or authz.actionAllowed('stopChange', req, control): 240 control.cancel() 241 else: 242 return Redirect(path_to_authfail(req)) 243 return Redirect(path_to_builder(req, self.builder_status))
244
245 - def getChild(self, path, req):
246 if path == "force": 247 return self.force(req) 248 if path == "ping": 249 return self.ping(req) 250 if path == "cancelbuild": 251 return self.cancelbuild(req) 252 if path == "stopchange": 253 return self.stopchange(req) 254 if path == "builds": 255 return BuildsResource(self.builder_status) 256 257 return HtmlResource.getChild(self, path, req)
258 259 260 # /builders/_all
261 -class StatusResourceAllBuilders(HtmlResource, BuildLineMixin):
262
263 - def __init__(self, status):
264 HtmlResource.__init__(self) 265 self.status = status
266
267 - def getChild(self, path, req):
268 if path == "forceall": 269 return self.forceall(req) 270 if path == "stopall": 271 return self.stopall(req) 272 if path == "stopchangeall": 273 return self.stopchangeall(req) 274 275 return HtmlResource.getChild(self, path, req)
276
277 - def forceall(self, req):
278 authz = self.getAuthz(req) 279 if not authz.actionAllowed('forceAllBuilds', req): 280 return Redirect(path_to_authfail(req)) 281 282 for bname in self.status.getBuilderNames(): 283 builder_status = self.status.getBuilder(bname) 284 build = StatusResourceBuilder(builder_status) 285 build.force(req, auth_ok=True) # auth_ok because we already checked 286 # back to the welcome page 287 return Redirect(path_to_root(req))
288
289 - def stopall(self, req):
290 authz = self.getAuthz(req) 291 if not authz.actionAllowed('stopAllBuilds', req): 292 return Redirect(path_to_authfail(req)) 293 294 for bname in self.status.getBuilderNames(): 295 builder_status = self.status.getBuilder(bname) 296 (state, current_builds) = builder_status.getState() 297 if state != "building": 298 continue 299 for b in current_builds: 300 build_status = builder_status.getBuild(b.number) 301 if not build_status: 302 continue 303 build = StatusResourceBuild(build_status) 304 build.stop(req, auth_ok=True) 305 # go back to the welcome page 306 return Redirect(path_to_root(req))
307
308 - def stopchangeall(self, req):
309 authz = self.getAuthz(req) 310 if not authz.actionAllowed('stopChange', req): 311 return Redirect(path_to_authfail(req)) 312 313 for bname in self.status.getBuilderNames(): 314 builder_status = self.status.getBuilder(bname) 315 build = StatusResourceBuilder(builder_status) 316 build.stopchange(req, auth_ok=True) 317 318 return Redirect(path_to_root(req))
319 320 321 # /builders
322 -class BuildersResource(HtmlResource):
323 title = "Builders" 324 addSlash = True 325
326 - def content(self, req, cxt):
327 status = self.getStatus(req) 328 329 builders = req.args.get("builder", status.getBuilderNames()) 330 branches = [b for b in req.args.get("branch", []) if b] 331 332 cxt['branches'] = branches 333 bs = cxt['builders'] = [] 334 335 building = 0 336 online = 0 337 base_builders_url = path_to_root(req) + "builders/" 338 for bn in builders: 339 bld = { 'link': base_builders_url + urllib.quote(bn, safe=''), 340 'name': bn } 341 bs.append(bld) 342 343 builder = status.getBuilder(bn) 344 builds = list(builder.generateFinishedBuilds(map_branches(branches), 345 num_builds=1)) 346 if builds: 347 b = builds[0] 348 bld['build_url'] = (bld['link'] + "/builds/%d" % b.getNumber()) 349 try: 350 label = b.getProperty("got_revision") 351 except KeyError: 352 label = None 353 if not label or len(str(label)) > 20: 354 label = "#%d" % b.getNumber() 355 356 bld['build_label'] = label 357 bld['build_text'] = " ".join(b.getText()) 358 bld['build_css_class'] = build_get_class(b) 359 360 current_box = ICurrentBox(builder).getBox(status) 361 bld['current_box'] = current_box.td() 362 363 builder_status = builder.getState()[0] 364 if builder_status == "building": 365 building += 1 366 online += 1 367 elif builder_status != "offline": 368 online += 1 369 370 cxt['authz'] = self.getAuthz(req) 371 cxt['num_building'] = building 372 cxt['num_online'] = online 373 374 template = req.site.buildbot_service.templates.get_template("builders.html") 375 return template.render(**cxt)
376
377 - def getChild(self, path, req):
378 s = self.getStatus(req) 379 if path in s.getBuilderNames(): 380 builder_status = s.getBuilder(path) 381 return StatusResourceBuilder(builder_status) 382 if path == "_all": 383 return StatusResourceAllBuilders(self.getStatus(req)) 384 385 return HtmlResource.getChild(self, path, req)
386