Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FIXED JENKINS-43339] Properly set result from FlowInterruptedException
We should be setting the build result to
FlowInterruptedException.getResult() when we catch a
FlowInterruptedException, since there's actually a relevant result
there. Also added a notBuilt post status while I was here.
  • Loading branch information
abayer committed Apr 21, 2017
1 parent a185431 commit d9a8fb1
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 10 deletions.
12 changes: 12 additions & 0 deletions pipeline-model-definition/pom.xml
Expand Up @@ -158,6 +158,18 @@
<artifactId>pipeline-build-step</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>pipeline-milestone-step</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>pipeline-input-step</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down
Expand Up @@ -33,6 +33,7 @@ import groovy.json.StringEscapeUtils
import hudson.ExtensionList
import hudson.model.Describable
import hudson.model.Descriptor
import hudson.model.Result
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.lang.StringUtils
import org.jenkinsci.plugins.pipeline.StageStatus
Expand Down Expand Up @@ -71,6 +72,7 @@ import org.jenkinsci.plugins.workflow.cps.nodes.StepEndNode
import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode
import org.jenkinsci.plugins.workflow.graph.FlowNode
import org.jenkinsci.plugins.workflow.job.WorkflowRun
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException
import org.jenkinsci.plugins.workflow.steps.StepDescriptor
import org.jenkinsci.plugins.workflow.support.steps.StageStep

Expand Down Expand Up @@ -689,4 +691,20 @@ public class Utils {
return Collections.singletonMap(UninstantiatedDescribable.ANONYMOUS_KEY, _args)
}
}

