|
1 | 1 | """Views for builds app."""
|
2 | 2 |
|
| 3 | +import signal |
| 4 | + |
3 | 5 | import structlog
|
4 | 6 | import textwrap
|
5 | 7 | from urllib.parse import urlparse
|
|
14 | 16 | from django.views.generic import DetailView, ListView
|
15 | 17 | from requests.utils import quote
|
16 | 18 |
|
| 19 | +from readthedocs.builds.constants import BUILD_STATE_TRIGGERED, BUILD_STATE_FINISHED |
17 | 20 | from readthedocs.builds.filters import BuildListFilter
|
18 | 21 | from readthedocs.builds.models import Build, Version
|
19 | 22 | from readthedocs.core.permissions import AdminPermission
|
20 | 23 | from readthedocs.core.utils import trigger_build
|
21 |
| -from readthedocs.doc_builder.exceptions import BuildAppError |
| 24 | +from readthedocs.doc_builder.exceptions import BuildAppError, BuildCancelled |
22 | 25 | from readthedocs.projects.models import Project
|
| 26 | +from readthedocs.worker import app |
| 27 | + |
23 | 28 |
|
24 | 29 | log = structlog.get_logger(__name__)
|
25 | 30 |
|
@@ -148,6 +153,46 @@ class BuildDetail(BuildBase, DetailView):
|
148 | 153 |
|
149 | 154 | pk_url_kwarg = 'build_pk'
|
150 | 155 |
|
| 156 | + @method_decorator(login_required) |
| 157 | + def post(self, request, project_slug, build_pk): |
| 158 | + project = get_object_or_404(Project, slug=project_slug) |
| 159 | + build = get_object_or_404(Build, pk=build_pk) |
| 160 | + |
| 161 | + if not AdminPermission.is_admin(request.user, project): |
| 162 | + return HttpResponseForbidden() |
| 163 | + |
| 164 | + # NOTE: `terminate=True` is required for the child to attend our call |
| 165 | + # immediately when it's running the build. Otherwise, it finishes the |
| 166 | + # task. However, to revoke a task that has not started yet, we don't |
| 167 | + # need it. |
| 168 | + if build.state == BUILD_STATE_TRIGGERED: |
| 169 | + # Since the task won't be executed at all, we need to update the |
| 170 | + # Build object here. |
| 171 | + terminate = False |
| 172 | + build.state = BUILD_STATE_FINISHED |
| 173 | + build.success = False |
| 174 | + build.error = BuildCancelled.message |
| 175 | + build.length = 0 |
| 176 | + build.save() |
| 177 | + else: |
| 178 | + # In this case, we left the update of the Build object to the task |
| 179 | + # itself to be executed in the `on_failure` handler. |
| 180 | + terminate = True |
| 181 | + |
| 182 | + log.warning( |
| 183 | + 'Canceling build.', |
| 184 | + project_slug=project.slug, |
| 185 | + version_slug=build.version.slug, |
| 186 | + build_id=build.pk, |
| 187 | + build_task_id=build.task_id, |
| 188 | + terminate=terminate, |
| 189 | + ) |
| 190 | + app.control.revoke(build.task_id, signal=signal.SIGINT, terminate=terminate) |
| 191 | + |
| 192 | + return HttpResponseRedirect( |
| 193 | + reverse('builds_detail', args=[project.slug, build.pk]), |
| 194 | + ) |
| 195 | + |
151 | 196 | def get_context_data(self, **kwargs):
|
152 | 197 | context = super().get_context_data(**kwargs)
|
153 | 198 | context['project'] = self.project
|
|
0 commit comments