diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java index 60a6d98290..bedf5e2c97 100644 --- a/core/src/main/java/hudson/EnvVars.java +++ b/core/src/main/java/hudson/EnvVars.java @@ -88,6 +88,15 @@ public class EnvVars extends TreeMap { } } + /** + * Resolves environment variables against each other. + */ + public static void resolve(Map env) { + for (Map.Entry entry: env.entrySet()) { + entry.setValue(Util.replaceMacro(entry.getValue(), env)); + } + } + /** * Takes a string that looks like "a=b" and adds that to this map. */ @@ -130,4 +139,5 @@ public class EnvVars extends TreeMap { * variable of the slave agent. */ public static final Map masterEnvVars = new EnvVars(System.getenv()); + } diff --git a/core/src/main/java/hudson/Launcher.java b/core/src/main/java/hudson/Launcher.java index d130ac5b33..b584c0ed13 100644 --- a/core/src/main/java/hudson/Launcher.java +++ b/core/src/main/java/hudson/Launcher.java @@ -340,7 +340,15 @@ public abstract class Launcher { } private Proc createLocalProc(String[] cmd, String[] env, InputStream in, OutputStream out, FilePath workDir) throws IOException { - return new LocalProc(cmd, Util.mapToEnv(inherit(env)), in, out, toFile(workDir)); + Map jobEnv = inherit(env); + + // replace variables in command line + String[] jobCmd = new String[cmd.length]; + for ( int idx = 0 ; idx < jobCmd.length; idx++ ) { + jobCmd[idx] = Util.replaceMacro(cmd[idx],jobEnv); + } + + return new LocalProc(jobCmd, Util.mapToEnv(jobEnv), in, out, toFile(workDir)); } private File toFile(FilePath f) { diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index a2c76b57a7..b93f1f9039 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -107,7 +107,7 @@ public class Util { public static String replaceMacro(String s, Map properties) { return replaceMacro(s,new VariableResolver.ByMap(properties)); } - + /** * Replaces the occurrence of '$key' by resolver.get('key'). * @@ -115,6 +115,10 @@ public class Util { * Unlike shell, undefined variables are left as-is (this behavior is the same as Ant.) */ public static String replaceMacro(String s, VariableResolver resolver) { + if (s == null) { + return null; + } + int idx=0; while(true) { Matcher m = VARIABLE.matcher(s); diff --git a/core/src/main/java/hudson/maven/MavenModuleSetBuild.java b/core/src/main/java/hudson/maven/MavenModuleSetBuild.java index d06ed29735..8f2fafab13 100644 --- a/core/src/main/java/hudson/maven/MavenModuleSetBuild.java +++ b/core/src/main/java/hudson/maven/MavenModuleSetBuild.java @@ -25,11 +25,9 @@ package hudson.maven; import hudson.AbortException; import hudson.FilePath; -import hudson.FilePath.FileCallable; import hudson.Launcher; import hudson.Util; -import hudson.tasks.BuildWrapper; -import hudson.tasks.Maven.MavenInstallation; +import hudson.FilePath.FileCallable; import hudson.maven.MavenBuild.ProxyImpl2; import hudson.maven.reporters.MavenFingerprinter; import hudson.model.AbstractBuild; @@ -37,25 +35,18 @@ import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.Build; import hudson.model.BuildListener; +import hudson.model.Environment; import hudson.model.Fingerprint; import hudson.model.Hudson; -import hudson.model.Result; import hudson.model.ParametersAction; +import hudson.model.Result; import hudson.model.Cause.UpstreamCause; import hudson.remoting.Channel; import hudson.remoting.VirtualChannel; +import hudson.tasks.BuildWrapper; +import hudson.tasks.Maven.MavenInstallation; import hudson.util.ArgumentListBuilder; import hudson.util.StreamTaskListener; -import org.apache.maven.BuildFailureException; -import org.apache.maven.embedder.MavenEmbedderException; -import org.apache.maven.execution.MavenSession; -import org.apache.maven.execution.ReactorManager; -import org.apache.maven.lifecycle.LifecycleExecutionException; -import org.apache.maven.monitor.event.EventDispatcher; -import org.apache.maven.project.MavenProject; -import org.apache.maven.project.ProjectBuildingException; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; import java.io.File; import java.io.IOException; @@ -68,12 +59,23 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; import java.util.Properties; +import java.util.Set; +import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; +import org.apache.maven.BuildFailureException; +import org.apache.maven.embedder.MavenEmbedderException; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.execution.ReactorManager; +import org.apache.maven.lifecycle.LifecycleExecutionException; +import org.apache.maven.monitor.event.EventDispatcher; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuildingException; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; + /** * {@link Build} for {@link MavenModuleSet}. * @@ -322,7 +324,7 @@ public final class MavenModuleSetBuild extends AbstractBuild(); + buildEnvironments = new ArrayList(); for( BuildWrapper w : wrappers) { BuildWrapper.Environment e = w.setUp((AbstractBuild)MavenModuleSetBuild.this, launcher, listener); if(e==null) diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index 47da8fdf7b..f9c5ea99f5 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -23,6 +23,7 @@ */ package hudson.model; +import hudson.EnvVars; import hudson.Functions; import hudson.Launcher; import hudson.Util; @@ -117,7 +118,7 @@ public abstract class AbstractBuild

,R extends Abs * During the build this field remembers {@link BuildWrapper.Environment}s created by * {@link BuildWrapper}. This design is bit ugly but forced due to compatibility. */ - protected transient List buildEnvironments; + protected transient List buildEnvironments; protected AbstractBuild(P job) throws IOException { super(job); @@ -434,13 +435,15 @@ public abstract class AbstractBuild

,R extends Abs jdk.buildEnvVars(env); project.getScm().buildEnvVars(this,env); + if(buildEnvironments!=null) + for (Environment e : buildEnvironments) + e.buildEnvVars(env); + ParametersAction parameters = getAction(ParametersAction.class); if (parameters != null) - parameters.buildEnvVars(this,env); - - if(buildEnvironments!=null) - for (BuildWrapper.Environment e : buildEnvironments) - e.buildEnvVars(env); + parameters.buildEnvVars(this,env); + + EnvVars.resolve(env); return env; } diff --git a/core/src/main/java/hudson/model/Build.java b/core/src/main/java/hudson/model/Build.java index b031f417e2..a53c1f151a 100644 --- a/core/src/main/java/hudson/model/Build.java +++ b/core/src/main/java/hudson/model/Build.java @@ -23,20 +23,20 @@ */ package hudson.model; -import hudson.tasks.BuildStep; -import hudson.tasks.BuildWrapper; -import hudson.tasks.BuildWrapper.Environment; -import hudson.tasks.Builder; -import hudson.tasks.BuildTrigger; -import hudson.triggers.SCMTrigger; import hudson.Launcher; +import hudson.slaves.NodeProperty; +import hudson.tasks.BuildStep; +import hudson.tasks.BuildTrigger; +import hudson.tasks.BuildWrapper; +import hudson.tasks.Builder; +import hudson.triggers.SCMTrigger; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; -import java.util.List; import java.util.Collection; +import java.util.List; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; @@ -122,6 +122,9 @@ public abstract class Build

,B extends Build> buildEnvironments = new ArrayList(); try { List wrappers = new ArrayList(project.getBuildWrappers().values()); + + Node node = Computer.currentComputer().getNode(); + NodeProperty.setup(node, buildEnvironments, Build.this, launcher, listener); ParametersAction parameters = getAction(ParametersAction.class); if (parameters != null) diff --git a/core/src/main/java/hudson/model/Environment.java b/core/src/main/java/hudson/model/Environment.java new file mode 100644 index 0000000000..284949a7e5 --- /dev/null +++ b/core/src/main/java/hudson/model/Environment.java @@ -0,0 +1,74 @@ +package hudson.model; + +import hudson.Launcher; +import hudson.Util; +import hudson.model.AbstractBuild; +import hudson.model.Build; +import hudson.model.BuildListener; +import hudson.slaves.NodeProperty; + +import java.io.IOException; +import java.util.Map; + +/** + * Represents the environment set up by + * {@link NodeProperty#setUp(Build,Launcher,BuildListener)}. + * + *

+ * It is expected that the subclasses of {@link NodeProperty} extends this class + * and implements its own semantics. + */ +public abstract class Environment { + /** + * Adds environmental variables for the builds to the given map. + * + *

+ * If the {@link Environment} object wants to pass in information to the + * build that runs, it can do so by exporting additional environment + * variables to the map. + * + *

+ * When this method is invoked, the map already contains the current + * "planned export" list. + * + * @param env + * never null. + */ + public void buildEnvVars(Map env) { + // no-op by default + } + + /** + * Runs after the {@link Builder} completes, and performs a tear down. + * + *

+ * This method is invoked even when the build failed, so that the clean up + * operation can be performed regardless of the build result (for example, + * you'll want to stop application server even if a build fails.) + * + * @param build + * The same {@link Build} object given to the set up method. + * @param listener + * The same {@link BuildListener} object given to the set up + * method. + * @return true if the build can continue, false if there was an error and + * the build needs to be aborted. + * @throws IOException + * terminates the build abnormally. Hudson will handle the + * exception and reports a nice error message. + */ + public boolean tearDown(AbstractBuild build, BuildListener listener) + throws IOException, InterruptedException { + return true; + } + + public static Environment create(final Map envVars) { + return new Environment() { + @Override + public void buildEnvVars(Map env) { + env.putAll(envVars); + } + }; + } + +} \ No newline at end of file diff --git a/core/src/main/java/hudson/model/EnvironmentSpecific.java b/core/src/main/java/hudson/model/EnvironmentSpecific.java new file mode 100644 index 0000000000..b1554cdc35 --- /dev/null +++ b/core/src/main/java/hudson/model/EnvironmentSpecific.java @@ -0,0 +1,19 @@ +package hudson.model; + +import java.util.Map; + +/** + * Represents any concept that can be adapted for a certain environment. + * + * Mainly for documentation purposes. + * + * @param + */ +public interface EnvironmentSpecific { + + /** + * Returns a specialized copy of T for functioning in the given environment. + */ + T forEnvironment(Map environment); + +} diff --git a/core/src/main/java/hudson/model/Hudson.java b/core/src/main/java/hudson/model/Hudson.java index 14e5e51fa1..aaaa2a66af 100644 --- a/core/src/main/java/hudson/model/Hudson.java +++ b/core/src/main/java/hudson/model/Hudson.java @@ -72,6 +72,8 @@ import hudson.security.PermissionGroup; import hudson.security.SecurityMode; import hudson.security.SecurityRealm; import hudson.slaves.ComputerListener; +import hudson.slaves.NodeProperty; +import hudson.slaves.NodePropertyDescriptor; import hudson.slaves.RetentionStrategy; import hudson.slaves.NodeList; import hudson.slaves.Cloud; @@ -387,6 +389,11 @@ public final class Hudson extends Node implements ItemGroup, Stapl */ private transient final List actions = new CopyOnWriteArrayList(); + /** + * List of global/master node properties + */ + private DescribableList,NodePropertyDescriptor> nodeProperties = new DescribableList,NodePropertyDescriptor>(this); + /** * {@link AdministrativeMonitor}s installed on this system. * @@ -1185,6 +1192,14 @@ public final class Hudson extends Node implements ItemGroup, Stapl trimLabels(); save(); } + + public DescribableList, NodePropertyDescriptor> getNodeProperties() { + return nodeProperties; + } + + public void setNodeProperties(Collection> nodeProperties) throws IOException { + this.nodeProperties.replaceBy(nodeProperties); + } /** * Resets all labels and remove invalid ones. @@ -1973,6 +1988,8 @@ public final class Hudson extends Node implements ItemGroup, Stapl clouds.rebuildHetero(req,json, Cloud.ALL, "cloud"); + nodeProperties.rebuild(req, json.getJSONObject("nodeProperties"), getNodePropertyDescriptors()); + save(); if(result) rsp.sendRedirect(req.getContextPath()+'/'); // go to the top page diff --git a/core/src/main/java/hudson/model/Node.java b/core/src/main/java/hudson/model/Node.java index 287e2844d9..53cd385ba2 100644 --- a/core/src/main/java/hudson/model/Node.java +++ b/core/src/main/java/hudson/model/Node.java @@ -23,23 +23,31 @@ */ package hudson.model; -import hudson.FilePath; -import hudson.Launcher; import hudson.ExtensionPoint; +import hudson.FilePath; import hudson.FileSystemProvisioner; +import hudson.Launcher; +import hudson.node_monitors.NodeMonitor; import hudson.remoting.VirtualChannel; +import hudson.security.ACL; import hudson.security.AccessControlled; import hudson.security.Permission; -import hudson.security.ACL; +import hudson.slaves.EnvironmentVariablesNodeProperty; import hudson.slaves.NodeDescriptor; -import hudson.node_monitors.NodeMonitor; +import hudson.slaves.NodeProperty; +import hudson.slaves.NodePropertyDescriptor; import hudson.util.ClockDifference; +import hudson.util.DescribableList; import hudson.util.EnumConverter; -import org.kohsuke.stapler.Stapler; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Set; +import org.kohsuke.stapler.Stapler; + /** * Base type of Hudson slaves (although in practice, you probably extend {@link Slave} to define a new slave type.) * @@ -52,6 +60,9 @@ import java.util.Set; */ public abstract class Node extends AbstractModelObject implements Describable, ExtensionPoint, AccessControlled { + public static final List PROPERTIES = Descriptor + .toList((NodePropertyDescriptor) EnvironmentVariablesNodeProperty.DESCRIPTOR); + public String getDisplayName() { return getNodeName(); // default implementation } @@ -128,7 +139,7 @@ public abstract class Node extends AbstractModelObject implements Describable getAssignedLabels(); - /** + /* * Returns the possibly empty set of labels that it has been determined as supported by this node. * @see hudson.tasks.LabelFinder */ @@ -182,6 +193,30 @@ public abstract class Node extends AbstractModelObject implements Describable, NodePropertyDescriptor> getNodeProperties(); + + public abstract void setNodeProperties(Collection> nodeProperties) throws IOException; + + public List getNodePropertyDescriptors() { + List result = new ArrayList(); + for (NodePropertyDescriptor npd : PROPERTIES) { + if (npd.isApplicable(getClass())) { + result.add(npd); + } + } + return result; + } + + public > N getNodeProperty(Class clazz) { + for (NodeProperty p: getNodeProperties()) { + if (clazz.isInstance(p)) { + return clazz.cast(p); + } + } + return null; + } + public ACL getACL() { return Hudson.getInstance().getAuthorizationStrategy().getACL(this); } diff --git a/core/src/main/java/hudson/model/ParametersDefinitionProperty.java b/core/src/main/java/hudson/model/ParametersDefinitionProperty.java index e15033d312..62904c49b7 100644 --- a/core/src/main/java/hudson/model/ParametersDefinitionProperty.java +++ b/core/src/main/java/hudson/model/ParametersDefinitionProperty.java @@ -25,6 +25,7 @@ package hudson.model; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.servlet.ServletException; @@ -51,6 +52,10 @@ public class ParametersDefinitionProperty extends JobProperty getOwner() { return owner; } diff --git a/core/src/main/java/hudson/model/Slave.java b/core/src/main/java/hudson/model/Slave.java index a058690985..ee904fa870 100644 --- a/core/src/main/java/hudson/model/Slave.java +++ b/core/src/main/java/hudson/model/Slave.java @@ -25,32 +25,49 @@ package hudson.model; import hudson.FilePath; import hudson.Launcher; -import hudson.Launcher.RemoteLauncher; import hudson.Util; -import hudson.slaves.*; +import hudson.Launcher.RemoteLauncher; import hudson.model.Descriptor.FormException; import hudson.remoting.Callable; import hudson.remoting.VirtualChannel; +import hudson.slaves.CommandLauncher; +import hudson.slaves.ComputerLauncher; +import hudson.slaves.DumbSlave; +import hudson.slaves.JNLPLauncher; +import hudson.slaves.NodeDescriptor; +import hudson.slaves.NodeProperties; +import hudson.slaves.NodeProperty; +import hudson.slaves.NodePropertyDescriptor; +import hudson.slaves.RetentionStrategy; +import hudson.slaves.SlaveComputer; import hudson.tasks.DynamicLabeler; import hudson.tasks.LabelFinder; import hudson.util.ClockDifference; +import hudson.util.DescribableList; import hudson.util.FormFieldValidator; import hudson.util.FormFieldValidator.NonNegativeInteger; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.QueryParameter; -import org.apache.commons.io.IOUtils; -import javax.servlet.ServletException; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; -import java.net.MalformedURLException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.servlet.ServletException; + +import org.apache.commons.io.IOUtils; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; /** * Information about a Hudson slave node. @@ -105,6 +122,8 @@ public abstract class Slave extends Node implements Serializable { * Whitespace-separated labels. */ private String label=""; + + private final DescribableList,NodePropertyDescriptor> nodeProperties = new DescribableList,NodePropertyDescriptor>(Hudson.getInstance()); /** * Lazily computed set of labels from {@link #label}. @@ -116,12 +135,18 @@ public abstract class Slave extends Node implements Serializable { @DataBoundConstructor public Slave(String name, String nodeDescription, String remoteFS, String numExecutors, - Mode mode, String label, ComputerLauncher launcher, RetentionStrategy retentionStrategy) throws FormException { - this(name,nodeDescription,remoteFS,Util.tryParseNumber(numExecutors, 1).intValue(),mode,label,launcher,retentionStrategy); + Mode mode, String label, ComputerLauncher launcher, RetentionStrategy retentionStrategy, List> nodeProperties) throws FormException, IOException { + this(name,nodeDescription,remoteFS,Util.tryParseNumber(numExecutors, 1).intValue(),mode,label,launcher,retentionStrategy, nodeProperties); } + @Deprecated public Slave(String name, String nodeDescription, String remoteFS, int numExecutors, - Mode mode, String label, ComputerLauncher launcher, RetentionStrategy retentionStrategy) throws FormException { + Mode mode, String label, ComputerLauncher launcher, RetentionStrategy retentionStrategy) throws FormException, IOException { + this(name, nodeDescription, remoteFS, numExecutors, mode, label, launcher, retentionStrategy, new ArrayList()); + } + + public Slave(String name, String nodeDescription, String remoteFS, int numExecutors, + Mode mode, String label, ComputerLauncher launcher, RetentionStrategy retentionStrategy, List> nodeProperties) throws FormException, IOException { this.name = name; this.description = nodeDescription; this.numExecutors = numExecutors; @@ -131,6 +156,8 @@ public abstract class Slave extends Node implements Serializable { this.launcher = launcher; this.retentionStrategy = retentionStrategy; getAssignedLabels(); // compute labels now + + this.nodeProperties.replaceBy(nodeProperties); if (name.equals("")) throw new FormException(Messages.Slave_InvalidConfig_NoName(), null); @@ -184,6 +211,14 @@ public abstract class Slave extends Node implements Serializable { this.mode = mode; } + public DescribableList, NodePropertyDescriptor> getNodeProperties() { + return nodeProperties; + } + + public void setNodeProperties(Collection> nodeProperties) throws IOException { + this.nodeProperties.replaceBy(nodeProperties); + } + public RetentionStrategy getRetentionStrategy() { return retentionStrategy == null ? RetentionStrategy.Always.INSTANCE : retentionStrategy; } diff --git a/core/src/main/java/hudson/slaves/CommandLauncher.java b/core/src/main/java/hudson/slaves/CommandLauncher.java index d67e1c720b..f917c50ce6 100644 --- a/core/src/main/java/hudson/slaves/CommandLauncher.java +++ b/core/src/main/java/hudson/slaves/CommandLauncher.java @@ -23,24 +23,27 @@ */ package hudson.slaves; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; -import org.kohsuke.stapler.QueryParameter; +import hudson.EnvVars; +import hudson.Util; import hudson.model.Descriptor; -import hudson.util.StreamTaskListener; +import hudson.remoting.Channel; +import hudson.util.FormFieldValidator; import hudson.util.ProcessTreeKiller; import hudson.util.StreamCopyThread; -import hudson.util.FormFieldValidator; -import hudson.Util; -import hudson.EnvVars; -import hudson.remoting.Channel; +import hudson.util.StreamTaskListener; -import javax.servlet.ServletException; +import java.io.IOException; import java.util.Date; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; -import java.io.IOException; + +import javax.servlet.ServletException; + +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; /** * {@link ComputerLauncher} through a remote login mechanism like ssh/rsh. @@ -54,11 +57,21 @@ public class CommandLauncher extends ComputerLauncher { * Command line to launch the agent, like * "ssh myslave java -jar /path/to/hudson-remoting.jar" */ - private String agentCommand; + private final String agentCommand; + + /** + * Optional environment variables to add to the current environment + */ + private final Map env; @DataBoundConstructor public CommandLauncher(String command) { - this.agentCommand = command; + this(command, null); + } + + public CommandLauncher(String command, Map env) { + this.agentCommand = command; + this.env = env; } public String getCommand() { @@ -93,6 +106,11 @@ public class CommandLauncher extends ComputerLauncher { ProcessBuilder pb = new ProcessBuilder(Util.tokenize(getCommand())); final EnvVars cookie = _cookie = ProcessTreeKiller.createCookie(); pb.environment().putAll(cookie); + + if (env != null) { + pb.environment().putAll(env); + } + final Process proc = _proc = pb.start(); // capture error information from stderr. this will terminate itself diff --git a/core/src/main/java/hudson/slaves/DumbSlave.java b/core/src/main/java/hudson/slaves/DumbSlave.java index c343d0da21..53bbdd3bc2 100644 --- a/core/src/main/java/hudson/slaves/DumbSlave.java +++ b/core/src/main/java/hudson/slaves/DumbSlave.java @@ -23,8 +23,13 @@ */ package hudson.slaves; -import hudson.model.Descriptor.FormException; import hudson.model.Slave; +import hudson.model.Descriptor.FormException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + import org.kohsuke.stapler.DataBoundConstructor; /** @@ -34,9 +39,15 @@ import org.kohsuke.stapler.DataBoundConstructor; * @author Kohsuke Kawaguchi */ public final class DumbSlave extends Slave { + + @Deprecated + public DumbSlave(String name, String nodeDescription, String remoteFS, String numExecutors, Mode mode, String label, ComputerLauncher launcher, RetentionStrategy retentionStrategy) throws FormException, IOException { + this(name, nodeDescription, remoteFS, numExecutors, mode, label, launcher, retentionStrategy, new ArrayList()); + } + @DataBoundConstructor - public DumbSlave(String name, String nodeDescription, String remoteFS, String numExecutors, Mode mode, String label, ComputerLauncher launcher, RetentionStrategy retentionStrategy) throws FormException { - super(name, nodeDescription, remoteFS, numExecutors, mode, label, launcher, retentionStrategy); + public DumbSlave(String name, String nodeDescription, String remoteFS, String numExecutors, Mode mode, String label, ComputerLauncher launcher, RetentionStrategy retentionStrategy, List> nodeProperties) throws IOException, FormException { + super(name, nodeDescription, remoteFS, numExecutors, mode, label, launcher, retentionStrategy, nodeProperties); } public DescriptorImpl getDescriptor() { diff --git a/core/src/main/java/hudson/slaves/EnvironmentVariablesNodeProperty.java b/core/src/main/java/hudson/slaves/EnvironmentVariablesNodeProperty.java new file mode 100644 index 0000000000..02ca758f4d --- /dev/null +++ b/core/src/main/java/hudson/slaves/EnvironmentVariablesNodeProperty.java @@ -0,0 +1,86 @@ +package hudson.slaves; + +import hudson.Launcher; +import hudson.model.*; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.Stapler; + +public class EnvironmentVariablesNodeProperty extends NodeProperty { + + /** + * Slave-specific environment variables + */ + private final Map envVars; + + @DataBoundConstructor + public EnvironmentVariablesNodeProperty(List env) { + this.envVars = toMap(env); + } + + public EnvironmentVariablesNodeProperty(Entry... env) { + this(Arrays.asList(env)); + } + + public Map getEnvVars() { + return envVars; + } + + @Override + public Environment setUp(AbstractBuild build, Launcher launcher, + BuildListener listener) throws IOException, InterruptedException { + return Environment.create(envVars); + } + + public NodePropertyDescriptor getDescriptor() { + return DESCRIPTOR; + } + + public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); + + public static class DescriptorImpl extends NodePropertyDescriptor { + + @Override + public String getDisplayName() { + return "Environment variables"; + } + + public String getHelpPage() { + // yes, I know this is a hack. + ComputerSet object = Stapler.getCurrentRequest().findAncestorObject(ComputerSet.class); + if (object != null) { + // we're on a node configuration page, show show that help page + return "/help/system-config/nodeEnvironmentVariables.html"; + } else { + // show the help for the global config page + return "/help/system-config/globalEnvironmentVariables.html"; + } + } + + } + + public static class Entry { + public String key, value; + + @DataBoundConstructor + public Entry(String key, String value) { + this.key = key; + this.value = value; + } + } + + private static Map toMap(List entries) { + Map map = new HashMap(); + for (Entry entry: entries) { + map.put(entry.key,entry.value); + } + return map; + } + +} diff --git a/core/src/main/java/hudson/slaves/NodeProperties.java b/core/src/main/java/hudson/slaves/NodeProperties.java new file mode 100644 index 0000000000..13785f88d4 --- /dev/null +++ b/core/src/main/java/hudson/slaves/NodeProperties.java @@ -0,0 +1,27 @@ +package hudson.slaves; + +import hudson.model.Descriptor; +import hudson.model.Node; + +import java.util.ArrayList; +import java.util.List; + +public class NodeProperties { + public static final List PROPERTIES = Descriptor + .toList((NodePropertyDescriptor) EnvironmentVariablesNodeProperty.DESCRIPTOR); + + /** + * List up all {@link NodePropertyDescriptor}s that are applicable for the + * given project. + */ + public static List getFor(Node node) { + List result = new ArrayList(); + for (NodePropertyDescriptor npd : PROPERTIES) { + if (npd.isApplicable(node.getClass())) { + result.add(npd); + } + } + return result; + } + +} diff --git a/core/src/main/java/hudson/slaves/NodeProperty.java b/core/src/main/java/hudson/slaves/NodeProperty.java new file mode 100644 index 0000000000..c91036a033 --- /dev/null +++ b/core/src/main/java/hudson/slaves/NodeProperty.java @@ -0,0 +1,67 @@ +package hudson.slaves; + +import hudson.ExtensionPoint; +import hudson.Launcher; +import hudson.model.AbstractBuild; +import hudson.model.BuildListener; +import hudson.model.Describable; +import hudson.model.Environment; +import hudson.model.Hudson; +import hudson.model.Node; +import hudson.tasks.Builder; + +import java.io.IOException; +import java.util.List; + +public abstract class NodeProperty implements Describable>, ExtensionPoint { + + public abstract NodePropertyDescriptor getDescriptor(); + + protected transient N node; + + protected void setNode(N node) { this.node = node; } + + /** + * Runs before the {@link Builder} runs, and performs a set up. Can contribute additional properties + * to the environment. + * + * @param build + * The build in progress for which an {@link Environment} object is created. + * Never null. + * @param launcher + * This launcher can be used to launch processes for this build. + * If the build runs remotely, launcher will also run a job on that remote machine. + * Never null. + * @param listener + * Can be used to send any message. + * @return + * non-null if the build can continue, null if there was an error + * and the build needs to be aborted. + * @throws IOException + * terminates the build abnormally. Hudson will handle the exception + * and reports a nice error message. + */ + public Environment setUp( AbstractBuild build, Launcher launcher, BuildListener listener ) throws IOException, InterruptedException { + return new Environment() {}; + } + + public static void setup(Node node, List buildEnvironments, AbstractBuild build, Launcher launcher, BuildListener listener) + throws IOException, InterruptedException { + if (node != Hudson.getInstance()) { + for (NodeProperty nodeProperty: Hudson.getInstance().getNodeProperties()) { + Environment environment = nodeProperty.setUp(build, launcher, listener); + if (environment != null) { + buildEnvironments.add(environment); + } + } + } + + for (NodeProperty nodeProperty: node.getNodeProperties()) { + Environment environment = nodeProperty.setUp(build, launcher, listener); + if (environment != null) { + buildEnvironments.add(environment); + } + } + } + +} diff --git a/core/src/main/java/hudson/slaves/NodePropertyDescriptor.java b/core/src/main/java/hudson/slaves/NodePropertyDescriptor.java new file mode 100644 index 0000000000..ae387d0a62 --- /dev/null +++ b/core/src/main/java/hudson/slaves/NodePropertyDescriptor.java @@ -0,0 +1,38 @@ +package hudson.slaves; + +import hudson.model.Descriptor; +import hudson.model.Node; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import org.jvnet.tiger_types.Types; + +public abstract class NodePropertyDescriptor extends Descriptor> { + + protected NodePropertyDescriptor() {} + + /** + * Returns true if this {@link NodeProperty} type is applicable to the + * given job type. + * + *

+ * The default implementation of this method checks if the given node type is assignable to 'N' of + * {@link NodeProperty}<N>, but subtypes can extend this to change this behavior. + * + * @return + * true to indicate applicable, in which case the property will be + * displayed in the configuration screen of this node. + */ + public boolean isApplicable(Class nodeType) { + Type parameterization = Types.getBaseClass(clazz, NodeProperty.class); + if (parameterization instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) parameterization; + Class applicable = Types.erasure(Types.getTypeArgument(pt, 0)); + return applicable.isAssignableFrom(nodeType); + } else { + throw new AssertionError(clazz+" doesn't properly parameterize NodeProperty. The isApplicable() method must be overriden."); + } + } + +} diff --git a/core/src/main/java/hudson/tasks/Ant.java b/core/src/main/java/hudson/tasks/Ant.java index 0e87c698cc..12d6f3c1f5 100644 --- a/core/src/main/java/hudson/tasks/Ant.java +++ b/core/src/main/java/hudson/tasks/Ant.java @@ -32,6 +32,7 @@ import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Descriptor; +import hudson.model.EnvironmentSpecific; import hudson.model.ParametersAction; import hudson.model.TaskListener; import hudson.remoting.Callable; @@ -126,12 +127,18 @@ public class Ant extends Builder { public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { AbstractProject proj = build.getProject(); + ArgumentListBuilder args = new ArgumentListBuilder(); + Map slaveEnv = EnvVars.getRemote(launcher.getChannel()); + EnvVars env = new EnvVars(slaveEnv); + env.overrideAll(build.getEnvVars()); + AntInstallation ai = getAnt(); if(ai==null) { args.add(launcher.isUnix() ? "ant" : "ant.bat"); } else { + ai = ai.forEnvironment(env); String exe = ai.getExecutable(launcher); if (exe==null) { listener.fatalError(Messages.Ant_ExecutableNotFound(ai.name)); @@ -140,7 +147,12 @@ public class Ant extends Builder { args.add(exe); } - FilePath buildFilePath = buildFilePath(proj.getModuleRoot()); + VariableResolver vr = build.getBuildVariableResolver(); + + String buildFile = Util.replaceMacro(this.buildFile, env); + String targets = Util.replaceMacro(Util.replaceMacro(this.targets, env), vr); + + FilePath buildFilePath = buildFilePath(proj.getModuleRoot(), buildFile, targets); if(!buildFilePath.exists()) { // because of the poor choice of getModuleRoot() with CVS/Subversion, people often get confused @@ -149,7 +161,7 @@ public class Ant extends Builder { // and diagnosing it nicely. See HUDSON-1782 // first check if this appears to be a valid relative path from workspace root - FilePath buildFilePath2 = buildFilePath(proj.getWorkspace()); + FilePath buildFilePath2 = buildFilePath(proj.getWorkspace(), buildFile, targets); if(buildFilePath2.exists()) { // This must be what the user meant. Let it continue. buildFilePath = buildFilePath2; @@ -166,17 +178,14 @@ public class Ant extends Builder { args.addKeyValuePairs("-D",build.getBuildVariables()); - VariableResolver vr = build.getBuildVariableResolver(); - args.addKeyValuePairsFromPropertyString("-D",properties,vr); - args.addTokenized(Util.replaceMacro(targets,vr).replaceAll("[\t\r\n]+"," ")); + args.addTokenized(targets.replaceAll("[\t\r\n]+"," ")); - Map env = build.getEnvVars(); if(ai!=null) env.put("ANT_HOME",ai.getAntHome()); if(antOpts!=null) - env.put("ANT_OPTS",antOpts); + env.put("ANT_OPTS",Util.replaceMacro(antOpts, env)); if(!launcher.isUnix()) { // on Windows, executing batch file can't return the correct error code, @@ -216,7 +225,7 @@ public class Ant extends Builder { } } - private FilePath buildFilePath(FilePath base) { + private static FilePath buildFilePath(FilePath base, String buildFile, String targets) { if(buildFile!=null) return base.child(buildFile); // some users specify the -f option in the targets field, so take that into account as well. // see @@ -302,9 +311,13 @@ public class Ant extends Builder { } }.process(); } + + public void setInstallations(AntInstallation... antInstallations) { + this.installations = antInstallations; + } } - public static final class AntInstallation implements Serializable { + public static final class AntInstallation implements Serializable, EnvironmentSpecific { private final String name; private final String antHome; @@ -367,5 +380,9 @@ public class Ant extends Builder { } private static final long serialVersionUID = 1L; + + public AntInstallation forEnvironment(Map environment) { + return new AntInstallation(name, Util.replaceMacro(antHome, environment)); + } } } diff --git a/core/src/main/java/hudson/tasks/BuildWrapper.java b/core/src/main/java/hudson/tasks/BuildWrapper.java index ddfc665c1c..e32b4acd07 100644 --- a/core/src/main/java/hudson/tasks/BuildWrapper.java +++ b/core/src/main/java/hudson/tasks/BuildWrapper.java @@ -66,26 +66,7 @@ public abstract class BuildWrapper implements ExtensionPoint, Describable - * If the {@link Environment} object wants to pass in information - * to the build that runs, it can do so by exporting additional - * environment variables to the map. - * - *

- * When this method is invoked, the map already contains the - * current "planned export" list. - * - * @param env - * never null. - */ - public void buildEnvVars(Map env) { - // no-op by default - } - + public abstract class Environment extends hudson.model.Environment { /** * Runs after the {@link Builder} completes, and performs a tear down. * diff --git a/core/src/main/java/hudson/tasks/Maven.java b/core/src/main/java/hudson/tasks/Maven.java index dcd91c6460..8ed9590a53 100644 --- a/core/src/main/java/hudson/tasks/Maven.java +++ b/core/src/main/java/hudson/tasks/Maven.java @@ -37,6 +37,7 @@ import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Descriptor; +import hudson.model.EnvironmentSpecific; import hudson.model.ParametersAction; import hudson.remoting.Callable; import hudson.remoting.VirtualChannel; @@ -178,7 +179,15 @@ public class Maven extends Builder { VariableResolver vr = build.getBuildVariableResolver(); + Map slaveEnv = EnvVars.getRemote(launcher.getChannel()); + EnvVars env = new EnvVars(slaveEnv); + env.overrideAll(build.getEnvVars()); + String targets = Util.replaceMacro(this.targets,vr); + targets = Util.replaceMacro(targets, env); + String pom = Util.replaceMacro(this.pom, env); + String jvmOptions = Util.replaceMacro(this.jvmOptions, env); + String properties =Util.replaceMacro(this.properties, env); int startIndex = 0; int endIndex; @@ -189,12 +198,9 @@ public class Maven extends Builder { endIndex = targets.length(); } - Map env = build.getEnvVars(); - String normalizedTarget = targets .substring(startIndex, endIndex) .replaceAll("[\t\r\n]+"," "); - normalizedTarget = Util.replaceMacro(normalizedTarget,env); ArgumentListBuilder args = new ArgumentListBuilder(); MavenInstallation ai = getMaven(); @@ -202,6 +208,7 @@ public class Maven extends Builder { String execName = proj.getWorkspace().act(new DecideDefaultMavenCommand(normalizedTarget)); args.add(execName); } else { + ai = ai.forEnvironment(env); String exec = ai.getExecutable(launcher); if(exec==null) { listener.fatalError(Messages.Maven_NoExecutable(ai.getMavenHome())); @@ -330,7 +337,7 @@ public class Maven extends Builder { } } - public static final class MavenInstallation implements Serializable { + public static final class MavenInstallation implements Serializable, EnvironmentSpecific { private final String name; private final String mavenHome; @@ -402,6 +409,11 @@ public class Maven extends Builder { } private static final long serialVersionUID = 1L; + + @Override + public MavenInstallation forEnvironment(Map environment) { + return new MavenInstallation(name, Util.replaceMacro(mavenHome, environment)); + } } /** diff --git a/core/src/main/resources/hudson/model/Hudson/configure.jelly b/core/src/main/resources/hudson/model/Hudson/configure.jelly index 0dc7ba59a2..6e10b4a04c 100644 --- a/core/src/main/resources/hudson/model/Hudson/configure.jelly +++ b/core/src/main/resources/hudson/model/Hudson/configure.jelly @@ -90,6 +90,14 @@ THE SOFTWARE. title="${%statsBlurb}" help="/help/system-config/usage-statistics.html" /> + + + + + diff --git a/core/src/main/resources/hudson/slaves/DumbSlave/configure-entries.jelly b/core/src/main/resources/hudson/slaves/DumbSlave/configure-entries.jelly index 046202ee76..183e3d2f7c 100644 --- a/core/src/main/resources/hudson/slaves/DumbSlave/configure-entries.jelly +++ b/core/src/main/resources/hudson/slaves/DumbSlave/configure-entries.jelly @@ -84,4 +84,7 @@ THE SOFTWARE. + + + \ No newline at end of file diff --git a/core/src/main/resources/hudson/slaves/EnvironmentVariablesNodeProperty/config.jelly b/core/src/main/resources/hudson/slaves/EnvironmentVariablesNodeProperty/config.jelly new file mode 100644 index 0000000000..c3a7f3cc1d --- /dev/null +++ b/core/src/main/resources/hudson/slaves/EnvironmentVariablesNodeProperty/config.jelly @@ -0,0 +1,23 @@ + + + + + + + + + + + +
+ +
+
+
+
+
+ +
\ No newline at end of file diff --git a/core/src/main/resources/lib/form/descriptorList.jelly b/core/src/main/resources/lib/form/descriptorList.jelly index 7b24c79e0d..827e3a9f2e 100644 --- a/core/src/main/resources/lib/form/descriptorList.jelly +++ b/core/src/main/resources/lib/form/descriptorList.jelly @@ -55,7 +55,7 @@ THE SOFTWARE. - + diff --git a/core/src/test/java/hudson/slaves/NodeListTest.java b/core/src/test/java/hudson/slaves/NodeListTest.java index 7d4629c6c0..cad3fabd4b 100644 --- a/core/src/test/java/hudson/slaves/NodeListTest.java +++ b/core/src/test/java/hudson/slaves/NodeListTest.java @@ -36,9 +36,11 @@ import hudson.FilePath; import hudson.security.ACL; import hudson.security.Permission; import hudson.util.ClockDifference; +import hudson.util.DescribableList; import java.io.File; import java.io.IOException; +import java.util.Collection; import java.util.Set; import org.apache.commons.io.FileUtils; @@ -99,6 +101,17 @@ public class NodeListTest extends TestCase { public NodeDescriptor getDescriptor() { throw new UnsupportedOperationException(); } + + @Override + public DescribableList, NodePropertyDescriptor> getNodeProperties() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNodeProperties(Collection> nodeProperties) + throws IOException { + throw new UnsupportedOperationException(); + } } static class EphemeralNode extends DummyNode implements hudson.slaves.EphemeralNode { public Cloud getCloud() { diff --git a/test/src/main/java/org/jvnet/hudson/test/CaptureEnvironmentBuilder.java b/test/src/main/java/org/jvnet/hudson/test/CaptureEnvironmentBuilder.java new file mode 100644 index 0000000000..eb8d79eef5 --- /dev/null +++ b/test/src/main/java/org/jvnet/hudson/test/CaptureEnvironmentBuilder.java @@ -0,0 +1,53 @@ +package org.jvnet.hudson.test; + +import hudson.Launcher; +import hudson.model.AbstractBuild; +import hudson.model.BuildListener; +import hudson.model.Descriptor; +import hudson.model.Result; +import hudson.tasks.Builder; +import hudson.tasks.BuildStep; +import net.sf.json.JSONObject; +import org.kohsuke.stapler.StaplerRequest; + +import java.io.IOException; +import java.util.Map; + +/** + * Mock {@link Builder} that always cause a build to fail. + * + * @author Kohsuke Kawaguchi + */ +public class CaptureEnvironmentBuilder extends Builder { + + private Map envVars; + + public Map getEnvVars() { + return envVars; + } + + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + envVars = build.getEnvVars(); + return true; + } + + public Descriptor getDescriptor() { + return DESCRIPTOR; + } + + public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); + + public static final class DescriptorImpl extends Descriptor { + public Builder newInstance(StaplerRequest req, JSONObject data) { + throw new UnsupportedOperationException(); + } + + public String getDisplayName() { + return "Capture Environment Variables"; + } + } + + static { + BuildStep.BUILDERS.add(DESCRIPTOR); + } +} diff --git a/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java b/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java index 34baf35886..391b6ffce2 100644 --- a/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java +++ b/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java @@ -23,22 +23,11 @@ */ package org.jvnet.hudson.test; -import com.gargoylesoftware.htmlunit.AjaxController; -import com.gargoylesoftware.htmlunit.BrowserVersion; -import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; -import com.gargoylesoftware.htmlunit.Page; -import com.gargoylesoftware.htmlunit.WebRequestSettings; -import com.gargoylesoftware.htmlunit.html.HtmlButton; -import com.gargoylesoftware.htmlunit.html.HtmlForm; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine; -import com.gargoylesoftware.htmlunit.javascript.host.Stylesheet; -import com.gargoylesoftware.htmlunit.javascript.host.XMLHttpRequest; import hudson.CloseProofOutputStream; import hudson.FilePath; import hudson.Functions; -import hudson.Launcher.LocalLauncher; import hudson.WebAppMain; +import hudson.Launcher.LocalLauncher; import hudson.matrix.MatrixProject; import hudson.maven.MavenModuleSet; import hudson.maven.MavenReporters; @@ -48,13 +37,13 @@ import hudson.model.Hudson; import hudson.model.Item; import hudson.model.JDK; import hudson.model.Label; -import hudson.model.Node.Mode; +import hudson.model.Node; import hudson.model.Result; import hudson.model.Run; import hudson.model.Saveable; import hudson.model.TaskListener; import hudson.model.UpdateCenter; -import hudson.model.Node; +import hudson.model.Node.Mode; import hudson.slaves.CommandLauncher; import hudson.slaves.DumbSlave; import hudson.slaves.RetentionStrategy; @@ -64,13 +53,44 @@ import hudson.tasks.Maven; import hudson.tasks.Maven.MavenInstallation; import hudson.util.ProcessTreeKiller; import hudson.util.StreamTaskListener; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.jar.Manifest; +import java.util.logging.Filter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; + import junit.framework.TestCase; -import org.apache.commons.io.IOUtils; + import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.jvnet.hudson.test.HudsonHomeLoader.CopyExisting; import org.jvnet.hudson.test.recipes.Recipe; import org.jvnet.hudson.test.recipes.Recipe.Runner; import org.jvnet.hudson.test.rhino.JavaScriptDebugger; +import org.kohsuke.stapler.Dispatcher; +import org.kohsuke.stapler.MetaClass; +import org.kohsuke.stapler.MetaClassLoader; import org.mortbay.jetty.Server; import org.mortbay.jetty.bio.SocketConnector; import org.mortbay.jetty.security.HashUserRealm; @@ -84,33 +104,18 @@ import org.w3c.css.sac.CSSException; import org.w3c.css.sac.CSSParseException; import org.w3c.css.sac.ErrorHandler; import org.xml.sax.SAXException; -import org.kohsuke.stapler.MetaClass; -import org.kohsuke.stapler.Dispatcher; -import org.kohsuke.stapler.MetaClassLoader; -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.lang.annotation.Annotation; -import java.lang.ref.WeakReference; -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; -import java.net.MalformedURLException; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.jar.Manifest; -import java.util.logging.Filter; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.Logger; +import com.gargoylesoftware.htmlunit.AjaxController; +import com.gargoylesoftware.htmlunit.BrowserVersion; +import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.WebRequestSettings; +import com.gargoylesoftware.htmlunit.html.HtmlButton; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine; +import com.gargoylesoftware.htmlunit.javascript.host.Stylesheet; +import com.gargoylesoftware.htmlunit.javascript.host.XMLHttpRequest; /** * Base class for all Hudson test cases. @@ -292,12 +297,13 @@ public abstract class HudsonTestCase extends TestCase { /** * Locates Maven2 and configure that as the only Maven in the system. */ - protected void configureDefaultMaven() throws Exception { + protected MavenInstallation configureDefaultMaven() throws Exception { // first if we are running inside Maven, pick that Maven. String home = System.getProperty("maven.home"); if(home!=null) { - Maven.DESCRIPTOR.setInstallations(new MavenInstallation("default",home)); - return; + MavenInstallation mavenInstallation = new MavenInstallation("default",home); + Maven.DESCRIPTOR.setInstallations(mavenInstallation); + return mavenInstallation; } // otherwise extract the copy we have. @@ -314,8 +320,10 @@ public abstract class HudsonTestCase extends TestCase { File mvnHome = createTmpDir(); mvn.unzip(new FilePath(mvnHome)); - Maven.DESCRIPTOR.setInstallations(new MavenInstallation("default", - new File(mvnHome,"maven-2.0.7").getAbsolutePath())); + MavenInstallation mavenInstallation = new MavenInstallation("default", + new File(mvnHome,"maven-2.0.7").getAbsolutePath()); + Maven.DESCRIPTOR.setInstallations(mavenInstallation); + return mavenInstallation; } // @@ -382,16 +390,24 @@ public abstract class HudsonTestCase extends TestCase { * Creates and launches a new slave on the local host. */ public DumbSlave createSlave(Label l) throws Exception { - CommandLauncher launcher = new CommandLauncher( - System.getProperty("java.home") + "/bin/java -jar " + new File(hudson.getJnlpJars("slave.jar").getURL().getPath()).getPath()); - - // this synchronization block is so that we don't end up adding the same slave name more than once. - synchronized (hudson) { - DumbSlave slave = new DumbSlave("slave" + hudson.getNodes().size(), "dummy", - createTmpDir().getPath(), "1", Mode.NORMAL, l==null?"":l.getName(), launcher, RetentionStrategy.NOOP); - hudson.addNode(slave); - return slave; - } + return createSlave(l, null); + } + + /** + * Creates a slave with certain additional environment variables + */ + public DumbSlave createSlave(Label l, Map env) throws Exception { + CommandLauncher launcher = new CommandLauncher( + System.getProperty("java.home") + "/bin/java -jar \"" + + new File(hudson.getJnlpJars("slave.jar").getURL().toURI()).getAbsolutePath() + "\"", env); + + // this synchronization block is so that we don't end up adding the same slave name more than once. + synchronized (hudson) { + DumbSlave slave = new DumbSlave("slave" + hudson.getNodes().size(), "dummy", + createTmpDir().getPath(), "1", Mode.NORMAL, l==null?"":l.getName(), launcher, RetentionStrategy.NOOP); + hudson.addNode(slave); + return slave; + } } /** diff --git a/test/src/main/preset-data/simple-projects/build.xml b/test/src/main/preset-data/simple-projects/build.xml new file mode 100644 index 0000000000..740bcd7662 --- /dev/null +++ b/test/src/main/preset-data/simple-projects/build.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/test/src/main/preset-data/simple-projects/pom.xml b/test/src/main/preset-data/simple-projects/pom.xml new file mode 100644 index 0000000000..609dccbf64 --- /dev/null +++ b/test/src/main/preset-data/simple-projects/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + com.example + test.prop.hudson + 1.0 + pom + + + default + + + + + diff --git a/test/src/main/resources/org/jvnet/hudson/test/CaptureEnvironmentBuilder/config.jelly b/test/src/main/resources/org/jvnet/hudson/test/CaptureEnvironmentBuilder/config.jelly new file mode 100644 index 0000000000..31b61f2ada --- /dev/null +++ b/test/src/main/resources/org/jvnet/hudson/test/CaptureEnvironmentBuilder/config.jelly @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/test/src/test/java/hudson/model/AbstractBuildTest.java b/test/src/test/java/hudson/model/AbstractBuildTest.java new file mode 100644 index 0000000000..5d2bb5b893 --- /dev/null +++ b/test/src/test/java/hudson/model/AbstractBuildTest.java @@ -0,0 +1,35 @@ +package hudson.model; + +import hudson.slaves.EnvironmentVariablesNodeProperty; +import hudson.slaves.NodeProperty; +import hudson.slaves.EnvironmentVariablesNodeProperty.Entry; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import junit.framework.Assert; + +import org.jvnet.hudson.test.CaptureEnvironmentBuilder; +import org.jvnet.hudson.test.HudsonTestCase; + +public class AbstractBuildTest extends HudsonTestCase { + + public void testVariablesResolved() throws Exception { + FreeStyleProject project = createFreeStyleProject(); + Hudson.getInstance().setNodeProperties( + Collections + .>singleton(new EnvironmentVariablesNodeProperty( + new Entry("KEY1", "value"), new Entry("KEY2", + "$KEY1")))); + CaptureEnvironmentBuilder builder = new CaptureEnvironmentBuilder(); + project.getBuildersList().add(builder); + + AbstractBuild build = project.scheduleBuild2(0).get(10, TimeUnit.SECONDS); + + Map envVars = builder.getEnvVars(); + Assert.assertEquals("value", envVars.get("KEY1")); + Assert.assertEquals("value", envVars.get("KEY2")); + } + +} diff --git a/test/src/test/java/hudson/slaves/EnvironmentVariableNodePropertyTest.java b/test/src/test/java/hudson/slaves/EnvironmentVariableNodePropertyTest.java new file mode 100644 index 0000000000..8602600bff --- /dev/null +++ b/test/src/test/java/hudson/slaves/EnvironmentVariableNodePropertyTest.java @@ -0,0 +1,162 @@ +package hudson.slaves; + +import hudson.model.Build; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.Hudson; +import hudson.model.JDK; +import hudson.model.Node; +import hudson.model.ParametersDefinitionProperty; +import hudson.model.Result; +import hudson.model.StringParameterDefinition; +import hudson.slaves.EnvironmentVariablesNodeProperty.Entry; +import hudson.tasks.Maven; +import hudson.tasks.Maven.MavenInstallation; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import junit.framework.Assert; + +import org.jvnet.hudson.test.CaptureEnvironmentBuilder; +import org.jvnet.hudson.test.HudsonTestCase; +import org.jvnet.hudson.test.HudsonTestCase.WebClient; + +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; + +/** + * This class tests that environment variables from node properties are applied, + * and that the priority is maintained: parameters > slave node properties > + * master node properties + */ +public class EnvironmentVariableNodePropertyTest extends HudsonTestCase { + + private DumbSlave slave; + private FreeStyleProject project; + + /** + * Slave properties are available + */ + public void testSlavePropertyOnSlave() throws Exception { + setVariables(slave, new Entry("KEY", "slaveValue")); + Map envVars = executeBuild(slave); + Assert.assertEquals("slaveValue", envVars.get("KEY")); + } + + /** + * Master properties are available + */ + public void testMasterPropertyOnMaster() throws Exception { + setVariables(Hudson.getInstance(), new Entry("KEY", "masterValue")); + + Map envVars = executeBuild(Hudson.getInstance()); + + Assert.assertEquals("masterValue", envVars.get("KEY")); + } + + /** + * Both slave and master properties are available, but slave properties have priority + */ + public void testSlaveAndMasterPropertyOnSlave() throws Exception { + setVariables(Hudson.getInstance(), new Entry("KEY", "masterValue")); + setVariables(slave, new Entry("KEY", "slaveValue")); + + Map envVars = executeBuild(slave); + + Assert.assertEquals("slaveValue", envVars.get("KEY")); + } + + /** + * Slave and master properties and parameters are available. + * Priority: parameters > slave > master + * @throws Exception + */ + public void testSlaveAndMasterPropertyAndParameterOnSlave() + throws Exception { + ParametersDefinitionProperty pdp = new ParametersDefinitionProperty( + new StringParameterDefinition("KEY", "parameterValue")); + project.addProperty(pdp); + + setVariables(Hudson.getInstance(), new Entry("KEY", "masterValue")); + setVariables(slave, new Entry("KEY", "slaveValue")); + + Map envVars = executeBuild(slave); + + Assert.assertEquals("parameterValue", envVars.get("KEY")); + } + + public void testVariableResolving() throws Exception { + setVariables(Hudson.getInstance(), new Entry("KEY1", "value"), new Entry("KEY2", "$KEY1")); + Map envVars = executeBuild(Hudson.getInstance()); + Assert.assertEquals("value", envVars.get("KEY1")); + Assert.assertEquals("value", envVars.get("KEY2")); + } + + public void testFormRoundTripForMaster() throws Exception { + setVariables(hudson, new Entry("KEY", "value")); + + WebClient webClient = new WebClient(); + HtmlPage page = webClient.getPage(hudson, "configure"); + HtmlForm form = page.getFormByName("config"); + submit(form); + + Assert.assertEquals(1, hudson.getNodeProperties().toList().size()); + + EnvironmentVariablesNodeProperty prop = hudson.getNodeProperty(EnvironmentVariablesNodeProperty.class); + Assert.assertEquals(1, prop.getEnvVars().size()); + Assert.assertEquals("value", prop.getEnvVars().get("KEY")); + } + + public void testFormRoundTripForSlave() throws Exception { + setVariables(slave, new Entry("KEY", "value")); + + WebClient webClient = new WebClient(); + HtmlPage page = webClient.getPage(slave, "configure"); + HtmlForm form = page.getFormByName("config"); + submit(form); + + Assert.assertEquals(1, slave.getNodeProperties().toList().size()); + + EnvironmentVariablesNodeProperty prop = slave.getNodeProperty(EnvironmentVariablesNodeProperty.class); + Assert.assertEquals(1, prop.getEnvVars().size()); + Assert.assertEquals("value", prop.getEnvVars().get("KEY")); + } + + // //////////////////////// setup ////////////////////////////////////////// + + public void setUp() throws Exception { + super.setUp(); + slave = createSlave(); + project = createFreeStyleProject(); + } + + // ////////////////////// helper methods ///////////////////////////////// + + private void setVariables(Node node, Entry... entries) throws IOException { + node.getNodeProperties().replaceBy( + Collections.singleton(new EnvironmentVariablesNodeProperty( + entries))); + + } + + /** + * Launches project on this node, waits for the result, and returns the environment that is used + */ + private Map executeBuild(Node node) throws Exception { + CaptureEnvironmentBuilder builder = new CaptureEnvironmentBuilder(); + + project.getBuildersList().add(builder); + project.setAssignedLabel(node.getSelfLabel()); + + // use a timeout so we don't wait infinitely in case of failure + FreeStyleBuild build = project.scheduleBuild2(0).get(/*10, TimeUnit.SECONDS*/); + + System.out.println(build.getLog()); + Assert.assertEquals(Result.SUCCESS, build.getResult()); + + return builder.getEnvVars(); + } + +} diff --git a/test/src/test/java/hudson/tasks/EnvVarsInConfigTasksTest.java b/test/src/test/java/hudson/tasks/EnvVarsInConfigTasksTest.java new file mode 100644 index 0000000000..b3b62ef8d1 --- /dev/null +++ b/test/src/test/java/hudson/tasks/EnvVarsInConfigTasksTest.java @@ -0,0 +1,191 @@ +package hudson.tasks; + +import hudson.EnvVars; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.JDK; +import hudson.model.Label; +import hudson.model.Result; +import hudson.slaves.DumbSlave; +import hudson.tasks.Ant.AntInstallation; +import hudson.tasks.Maven.MavenInstallation; + +import java.io.File; +import java.util.TreeMap; + +import org.apache.tools.ant.taskdefs.condition.Os; +import org.jvnet.hudson.test.ExtractResourceSCM; +import org.jvnet.hudson.test.HudsonTestCase; + +public class EnvVarsInConfigTasksTest extends HudsonTestCase { + public static final String DUMMY_LOCATION_VARNAME = "TOOLS_DUMMY_LOCATION"; + + private DumbSlave slaveEnv = null; + private DumbSlave slaveRegular = null; + + public void setUp() throws Exception { + super.setUp(); + + JDK defaultJDK = hudson.getJDK(null); + JDK varJDK = new JDK("varJDK", withVariable(defaultJDK.getJavaHome())); + hudson.getJDKs().add(varJDK); + + // Maven with a variable in its path + configureDefaultMaven(); + MavenInstallation defaultMaven = Maven.DESCRIPTOR.getInstallations()[0]; + MavenInstallation varMaven = new MavenInstallation("varMaven", + withVariable(defaultMaven.getMavenHome())); + Maven.DESCRIPTOR.setInstallations(varMaven); + + // Ant with a variable in its path + // TODO: create HudsonTestCase.configureDefaultAnt() + String guessedAntHome = guessAntHome(); + if (guessedAntHome != null) { + AntInstallation antInstallation = new AntInstallation("varAnt", + withVariable(guessedAntHome)); + Ant.DESCRIPTOR.setInstallations(antInstallation); + } + + // createSlaves + TreeMap additionalEnv = new TreeMap(); + additionalEnv.put(DUMMY_LOCATION_VARNAME, ""); + slaveEnv = createSlave(new Label("slaveEnv"), additionalEnv); + + slaveRegular = createSlave(new Label("slaveRegular")); + } + + private String withVariable(String s) { + return s + "${" + DUMMY_LOCATION_VARNAME + "}"; + } + + public void testFreeStyleShellOnSlave() throws Exception { + FreeStyleProject project = createFreeStyleProject(); + if (Os.isFamily("dos")) { + project.getBuildersList().add(new BatchFile("echo %JAVA_HOME%")); + } else { + project.getBuildersList().add(new Shell("echo \"$JAVA_HOME\"")); + } + project.setJDK(hudson.getJDK("varJDK")); + + // set appropriate SCM to get the necessary build files + project.setScm(new ExtractResourceSCM(getClass().getResource( + "/simple-projects.zip"))); + + // test the regular slave - variable not expanded + project.setAssignedLabel(slaveRegular.getSelfLabel()); + FreeStyleBuild build = project.scheduleBuild2(0).get(); + System.out.println(build.getDisplayName() + " completed"); + + assertBuildStatusSuccess(build); + + String buildLogRegular = build.getLog(); + System.out.println(buildLogRegular); + assertTrue(buildLogRegular.contains(DUMMY_LOCATION_VARNAME)); + + // test the slave with prepared environment + project.setAssignedLabel(slaveEnv.getSelfLabel()); + build = project.scheduleBuild2(0).get(); + System.out.println(build.getDisplayName() + " completed"); + + assertBuildStatusSuccess(build); + + // Check variable was expanded + String buildLogEnv = build.getLog(); + System.out.println(buildLogEnv); + assertFalse(buildLogEnv.contains(DUMMY_LOCATION_VARNAME)); + } + + public void testFreeStyleAntOnSlave() throws Exception { + if (Ant.DESCRIPTOR.getInstallations().length == 0) { + System.out.println("Cannot do testFreeStyleAntOnSlave without ANT_HOME"); + return; + } + FreeStyleProject project = createFreeStyleProject(); + project.setJDK(hudson.getJDK("varJDK")); + project.setScm(new ExtractResourceSCM(getClass().getResource( + "/simple-projects.zip"))); + + String buildFile = "build.xml${" + DUMMY_LOCATION_VARNAME + "}"; + // we need additional escapes because bash itself expanding + project.getBuildersList().add( + new Ant("-Dtest.property=cor${" + DUMMY_LOCATION_VARNAME + + "}rect", "varAnt", "", buildFile, "")); + + // test the regular slave - variable not expanded + project.setAssignedLabel(slaveRegular.getSelfLabel()); + FreeStyleBuild build = project.scheduleBuild2(0).get(); + System.out.println(build.getDisplayName() + " completed"); + + assertBuildStatus(Result.FAILURE, build); + + String buildLogRegular = build.getLog(); + assertTrue(buildLogRegular.contains(Messages + .Ant_ExecutableNotFound("varAnt"))); + + // test the slave with prepared environment + project.setAssignedLabel(slaveEnv.getSelfLabel()); + build = project.scheduleBuild2(0).get(); + System.out.println(build.getDisplayName() + " completed"); + + assertBuildStatusSuccess(build); + + // Check variable was expanded + String buildLogEnv = build.getLog(); + System.out.println(buildLogEnv); + assertTrue(buildLogEnv.contains("Ant home: ")); + assertTrue(buildLogEnv.contains("Test property: correct")); + assertFalse(buildLogEnv.matches("(?s)^.*Ant home: [^\\n\\r]*" + + DUMMY_LOCATION_VARNAME + ".*$")); + assertFalse(buildLogEnv.matches("(?s)^.*Test property: [^\\n\\r]*" + + DUMMY_LOCATION_VARNAME + ".*$")); + } + + public void testFreeStyleMavenOnSlave() throws Exception { + FreeStyleProject project = createFreeStyleProject(); + project.setJDK(hudson.getJDK("varJDK")); + project.setScm(new ExtractResourceSCM(getClass().getResource( + "/simple-projects.zip"))); + + project.getBuildersList().add( + new Maven("test", "varMaven", "pom.xml${" + + DUMMY_LOCATION_VARNAME + "}", "", "")); + + // test the regular slave - variable not expanded + project.setAssignedLabel(slaveRegular.getSelfLabel()); + FreeStyleBuild build = project.scheduleBuild2(0).get(); + System.out.println(build.getDisplayName() + " completed"); + + assertBuildStatus(Result.FAILURE, build); + + String buildLogRegular = build.getLog(); + System.out.println(buildLogRegular); + assertTrue(buildLogRegular.contains(DUMMY_LOCATION_VARNAME)); + + // test the slave with prepared environment + project.setAssignedLabel(slaveEnv.getSelfLabel()); + build = project.scheduleBuild2(0).get(); + System.out.println(build.getDisplayName() + " completed"); + + assertBuildStatusSuccess(build); + + // Check variable was expanded + String buildLogEnv = build.getLog(); + System.out.println(buildLogEnv); + assertFalse(buildLogEnv.contains(DUMMY_LOCATION_VARNAME)); + } + + public static String guessAntHome() { + String antHome = EnvVars.masterEnvVars.get("ANT_HOME"); + if (antHome != null) + return antHome; + + // will break if PATH variable is not found (e.g. case sensitivity) + String[] sysPath = EnvVars.masterEnvVars.get("PATH").split( + File.pathSeparator); + for (String p : sysPath) + if (new File(p, "ant").isFile() || new File(p, "ant.bat").isFile()) + return new File(p).getParent(); + + throw new RuntimeException("cannot find Apache Ant"); + } +} diff --git a/test/src/test/java/hudson/tasks/MavenTest.java b/test/src/test/java/hudson/tasks/MavenTest.java index b05a187566..400c0a3e5a 100644 --- a/test/src/test/java/hudson/tasks/MavenTest.java +++ b/test/src/test/java/hudson/tasks/MavenTest.java @@ -23,36 +23,115 @@ */ package hudson.tasks; +import hudson.model.Build; +import hudson.model.FreeStyleProject; +import hudson.model.Hudson; +import hudson.model.JDK; +import hudson.model.ParametersDefinitionProperty; +import hudson.model.Result; +import hudson.model.StringParameterDefinition; +import hudson.slaves.EnvironmentVariablesNodeProperty; +import hudson.slaves.EnvironmentVariablesNodeProperty.Entry; +import hudson.tasks.Maven.MavenInstallation; + +import java.util.Collections; + +import junit.framework.Assert; + +import org.jvnet.hudson.test.HudsonTestCase; + import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlPage; -import hudson.model.FreeStyleProject; -import org.jvnet.hudson.test.HudsonTestCase; /** * @author Kohsuke Kawaguchi */ public class MavenTest extends HudsonTestCase { - /** - * Tests the round-tripping of the configuration. - */ - public void testConfigRoundtrip() throws Exception { - Maven.DESCRIPTOR.setInstallations(); // reset + /** + * Tests the round-tripping of the configuration. + */ + public void testConfigRoundtrip() throws Exception { + Maven.DESCRIPTOR.setInstallations(); // reset - FreeStyleProject p = createFreeStyleProject(); - p.getBuildersList().add(new Maven("a",null,"b.pom","c=d","-e")); + FreeStyleProject p = createFreeStyleProject(); + p.getBuildersList().add(new Maven("a", null, "b.pom", "c=d", "-e")); - WebClient webClient = new WebClient(); - HtmlPage page = webClient.getPage(p,"configure"); + WebClient webClient = new WebClient(); + HtmlPage page = webClient.getPage(p, "configure"); - HtmlForm form = page.getFormByName("config"); - submit(form); + HtmlForm form = page.getFormByName("config"); + submit(form); - Maven m = (Maven)p.getBuildersList().get(Maven.DESCRIPTOR); - assertNotNull(m); - assertEquals("a",m.targets); - assertNull("found "+m.mavenName,m.mavenName); - assertEquals("b.pom",m.pom); - assertEquals("c=d",m.properties); - assertEquals("-e",m.jvmOptions); - } + Maven m = (Maven) p.getBuildersList().get(Maven.DESCRIPTOR); + assertNotNull(m); + assertEquals("a", m.targets); + assertNull("found " + m.mavenName, m.mavenName); + assertEquals("b.pom", m.pom); + assertEquals("c=d", m.properties); + assertEquals("-e", m.jvmOptions); + } + + public void testWithNodeProperty() throws Exception { + MavenInstallation maven = configureDefaultMaven(); + String mavenHome = maven.getMavenHome(); + String mavenHomeVar = "${VAR_MAVEN}" + mavenHome.substring(3); + String mavenVar = mavenHome.substring(0, 3); + MavenInstallation varMaven = new MavenInstallation("varMaven", + mavenHomeVar); + Maven.DESCRIPTOR.setInstallations(maven, varMaven); + + JDK jdk = hudson.getJDK("default"); + String javaHome = jdk.getJavaHome(); + String javaHomeVar = "${VAR_JAVA}" + javaHome.substring(3); + String javaVar = javaHome.substring(0, 3); + JDK varJDK = new JDK("varJDK", javaHomeVar); + hudson.getJDKs().add(varJDK); + Hudson.getInstance().getNodeProperties().replaceBy( + Collections.singleton(new EnvironmentVariablesNodeProperty( + new Entry("VAR_MAVEN", mavenVar), new Entry("VAR_JAVA", + javaVar)))); + + FreeStyleProject project = createFreeStyleProject(); + project.getBuildersList().add(new Maven("--help", varMaven.getName())); + project.setJDK(varJDK); + + Build build = project.scheduleBuild2(0).get(); + Assert.assertEquals(Result.SUCCESS, build.getResult()); + + } + + public void testWithParameter() throws Exception { + MavenInstallation maven = configureDefaultMaven(); + String mavenHome = maven.getMavenHome(); + String mavenHomeVar = "${VAR_MAVEN}" + mavenHome.substring(3); + String mavenVar = mavenHome.substring(0, 3); + MavenInstallation varMaven = new MavenInstallation("varMaven", + mavenHomeVar); + Maven.DESCRIPTOR.setInstallations(maven, varMaven); + + JDK jdk = hudson.getJDK("default"); + String javaHome = jdk.getJavaHome(); + String javaHomeVar = "${VAR_JAVA}" + javaHome.substring(3); + String javaVar = javaHome.substring(0, 3); + JDK varJDK = new JDK("varJDK", javaHomeVar); + hudson.getJDKs().add(varJDK); + + FreeStyleProject project = createFreeStyleProject(); + project.addProperty(new ParametersDefinitionProperty( + new StringParameterDefinition("VAR_MAVEN", "XXX"), + new StringParameterDefinition("VAR_JAVA", "XXX"))); + project.getBuildersList().add(new Maven("--help", varMaven.getName())); + project.setJDK(varJDK); + + WebClient webClient = new WebClient(); + webClient.getPage(project, "buildWithParameters?VAR_MAVEN=" + mavenVar + + "&VAR_JAVA=" + javaVar); + + Thread.sleep(2000); + + Build build = project.getLastBuild(); + + Assert.assertEquals(Result.SUCCESS, build.getResult()); + + } } diff --git a/war/resources/help/system-config/globalEnvironmentVariables.html b/war/resources/help/system-config/globalEnvironmentVariables.html new file mode 100644 index 0000000000..8ab7925258 --- /dev/null +++ b/war/resources/help/system-config/globalEnvironmentVariables.html @@ -0,0 +1,4 @@ +

+ These key-value pairs apply for every build on every node. They can be used in Hudsons + configuration (as $key or ${key}) and will be added to the environment for processes launched from the build. +
diff --git a/war/resources/help/system-config/nodeEnvironmentVariables.html b/war/resources/help/system-config/nodeEnvironmentVariables.html new file mode 100644 index 0000000000..9200bbf875 --- /dev/null +++ b/war/resources/help/system-config/nodeEnvironmentVariables.html @@ -0,0 +1,4 @@ +
+ These key-value pairs apply for every build on this node and override any global values. They can be used in Hudsons + configuration (as $key or ${key}) and be will added to the environment for processes launched from the build. +
\ No newline at end of file