/**
* Get the appropriate result given an exception. If it's a {@link FlowInterruptedException}, return the result
* on the exception, otherwise return {@link Result#FAILURE}.
*
* @param e The exception.
* @return The result.
*/
static Result getResultFromException(Exception e) {
if (e instanceof FlowInterruptedException) {
return ((FlowInterruptedException)e).result
} else {
return Result.FAILURE
}
}

}
@@ -0,0 +1,50 @@
/*
* The MIT License
*
* Copyright (c) 2017, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins.pipeline.modeldefinition.model.conditions

import hudson.Extension
import hudson.model.Result
import org.jenkinsci.Symbol
import org.jenkinsci.plugins.pipeline.modeldefinition.model.BuildCondition
import org.jenkinsci.plugins.workflow.job.WorkflowRun

/**
* A {@link BuildCondition} for matching unbuilt builds, such as those stopped by milestones.
*
* @author Andrew Bayer
*/
@Extension(ordinal=400d) @Symbol("notBuilt")
public class NotBuilt extends BuildCondition {
@Override
public boolean meetsCondition(WorkflowRun r) {
return r.getResult() != null && r.getResult().equals(Result.NOT_BUILT)
}

@Override
public String getDescription() {
return Messages.NotBuilt_Description()
}

public static final long serialVersionUID = 1L
}
Expand Up @@ -27,7 +27,6 @@ import com.cloudbees.groovy.cps.NonCPS
import com.cloudbees.groovy.cps.impl.CpsClosure
import hudson.FilePath
import hudson.Launcher
import hudson.model.Result
import org.jenkinsci.plugins.pipeline.modeldefinition.model.*
import org.jenkinsci.plugins.pipeline.modeldefinition.steps.CredentialWrapper
import org.jenkinsci.plugins.pipeline.modeldefinition.when.DeclarativeStageConditional
Expand Down Expand Up @@ -111,7 +110,7 @@ public class ModelInterpreter implements Serializable {
}
}
} catch (Exception e) {
script.getProperty("currentBuild").result = Result.FAILURE
script.getProperty("currentBuild").result = Utils.getResultFromException(e)
Utils.markStageFailedAndContinued(thisStage.name)
if (firstError == null) {
firstError = e
Expand Down Expand Up @@ -427,7 +426,7 @@ public class ModelInterpreter implements Serializable {
delegateAndExecute(thisStage.steps.closure)
}
} catch (Exception e) {
script.getProperty("currentBuild").result = Result.FAILURE
script.getProperty("currentBuild").result = Utils.getResultFromException(e)
Utils.markStageFailedAndContinued(thisStage.name)
if (stageError == null) {
stageError = e
Expand Down Expand Up @@ -503,7 +502,7 @@ public class ModelInterpreter implements Serializable {
}
}
} catch (Exception e) {
script.getProperty("currentBuild").result = Result.FAILURE
script.getProperty("currentBuild").result = Utils.getResultFromException(e)
if (stageName != null) {
Utils.markStageFailedAndContinued(stageName)
}
Expand Down
Expand Up @@ -46,7 +46,7 @@ public class DockerPipelineFromDockerfileScript extends AbstractDockerPipelineSc
try {
img = buildImage().call()
} catch (Exception e) {
script.getProperty("currentBuild").result = Result.FAILURE
script.getProperty("currentBuild").result = Utils.getResultFromException(e)
Utils.markStageFailedAndContinued(SyntheticStageNames.agentSetup())
throw e
}
Expand All @@ -55,7 +55,7 @@ public class DockerPipelineFromDockerfileScript extends AbstractDockerPipelineSc
try {
img = buildImage().call()
} catch (Exception e) {
script.getProperty("currentBuild").result = Result.FAILURE
script.getProperty("currentBuild").result = Utils.getResultFromException(e)
throw e
}
}
Expand All @@ -65,7 +65,7 @@ public class DockerPipelineFromDockerfileScript extends AbstractDockerPipelineSc
body.call()
})
} catch (Exception e) {
script.getProperty("currentBuild").result = Result.FAILURE
script.getProperty("currentBuild").result = Utils.getResultFromException(e)
throw e
}
}
Expand Down
Expand Up @@ -44,7 +44,7 @@ public class DockerPipelineScript extends AbstractDockerPipelineScript<DockerPip
try {
script.getProperty("docker").image(describable.image).pull()
} catch (Exception e) {
script.getProperty("currentBuild").result = Result.FAILURE
script.getProperty("currentBuild").result = Utils.getResultFromException(e)
Utils.markStageFailedAndContinued(SyntheticStageNames.agentSetup())
throw e
}
Expand All @@ -55,7 +55,7 @@ public class DockerPipelineScript extends AbstractDockerPipelineScript<DockerPip
body.call()
})
} catch (Exception e) {
script.getProperty("currentBuild").result = Result.FAILURE
script.getProperty("currentBuild").result = Utils.getResultFromException(e)
throw e
}
}
Expand Down
Expand Up @@ -26,6 +26,7 @@
package org.jenkinsci.plugins.pipeline.modeldefinition.agent.impl

import hudson.model.Result
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
import org.jenkinsci.plugins.pipeline.modeldefinition.agent.CheckoutScript
import org.jenkinsci.plugins.pipeline.modeldefinition.agent.DeclarativeAgentScript
import org.jenkinsci.plugins.workflow.cps.CpsScript
Expand All @@ -44,7 +45,7 @@ public class LabelScript extends DeclarativeAgentScript<Label> {
CheckoutScript.doCheckout(script, describable, describable.customWorkspace, body).call()
}
} catch (Exception e) {
script.getProperty("currentBuild").result = Result.FAILURE
script.getProperty("currentBuild").result = Utils.getResultFromException(e)
throw e
}
}
Expand Down
Expand Up @@ -26,5 +26,6 @@ Aborted.Description=Run when the build status is "Aborted"
Always.Description=Always run, regardless of build status
Changed.Description=Run if the current build's status is different than the previous build's status
Failure.Description=Run if the build status is "Failure"
NotBuilt.Description=Run if the build status is "Not Built"
Success.Description=Run if the build status is "Success" or hasn't been set yet
Unstable.Description=Run if the build status is "Unstable"
Expand Up @@ -24,10 +24,13 @@
package org.jenkinsci.plugins.pipeline.modeldefinition;

