提交 87eb6367 编写于 作者: H huybrechts

[HUDSON-2918] node properties and environment variables

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@15502 71c3de6d-444a-0410-be80-ed276b4c234a
上级 481fd5ab
......@@ -88,6 +88,15 @@ public class EnvVars extends TreeMap<String,String> {
}
}
/**
* Resolves environment variables against each other.
*/
public static void resolve(Map<String, String> env) {
for (Map.Entry<String,String> 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<String,String> {
* variable of the slave agent.
*/
public static final Map<String,String> masterEnvVars = new EnvVars(System.getenv());
}
......@@ -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) {
......
......@@ -107,7 +107,7 @@ public class Util {
public static String replaceMacro(String s, Map<String,String> properties) {
return replaceMacro(s,new VariableResolver.ByMap<String>(properties));
}
/**
* Replaces the occurrence of '$key' by <tt>resolver.get('key')</tt>.
*
......@@ -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<String> resolver) {
if (s == null) {
return null;
}
int idx=0;
while(true) {
Matcher m = VARIABLE.matcher(s);
......
......@@ -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<MavenModuleSet,Mave
if (parameters != null)
parameters.createBuildWrappers(MavenModuleSetBuild.this,wrappers);
buildEnvironments = new ArrayList<BuildWrapper.Environment>();
buildEnvironments = new ArrayList<Environment>();
for( BuildWrapper w : wrappers) {
BuildWrapper.Environment e = w.setUp((AbstractBuild)MavenModuleSetBuild.this, launcher, listener);
if(e==null)
......
......@@ -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<P extends AbstractProject<P,R>,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<BuildWrapper.Environment> buildEnvironments;
protected transient List<Environment> buildEnvironments;
protected AbstractBuild(P job) throws IOException {
super(job);
......@@ -434,13 +435,15 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,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;
}
......
......@@ -23,20 +23,20 @@
*/
package hudson.model;
import hudson.Launcher;
import hudson.slaves.NodeProperty;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildTrigger;
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 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 <P extends Project<P,B>,B extends Build<P,B>>
buildEnvironments = new ArrayList<Environment>();
try {
List<BuildWrapper> wrappers = new ArrayList<BuildWrapper>(project.getBuildWrappers().values());
Node node = Computer.currentComputer().getNode();
NodeProperty.setup(node, buildEnvironments, Build.this, launcher, listener);
ParametersAction parameters = getAction(ParametersAction.class);
if (parameters != null)
......
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)}.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* When this method is invoked, the map already contains the current
* "planned export" list.
*
* @param env
* never null.
*/
public void buildEnvVars(Map<String, String> env) {
// no-op by default
}
/**
* Runs after the {@link Builder} completes, and performs a tear down.
*
* <p>
* 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<String,String> envVars) {
return new Environment() {
@Override
public void buildEnvVars(Map<String, String> env) {
env.putAll(envVars);
}
};
}
}
\ No newline at end of file
package hudson.model;
import java.util.Map;
/**
* Represents any concept that can be adapted for a certain environment.
*
* Mainly for documentation purposes.
*
* @param <T>
*/
public interface EnvironmentSpecific<T> {
/**
* Returns a specialized copy of T for functioning in the given environment.
*/
T forEnvironment(Map<String,String> environment);
}
......@@ -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<TopLevelItem>, Stapl
*/
private transient final List<Action> actions = new CopyOnWriteArrayList<Action>();
/**
* List of global/master node properties
*/
private DescribableList<NodeProperty<?>,NodePropertyDescriptor> nodeProperties = new DescribableList<NodeProperty<?>,NodePropertyDescriptor>(this);
/**
* {@link AdministrativeMonitor}s installed on this system.
*
......@@ -1185,6 +1192,14 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
trimLabels();
save();
}
public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties() {
return nodeProperties;
}
public void setNodeProperties(Collection<NodeProperty<?>> 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<TopLevelItem>, 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
......
......@@ -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<Node>, ExtensionPoint, AccessControlled {
public static final List<NodePropertyDescriptor> 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<No
*/
public abstract Set<Label> 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<No
return FileSystemProvisioner.DEFAULT;
}
// these are abstract because of possibly different owners for the nodeProperties list in subclasses
public abstract DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties();
public abstract void setNodeProperties(Collection<NodeProperty<?>> nodeProperties) throws IOException;
public List<NodePropertyDescriptor> getNodePropertyDescriptors() {
List<NodePropertyDescriptor> result = new ArrayList<NodePropertyDescriptor>();
for (NodePropertyDescriptor npd : PROPERTIES) {
if (npd.isApplicable(getClass())) {
result.add(npd);
}
}
return result;
}
public <N extends NodeProperty<?>> N getNodeProperty(Class<N> clazz) {
for (NodeProperty<?> p: getNodeProperties()) {
if (clazz.isInstance(p)) {
return clazz.cast(p);
}
}
return null;
}
public ACL getACL() {
return Hudson.getInstance().getAuthorizationStrategy().getACL(this);
}
......
......@@ -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<AbstractProject<?,
this.parameterDefinitions = parameterDefinitions;
}
public ParametersDefinitionProperty(ParameterDefinition... parameterDefinitions) {
this.parameterDefinitions = Arrays.asList(parameterDefinitions);
}
public AbstractProject<?,?> getOwner() {
return owner;
}
......
......@@ -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<NodeProperty<?>,NodePropertyDescriptor> nodeProperties = new DescribableList<NodeProperty<?>,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<? extends NodeProperty<?>> 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, 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) throws FormException {
Mode mode, String label, ComputerLauncher launcher, RetentionStrategy retentionStrategy, List<? extends NodeProperty<?>> 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<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties() {
return nodeProperties;
}
public void setNodeProperties(Collection<NodeProperty<?>> nodeProperties) throws IOException {
this.nodeProperties.replaceBy(nodeProperties);
}
public RetentionStrategy getRetentionStrategy() {
return retentionStrategy == null ? RetentionStrategy.Always.INSTANCE : retentionStrategy;
}
......
......@@ -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<String, String> env;
@DataBoundConstructor
public CommandLauncher(String command) {
this.agentCommand = command;
this(command, null);
}
public CommandLauncher(String command, Map<String,String> 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
......
......@@ -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<? extends NodeProperty<?>> nodeProperties) throws IOException, FormException {
super(name, nodeDescription, remoteFS, numExecutors, mode, label, launcher, retentionStrategy, nodeProperties);
}
public DescriptorImpl getDescriptor() {
......
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<Node> {
/**
* Slave-specific environment variables
*/
private final Map<String,String> envVars;
@DataBoundConstructor
public EnvironmentVariablesNodeProperty(List<Entry> env) {
this.envVars = toMap(env);
}
public EnvironmentVariablesNodeProperty(Entry... env) {
this(Arrays.asList(env));
}
public Map<String, String> 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<String,String> toMap(List<Entry> entries) {
Map<String,String> map = new HashMap<String,String>();
for (Entry entry: entries) {
map.put(entry.key,entry.value);
}
return map;
}
}
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<NodePropertyDescriptor> PROPERTIES = Descriptor
.toList((NodePropertyDescriptor) EnvironmentVariablesNodeProperty.DESCRIPTOR);
/**
* List up all {@link NodePropertyDescriptor}s that are applicable for the
* given project.
*/
public static List<NodePropertyDescriptor> getFor(Node node) {
List<NodePropertyDescriptor> result = new ArrayList<NodePropertyDescriptor>();
for (NodePropertyDescriptor npd : PROPERTIES) {
if (npd.isApplicable(node.getClass())) {
result.add(npd);
}
}
return result;
}
}
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<N extends Node> implements Describable<NodeProperty<?>>, 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<Environment> 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);
}
}
}
}
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<NodeProperty<?>> {
protected NodePropertyDescriptor() {}
/**
* Returns true if this {@link NodeProperty} type is applicable to the
* given job type.
*
* <p>
* The default implementation of this method checks if the given node type is assignable to 'N' of
* {@link NodeProperty}<tt>&lt;N></tt>, 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<? extends Node> 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.");
}
}
}
......@@ -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<String,String> 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<String> 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<String> 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<String,String> 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<AntInstallation> {
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<String, String> environment) {
return new AntInstallation(name, Util.replaceMacro(antHome, environment));
}
}
}
......@@ -66,26 +66,7 @@ public abstract class BuildWrapper implements ExtensionPoint, Describable<BuildW
* It is expected that the subclasses of {@link BuildWrapper} extends this
* class and implements its own semantics.
*/
public abstract class Environment {
/**
* Adds environmental variables for the builds to the given map.
*
* <p>
* 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.
*
* <p>
* When this method is invoked, the map already contains the
* current "planned export" list.
*
* @param env
* never null.
*/
public void buildEnvVars(Map<String,String> env) {
// no-op by default
}
public abstract class Environment extends hudson.model.Environment {
/**
* Runs after the {@link Builder} completes, and performs a tear down.
*
......
......@@ -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<String> vr = build.getBuildVariableResolver();
Map<String,String> 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<String,String> 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<MavenInstallation> {
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<String, String> environment) {
return new MavenInstallation(name, Util.replaceMacro(mavenHome, environment));
}
}
/**
......
......@@ -90,6 +90,14 @@ THE SOFTWARE.
title="${%statsBlurb}"
help="/help/system-config/usage-statistics.html" />
<j:set var="propertyDescriptors" value="${it.nodePropertyDescriptors}" />
<j:if test="${!empty(propertyDescriptors)}">
<f:descriptorList title="${%Global properties}"
name="nodeProperties"
descriptors="${propertyDescriptors}"
instances="${it.nodeProperties}" />
</j:if>
<f:section title="${%JDKs}">
<f:entry title="${%JDK installations}"
description="${%List of JDK installations on this system}">
......
......@@ -84,4 +84,7 @@ THE SOFTWARE.
</j:forEach>
</f:dropdownList>
</j:if>
<f:descriptorList title="Node Properties" descriptors="${it.nodePropertyDescriptors}" field="nodeProperties" />
</j:jelly>
\ No newline at end of file
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="List of key-value pairs" help="${descriptor.getHelpPage()}">
<f:repeatable var="env" items="${instance.envVars.entrySet()}">
<table width="100%">
<f:entry title="${%name}">
<input class="setting-input" name="env.key" type="text"
value="${env.key}" />
</f:entry>
<f:entry title="${%value}">
<input class="setting-input" name="env.value" type="text"
value="${env.value}" />
</f:entry>
<f:entry title="">
<div align="right">
<f:repeatableDeleteButton />
</div>
</f:entry>
</table>
</f:repeatable>
</f:entry>
</j:jelly>
\ No newline at end of file
......@@ -55,7 +55,7 @@ THE SOFTWARE.
<j:set var="targetType" value="${h.defaulted(attrs.targetType,it.class)}"/>
<j:set var="instances" value="${h.ifThenElse(attrs.instances!=null,attrs.instances,instance[field])}"/>
<f:section title="${attrs.title}" name="${attrs.field}">
<f:section title="${attrs.title}" name="${h.defaulted(attrs.field,attrs.name)}">
<j:if test="${attrs.field!=null}">
<tr>
<td>
......
......@@ -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<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties() {
throw new UnsupportedOperationException();
}
@Override
public void setNodeProperties(Collection<NodeProperty<?>> nodeProperties)
throws IOException {
throw new UnsupportedOperationException();
}
}
static class EphemeralNode extends DummyNode implements hudson.slaves.EphemeralNode {
public Cloud getCloud() {
......
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<String, String> envVars;
public Map<String, String> getEnvVars() {
return envVars;
}
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
envVars = build.getEnvVars();
return true;
}
public Descriptor<Builder> getDescriptor() {
return DESCRIPTOR;
}
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
public static final class DescriptorImpl extends Descriptor<Builder> {
public Builder newInstance(StaplerRequest req, JSONObject data) {
throw new UnsupportedOperationException();
}
public String getDisplayName() {
return "Capture Environment Variables";
}
}
static {
BuildStep.BUILDERS.add(DESCRIPTOR);
}
}
......@@ -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,32 +53,7 @@ import hudson.tasks.Maven;
import hudson.tasks.Maven.MavenInstallation;
import hudson.util.ProcessTreeKiller;
import hudson.util.StreamTaskListener;
import junit.framework.TestCase;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.FileUtils;
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.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.security.HashUserRealm;
import org.mortbay.jetty.security.UserRealm;
import org.mortbay.jetty.webapp.Configuration;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.jetty.webapp.WebXmlConfiguration;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
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;
......@@ -98,20 +62,61 @@ 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.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.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.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;
import org.mortbay.jetty.security.UserRealm;
import org.mortbay.jetty.webapp.Configuration;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.jetty.webapp.WebXmlConfiguration;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.CSSParseException;
import org.w3c.css.sac.ErrorHandler;
import org.xml.sax.SAXException;
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<String,String> 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;
}
}
/**
......
<project name="proba" default="viewprops">
<target name="viewprops">
<echo message="The base directory: ${basedir}"/>
<echo message="This file: ${ant.file}"/>
<echo message="Ant version: ${ant.version}"/>
<echo message="Project name: ${ant.project.name}"/>
<echo message="Java version: ${ant.java.version}"/>
<echo message="Ant home: ${ant.home}"/>
<echo message="Test property: ${test.property}"/>
</target>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>test.prop.hudson</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<properties>
<test>default</test>
</properties>
<build>
</build>
</project>
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
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.
-->
<j:jelly xmlns:j="jelly:core" />
\ No newline at end of file
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
.<NodeProperty<?>>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<String, String> envVars = builder.getEnvVars();
Assert.assertEquals("value", envVars.get("KEY1"));
Assert.assertEquals("value", envVars.get("KEY2"));
}
}
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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String,String> 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<String, String> 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();
}
}
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<String, String> additionalEnv = new TreeMap<String, String>();
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");
}
}
......@@ -23,36 +23,115 @@
*/
package hudson.tasks;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
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;
/**
* @author Kohsuke Kawaguchi
*/
public class MavenTest extends HudsonTestCase {
/**
* 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"));
WebClient webClient = new WebClient();
HtmlPage page = webClient.getPage(p,"configure");
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);
}
/**
* 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"));
WebClient webClient = new WebClient();
HtmlPage page = webClient.getPage(p, "configure");
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);
}
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());
}
}
<div>
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.
</div>
<div>
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.
</div>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册