Skip to content

Commit

Permalink
Add the ability to cancel builds for a change.
Browse files Browse the repository at this point in the history
Includes a new authz privledge 'stopChange' which
governs which users can utilize this functionality.

To stop builds for a given change, go to that change's
page in the web status and press the Cancel button.
  • Loading branch information
Amber Yust committed Oct 26, 2010
1 parent 0592222 commit a5d2494
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 1 deletion.
6 changes: 6 additions & 0 deletions master/NEWS
Expand Up @@ -51,6 +51,12 @@ high value (e.g. 10,000)
This extends the support already included for EC2 buildslaves to include any
virtualization platform supported by libvirt.

** Canceling Pending Builds for a Change

Change pages on the webstatus now have buttons to cancel any pending
builds that include that change (across all builders). The corresponding
authz privledge to control access to this feature is 'stopChange'.

** New Change source

*** CVSMaildirSource
Expand Down
1 change: 1 addition & 0 deletions master/buildbot/status/web/authz.py
Expand Up @@ -13,6 +13,7 @@ class Authz(object):
'stopBuild',
'stopAllBuilds',
'cancelPendingBuild',
'stopChange',
'cleanShutdown',
]

Expand Down
46 changes: 46 additions & 0 deletions master/buildbot/status/web/builder.py
Expand Up @@ -11,6 +11,7 @@
map_branches, path_to_authfail
from buildbot.sourcestamp import SourceStamp

from buildbot.status.builder import BuildRequestStatus
from buildbot.status.web.build import BuildsResource, StatusResourceBuild
from buildbot import util

Expand Down Expand Up @@ -200,13 +201,44 @@ def cancelbuild(self, req):
break
return Redirect(path_to_builder(req, self.builder_status))

def stopchange(self, req, auth_ok=False):
"""Cancel all pending builds that include a given numbered change."""
try:
request_change = req.args.get("change", [None])[0]
request_change = int(request_change)
except:
request_change = None

authz = self.getAuthz(req)
if request_change:
# FIXME: Please, for the love of god one day make there only be
# one getPendingBuilds() with combined status info/controls
c = interfaces.IControl(self.getBuildmaster(req))
builder_control = c.getBuilder(self.builder_status.getName())
build_controls = dict((x.brid, x) for x in builder_control.getPendingBuilds())
for build_req in self.builder_status.getPendingBuilds():
ss = build_req.getSourceStamp()
if not ss.changes:
continue
for change in ss.changes:
if change.number == request_change:
control = build_controls[build_req.brid]
log.msg("Cancelling %s" % control)
if auth_ok or authz.actionAllowed('stopChange', req, control):
control.cancel()
else:
return Redirect(path_to_authfail(req))
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 == "stopchange":
return self.stopchange(req)
if path == "builds":
return BuildsResource(self.builder_status)

Expand All @@ -225,6 +257,8 @@ def getChild(self, path, req):
return self.forceall(req)
if path == "stopall":
return self.stopall(req)
if path == "stopchangeall":
return self.stopchangeall(req)

return HtmlResource.getChild(self, path, req)

Expand Down Expand Up @@ -259,6 +293,18 @@ def stopall(self, req):
# go back to the welcome page
return Redirect(path_to_root(req))

def stopchangeall(self, req):
authz = self.getAuthz(req)
if not authz.actionAllowed('stopChange', req):
return Redirect(path_to_authfail(req))

for bname in self.status.getBuilderNames():
builder_status = self.status.getBuilder(bname)
build = StatusResourceBuilder(builder_status)
build.stopchange(req, auth_ok=True)

return Redirect(path_to_root(req))


# /builders
class BuildersResource(HtmlResource):
Expand Down
8 changes: 7 additions & 1 deletion master/buildbot/status/web/templates/change.html
@@ -1,5 +1,6 @@
{% extends "layout.html" %}
{% from "change_macros.html" import change with context %}
{% import 'forms.html' as forms %}

{% block content %}

Expand All @@ -9,6 +10,11 @@ <h1>{{ title }}</h1>

{{ change(c) }}

{% if authz.advertiseAction('stopChange') %}
<h3>Cancel Builds For Change:</h3>
{{ forms.stop_change_builds("/builders/_all/stopchangeall", c.number, authz) }}
{% endif %}

</div>

{% endblock %}
{% endblock %}
27 changes: 27 additions & 0 deletions master/buildbot/status/web/templates/forms.html
Expand Up @@ -36,6 +36,33 @@
{% endif %}
{% endmacro %}

{% macro stop_change_builds(stopchange_url, changenum, authz) %}
{% if not changenum or not authz.needAuthForm('stopChange') %}
<form method="post" action="{{ stopchange_url }}" class='command stopchange'>
{% if changenum %}
<p>To cancel all builds for this change, push the 'Cancel' button</p>
{% else %}
<p>To cancel builds for this builder for a given change, fill out all
fields and push the 'Cancel' button</p>
{% endif %}

{% if authz.needAuthForm('cancelPendingBuild') %}
{{ auth() }}
{% endif %}

{% if changenum %}
<input type="hidden" name="change" value="{{ changenum }}" />
{% else %}
<div class="row">
<span class="label">Change #:</span>
<input type="text" name="change"/>
</div>
{% endif %}
<input type="submit" value="Cancel" />
</form>
{% endif %}
{% endmacro %}

{% macro stop_build(stop_url, authz, on_all=False, short=False, label="Build") %}
{% if not short or not authz.needAuthForm('stopBuild') %}
<form method="post" name="stop_build" action="{{ stop_url }}" class='command stopbuild'
Expand Down
4 changes: 4 additions & 0 deletions master/docs/cfg-statustargets.texinfo
Expand Up @@ -468,6 +468,10 @@ stop all running builds

cancel a build that has not yet started

@item stopChange

cancel builds that include a given change number

@item cleanShutdown

shut down the master gracefully, without interrupting builds
Expand Down

0 comments on commit a5d2494

Please sign in to comment.