Skip to content

Commit 821aabd

Browse files
[JENKINS-54250] Disable restarting stages in jenkinsfile optionally (#560)
* Introduce DisableRestartFromStage declarative option. Change logic of restart declarative pipeline action to not return the icon if restart is disabled. Parse new option in Utils.groovy * Handle disableRestartFromStage option in Utils.groovy when it is not set
1 parent 31fd5b9 commit 821aabd

File tree

9 files changed

+153
-1
lines changed

9 files changed

+153
-1
lines changed

pipeline-model-definition/src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/Utils.groovy

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ import hudson.ExtensionList
3838
import hudson.model.*
3939
import hudson.util.Secret
4040
import hudson.triggers.Trigger
41+
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.DisableRestartFromStageAction
4142
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTStage
43+
import org.jenkinsci.plugins.pipeline.modeldefinition.options.impl.DisableRestartFromStage
4244
import org.jenkinsci.plugins.pipeline.modeldefinition.parser.JSONParser
4345

4446
import java.util.function.Function
@@ -622,6 +624,13 @@ class Utils {
622624
isJobChanged = true
623625
}
624626

627+
DisableRestartFromStage declaredDisableRestartFromStageOption = (DisableRestartFromStage) rawOptions.find { it instanceof DisableRestartFromStage }
628+
DisableRestartFromStageAction currentRestartFromStageAction = j.getAction(DisableRestartFromStageAction.class);
629+
if(currentRestartFromStageAction == null && declaredDisableRestartFromStageOption != null){
630+
j.addAction(new DisableRestartFromStageAction())
631+
} else if(currentRestartFromStageAction != null && declaredDisableRestartFromStageOption == null) {
632+
j.removeAction(currentRestartFromStageAction)
633+
}
625634

626635
// If there are any triggers update them if needed
627636
// It would be cool to only add or remove individual triggers,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.jenkinsci.plugins.pipeline.modeldefinition.actions;
2+
3+
import hudson.model.InvisibleAction;
4+
5+
public class DisableRestartFromStageAction extends InvisibleAction {
6+
7+
public DisableRestartFromStageAction(){}
8+
}

pipeline-model-definition/src/main/java/org/jenkinsci/plugins/pipeline/modeldefinition/actions/RestartDeclarativePipelineAction.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils;
4040
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTStage;
4141
import org.jenkinsci.plugins.pipeline.modeldefinition.causes.RestartDeclarativePipelineCause;
42+
import org.jenkinsci.plugins.pipeline.modeldefinition.options.impl.DisableRestartFromStage;
4243
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
4344
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
4445
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
46+
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
4547
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
4648
import org.kohsuke.accmod.Restricted;
4749
import org.kohsuke.accmod.restrictions.DoNotUse;
@@ -103,7 +105,8 @@ public boolean isRestartEnabled() {
103105
!run.isBuilding() &&
104106
run.hasPermission(Item.BUILD) &&
105107
run.getParent().isBuildable() &&
106-
getExecution() != null;
108+
getExecution() != null &&
109+
run.getParent().getAction(DisableRestartFromStageAction.class) == null;
107110
}
108111

109112
public Api getApi() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.jenkinsci.plugins.pipeline.modeldefinition.options.impl;
2+
3+
import edu.umd.cs.findbugs.annotations.NonNull;
4+
import hudson.Extension;
5+
import org.jenkinsci.Symbol;
6+
import org.jenkinsci.plugins.pipeline.modeldefinition.options.DeclarativeOption;
7+
import org.jenkinsci.plugins.pipeline.modeldefinition.options.DeclarativeOptionDescriptor;
8+
import org.kohsuke.stapler.DataBoundConstructor;
9+
10+
public class DisableRestartFromStage extends DeclarativeOption {
11+
12+
@DataBoundConstructor
13+
public DisableRestartFromStage() {}
14+
15+
@Extension @Symbol("disableRestartFromStage")
16+
public static class DescriptorImpl extends DeclarativeOptionDescriptor {
17+
@Override
18+
@NonNull
19+
public String getDisplayName() {
20+
return "Disable the ability to restart this Pipeline from a specific stage";
21+
}
22+
23+
@Override
24+
public boolean canUseInStage() {
25+
return false;
26+
}
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ The MIT License
4+
~
5+
~ Copyright (c) 2018, CloudBees, Inc.
6+
~
7+
~ Permission is hereby granted, free of charge, to any person obtaining a copy
8+
~ of this software and associated documentation files (the "Software"), to deal
9+
~ in the Software without restriction, including without limitation the rights
10+
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
~ copies of the Software, and to permit persons to whom the Software is
12+
~ furnished to do so, subject to the following conditions:
13+
~
14+
~ The above copyright notice and this permission notice shall be included in
15+
~ all copies or substantial portions of the Software.
16+
~
17+
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
~ THE SOFTWARE.
24+
-->
25+
26+
<?jelly escape-by-default='true'?>
27+
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
28+
</j:jelly>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!--
2+
~ The MIT License
3+
~
4+
~ Copyright (c) 2017, CloudBees, Inc.
5+
~
6+
~ Permission is hereby granted, free of charge, to any person obtaining a copy
7+
~ of this software and associated documentation files (the "Software"), to deal
8+
~ in the Software without restriction, including without limitation the rights
9+
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
~ copies of the Software, and to permit persons to whom the Software is
11+
~ furnished to do so, subject to the following conditions:
12+
~
13+
~ The above copyright notice and this permission notice shall be included in
14+
~ all copies or substantial portions of the Software.
15+
~
16+
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
~ THE SOFTWARE.
23+
-->
24+
25+
<p>
26+
If specified, Restart From Stage button will not be displayed.
27+
</p>

pipeline-model-definition/src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/OptionsTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
import jenkins.model.BuildDiscarder;
4141
import jenkins.model.BuildDiscarderProperty;
4242
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.DeclarativeJobPropertyTrackerAction;
43+
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.DisableRestartFromStageAction;
44+
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.ExecutionModelAction;
45+
import org.jenkinsci.plugins.pipeline.modeldefinition.options.impl.DisableRestartFromStage;
4346
import org.jenkinsci.plugins.pipeline.modeldefinition.parser.RuntimeASTTransformer;
4447
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
4548
import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode;
@@ -556,6 +559,29 @@ public void sameJobPropertiesNotOverride() throws Exception {
556559
assertSame(strategy, strategy2);
557560
}
558561

562+
@Issue("JENKINS-54250")
563+
@Test
564+
public void verifyDisableRestartFromStageActionIsAdded() throws Exception {
565+
WorkflowRun b = expect("options/restartableFromStageDisabled").go();
566+
567+
DisableRestartFromStageAction action = b.getParent().getAction(DisableRestartFromStageAction.class);
568+
assertNotNull(action);
569+
}
570+
571+
@Issue("JENKINS-54250")
572+
@Test
573+
public void verifyDisableRestartFromStageActionIsNotAdded() throws Exception {
574+
ExpectationsBuilder expectationsBuilder = expect("options/restartableFromStageEnabled");
575+
WorkflowRun run = expectationsBuilder.go();
576+
DisableRestartFromStageAction action = run.getParent().getAction(DisableRestartFromStageAction.class);
577+
578+
assertNull(action);
579+
580+
run.getParent().addAction(new DisableRestartFromStageAction());
581+
run = expectationsBuilder.go();
582+
action = run.getParent().getAction(DisableRestartFromStageAction.class);
583+
assertNull(action);
584+
}
559585

560586
private static class DummyPrivateKey extends BaseCredentials implements SSHUserPrivateKey, Serializable {
561587

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
pipeline {
2+
agent none
3+
options {
4+
disableRestartFromStage()
5+
}
6+
stages {
7+
stage("foo") {
8+
steps {
9+
echo "hello"
10+
}
11+
}
12+
}
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
pipeline {
2+
agent none
3+
stages {
4+
stage("foo") {
5+
steps {
6+
echo "hello"
7+
}
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)