diff --git a/src/main/java/org/jenkinsci/plugins/workflow/steps/EnvStep.java b/src/main/java/org/jenkinsci/plugins/workflow/steps/EnvStep.java index a6485c6c..3350d92c 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/steps/EnvStep.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/steps/EnvStep.java @@ -35,7 +35,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import net.sf.json.JSONObject; +import org.jenkinsci.plugins.structs.describable.CustomDescribableModel; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest2; @@ -104,7 +106,7 @@ private static final class ExpanderImpl extends EnvironmentExpander { } } - @Extension public static class DescriptorImpl extends StepDescriptor { + @Extension public static class DescriptorImpl extends StepDescriptor implements CustomDescribableModel { @Override public String getFunctionName() { return "withEnv"; @@ -152,11 +154,34 @@ private static final class ExpanderImpl extends EnvironmentExpander { } } return b.toString(); + } else if (overrides instanceof Map) { + return ((Map) overrides).keySet() + .stream() + .filter(e -> e instanceof String) + .map(Object::toString) + .collect(Collectors.joining(", ")); } else { return null; } } + @NonNull + @Override + public Map customInstantiate(@NonNull Map arguments) { + Map r = new HashMap<>(arguments); + final String key = "overrides"; + Object overrides = r.get(key); + if (overrides instanceof Map) { + r.put(key, toKeyValueList((Map) overrides)); + } + return r; + } + + private static List toKeyValueList(Map map) { + return map.entrySet().stream() + .map(m -> (String) m.getKey() + "=" + (m.getValue() == null ? "" : m.getValue().toString())) + .collect(Collectors.toList()); + } } } diff --git a/src/main/resources/org/jenkinsci/plugins/workflow/steps/EnvStep/help-overrides.html b/src/main/resources/org/jenkinsci/plugins/workflow/steps/EnvStep/help-overrides.html index 6a4a7625..dfd68b9e 100644 --- a/src/main/resources/org/jenkinsci/plugins/workflow/steps/EnvStep/help-overrides.html +++ b/src/main/resources/org/jenkinsci/plugins/workflow/steps/EnvStep/help-overrides.html @@ -1,6 +1,11 @@
+

A list of environment variables to set, each in the form VARIABLE=value or VARIABLE= to unset variables otherwise defined. You may also use the syntax PATH+WHATEVER=/something to prepend /something to $PATH. +

+

+ Environment variables to set can also be defined in a map, each entry of the form [VARIABLE: 'value']. +

diff --git a/src/main/resources/org/jenkinsci/plugins/workflow/steps/EnvStep/help.html b/src/main/resources/org/jenkinsci/plugins/workflow/steps/EnvStep/help.html index 980ca27e..8ab96e87 100644 --- a/src/main/resources/org/jenkinsci/plugins/workflow/steps/EnvStep/help.html +++ b/src/main/resources/org/jenkinsci/plugins/workflow/steps/EnvStep/help.html @@ -11,5 +11,14 @@ }

(Note that here we are using single quotes in Groovy, so the variable expansion is being done by the Bourne shell, not Jenkins.) +

Environment variables to be set may also be defined in a map. For example: +

+node {
+  withEnv([MYTOOL_HOME: '/usr/local/mytool']) {
+    sh '$MYTOOL_HOME/bin/start'
+  }
+}
+
+

See the documentation for the env singleton for more information on environment variables. diff --git a/src/test/java/org/jenkinsci/plugins/workflow/steps/EnvStepTest.java b/src/test/java/org/jenkinsci/plugins/workflow/steps/EnvStepTest.java index 41e41177..73f3855b 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/steps/EnvStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/steps/EnvStepTest.java @@ -149,6 +149,48 @@ public class EnvStepTest { }); } + @Test public void mapArguments() throws Throwable { + sessions.then(j -> { + WorkflowJob p = j.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition( + "node {\n" + + " withEnv(a: 1, b: 2, c: 'hello world', d: true, e: null) {\n" + + " echo(/a=$a b=$b c=$c d=$d e=${env.e}/)" + + " }\n" + + "}", true)); + WorkflowRun b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + j.assertLogContains("a=1 b=2 c=hello world d=true e=null", b); + List coreStepNodes = new DepthFirstScanner().filteredNodes( + b.getExecution(), + Predicates.and( + new NodeStepTypePredicate("withEnv"), + n -> n instanceof StepStartNode && !((StepStartNode) n).isBody())); + assertThat(coreStepNodes, Matchers.hasSize(1)); + assertEquals("a, b, c, d, e", ArgumentsAction.getStepArgumentsAsString(coreStepNodes.get(0))); + }); + } + + @Test public void mapArgumentsAsMap() throws Throwable { + sessions.then(j -> { + WorkflowJob p = j.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition( + "node {\n" + + " withEnv([A: 1, B: 2, C: 'hello world', D: true, E: null]) {\n" + + " echo(/A=$A B=$B C=$C D=$D E=${env.E}/)\n" + + " }\n" + + "}", true)); + WorkflowRun b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + j.assertLogContains("A=1 B=2 C=hello world D=true E=null", b); + List coreStepNodes = new DepthFirstScanner().filteredNodes( + b.getExecution(), + Predicates.and( + new NodeStepTypePredicate("withEnv"), + n -> n instanceof StepStartNode && !((StepStartNode) n).isBody())); + assertThat(coreStepNodes, Matchers.hasSize(1)); + assertEquals("A, B, C, D, E", ArgumentsAction.getStepArgumentsAsString(coreStepNodes.get(0))); + }); + } + @Test public void configRoundTrip() throws Throwable { sessions.then(j -> { configRoundTrip(Collections.emptyList(), j);