import com.cloudbees.hudson.plugins.folder.Folder;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.google.common.base.Predicate;
import htmlpublisher.HtmlPublisherTarget;
import hudson.model.Result;
import hudson.model.Slave;
import hudson.model.queue.QueueTaskFuture;
import jenkins.model.Jenkins;
import jenkins.plugins.git.GitSCMSource;
import org.apache.commons.io.FileUtils;
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.ExecutionModelAction;
Expand All @@ -41,20 +44,28 @@
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTTreeStep;
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTValue;
import org.jenkinsci.plugins.workflow.actions.TagsAction;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.graphanalysis.DepthFirstScanner;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.libs.FolderLibraries;
import org.jenkinsci.plugins.workflow.libs.GlobalLibraries;
import org.jenkinsci.plugins.workflow.libs.LibraryConfiguration;
import org.jenkinsci.plugins.workflow.libs.SCMSourceRetriever;
import org.jenkinsci.plugins.workflow.pipelinegraphanalysis.GenericStatus;
import org.jenkinsci.plugins.workflow.pipelinegraphanalysis.StatusAndTiming;
import org.jenkinsci.plugins.workflow.support.steps.input.InputAction;
import org.jenkinsci.plugins.workflow.support.steps.input.InputStepExecution;
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
import org.junit.BeforeClass;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;

import java.util.Arrays;
import java.io.File;
Expand Down Expand Up @@ -728,4 +739,89 @@ public void inCustomWorkspaceInStage() throws Exception {
.logMatches("Workspace dir is .*some-sub-dir")
.go();
}

@Issue("JENKINS-43339")
@Test
public void notBuiltFlowInterruptedException() throws Exception {
WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "milestone-flow-interrupted");
job.setDefinition(new CpsFlowDefinition("" +
"pipeline {\n" +
" agent none\n" +
" stages {\n" +
" stage('milestones') {\n" +
" steps {\n" +
" milestone(1)\n" +
" semaphore 'wait'\n" +
" milestone(2)\n" +
" }\n" +
" post {\n" +
" notBuilt {\n" +
" echo 'Job not built due to milestone'\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}\n", true));

WorkflowRun run1 = job.scheduleBuild2(0).waitForStart();
SemaphoreStep.waitForStart("wait/1", run1);
WorkflowRun run2 = job.scheduleBuild2(0).waitForStart();
SemaphoreStep.waitForStart("wait/2", run2);

SemaphoreStep.success("wait/2", null);

j.waitForCompletion(run2);

j.assertBuildStatus(Result.NOT_BUILT, j.waitForCompletion(run1));

j.assertLogContains("Job not built due to milestone", run1);
}

@Issue("JENKINS-43339")
@Test
public void abortedFlowInterruptedException() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to("ops"));
WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "input-flow-interrupted");
job.setDefinition(new CpsFlowDefinition("" +
"pipeline {\n" +
" agent none\n" +
" stages {\n" +
" stage('input') {\n" +
" steps {\n" +
" input(id: 'InputX', message: 'OK?', ok: 'Yes', submitter: 'ops')\n" +
" }\n" +
" post {\n" +
" aborted {\n" +
" echo 'Job aborted due to input'\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}\n", true));

QueueTaskFuture<WorkflowRun> queueTaskFuture = job.scheduleBuild2(0);
WorkflowRun run = queueTaskFuture.getStartCondition().get();
CpsFlowExecution execution = (CpsFlowExecution) run.getExecutionPromise().get();

while (run.getAction(InputAction.class) == null) {
execution.waitForSuspension();
}

JenkinsRule.WebClient webClient = j.createWebClient();

webClient.login("ops");

InputAction inputAction = run.getAction(InputAction.class);
InputStepExecution is = inputAction.getExecution("InputX");
HtmlPage p = webClient.getPage(run, inputAction.getUrlName());

j.submit(p.getFormByName(is.getId()), "abort");
assertEquals(0, inputAction.getExecutions().size());
queueTaskFuture.get();

j.assertBuildStatus(Result.ABORTED, j.waitForCompletion(run));

j.assertLogContains("Job aborted due to input", run);
}
}
10 changes: 10 additions & 0 deletions pom.xml
Expand Up @@ -217,6 +217,16 @@
<artifactId>jcabi-matchers</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>pipeline-milestone-step</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>pipeline-input-step</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down

0 comments on commit d9a8fb1

Please sign in to comment.