From 19f76af2e8e97a69068c69240a9c57febf0f1349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tommy=20Tynj=C3=A4?= Date: Tue, 20 Nov 2018 10:10:48 +0100 Subject: [PATCH] JENKINS-28708 Allow aborting builds from pipeline views --- .../pipeline/DeliveryPipelineView.java | 24 ++ .../diabol/jenkins/pipeline/PipelineApi.java | 22 +- .../diabol/jenkins/pipeline/PipelineView.java | 2 + .../workflow/WorkflowPipelineView.java | 32 +- .../configure-entries.jelly | 4 + .../DeliveryPipelineView/help-allowAbort.html | 3 + .../configure-entries.jelly | 3 + .../WorkflowPipelineView/help-allowAbort.html | 3 + src/main/webapp/pipe.js | 45 +++ src/main/webapp/themes/contrast/abort.png | Bin 0 -> 1680 bytes .../themes/contrast/pipeline-common.css | 24 +- .../themes/contrast/pipeline-fullscreen.css | 9 + src/main/webapp/themes/default/abort.png | Bin 0 -> 1680 bytes .../webapp/themes/default/pipeline-common.css | 21 ++ .../themes/default/pipeline-fullscreen.css | 8 + src/main/webapp/themes/overview/abort.png | Bin 0 -> 1680 bytes .../themes/overview/pipe-fullscreen.css | 320 ------------------ src/main/webapp/themes/overview/pipe.js | 45 +++ .../themes/overview/pipeline-common.css | 22 ++ .../themes/overview/pipeline-fullscreen.css | 8 + 20 files changed, 269 insertions(+), 326 deletions(-) create mode 100644 src/main/resources/se/diabol/jenkins/pipeline/DeliveryPipelineView/help-allowAbort.html create mode 100644 src/main/resources/se/diabol/jenkins/workflow/WorkflowPipelineView/help-allowAbort.html create mode 100644 src/main/webapp/themes/contrast/abort.png create mode 100644 src/main/webapp/themes/default/abort.png create mode 100644 src/main/webapp/themes/overview/abort.png delete mode 100644 src/main/webapp/themes/overview/pipe-fullscreen.css diff --git a/src/main/java/se/diabol/jenkins/pipeline/DeliveryPipelineView.java b/src/main/java/se/diabol/jenkins/pipeline/DeliveryPipelineView.java index d115dd05f..93400ed8f 100644 --- a/src/main/java/se/diabol/jenkins/pipeline/DeliveryPipelineView.java +++ b/src/main/java/se/diabol/jenkins/pipeline/DeliveryPipelineView.java @@ -103,6 +103,7 @@ public class DeliveryPipelineView extends View implements PipelineView { private boolean showTotalBuildTime = false; private boolean allowRebuild = false; private boolean allowPipelineStart = false; + private boolean allowAbort = false; private boolean showDescription = false; private boolean showPromotions = false; private boolean showTestResults = false; @@ -211,6 +212,15 @@ public void setAllowPipelineStart(boolean allowPipelineStart) { this.allowPipelineStart = allowPipelineStart; } + @Exported + public boolean isAllowAbort() { + return allowAbort; + } + + public void setAllowAbort(boolean allowAbort) { + this.allowAbort = allowAbort; + } + @Exported public boolean isAllowManualTriggers() { return allowManualTriggers; @@ -482,6 +492,20 @@ public void triggerRebuild(String projectName, String buildId) { build.getAction(ParametersAction.class)); } + @Override + public void abortBuild(String projectName, String buildId) throws TriggerException { + AbstractProject project = ProjectUtil.getProject(projectName, Jenkins.getInstance()); + if (!project.hasPermission(Item.CANCEL)) { + throw new BadCredentialsException("Not authorized to abort build"); + } + AbstractBuild build = project.getBuildByNumber(Integer.parseInt(buildId)); + try { + build.doStop(); + } catch (IOException | ServletException e) { + throw new TriggerException("Could not abort build"); + } + } + protected static String triggerExceptionMessage(final String projectName, final String upstreamName, final String buildId) { String message = "Could not trigger manual build " + projectName + " for upstream " + upstreamName diff --git a/src/main/java/se/diabol/jenkins/pipeline/PipelineApi.java b/src/main/java/se/diabol/jenkins/pipeline/PipelineApi.java index 131a159f2..95e8758ab 100644 --- a/src/main/java/se/diabol/jenkins/pipeline/PipelineApi.java +++ b/src/main/java/se/diabol/jenkins/pipeline/PipelineApi.java @@ -46,7 +46,7 @@ public void doManualStep(StaplerRequest request, StaplerResponse response, @QueryParameter String project, @QueryParameter String upstream, - @QueryParameter String buildId) throws IOException, ServletException { + @QueryParameter String buildId) { if (project != null && upstream != null && buildId != null) { try { view.triggerManual(project, upstream, buildId); @@ -65,7 +65,7 @@ public void doManualStep(StaplerRequest request, public void doRebuildStep(StaplerRequest request, StaplerResponse response, @QueryParameter String project, - @QueryParameter String buildId) throws IOException, ServletException { + @QueryParameter String buildId) { if (project != null && buildId != null) { try { view.triggerRebuild(project, buildId); @@ -87,4 +87,22 @@ public void doInputStep(StaplerRequest request, doManualStep(request, response, project, upstream, buildId); } + @SuppressWarnings("UnusedDeclaration") + public void doAbortBuild(StaplerRequest request, + StaplerResponse response, + @QueryParameter String project, + @QueryParameter String buildId) { + if (project != null && buildId != null) { + try { + view.abortBuild(project, buildId); + } catch (AuthenticationException e) { + response.setStatus(SC_FORBIDDEN); + } catch (TriggerException e) { + response.setStatus(SC_NOT_ACCEPTABLE); + } + } else { + response.setStatus(SC_NOT_ACCEPTABLE); + } + } + } diff --git a/src/main/java/se/diabol/jenkins/pipeline/PipelineView.java b/src/main/java/se/diabol/jenkins/pipeline/PipelineView.java index 40eed878d..434f12563 100644 --- a/src/main/java/se/diabol/jenkins/pipeline/PipelineView.java +++ b/src/main/java/se/diabol/jenkins/pipeline/PipelineView.java @@ -26,4 +26,6 @@ void triggerManual(String projectName, String upstreamName, String buildId) throws TriggerException, AuthenticationException; void triggerRebuild(String projectName, String buildId); + + void abortBuild(String projectName, String buildId) throws TriggerException, AuthenticationException; } diff --git a/src/main/java/se/diabol/jenkins/workflow/WorkflowPipelineView.java b/src/main/java/se/diabol/jenkins/workflow/WorkflowPipelineView.java index 4f828ce9e..48f816625 100644 --- a/src/main/java/se/diabol/jenkins/workflow/WorkflowPipelineView.java +++ b/src/main/java/se/diabol/jenkins/workflow/WorkflowPipelineView.java @@ -36,6 +36,7 @@ import hudson.util.RunList; import jenkins.model.Jenkins; import org.acegisecurity.AuthenticationException; +import org.acegisecurity.BadCredentialsException; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.jenkinsci.plugins.workflow.support.steps.input.InputAction; @@ -64,6 +65,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -85,6 +87,7 @@ public class WorkflowPipelineView extends View implements PipelineView { private int noOfColumns = 1; private String sorting = NONE_SORTER; private boolean allowPipelineStart = false; + private boolean allowAbort = false; private boolean showChanges = false; private String theme = DEFAULT_THEME; private int maxNumberOfVisiblePipelines = -1; @@ -152,6 +155,15 @@ public void setAllowPipelineStart(boolean allowPipelineStart) { this.allowPipelineStart = allowPipelineStart; } + @Exported + public boolean isAllowAbort() { + return allowAbort; + } + + public void setAllowAbort(boolean allowAbort) { + this.allowAbort = allowAbort; + } + public boolean isShowChanges() { return showChanges; } @@ -303,8 +315,7 @@ public Item doCreateItem(StaplerRequest req, StaplerResponse rsp) throws IOExcep } @Override - public void triggerManual(String projectName, String upstreamName, String buildId) - throws TriggerException, AuthenticationException { + public void triggerManual(String projectName, String upstreamName, String buildId) throws AuthenticationException { LOG.fine("Manual/Input step called for project: " + projectName + " and build id: " + buildId); WorkflowJob workflowJob; @@ -329,6 +340,23 @@ public void triggerRebuild(String projectName, String buildId) { LOG.log(Level.SEVERE, "Rebuild not implemented for workflow/pipeline projects"); } + @Override + public void abortBuild(String projectName, String buildId) throws TriggerException { + try { + WorkflowJob workflowJob = ProjectUtil.getWorkflowJob(projectName, getOwnerItemGroup()); + if (!workflowJob.hasAbortPermission()) { + throw new BadCredentialsException("Not authorized to abort build"); + } + RunList builds = workflowJob.getBuilds(); + Optional run = builds.stream() + .filter(r -> Integer.toString(r.getNumber()).equals(buildId)) + .findFirst(); + run.ifPresent(WorkflowRun::doStop); + } catch (PipelineException e) { + throw new TriggerException("Could not abort build"); + } + } + @Override public Collection getItems() { Set jobs = Sets.newHashSet(); diff --git a/src/main/resources/se/diabol/jenkins/pipeline/DeliveryPipelineView/configure-entries.jelly b/src/main/resources/se/diabol/jenkins/pipeline/DeliveryPipelineView/configure-entries.jelly index 67805b379..4be3eee2e 100644 --- a/src/main/resources/se/diabol/jenkins/pipeline/DeliveryPipelineView/configure-entries.jelly +++ b/src/main/resources/se/diabol/jenkins/pipeline/DeliveryPipelineView/configure-entries.jelly @@ -51,6 +51,10 @@ + + + + diff --git a/src/main/resources/se/diabol/jenkins/pipeline/DeliveryPipelineView/help-allowAbort.html b/src/main/resources/se/diabol/jenkins/pipeline/DeliveryPipelineView/help-allowAbort.html new file mode 100644 index 000000000..809d33a59 --- /dev/null +++ b/src/main/resources/se/diabol/jenkins/pipeline/DeliveryPipelineView/help-allowAbort.html @@ -0,0 +1,3 @@ +
+ Allow cancelling a running job from the delivery pipeline view. +
diff --git a/src/main/resources/se/diabol/jenkins/workflow/WorkflowPipelineView/configure-entries.jelly b/src/main/resources/se/diabol/jenkins/workflow/WorkflowPipelineView/configure-entries.jelly index adee9cb0c..6e858f63d 100644 --- a/src/main/resources/se/diabol/jenkins/workflow/WorkflowPipelineView/configure-entries.jelly +++ b/src/main/resources/se/diabol/jenkins/workflow/WorkflowPipelineView/configure-entries.jelly @@ -25,6 +25,9 @@ + + + diff --git a/src/main/resources/se/diabol/jenkins/workflow/WorkflowPipelineView/help-allowAbort.html b/src/main/resources/se/diabol/jenkins/workflow/WorkflowPipelineView/help-allowAbort.html new file mode 100644 index 000000000..809d33a59 --- /dev/null +++ b/src/main/resources/se/diabol/jenkins/workflow/WorkflowPipelineView/help-allowAbort.html @@ -0,0 +1,3 @@ +
+ Allow cancelling a running job from the delivery pipeline view. +
diff --git a/src/main/webapp/pipe.js b/src/main/webapp/pipe.js index 139a86389..e64905dd4 100644 --- a/src/main/webapp/pipe.js +++ b/src/main/webapp/pipe.js @@ -171,6 +171,14 @@ function pipelineUtils() { consoleLogLink = 'console'; } + var showAbortButton = false; + if (data.allowAbort) { + progressClass += ' task-abortable'; + if (progressClass.indexOf('task-progress-running') !== -1) { + showAbortButton = true; + } + } + html.push( '
' + '
' @@ -190,9 +198,18 @@ function pipelineUtils() { html.push('
'); } if (task.requiringInput) { + showAbortButton = true; html.push('
'); html.push('
'); } + if (showAbortButton) { + var projectName = component.fullJobName; + if (typeof projectName === "undefined") { + projectName = task.id; + } + html.push('
'); + html.push('
'); + } } html.push('
'); @@ -660,6 +677,34 @@ function specifyInput(taskId, project, buildId, viewUrl) { }); } +function abortBuild(taskId, project, buildId, viewUrl) { + Q('#abort-' + taskId).hide(); + var formData = {project: project, upstream: 'N/A', buildId: buildId}, before; + + var before; + if (crumb.value !== null && crumb.value !== '') { + console.info('Crumb found and will be added to request header'); + before = function(xhr){xhr.setRequestHeader(crumb.fieldName, crumb.value);} + } else { + console.info('Crumb not needed'); + before = function(xhr){} + } + + Q.ajax({ + url: rootURL + '/' + viewUrl + 'api/abortBuild', + type: 'POST', + data: formData, + beforeSend: before, + timeout: 20000, + success: function (data, textStatus, jqXHR) { + console.info('Successfully aborted build of ' + project + '!') + }, + error: function (jqXHR, textStatus, errorThrown) { + window.alert('Could not abort build! error: ' + errorThrown + ' status: ' + textStatus) + } + }); +} + function triggerParameterizedBuild(url, taskId) { console.info('Job is parameterized'); window.location.href = rootURL + '/' + url + 'build?delay=0sec'; diff --git a/src/main/webapp/themes/contrast/abort.png b/src/main/webapp/themes/contrast/abort.png new file mode 100644 index 0000000000000000000000000000000000000000..81565d554225992ef507ef2ec6e0238aed17b44f GIT binary patch literal 1680 zcmV;B254Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%S zK1oDDR7efoR!eT%Fc5vBzB11CN> z7xmSWL)nWL?DN$XzW(~z_!k-s|v8TEhy|r(iBOS!3iGoA(&hkZw3QQ z?|$ITrwOzp7O1AvmdKGzX0JoK35jApoj@c$5hDBesNFV1Qn(RMkMh&!z=3Q1}@;w)%; z&4S(zbwd=-VccFvOsEzQu*)S)aA(o>I)>@)6UU((`Z=Dg5exZ*(0&dJeX*Rb-!SN3 zh^`TdijDk}_8@s#Xw_aj@+4$xI|{)b8bhxgg%kcih^!m}UV7~)JwEn4yUh@?x84^T z)nbAC>`be)yN+N?3~yIC@_G4LIo;i6gY4u)?Nm-^VqMLcuCzp5hyTqJ7{RwD?k81k z3wE{74Ry0dkr{+RFR{&dtm#ch@>%lpbJ+PD_CZeZqSR}_$vc1#jC3fM96wzbACI)i z9xm}_G(vWI3d7fvh!S1i+~9IDkvpkuUO{|5ZJ$tl)sRHxFL17@8D#i!d#kDbDb^I~ a@$Vl1Lh|FQc9pLH0000 ul { .pagination { display:block; text-align:left; - clear:both; + clear:both; font-family:Arial, Helvetica, sans-serif; font-size:14px; font-weight:normal; @@ -420,7 +440,7 @@ div.aggregatedChangesPanelInner > ul { .pagination a:hover { background-color:#DDEEFF; border:1px solid #BBDDFF; - color:#0072BC; + color:#0072BC; } .pagination .active_link a { diff --git a/src/main/webapp/themes/contrast/pipeline-fullscreen.css b/src/main/webapp/themes/contrast/pipeline-fullscreen.css index 783d52d06..73645090a 100644 --- a/src/main/webapp/themes/contrast/pipeline-fullscreen.css +++ b/src/main/webapp/themes/contrast/pipeline-fullscreen.css @@ -45,6 +45,15 @@ div.task-progress { min-height: 36px; } +div.task-abort { + bottom: 4px; +} + +div.task-details { + padding-top: 5px; +} + + .stage-name { padding: 10px 10px 10px 10px; } diff --git a/src/main/webapp/themes/default/abort.png b/src/main/webapp/themes/default/abort.png new file mode 100644 index 0000000000000000000000000000000000000000..81565d554225992ef507ef2ec6e0238aed17b44f GIT binary patch literal 1680 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 a@$Vl1Lh|FQc9pLH0000' + '
' @@ -192,9 +200,18 @@ function pipelineUtils() { html.push('
'); } if (task.requiringInput) { + showAbortButton = true; html.push('
'); html.push('
'); } + if (showAbortButton) { + var projectName = component.fullJobName; + if (typeof projectName === "undefined") { + projectName = task.id; + } + html.push('
'); + html.push('
'); + } } html.push('
'); @@ -684,6 +701,34 @@ function specifyInput(taskId, project, buildId, viewUrl) { }); } +function abortBuild(taskId, project, buildId, viewUrl) { + Q('#abort-' + taskId).hide(); + var formData = {project: project, upstream: 'N/A', buildId: buildId}, before; + + var before; + if (crumb.value !== null && crumb.value !== '') { + console.info('Crumb found and will be added to request header'); + before = function(xhr){xhr.setRequestHeader(crumb.fieldName, crumb.value);} + } else { + console.info('Crumb not needed'); + before = function(xhr){} + } + + Q.ajax({ + url: rootURL + '/' + viewUrl + 'api/abortBuild', + type: 'POST', + data: formData, + beforeSend: before, + timeout: 20000, + success: function (data, textStatus, jqXHR) { + console.info('Successfully aborted build of ' + project + '!') + }, + error: function (jqXHR, textStatus, errorThrown) { + window.alert('Could not abort build! error: ' + errorThrown + ' status: ' + textStatus) + } + }); +} + function triggerParameterizedBuild(url, taskId) { console.info('Job is parameterized'); window.location.href = rootURL + '/' + url + 'build?delay=0sec'; diff --git a/src/main/webapp/themes/overview/pipeline-common.css b/src/main/webapp/themes/overview/pipeline-common.css index 30d28305e..0bdb3b683 100644 --- a/src/main/webapp/themes/overview/pipeline-common.css +++ b/src/main/webapp/themes/overview/pipeline-common.css @@ -53,6 +53,7 @@ div.stage a:visited { } div.stage-task { + position: relative; white-space: nowrap; margin: 2px 2px 2px 2px; } @@ -104,6 +105,7 @@ div.task-manual { float: right; padding-right: 5px; z-index: 100; + margin-top: -18px; } div.task-rebuild { @@ -118,6 +120,22 @@ div.task-rebuild { z-index: 100; } +div.task-abort { + display: table-cell; + background-color: #808080; + background: url("abort.png") no-repeat; + background-size: 100%; + position: absolute; + bottom: 6px; + right: 3px; + cursor: pointer; + height: 14px; + width: 14px; + float: right; + margin-left: 5px; + z-index: 100; +} + .timestamp { display: inline-block; } @@ -127,6 +145,10 @@ div.task-rebuild { display: inline-block; } +.task-abortable .duration { + padding-right: 20px; +} + div.task-progress-running { background-color: lightskyblue; animation-duration: 1s; diff --git a/src/main/webapp/themes/overview/pipeline-fullscreen.css b/src/main/webapp/themes/overview/pipeline-fullscreen.css index 1d86203cd..40fd98bad 100644 --- a/src/main/webapp/themes/overview/pipeline-fullscreen.css +++ b/src/main/webapp/themes/overview/pipeline-fullscreen.css @@ -43,6 +43,14 @@ div.stage-task, div.task-progress { min-height: 42px; } +div.task-abort { + bottom: 4px; +} + +div.task-details { + padding-top: 5px; +} + .stage-name, .stage-version { padding: 10px; }