diff --git a/src/main/java/io/jenkins/plugins/util/AbstractExecution.java b/src/main/java/io/jenkins/plugins/util/AbstractExecution.java index f7bbb18e..cf4ae193 100644 --- a/src/main/java/io/jenkins/plugins/util/AbstractExecution.java +++ b/src/main/java/io/jenkins/plugins/util/AbstractExecution.java @@ -144,8 +144,28 @@ protected Charset getCharset(final String charset) { * if the user canceled the execution * @throws IOException * if the required {@link FlowNode} instance is not found + * @deprecated use {@link #createQualityGateNotifier()} instead */ + @Deprecated protected StageResultHandler createStageResultHandler() throws InterruptedException, IOException { + return createPipelineResultHandler(); + } + + /** + * Creates a {@link QualityGateNotifier} that sets build result of the {@link Run} or step. + * + * @return a {@link QualityGateNotifier} that sets the build result of the {@link Run} or step + * @throws InterruptedException + * if the user canceled the execution + * @throws IOException + * if the required {@link FlowNode} instance is not found + * + */ + protected QualityGateNotifier createQualityGateNotifier() throws InterruptedException, IOException { + return createPipelineResultHandler(); + } + + private PipelineResultHandler createPipelineResultHandler() throws IOException, InterruptedException { return new PipelineResultHandler(getRun(), getContext().get(FlowNode.class)); } } diff --git a/src/main/java/io/jenkins/plugins/util/PipelineResultHandler.java b/src/main/java/io/jenkins/plugins/util/PipelineResultHandler.java index 4193cec0..0eb7a775 100644 --- a/src/main/java/io/jenkins/plugins/util/PipelineResultHandler.java +++ b/src/main/java/io/jenkins/plugins/util/PipelineResultHandler.java @@ -6,12 +6,13 @@ import hudson.model.Run; /** - * {@link StageResultHandler} that sets the overall build result of the {@link Run} and annotates the given Pipeline + * A {@link QualityGateNotifier} that sets the overall build result of the {@link Run} and annotates the given Pipeline * step with a {@link WarningAction}. * * @author Devin Nusbaum */ -public class PipelineResultHandler implements StageResultHandler { +@SuppressWarnings("deprecation") +public class PipelineResultHandler implements StageResultHandler, QualityGateNotifier { private final Run run; private final FlowNode flowNode; @@ -31,9 +32,30 @@ public PipelineResultHandler(final Run run, final FlowNode flowNode) { @Override public void setResult(final Result result, final String message) { run.setResult(result); + + setStageResult(result, message); + } + + private void setStageResult(final Result result, final String message) { WarningAction existing = flowNode.getPersistentAction(WarningAction.class); if (existing == null || existing.getResult().isBetterThan(result)) { flowNode.addOrReplaceAction(new WarningAction(result).withMessage(message)); } } + + @Override + public void publishResult(final QualityGateStatus status, final String message) { + switch (status) { + case NOTE: + case ERROR: + setStageResult(status.getResult(), message); + break; + case WARNING: + case FAILED: + setResult(status.getResult(), message); + break; + default: + // ignore and do nothing + } + } } diff --git a/src/main/java/io/jenkins/plugins/util/QualityGate.java b/src/main/java/io/jenkins/plugins/util/QualityGate.java index 8b4bfb42..e3a4ee89 100644 --- a/src/main/java/io/jenkins/plugins/util/QualityGate.java +++ b/src/main/java/io/jenkins/plugins/util/QualityGate.java @@ -4,11 +4,14 @@ import edu.hm.hafner.util.VisibleForTesting; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.verb.POST; import hudson.Extension; import hudson.model.AbstractDescribableImpl; +import hudson.model.BuildableItem; import hudson.model.Descriptor; +import hudson.model.FreeStyleProject; import hudson.util.ListBoxModel; import jenkins.model.Jenkins; @@ -77,10 +80,16 @@ public final QualityGateStatus getStatus() { * Determines the Jenkins build result if the quality gate is failed. */ public enum QualityGateCriticality { - /** The build will be marked as unstable. */ + /** The stage will be marked with a warning. */ + NOTE(QualityGateStatus.NOTE), + + /** The stage and the build will be marked as unstable. */ UNSTABLE(QualityGateStatus.WARNING), - /** The build will be marked as failed. */ + /** The stage will be marked as failed. */ + ERROR(QualityGateStatus.ERROR), + + /** The stage and the build will be marked as failed. */ FAILURE(QualityGateStatus.FAILED); private final QualityGateStatus status; @@ -124,15 +133,26 @@ public QualityGateDescriptor() { /** * Returns a model with all {@link QualityGateCriticality criticalities} that can be used in quality gates. * + * @param project + * the project that is configured + * * @return a model with all {@link QualityGateCriticality criticalities}. */ @POST @SuppressWarnings("unused") // used by Stapler view data binding - public ListBoxModel doFillCriticalityItems() { + public ListBoxModel doFillCriticalityItems(@AncestorInPath final BuildableItem project) { if (jenkins.hasPermission(Jenkins.READ)) { ListBoxModel options = new ListBoxModel(); - options.add(Messages.QualityGate_Unstable(), QualityGateCriticality.UNSTABLE.name()); - options.add(Messages.QualityGate_Failure(), QualityGateCriticality.FAILURE.name()); + if (project instanceof FreeStyleProject) { + options.add(Messages.QualityGate_Unstable(), QualityGateCriticality.UNSTABLE.name()); + options.add(Messages.QualityGate_Failure(), QualityGateCriticality.FAILURE.name()); + } + else { + options.add(Messages.QualityGate_UnstableStage(), QualityGateCriticality.NOTE.name()); + options.add(Messages.QualityGate_UnstableRun(), QualityGateCriticality.UNSTABLE.name()); + options.add(Messages.QualityGate_FailureStage(), QualityGateCriticality.FAILURE.name()); + options.add(Messages.QualityGate_FailureRun(), QualityGateCriticality.FAILURE.name()); + } return options; } return new ListBoxModel(); diff --git a/src/main/java/io/jenkins/plugins/util/QualityGateEvaluator.java b/src/main/java/io/jenkins/plugins/util/QualityGateEvaluator.java index dea8f9b6..67ba0441 100644 --- a/src/main/java/io/jenkins/plugins/util/QualityGateEvaluator.java +++ b/src/main/java/io/jenkins/plugins/util/QualityGateEvaluator.java @@ -43,7 +43,7 @@ public QualityGateResult evaluate() { protected abstract void evaluate(T qualityGate, QualityGateResult result); /** - * Appends all the quality gates in the specified collection to the end of the list of quality gates. + * Appends all the specified quality gates to the end of the existing quality gates. * * @param additionalQualityGates * the quality gates to add diff --git a/src/main/java/io/jenkins/plugins/util/QualityGateNotifier.java b/src/main/java/io/jenkins/plugins/util/QualityGateNotifier.java new file mode 100644 index 00000000..925001b6 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/util/QualityGateNotifier.java @@ -0,0 +1,18 @@ +package io.jenkins.plugins.util; + +/** + * Notifies the build or stage about the quality gate result. + * + * @author Ullrich Hafner + */ +public interface QualityGateNotifier { + /** + * Called to notify the build or stage about the quality gate status. + * + * @param status + * the quality gate status + * @param message + * a message that describes the cause for the result + */ + void publishResult(QualityGateStatus status, String message); +} diff --git a/src/main/java/io/jenkins/plugins/util/QualityGateStatus.java b/src/main/java/io/jenkins/plugins/util/QualityGateStatus.java index b7d7ca41..11807002 100644 --- a/src/main/java/io/jenkins/plugins/util/QualityGateStatus.java +++ b/src/main/java/io/jenkins/plugins/util/QualityGateStatus.java @@ -15,10 +15,16 @@ public enum QualityGateStatus { /** Quality gate has been passed. */ PASSED(Result.SUCCESS), + /** Quality gate has been missed: severity is a note. */ + NOTE(Result.UNSTABLE), + /** Quality gate has been missed: severity is a warning. */ WARNING(Result.UNSTABLE), /** Quality gate has been missed: severity is an error. */ + ERROR(Result.FAILURE), + + /** Quality gate has been missed: severity is a failure. */ FAILED(Result.FAILURE); private final Result result; diff --git a/src/main/java/io/jenkins/plugins/util/RunResultHandler.java b/src/main/java/io/jenkins/plugins/util/RunResultHandler.java index 7c7aeb09..d25a619d 100644 --- a/src/main/java/io/jenkins/plugins/util/RunResultHandler.java +++ b/src/main/java/io/jenkins/plugins/util/RunResultHandler.java @@ -4,11 +4,12 @@ import hudson.model.Run; /** - * {@link StageResultHandler} that sets the overall build result of the {@link Run}. + * A {@link QualityGateNotifier} that sets the overall build result of the {@link Run}. * * @author Devin Nusbaum */ -public class RunResultHandler implements StageResultHandler { +@SuppressWarnings("deprecation") +public class RunResultHandler implements StageResultHandler, QualityGateNotifier { private final Run run; /** @@ -23,6 +24,13 @@ public RunResultHandler(final Run run) { @Override public void setResult(final Result result, final String message) { - run.setResult(result); + if (result.equals(Result.UNSTABLE) || result.equals(Result.FAILURE)) { + run.setResult(result); + } + } + + @Override + public void publishResult(final QualityGateStatus status, final String message) { + setResult(status.getResult(), message); } } diff --git a/src/main/java/io/jenkins/plugins/util/StageResultHandler.java b/src/main/java/io/jenkins/plugins/util/StageResultHandler.java index 9d54ccb9..d685548c 100644 --- a/src/main/java/io/jenkins/plugins/util/StageResultHandler.java +++ b/src/main/java/io/jenkins/plugins/util/StageResultHandler.java @@ -5,8 +5,10 @@ /** * Handles the setting of the results of a stage. * - * @author Devin Nusbaum + * @author Devin Nusbaum + * @deprecated use {@link QualityGateNotifier} instead */ +@Deprecated public interface StageResultHandler { /** * Called to set the {@link Result} of a stage. diff --git a/src/main/resources/io/jenkins/plugins/util/Messages.properties b/src/main/resources/io/jenkins/plugins/util/Messages.properties index c514bce0..55af2d8f 100644 --- a/src/main/resources/io/jenkins/plugins/util/Messages.properties +++ b/src/main/resources/io/jenkins/plugins/util/Messages.properties @@ -2,6 +2,11 @@ FieldValidator.Error.DefaultEncoding=Encoding must be a supported encoding of th java.nio.charset.Charset FieldValidator.Error.WrongIdFormat=An ID must match the regexp pattern {0}, but {1} does not. -QualityGate.Failure=Fail the step if the quality gate has been missed -QualityGate.Unstable=Set the build status to unstable if the quality gate has been missed +QualityGate.Failure=Fail the build +QualityGate.Unstable=Mark the build as unstable + +QualityGate.FailureRun=Fail the step and the build +QualityGate.FailureStage=Fail the step but not the build +QualityGate.UnstableRun=Mark the step and the build as unstable +QualityGate.UnstableStage=Mark the step as unstable diff --git a/src/test/java/io/jenkins/plugins/util/PipelineResultHandlerTest.java b/src/test/java/io/jenkins/plugins/util/PipelineResultHandlerTest.java new file mode 100644 index 00000000..7cc3c146 --- /dev/null +++ b/src/test/java/io/jenkins/plugins/util/PipelineResultHandlerTest.java @@ -0,0 +1,64 @@ +package io.jenkins.plugins.util; + +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.Issue; + +import org.jenkinsci.plugins.workflow.actions.WarningAction; +import org.jenkinsci.plugins.workflow.graph.FlowNode; +import hudson.model.Action; +import hudson.model.Result; +import hudson.model.Run; + +import static org.mockito.Mockito.*; + +class PipelineResultHandlerTest { + private static final String MESSAGE = "message"; + + @Test + void shouldSetRunResult() { + var run = mock(Run.class); + var flowNode = mock(FlowNode.class); + + var handler = new PipelineResultHandler(run, flowNode); + handler.publishResult(QualityGateStatus.PASSED, MESSAGE); + + verifyNoInteractions(run); + verifyNoInteractions(flowNode); + + handler.publishResult(QualityGateStatus.WARNING, MESSAGE); + verify(run).setResult(Result.UNSTABLE); + verify(flowNode).addOrReplaceAction(argThat(action -> hasFlowNode(action, Result.UNSTABLE))); + + handler.publishResult(QualityGateStatus.FAILED, MESSAGE); + verify(run).setResult(Result.FAILURE); + verify(flowNode).addOrReplaceAction(argThat(action -> hasFlowNode(action, Result.FAILURE))); + } + + @Test @Issue("JENKINS-72059") + void shouldSetStageResult() { + var run = mock(Run.class); + var flowNode = mock(FlowNode.class); + + var handler = new PipelineResultHandler(run, flowNode); + handler.publishResult(QualityGateStatus.PASSED, MESSAGE); + + verifyNoInteractions(run); + verifyNoInteractions(flowNode); + + handler.publishResult(QualityGateStatus.NOTE, MESSAGE); + verifyNoInteractions(run); + verify(flowNode).addOrReplaceAction(argThat(action -> hasFlowNode(action, Result.UNSTABLE))); + + handler.publishResult(QualityGateStatus.ERROR, MESSAGE); + verifyNoInteractions(run); + verify(flowNode).addOrReplaceAction(argThat(action -> hasFlowNode(action, Result.FAILURE))); + } + + private boolean hasFlowNode(final Action action, final Result result) { + if (!(action instanceof WarningAction)) { + return false; + } + WarningAction warningAction = (WarningAction) action; + return result.equals(warningAction.getResult()) && MESSAGE.equals(warningAction.getMessage()); + } +} diff --git a/src/test/java/io/jenkins/plugins/util/RunResultHandlerTest.java b/src/test/java/io/jenkins/plugins/util/RunResultHandlerTest.java new file mode 100644 index 00000000..fb9b02a4 --- /dev/null +++ b/src/test/java/io/jenkins/plugins/util/RunResultHandlerTest.java @@ -0,0 +1,28 @@ +package io.jenkins.plugins.util; + +import org.junit.jupiter.api.Test; + +import hudson.model.Result; +import hudson.model.Run; + +import static org.mockito.Mockito.*; + +class RunResultHandlerTest { + private static final String MESSAGE = "message"; + + @Test + void shouldSetRunResult() { + var run = mock(Run.class); + + var handler = new RunResultHandler(run); + handler.publishResult(QualityGateStatus.PASSED, MESSAGE); + + verifyNoInteractions(run); + + handler.publishResult(QualityGateStatus.WARNING, MESSAGE); + verify(run).setResult(Result.UNSTABLE); + + handler.publishResult(QualityGateStatus.FAILED, MESSAGE); + verify(run).setResult(Result.FAILURE); + } +}