Skip to content

Commit

Permalink
[JENKINS-54250] Disable restarting stages in jenkinsfile optionally (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
pavlovic-ivan committed Mar 17, 2023
1 parent 31fd5b9 commit 821aabd
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 1 deletion.
Expand Up @@ -38,7 +38,9 @@ import hudson.ExtensionList
import hudson.model.*
import hudson.util.Secret
import hudson.triggers.Trigger
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.DisableRestartFromStageAction
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTStage
import org.jenkinsci.plugins.pipeline.modeldefinition.options.impl.DisableRestartFromStage
import org.jenkinsci.plugins.pipeline.modeldefinition.parser.JSONParser

import java.util.function.Function
Expand Down Expand Up @@ -622,6 +624,13 @@ class Utils {
isJobChanged = true
}

DisableRestartFromStage declaredDisableRestartFromStageOption = (DisableRestartFromStage) rawOptions.find { it instanceof DisableRestartFromStage }
DisableRestartFromStageAction currentRestartFromStageAction = j.getAction(DisableRestartFromStageAction.class);
if(currentRestartFromStageAction == null && declaredDisableRestartFromStageOption != null){
j.addAction(new DisableRestartFromStageAction())
} else if(currentRestartFromStageAction != null && declaredDisableRestartFromStageOption == null) {
j.removeAction(currentRestartFromStageAction)
}

// If there are any triggers update them if needed
// It would be cool to only add or remove individual triggers,
Expand Down
@@ -0,0 +1,8 @@
package org.jenkinsci.plugins.pipeline.modeldefinition.actions;

import hudson.model.InvisibleAction;

public class DisableRestartFromStageAction extends InvisibleAction {

public DisableRestartFromStageAction(){}
}
Expand Up @@ -39,9 +39,11 @@
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils;
import org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTStage;
import org.jenkinsci.plugins.pipeline.modeldefinition.causes.RestartDeclarativePipelineCause;
import org.jenkinsci.plugins.pipeline.modeldefinition.options.impl.DisableRestartFromStage;
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
Expand Down Expand Up @@ -103,7 +105,8 @@ public boolean isRestartEnabled() {
!run.isBuilding() &&
run.hasPermission(Item.BUILD) &&
run.getParent().isBuildable() &&
getExecution() != null;
getExecution() != null &&
run.getParent().getAction(DisableRestartFromStageAction.class) == null;
}

public Api getApi() {
Expand Down
@@ -0,0 +1,28 @@
package org.jenkinsci.plugins.pipeline.modeldefinition.options.impl;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import org.jenkinsci.Symbol;
import org.jenkinsci.plugins.pipeline.modeldefinition.options.DeclarativeOption;
import org.jenkinsci.plugins.pipeline.modeldefinition.options.DeclarativeOptionDescriptor;
import org.kohsuke.stapler.DataBoundConstructor;

public class DisableRestartFromStage extends DeclarativeOption {

@DataBoundConstructor
public DisableRestartFromStage() {}

@Extension @Symbol("disableRestartFromStage")
public static class DescriptorImpl extends DeclarativeOptionDescriptor {
@Override
@NonNull
public String getDisplayName() {
return "Disable the ability to restart this Pipeline from a specific stage";
}

@Override
public boolean canUseInStage() {
return false;
}
}
}
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ The MIT License
~
~ Copyright (c) 2018, 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.
-->

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
</j:jelly>
@@ -0,0 +1,27 @@
<!--
~ 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.
-->

<p>
If specified, Restart From Stage button will not be displayed.
</p>
Expand Up @@ -40,6 +40,9 @@
import jenkins.model.BuildDiscarder;
import jenkins.model.BuildDiscarderProperty;
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.DeclarativeJobPropertyTrackerAction;
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.DisableRestartFromStageAction;
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.ExecutionModelAction;
import org.jenkinsci.plugins.pipeline.modeldefinition.options.impl.DisableRestartFromStage;
import org.jenkinsci.plugins.pipeline.modeldefinition.parser.RuntimeASTTransformer;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode;
Expand Down Expand Up @@ -556,6 +559,29 @@ public void sameJobPropertiesNotOverride() throws Exception {
assertSame(strategy, strategy2);
}

@Issue("JENKINS-54250")
@Test
public void verifyDisableRestartFromStageActionIsAdded() throws Exception {
WorkflowRun b = expect("options/restartableFromStageDisabled").go();

DisableRestartFromStageAction action = b.getParent().getAction(DisableRestartFromStageAction.class);
assertNotNull(action);
}

@Issue("JENKINS-54250")
@Test
public void verifyDisableRestartFromStageActionIsNotAdded() throws Exception {
ExpectationsBuilder expectationsBuilder = expect("options/restartableFromStageEnabled");
WorkflowRun run = expectationsBuilder.go();
DisableRestartFromStageAction action = run.getParent().getAction(DisableRestartFromStageAction.class);

assertNull(action);

run.getParent().addAction(new DisableRestartFromStageAction());
run = expectationsBuilder.go();
action = run.getParent().getAction(DisableRestartFromStageAction.class);
assertNull(action);
}

private static class DummyPrivateKey extends BaseCredentials implements SSHUserPrivateKey, Serializable {

Expand Down
@@ -0,0 +1,13 @@
pipeline {
agent none
options {
disableRestartFromStage()
}
stages {
stage("foo") {
steps {
echo "hello"
}
}
}
}
@@ -0,0 +1,10 @@
pipeline {
agent none
stages {
stage("foo") {
steps {
echo "hello"
}
}
}
}

0 comments on commit 821aabd

Please sign in to comment.