From 163388032652bd9fb692770ef53650ff8d967cd2 Mon Sep 17 00:00:00 2001 From: Laure Bouquety Date: Mon, 5 Feb 2018 17:09:07 +0100 Subject: [PATCH 1/2] Change shell command --- .../plugins/kubernetes/ContainerTemplate.java | 24 +++++++++++++++++++ .../pipeline/ContainerExecDecorator.java | 13 +++++++++- .../kubernetes/pipeline/ContainerStep.java | 11 +++++++++ .../pipeline/ContainerStepExecution.java | 2 ++ .../KubernetesDeclarativeAgentScript.groovy | 2 +- .../kubernetes/pipeline/declarative.groovy | 2 +- 6 files changed, 51 insertions(+), 3 deletions(-) mode change 100755 => 100644 src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStep.java diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate.java index 30bba9a332..80fe48fd07 100644 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/ContainerTemplate.java @@ -4,6 +4,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import org.apache.commons.lang.StringUtils; import org.csanchez.jenkins.plugins.kubernetes.model.TemplateEnvVar; @@ -50,6 +52,7 @@ public class ContainerTemplate extends AbstractDescribableImpl envVars = new ArrayList<>(); private List ports = new ArrayList(); @@ -214,6 +217,18 @@ public void setResourceRequestCpu(String resourceRequestCpu) { this.resourceRequestCpu = resourceRequestCpu; } + public Map getAsArgs() { + Map argMap = new TreeMap<>(); + + argMap.put("name", name); + + if (!StringUtils.isEmpty(shell)) { + argMap.put("shell", shell); + } + + return argMap; + } + @Extension @Symbol("containerTemplate") public static class DescriptorImpl extends Descriptor { @@ -250,4 +265,13 @@ public String toString() { (livenessProbe == null ? "" : ", livenessProbe=" + livenessProbe) + '}'; } + + public String getShell() { + return shell; + } + + @DataBoundSetter + public void setShell(String shell) { + this.shell = shell; + } } diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java index f06bb7dcf9..45761c9ae5 100755 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java @@ -77,6 +77,7 @@ public class ContainerExecDecorator extends LauncherDecorator implements Seriali private static final String COOKIE_VAR = "JENKINS_SERVER_COOKIE"; private static final Logger LOGGER = Logger.getLogger(ContainerExecDecorator.class.getName()); + private static final String DEFAULT_SHELL="/bin/sh"; private transient KubernetesClient client; @@ -93,6 +94,7 @@ public class ContainerExecDecorator extends LauncherDecorator implements Seriali private EnvVars globalVars; private FilePath ws; private EnvVars rcEnvVars; + private String shell; public ContainerExecDecorator() { } @@ -105,6 +107,7 @@ public ContainerExecDecorator(KubernetesClient client, String podName, String co this.containerName = containerName; this.environmentExpander = environmentExpander; this.ws = ws; + this.shell = DEFAULT_SHELL; } @Deprecated @@ -196,6 +199,14 @@ public void setWs(FilePath ws) { this.ws = ws; } + public String getShell() { + return shell; + } + + public void setShell(String shell) { + this.shell = shell; + } + @Override public Launcher decorate(final Launcher launcher, final Node node) { return new Launcher.DecoratedLauncher(launcher) { @@ -309,7 +320,7 @@ public void onClose(int i, String s) { ExecWatch watch; try { - watch = execable.exec("/bin/sh"); + watch = execable.exec(shell); } catch (KubernetesClientException e) { if (e.getCause() instanceof InterruptedException) { throw new IOException("JENKINS-40825: interrupted while starting websocket connection", e); diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStep.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStep.java old mode 100755 new mode 100644 index 6974abac5f..fba493cbea --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStep.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStep.java @@ -8,6 +8,7 @@ import org.jenkinsci.plugins.workflow.steps.StepDescriptor; import org.jenkinsci.plugins.workflow.steps.StepExecution; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; import com.google.common.collect.ImmutableSet; @@ -21,6 +22,7 @@ public class ContainerStep extends Step implements Serializable { private static final long serialVersionUID = 5588861066775717487L; private final String name; + private String shell; @DataBoundConstructor public ContainerStep(String name) { @@ -31,6 +33,15 @@ public String getName() { return name; } + @DataBoundSetter + public void setShell(String shell){ + this.shell = shell; + } + + public String getShell() { + return shell; + } + @Override public StepExecution start(StepContext context) throws Exception { return new ContainerStepExecution(this, context); diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStepExecution.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStepExecution.java index 0c5f601859..0b6267495a 100755 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStepExecution.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStepExecution.java @@ -63,6 +63,7 @@ public void onResume() { public boolean start() throws Exception { LOGGER.log(Level.FINE, "Starting container step."); String containerName = step.getName(); + String shell = step.getShell(); KubernetesNodeContext nodeContext = new KubernetesNodeContext(getContext()); client = nodeContext.connectToCloud(); @@ -94,6 +95,7 @@ public boolean start() throws Exception { decorator.setWs(getContext().get(FilePath.class)); decorator.setGlobalVars(globalVars); decorator.setRunContextEnvVars(rcEnvVars); + decorator.setShell(shell); getContext().newBodyInvoker() .withContext(BodyInvoker .mergeLauncherDecorators(getContext().get(LauncherDecorator.class), decorator)) diff --git a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentScript.groovy b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentScript.groovy index d2433d5622..10c4461040 100644 --- a/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentScript.groovy +++ b/src/main/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesDeclarativeAgentScript.groovy @@ -50,7 +50,7 @@ public class KubernetesDeclarativeAgentScript extends DeclarativeAgentScript Date: Thu, 22 Feb 2018 09:17:25 +0100 Subject: [PATCH 2/2] Add test and docs on specifying a different shell command --- README.md | 16 ++++++++++++++++ .../pipeline/ContainerExecDecorator.java | 5 ++--- .../pipeline/KubernetesPipelineTest.java | 11 +++++++++++ .../kubernetes/pipeline/declarative.groovy | 9 ++++++++- .../pipeline/runInPodWithDifferentShell.groovy | 14 ++++++++++++++ 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/runInPodWithDifferentShell.groovy diff --git a/README.md b/README.md index 029b956aac..7244880783 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,22 @@ There might be cases, where you need to have the agent pod run inside a differen For example you may need the agent to run inside an `ephemeral` namespace for the sake of testing. For those cases you can explicitly configure a namespace either using the ui or the pipeline. +#### Specifying a different shell command other than /bin/sh + +By default, the shell command is /bin/sh. In some case, you would like to use another shell command like /bin/bash. + +```groovy +podTemplate(label: my-label) { + node(my-label) { + stage('Run specific shell') { + container(name:'mycontainer', shell:'/bin/bash') { + sh 'echo hello world' + } + } + } +} +``` + ## Container Configuration When configuring a container in a pipeline podTemplate the following options are available: diff --git a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java index 45761c9ae5..22d57455b2 100755 --- a/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java +++ b/src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerExecDecorator.java @@ -42,7 +42,6 @@ import org.csanchez.jenkins.plugins.kubernetes.pipeline.proc.CachedProc; import org.csanchez.jenkins.plugins.kubernetes.pipeline.proc.DeadProc; - import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.EnvVars; @@ -200,7 +199,7 @@ public void setWs(FilePath ws) { } public String getShell() { - return shell; + return shell == null? DEFAULT_SHELL:shell; } public void setShell(String shell) { @@ -320,7 +319,7 @@ public void onClose(int i, String s) { ExecWatch watch; try { - watch = execable.exec(shell); + watch = execable.exec(getShell()); } catch (KubernetesClientException e) { if (e.getCause() instanceof InterruptedException) { throw new IOException("JENKINS-40825: interrupted while starting websocket connection", e); diff --git a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java index 6aed35d77c..650de686a1 100644 --- a/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java +++ b/src/test/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/KubernetesPipelineTest.java @@ -40,6 +40,7 @@ import org.junit.rules.TemporaryFolder; import org.jvnet.hudson.test.JenkinsRuleNonLocalhost; +import hudson.model.Result; import io.fabric8.kubernetes.api.model.NamespaceBuilder; import io.fabric8.kubernetes.client.KubernetesClient; @@ -83,6 +84,16 @@ private boolean hasPodTemplateWithLabel(String label, List template .anyMatch(label::equals); } + @Test + public void runInPodWithDifferentShell() throws Exception { + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition(loadPipelineScript("runInPodWithDifferentShell.groovy"), true)); + WorkflowRun b = p.scheduleBuild2(0).waitForStart(); + assertNotNull(b); + r.assertBuildStatus(Result.FAILURE,r.waitForCompletion(b)); + r.assertLogContains("/bin/bash: no such file or directory", b); + } + @Test public void runInPodWithMultipleContainers() throws Exception { WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); diff --git a/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/declarative.groovy b/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/declarative.groovy index e33b55fa31..0fdf08e627 100644 --- a/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/declarative.groovy +++ b/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/declarative.groovy @@ -18,11 +18,18 @@ pipeline { steps { sh 'set' sh "echo OUTSIDE_CONTAINER_ENV_VAR = ${CONTAINER_ENV_VAR}" - container(name: 'maven', shell: '/bin/bash') { + container('maven') { sh 'echo INSIDE_CONTAINER_ENV_VAR = ${CONTAINER_ENV_VAR}' sh 'mvn -version' } } } + stage('Run maven with a different shell') { + steps { + container(name: 'maven', shell: '/bin/bash') { + sh 'mvn -version' + } + } + } } } diff --git a/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/runInPodWithDifferentShell.groovy b/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/runInPodWithDifferentShell.groovy new file mode 100644 index 0000000000..77e97f1262 --- /dev/null +++ b/src/test/resources/org/csanchez/jenkins/plugins/kubernetes/pipeline/runInPodWithDifferentShell.groovy @@ -0,0 +1,14 @@ +podTemplate(label: 'mypod', containers: [ + containerTemplate(name: 'busybox', image: 'busybox', ttyEnabled: true, command: '/bin/cat'), + ]) { + + node ('mypod') { + stage('Run') { + container(name:'busybox', shell: '/bin/bash') { + sh """ + echo "Run BusyBox shell" + """ + } + } + } +} \ No newline at end of file