提交 1b229432 编写于 作者: K kohsuke

Merged revisions...

Merged revisions 9727,9739,9765-9766,9927,10332,10334-10338,10340,10416,10421,10553,10839-10845,10876 via svnmerge from 
https://www.dev.java.net/svn/hudson/branches/tom

........
  r9727 | huybrechts | 2008-05-31 12:49:18 -0700 (Sat, 31 May 2008) | 3 lines
  
  Issue 1659
  - cleaning up static Trigger, Hudson instances
  - resetting commons-logging
........
  r9739 | huybrechts | 2008-06-01 12:05:51 -0700 (Sun, 01 Jun 2008) | 1 line
  
  #1770 catching and logging InstantiationError
........
  r9765 | huybrechts | 2008-06-02 11:34:45 -0700 (Mon, 02 Jun 2008) | 1 line
  
  project-based security: fixed a new isAdmin call
........
  r9766 | huybrechts | 2008-06-02 11:35:27 -0700 (Mon, 02 Jun 2008) | 1 line
  
  "theInstance = null" caused some reloading problems
........
  r9927 | huybrechts | 2008-06-07 15:31:20 -0700 (Sat, 07 Jun 2008) | 8 lines
  
  Initial commit of parameterized builds. Provided functionality:
  - users can define parameters per project (only string paramters for now)
  - if a project has parameters, the 'build' button will redirect to an input page
  - parameter values can be used as ${variable} in Ant, Maven and CommandInterpreter builders
  - after the build, parameter values can be viewed in a separate page
  - tasks for parameterized builds are persisted in the queue over restarts
  - queue persistence is now in XML (with backwards compatibility for text format)
........
  r10332 | huybrechts | 2008-06-23 05:32:45 -0700 (Mon, 23 Jun 2008) | 1 line
  
  made EmbedderLoggerImpl public for use in plugins
........
  r10334 | huybrechts | 2008-06-23 05:48:10 -0700 (Mon, 23 Jun 2008) | 1 line
  
  [HUDSON-1914] additional parameter types (job, run), bugfixes
........
  r10335 | huybrechts | 2008-06-23 05:49:10 -0700 (Mon, 23 Jun 2008) | 1 line
  
  [HUDSON-1573] initialize servlet filters
........
  r10336 | huybrechts | 2008-06-23 05:52:14 -0700 (Mon, 23 Jun 2008) | 1 line
  
  [HUDSON-1915] onStarted onevent
........
  r10337 | huybrechts | 2008-06-23 06:07:32 -0700 (Mon, 23 Jun 2008) | 4 lines
  
  [HUDSON-1504] basic ui for job-based access control
  
  This allows assigning permissions per user and per job.
  It is still possible to provide defaults using a global matrix.
........
  r10338 | huybrechts | 2008-06-23 06:18:13 -0700 (Mon, 23 Jun 2008) | 3 lines
  
  [HUDSON-1504] basic ui for job-based access control
  
  adding it to the list...
........
  r10340 | huybrechts | 2008-06-23 07:23:12 -0700 (Mon, 23 Jun 2008) | 1 line
  
  [HUDSON-1504] fix compile error
........
  r10416 | huybrechts | 2008-06-25 01:13:11 -0700 (Wed, 25 Jun 2008) | 1 line
  
  initial commit staging plugin: supports doing a maven release to a staging repository, serving that repository for Hudson, and uploading it on demand
........
  r10421 | huybrechts | 2008-06-25 12:32:06 -0700 (Wed, 25 Jun 2008) | 1 line
  
  [HUDSON-1954] initial commit jbpm plugin and staging workflow example
........
  r10553 | huybrechts | 2008-07-01 14:13:57 -0700 (Tue, 01 Jul 2008) | 1 line
  
  moving registration for parameter definition out of registered class
........
  r10839 | kohsuke | 2008-07-15 13:48:05 -0700 (Tue, 15 Jul 2008) | 6 lines
  
  Making a few adjustments in preparation of merging back to the trunk.
  
  - doc updates
  - added QueueTaskFilter as a plain delegation implementation to simplify ParameterizedProjectTask.
  - renamed ParameterDefinition.newInstance(...) to createValue, to avoid having two newInstance methods
    in this part of the system that does different things
........
  r10840 | kohsuke | 2008-07-15 13:51:16 -0700 (Tue, 15 Jul 2008) | 1 line
  
  Hudson's coding convention is to use WS and no TAB.
........
  r10841 | kohsuke | 2008-07-15 14:34:37 -0700 (Tue, 15 Jul 2008) | 2 lines
  
  - making ParameterValue an abstract class to allow evolution without breaking plugins in the future
  - tweaked the UI a bit so that parameterization don't get too much visibility.
........
  r10842 | kohsuke | 2008-07-15 14:49:35 -0700 (Tue, 15 Jul 2008) | 2 lines
  
  improving the UI a bit.
  Added an icon
........
  r10843 | kohsuke | 2008-07-15 15:08:28 -0700 (Tue, 15 Jul 2008) | 1 line
  
  adding help
........
  r10844 | kohsuke | 2008-07-15 15:10:42 -0700 (Tue, 15 Jul 2008) | 1 line
  
  TAB->WS
........
  r10845 | kohsuke | 2008-07-15 15:44:47 -0700 (Tue, 15 Jul 2008) | 2 lines
  
  - Use HTTP status code to notify the automated clients that the build triggering failed.
  - Overload the "/build" URL to render the parameter entry form.
........
  r10876 | kohsuke | 2008-07-18 13:52:51 -0700 (Fri, 18 Jul 2008) | 10 lines
  
  Giving more hooks to ParameterValue to affect a build
  
   - abstracting the variable substitution process so that the actual syntax for marking
     variables (e.g., %VAR% vs ${var} vs #{xyz}) and the actual resolution process are
     orthogonal.
  
   - parameter values can now contribute BuildWrapper to a build.
  
   - parameter values can now contribute environment variables to a build.
........


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@10950 71c3de6d-444a-0410-be80-ed276b4c234a
上级 355a9da6
......@@ -12,6 +12,7 @@ import hudson.model.Job;
import hudson.model.JobPropertyDescriptor;
import hudson.model.ModelObject;
import hudson.model.Node;
import hudson.model.ParameterDefinition;
import hudson.model.Project;
import hudson.model.Run;
import hudson.model.TopLevelItem;
......@@ -554,6 +555,10 @@ public class Functions {
return RetentionStrategy.LIST;
}
public static List<Descriptor<ParameterDefinition>> getParameterDescriptors() {
return ParameterDefinition.LIST;
}
/**
* Computes the path to the icon of the given action
* from the context path.
......
......@@ -5,6 +5,7 @@ import hudson.model.Hudson;
import static hudson.model.Hudson.isWindows;
import hudson.util.IOException2;
import hudson.util.QuotedStringTokenizer;
import hudson.util.VariableResolver;
import hudson.Proc.LocalProc;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
......@@ -99,6 +100,16 @@ 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>.
*
* <p>
* Unlike shell, undefined variables are left as-is (this behavior is the same as Ant.)
*/
public static String replaceMacro(String s, VariableResolver<String> resolver) {
int idx=0;
while(true) {
Matcher m = VARIABLE.matcher(s);
......@@ -107,7 +118,7 @@ public class Util {
String key = m.group().substring(1);
if(key.charAt(0)=='{') key = key.substring(1,key.length()-1);
String value = properties.get(key);
String value = resolver.resolve(key);
if(value==null)
idx = m.start()+1; // skip this
else {
......
......@@ -36,6 +36,7 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.Locale;
import java.util.Properties;
import java.util.Timer;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -152,6 +153,8 @@ public class WebAppMain implements ServletContextListener {
try {
computeVersion(context);
Trigger.timer = new Timer("Hudson cron thread");
try {
context.setAttribute(APP,new Hudson(home,context));
} catch( IOException e ) {
......
......@@ -13,7 +13,7 @@ import java.util.StringTokenizer;
*
* @author Kohsuke Kawaguchi
*/
final class EmbedderLoggerImpl extends AbstractMavenEmbedderLogger {
public final class EmbedderLoggerImpl extends AbstractMavenEmbedderLogger {
private final PrintStream logger;
public EmbedderLoggerImpl(TaskListener listener) {
......
......@@ -17,6 +17,7 @@ import hudson.model.BuildListener;
import hudson.model.Fingerprint;
import hudson.model.Hudson;
import hudson.model.Result;
import hudson.model.ParametersAction;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
import hudson.util.ArgumentListBuilder;
......@@ -289,8 +290,15 @@ public final class MavenModuleSetBuild extends AbstractBuild<MavenModuleSet,Mave
} else {
// do builds here
try {
List<BuildWrapper> wrappers = new ArrayList<BuildWrapper>();
for (BuildWrapper w : project.getBuildWrappers())
wrappers.add(w);
ParametersAction parameters = getAction(ParametersAction.class);
if (parameters != null)
parameters.createBuildWrappers(MavenModuleSetBuild.this,wrappers);
buildEnvironments = new ArrayList<BuildWrapper.Environment>();
for( BuildWrapper w : project.getBuildWrappers()) {
for( BuildWrapper w : wrappers) {
BuildWrapper.Environment e = w.setUp((AbstractBuild)MavenModuleSetBuild.this, launcher, listener);
if(e==null)
return Result.FAILURE;
......
......@@ -378,10 +378,14 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
jdk.buildEnvVars(env);
project.getScm().buildEnvVars(this,env);
if(buildEnvironments!=null) {
ParametersAction parameters = getAction(ParametersAction.class);
if (parameters != null)
parameters.buildEnvVars(this,env);
if(buildEnvironments!=null)
for (BuildWrapper.Environment e : buildEnvironments)
e.buildEnvVars(env);
}
return env;
}
......
......@@ -367,7 +367,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
*/
public boolean scheduleBuild() {
if(isDisabled()) return false;
return Hudson.getInstance().getQueue().add(this);
return Hudson.getInstance().getQueue().add(this, getQuietPeriod());
}
/**
......@@ -845,6 +845,10 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
protected HistoryWidget createHistoryWidget() {
return new BuildHistoryWidget<R>(this,getBuilds(),HISTORY_ADAPTER);
}
public boolean isParameterized() {
return getProperty(ParametersDefinitionProperty.class) != null;
}
//
//
......@@ -857,6 +861,13 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
public void doBuild( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
// if a build is parameterized, let that take over
ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
if (pp != null) {
pp._doBuild(req,rsp);
return;
}
String delay = req.getParameter("delay");
if (delay!=null) {
if (!isDisabled()) {
......
......@@ -12,7 +12,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
......@@ -94,14 +93,19 @@ public abstract class Build <P extends Project<P,B>,B extends Build<P,B>>
buildEnvironments = new ArrayList<Environment>();
try {
for( BuildWrapper w : project.getBuildWrappers().values() ) {
List<BuildWrapper> wrappers = new ArrayList<BuildWrapper>(project.getBuildWrappers().values());
ParametersAction parameters = getAction(ParametersAction.class);
if (parameters != null)
parameters.createBuildWrappers(Build.this,wrappers);
for( BuildWrapper w : wrappers ) {
Environment e = w.setUp((AbstractBuild)Build.this, launcher, listener);
if(e==null)
return Result.FAILURE;
buildEnvironments.add(e);
}
if(!build(listener,project.getBuilders()))
return Result.FAILURE;
} finally {
......
......@@ -56,7 +56,7 @@ public class Executor extends Thread implements ModelObject {
try {
finishTime = System.currentTimeMillis();
while(true) {
if(Hudson.getInstance().isTerminating())
if(Hudson.getInstance() == null || Hudson.getInstance().isTerminating())
return;
synchronized(owner) {
......
......@@ -76,6 +76,7 @@ import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.acegisecurity.ui.AbstractProcessingFilter;
import static org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY;
import org.apache.commons.logging.LogFactory;
import org.kohsuke.stapler.MetaClass;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
......@@ -1424,6 +1425,7 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
}
ExternalJob.reloadThread.interrupt();
Trigger.timer.cancel();
Trigger.timer = null;
if(tcpSlaveAgentListener!=null)
tcpSlaveAgentListener.shutdown();
......@@ -1436,6 +1438,8 @@ public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node,
getQueue().save();
threadPoolForLoad.shutdown();
LogFactory.releaseAll();
}
public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
......
package hudson.model;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
public class JobParameterDefinition extends ParameterDefinition {
@DataBoundConstructor
public JobParameterDefinition(String name) {
super(name);
}
@Override
public ParameterDescriptor getDescriptor() {
return DESCRIPTOR;
}
public static final ParameterDescriptor DESCRIPTOR = new DescriptorImpl();
public static class DescriptorImpl extends ParameterDescriptor {
protected DescriptorImpl() {
super(JobParameterDefinition.class);
}
@Override
public String getDisplayName() {
return "Project Parameter";
}
@Override
public ParameterDefinition newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return req.bindJSON(JobParameterDefinition.class, formData);
}
}
@Override
public ParameterValue createValue(StaplerRequest req, JSONObject jo) {
return req.bindJSON(JobParameterValue.class, jo);
}
}
package hudson.model;
import org.kohsuke.stapler.DataBoundConstructor;
import java.util.Map;
public class JobParameterValue extends ParameterValue {
public final Job job;
@DataBoundConstructor
public JobParameterValue(String name, Job job) {
super(name);
this.job = job;
}
/**
* Exposes the name/value as an environment variable.
*/
@Override
public void buildEnvVars(AbstractBuild<?,?> build, Map<String,String> env) {
// TODO: check with Tom if this is really what he had in mind
env.put(name.toUpperCase(),job.toString());
}
}
......@@ -61,6 +61,10 @@ public abstract class JobProperty<J extends Job<?,?>> implements Describable<Job
/**
* {@link Action} to be displayed in the job page.
*
* <p>
* Returning non-null from this method allows a job property to add an item
* to the left navigation bar in the job page.
*
* @param job
* Always the same as {@link #owner} but passed in anyway for backward compatibility (I guess.)
* You really need not use this value at all.
......
......@@ -17,5 +17,6 @@ public class Jobs {
* @see JobPropertyDescriptor#getPropertyDescriptors(Class)
*/
public static final List<JobPropertyDescriptor> PROPERTIES = Descriptor.toList(
ParametersDefinitionProperty.DESCRIPTOR
);
}
package hudson.model;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import hudson.util.DescriptorList;
import hudson.ExtensionPoint;
/**
* Defines a parameter for a build.
*
* <p>
* In Hudson, a user can configure a job to require parameters for a build.
* For example, imagine a test job that takes the bits to be tested as a parameter.
*
* <p>
* The actual meaning and the purpose of parameters are entirely up to users, so
* what the concrete parameter implmentation is pluggable. Write subclasses
* in a plugin and hook it up to {@link #LIST} to register it.
*
* <p>
* Three classes are used to model build parameters. First is the
* {@link ParameterDescriptor}, which tells Hudson what kind of implementations are
* available. From {@link ParameterDescriptor#newInstance(StaplerRequest, JSONObject)},
* Hudson creates {@link ParameterDefinition}s based on the job configuration.
* For example, if the user defines two string parameters "database-type" and
* "appserver-type", we'll get two {@link StringParameterDefinition} instances
* with their respective names.
*
* <p>
* When a job is configured with {@link ParameterDefinition} (or more precisely,
* {@link ParametersDefinitionProperty}, which in turns retains {@link ParameterDefinition}s),
* user would have to enter the values for the defined build parameters.
* The {@link #createValue(StaplerRequest, JSONObject)} method is used to convert this
* form submission into {@link ParameterValue} objects, which are then accessible
* during a build.
*
*
*
* <h2>Persistence</h2>
* <p>
* Instances of {@link ParameterDefinition}s are persisted into job <tt>config.xml</tt>
* through XStream.
*
*
* <h2>Assocaited Views</h2>
* <h4>config.jelly</h4>
* <p>
* {@link ParameterDefinition} class uses <tt>config.jelly</tt> to provide contribute a form
* fragment in the job configuration screen. Values entered there is fed back to
* {@link ParameterDescriptor#newInstance(StaplerRequest, JSONObject)} to create {@link ParameterDefinition}s.
*
* <h4>index.jelly</h4>
* The <tt>index.jelly</tt> view contributes a form fragment in the page where the user
* enters actual values of parameters for a build. The result of this form submission
* is then fed to {@link ParameterDefinition#createValue(StaplerRequest, JSONObject)} to
* create {@link ParameterValue}s.
*
* TODO: what Jelly pages does this object need for rendering UI?
* TODO: {@link ParameterValue} needs to have some mechanism to expose values to the build
* @see StringParameterDefinition
*/
public abstract class ParameterDefinition implements
Describable<ParameterDefinition>, ExtensionPoint {
private final String name;
public String getName() {
return name;
}
public ParameterDefinition(String name) {
super();
this.name = name;
}
/**
* {@inheritDoc}
*/
public abstract ParameterDescriptor getDescriptor();
public abstract ParameterValue createValue(StaplerRequest req, JSONObject jo);
/**
* A list of available parameter definition types
*/
public static final DescriptorList<ParameterDefinition> LIST = new DescriptorList<ParameterDefinition>();
public abstract static class ParameterDescriptor extends
Descriptor<ParameterDefinition> {
protected ParameterDescriptor(Class<? extends ParameterDefinition> klazz) {
super(klazz);
}
public String getValuePage() {
return getViewPage(clazz, "index.jelly");
}
@Override
public String getDisplayName() {
return "Parameter";
}
}
}
package hudson.model;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
import hudson.util.VariableResolver;
import java.util.Map;
import org.kohsuke.stapler.StaplerRequest;
import net.sf.json.JSONObject;
/**
* A value for a parameter in a build.
*
* Created by {@link ParameterDefinition#createValue(StaplerRequest, JSONObject)} for
* a particular build (although this 'owner' build object is passed in for every method
* call as a parameter so that the parameter won't have to persist it.)
*
* <h2>Persistence</h2>
* <p>
* Instances of {@link ParameterValue}s are persisted into build's <tt>build.xml</tt>
* through XStream (via {@link ParametersAction}), so instances need to be persistable.
*
* <h2>Assocaited Views</h2>
* <h4>value.jelly</h4>
* The <tt>value.jelly</tt> view contributes a UI fragment to display the parameter
* values used for a build.
*
* <h2>Notes</h2>
* <ol>
* <li>{@link ParameterValue} is used to record values of the past build, but
* {@link ParameterDefinition} used back then might be gone already, or represent
* a different parameter now. So don't try to use the name to infer
* {@link ParameterDefinition} is.
* </ol>
* @see ParameterDefinition
*/
public abstract class ParameterValue {
protected final String name;
protected ParameterValue(String name) {
this.name = name;
}
/**
* Name of the parameter.
*
* This uniquely distinguishes {@link ParameterValue} among other parameters
* for the same build. This must be the same as {@link ParameterDefinition#getName()}.
*/
public final String getName() {
return name;
}
/**
* Adds environmental variables for the builds to the given map.
*
* <p>
* This provides a means for a parameter to pass the parameter
* values to the build to be performed.
*
* <p>
* When this method is invoked, the map already contains the
* current "planned export" list. The implementation is
* expected to add more values to this map (or do nothing)
*
* <p>
* Environment variables should be by convention all upper case.
* (This is so that a Windows/Unix heterogenous environment
* won't get inconsistent result depending on which platform to
* execute.)
*
* @param env
* never null.
* @param build
* The build for which this parameter is being used. Never null.
*/
public void buildEnvVars(AbstractBuild<?,?> build, Map<String,String> env) {
// no-op by default
}
/**
* Called at the beginning of a build to let a {@link ParameterValue}
* contributes a {@link BuildWrapper} to the build.
*
* <p>
* This provides a means for a parameter to perform more extensive
* set up / tear down during a build.
*
* @param build
* The build for which this parameter is being used. Never null.
* @return
* null if the parameter has no {@link BuildWrapper} to contribute to.
*/
public BuildWrapper createBuildWrapper(AbstractBuild<?,?> build) {
return null;
}
/**
* Returns a {@link VariableResolver} so that other components like {@link Builder}s
* can perform variable substitution to reflect parameter values into the build process.
*
* <p.
* This is yet another means in which a {@link ParameterValue} can influence
* a build.
*
* @param build
* The build for which this parameter is being used. Never null.
* @return
* if the parameter value is not interested in participating to the
* variable replacement process, return {@link VariableResolver#NONE}.
*/
public VariableResolver<String> createVariableResolver(AbstractBuild<?,?> build) {
return VariableResolver.NONE;
}
}
package hudson.model;
import hudson.model.Queue.Executable;
import hudson.util.QueueTaskFilter;
import java.io.IOException;
import java.util.List;
/**
* A task representing a project that should be built with a certain set of
* parameter values
*/
public class ParameterizedProjectTask extends QueueTaskFilter {
private final AbstractProject<?, ?> project;
private final List<ParameterValue> parameters;
public ParameterizedProjectTask(AbstractProject<?, ?> project,
List<ParameterValue> parameters) {
super(project);
this.project = project;
this.parameters = parameters;
}
@Override
public Executable createExecutable() throws IOException {
AbstractBuild<?, ?> build = project.createExecutable();
build.addAction(new ParametersAction(parameters, build));
return build;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((parameters == null) ? 0 : parameters.hashCode());
result = prime * result + ((project == null) ? 0 : project.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ParameterizedProjectTask other = (ParameterizedProjectTask) obj;
if (parameters == null) {
if (other.parameters != null)
return false;
} else if (!parameters.equals(other.parameters)) {
return false;
}
if (project != other.project) {
return false;
}
return true;
}
}
package hudson.model;
import hudson.Util;
import hudson.tasks.BuildWrapper;
import hudson.util.VariableResolver;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Records the parameter values used for a build.
*
* <P>
* This object is associated with the build record so that we remember what parameters
* were used for what build.
*/
public class ParametersAction implements Action, Iterable<ParameterValue> {
private final List<ParameterValue> parameters;
private final AbstractBuild<?, ?> build;
public ParametersAction(List<ParameterValue> parameters, AbstractBuild<?, ?> build) {
this.parameters = parameters;
this.build = build;
}
public void createBuildWrappers(AbstractBuild<?,?> build, Collection<? super BuildWrapper> result) {
for (ParameterValue p : parameters) {
BuildWrapper w = p.createBuildWrapper(build);
if(w!=null) result.add(w);
}
}
public void buildEnvVars(AbstractBuild<?,?> build, Map<String,String> env) {
for (ParameterValue p : parameters)
p.buildEnvVars(build,env);
}
/**
* Performs a variable subsitution to the given text and return it.
*/
public String substitute(AbstractBuild<?,?> build, String text) {
return Util.replaceMacro(text,createVariableResolver(build));
}
/**
* Creates an {@link VariableResolver} that aggregates all the parameters.
*/
public VariableResolver<String> createVariableResolver(AbstractBuild<?,?> build) {
VariableResolver[] resolvers = new VariableResolver[parameters.size()];
int i=0;
for (ParameterValue p : parameters)
resolvers[i++] = p.createVariableResolver(build);
return new VariableResolver.Union<String>(resolvers);
}
public AbstractBuild<?, ?> getBuild() {
return build;
}
public Iterator<ParameterValue> iterator() {
return parameters.iterator();
}
public List<ParameterValue> getParameters() {
return parameters;
}
@Override
public String getDisplayName() {
return "Parameters";
}
@Override
public String getIconFileName() {
return "document-properties.gif";
}
@Override
public String getUrlName() {
return "parameters";
}
}
package hudson.model;
import hudson.StructuredForm;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletException;
/**
* Keeps a list of the parameters defined for a project.
*
* <p>
* This class also implements {@link Action} so that <tt>index.jelly</tt> provides
* a form to enter build parameters.
*/
public class ParametersDefinitionProperty extends JobProperty<AbstractProject<?, ?>>
implements Action {
private final List<ParameterDefinition> parameterDefinitions;
public ParametersDefinitionProperty(List<ParameterDefinition> parameterDefinitions) {
this.parameterDefinitions = parameterDefinitions;
}
public AbstractProject<?,?> getOwner() {
return owner;
}
public List<ParameterDefinition> getParameterDefinitions() {
return parameterDefinitions;
}
@Override
public Action getJobAction(AbstractProject<?, ?> job) {
return this;
}
public AbstractProject<?, ?> getProject() {
return (AbstractProject<?, ?>) owner;
}
/**
* Interprets the form submission and schedules a build for a parameterized job.
*
* <p>
* This method is supposed to be invoked from {@link AbstractProject#doBuild(StaplerRequest, StaplerResponse)}.
*/
public void _doBuild(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
if(!req.getMethod().equals("POST")) {
// show the parameter entry form.
req.getView(this,"index.jelly").forward(req,rsp);
return;
}
List<ParameterValue> values = new ArrayList<ParameterValue>();
JSONObject formData = StructuredForm.get(req);
JSONArray a = JSONArray.fromObject(formData.get("parameter"));
for (Object o : a) {
JSONObject jo = (JSONObject) o;
String name = jo.getString("name");
ParameterDefinition d = getParameterDefinition(name);
if(d==null)
throw new IllegalArgumentException("No such parameter definition: " + name);
values.add(d.createValue(req, jo));
}
Hudson.getInstance().getQueue().add(
new ParameterizedProjectTask(owner, values), 0);
// send the user back to the job top page.
rsp.sendRedirect(".");
}
/**
* Gets the {@link ParameterDefinition} of the given name, if any.
*/
public ParameterDefinition getParameterDefinition(String name) {
for (ParameterDefinition pd : parameterDefinitions)
if (pd.getName().equals(name))
return pd;
return null;
}
@Override
public JobPropertyDescriptor getDescriptor() {
return DESCRIPTOR;
}
public static final JobPropertyDescriptor DESCRIPTOR = new DescriptorImpl();
public static class DescriptorImpl extends JobPropertyDescriptor {
protected DescriptorImpl() {
super(ParametersDefinitionProperty.class);
}
@Override
public boolean isApplicable(Class<? extends Job> jobType) {
return AbstractProject.class.isAssignableFrom(jobType);
}
@Override
public JobProperty<?> newInstance(StaplerRequest req,
JSONObject formData) throws FormException {
if (formData.isNullObject()) {
return null;
}
List<ParameterDefinition> parameterDefinitions = Descriptor.newInstancesFromHeteroList(
req, formData, "parameter", ParameterDefinition.LIST);
if(parameterDefinitions.isEmpty())
return null;
return new ParametersDefinitionProperty(parameterDefinitions);
}
@Override
public String getDisplayName() {
return "Parameters";
}
}
@Override
public String getDisplayName() {
return null;
}
@Override
public String getIconFileName() {
return null;
}
@Override
public String getUrlName() {
return "parameters";
}
static {
ParameterDefinition.LIST.add(StringParameterDefinition.DESCRIPTOR);
}
}
package hudson.model;
import hudson.Util;
import hudson.XmlFile;
import hudson.model.Node.Mode;
import hudson.triggers.SafeTimerTask;
import hudson.triggers.Trigger;
import hudson.util.OneShotEvent;
import org.acegisecurity.AccessDeniedException;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import hudson.util.XStream2;
import javax.management.timer.Timer;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.timer.Timer;
import org.acegisecurity.AccessDeniedException;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
/**
* Build queue.
*
......@@ -130,7 +144,52 @@ public class Queue extends ResourceController {
*/
private final Map<Executor, JobOffer> parked = new HashMap<Executor, JobOffer>();
private final XStream xstream = new XStream2();
public Queue() {
xstream.registerConverter(new AbstractSingleValueConverter() {
@Override
@SuppressWarnings("unchecked")
public boolean canConvert(Class klazz) {
return TopLevelItem.class.isAssignableFrom(klazz);
}
@Override
public Object fromString(String string) {
return Hudson.getInstance().getItem(string);
}
@Override
public String toString(Object item) {
return ((TopLevelItem) item).getName();
}
});
xstream.registerConverter(new AbstractSingleValueConverter() {
@SuppressWarnings("unchecked")
@Override
public boolean canConvert(Class klazz) {
return Run.class.isAssignableFrom(klazz);
}
@Override
public Object fromString(String string) {
String[] split = string.split("#");
String projectName = split[0];
int buildNumber = Integer.parseInt(split[1]);
Job<?,?> job = (Job<?,?>) Hudson.getInstance().getItem(projectName);
Run<?,?> run = job.getBuildByNumber(buildNumber);
return run;
}
@Override
public String toString(Object object) {
Run<?,?> run = (Run<?,?>) object;
return run.getParent().getName() + "#" + run.getNumber();
}
});
// if all the executors are busy doing something, then the queue won't be maintained in
// timely fashion, so use another thread to make sure it happens.
new MaintainTask(this);
......@@ -140,22 +199,32 @@ public class Queue extends ResourceController {
* Loads the queue contents that was {@link #save() saved}.
*/
public synchronized void load() {
// write out the contents of the queue
try {
// first try the old format
File queueFile = getQueueFile();
if (!queueFile.exists())
return;
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(queueFile)));
String line;
while ((line = in.readLine()) != null) {
AbstractProject j = Hudson.getInstance().getItemByFullName(line, AbstractProject.class);
if (j != null)
j.scheduleBuild();
}
in.close();
// discard the queue file now that we are done
queueFile.delete();
if (queueFile.exists()) {
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(queueFile)));
String line;
while ((line = in.readLine()) != null) {
AbstractProject j = Hudson.getInstance().getItemByFullName(line, AbstractProject.class);
if (j != null)
j.scheduleBuild();
}
in.close();
// discard the queue file now that we are done
queueFile.delete();
return;
} else {
queueFile = getXMLQueueFile();
if (queueFile.exists()) {
List<Task> tasks = (List<Task>) new XmlFile(xstream, queueFile).read();
for (Task task: tasks) {
add(task, 0);
}
queueFile.delete();
}
}
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to load the queue file " + getQueueFile(), e);
}
......@@ -165,21 +234,26 @@ public class Queue extends ResourceController {
* Persists the queue contents to the disk.
*/
public synchronized void save() {
// write out the contents of the queue
// write out the tasks on the queue
ArrayList<Task> tasks = new ArrayList<Task>();
for (Item item: getItems()) {
tasks.add(item.task);
}
try {
PrintWriter w = new PrintWriter(new FileOutputStream(
getQueueFile()));
for (Item i : getItems())
w.println(i.task.getName());
w.close();
new XmlFile(xstream, getXMLQueueFile()).write(tasks);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to write out the queue file " + getQueueFile(), e);
}
}
private File getQueueFile() {
return new File(Hudson.getInstance().getRootDir(), "queue.txt");
}
return new File(Hudson.getInstance().getRootDir(), "queue.txt");
}
private File getXMLQueueFile() {
return new File(Hudson.getInstance().getRootDir(), "queue.xml");
}
/**
* Schedule a new build for this project.
......@@ -245,10 +319,10 @@ public class Queue extends ResourceController {
* @return true if the project was indeed in the queue and was removed.
* false if this was no-op.
*/
public synchronized boolean cancel(AbstractProject<?, ?> p) {
public synchronized boolean cancel(Task p) {
LOGGER.fine("Cancelling " + p.getName());
for (Iterator itr = waitingList.iterator(); itr.hasNext();) {
Item item = (Item) itr.next();
for (Iterator<WaitingItem> itr = waitingList.iterator(); itr.hasNext();) {
Item item = itr.next();
if (item.task == p) {
itr.remove();
return true;
......@@ -319,10 +393,10 @@ public class Queue extends ResourceController {
* Left for backward compatibility.
*
* @see #getItem(Task)
*/
public synchronized Item getItem(AbstractProject p) {
return getItem((Task) p);
}
*/
/**
* Returns true if this queue contains the said project.
......
......@@ -781,6 +781,8 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
listener.started();
RunListener.fireStarted(this,listener);
setResult(job.run(listener));
LOGGER.info(toString()+" main build action completed: "+result);
......
......@@ -175,6 +175,8 @@ public final class RunMap<R extends Run<?,R>> extends AbstractMap<Integer,R> imp
builds.put( b.getNumber(), b );
} catch (IOException e) {
e.printStackTrace();
} catch (InstantiationError e) {
e.printStackTrace();
}
}
}
......
package hudson.model;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
public class RunParameterDefinition extends ParameterDefinition {
@DataBoundConstructor
public RunParameterDefinition(String name) {
super(name);
}
@Override
public ParameterDescriptor getDescriptor() {
return DESCRIPTOR;
}
public static final ParameterDescriptor DESCRIPTOR = new DescriptorImpl();
public static class DescriptorImpl extends ParameterDescriptor {
protected DescriptorImpl() {
super(RunParameterDefinition.class);
}
@Override
public String getDisplayName() {
return "Run Parameter";
}
@Override
public ParameterDefinition newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return req.bindJSON(RunParameterDefinition.class, formData);
}
}
@Override
public ParameterValue createValue(StaplerRequest req, JSONObject jo) {
return req.bindJSON(RunParameterValue.class, jo);
}
}
package hudson.model;
import org.kohsuke.stapler.DataBoundConstructor;
import java.util.Map;
public class RunParameterValue extends ParameterValue {
public final Run run;
@DataBoundConstructor
public RunParameterValue(String name, Run run) {
super(name);
this.run = run;
}
/**
* Exposes the name/value as an environment variable.
*/
@Override
public void buildEnvVars(AbstractBuild<?,?> build, Map<String,String> env) {
// TODO: check with Tom if this is really what he had in mind
env.put(name.toUpperCase(),run.toString());
}
}
package hudson.model;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
/**
* Parameter whose value is a string value.
*/
public class StringParameterDefinition extends ParameterDefinition {
private boolean optional;
private String defaultValue;
@DataBoundConstructor
public StringParameterDefinition(String name, String defaultValue,
boolean optional) {
super(name);
this.defaultValue = defaultValue;
this.optional = optional;
}
@Override
public ParameterDescriptor getDescriptor() {
return DESCRIPTOR;
}
public boolean isOptional() {
return optional;
}
public void setOptional(boolean optional) {
this.optional = optional;
}
public String getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
public static final ParameterDescriptor DESCRIPTOR = new DescriptorImpl();
public static class DescriptorImpl extends ParameterDescriptor {
protected DescriptorImpl() {
super(StringParameterDefinition.class);
}
@Override
public String getDisplayName() {
return "String Parameter";
}
@Override
public ParameterDefinition newInstance(StaplerRequest req,
JSONObject formData) throws FormException {
return req.bindJSON(StringParameterDefinition.class, formData);
}
}
@Override
public ParameterValue createValue(StaplerRequest req, JSONObject jo) {
return req.bindJSON(StringParameterValue.class, jo);
}
}
package hudson.model;
import org.kohsuke.stapler.DataBoundConstructor;
import java.util.Map;
/**
* {@link ParameterValue} created from {@link StringParameterDefinition}.
*/
public class StringParameterValue extends ParameterValue {
public final String value;
@DataBoundConstructor
public StringParameterValue(String name, String value) {
super(name);
this.value = value;
}
/**
* Exposes the name/value as an environment variable.
*/
@Override
public void buildEnvVars(AbstractBuild<?,?> build, Map<String,String> env) {
env.put(name.toUpperCase(),value);
}
}
......@@ -36,6 +36,19 @@ public abstract class RunListener<R extends Run> implements ExtensionPoint {
* the build is considered completed, so its status cannot be changed anymore.
*/
public void onCompleted(R r, TaskListener listener) {}
/**
* Called when a build is started (i.e. it was in the queue, and will now start running
* on an executor)
*
* @param r
* The started build.
* @param listener
* The listener for this build. This can be used to produce log messages, for example,
* which becomes a part of the "console output" of this build. But when this method runs,
* the build is considered completed, so its status cannot be changed anymore.
*/
public void onStarted(R r, TaskListener listener) {}
/**
* Registers this object as an active listener so that it can start getting
......@@ -66,4 +79,14 @@ public abstract class RunListener<R extends Run> implements ExtensionPoint {
l.onCompleted(r,listener);
}
}
/**
* Fires the {@link #onStarted} event.
*/
public static void fireStarted(Run r, TaskListener listener) {
for (RunListener l : LISTENERS) {
if(l.targetType.isInstance(r))
l.onStarted(r,listener);
}
}
}
package hudson.security;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.JobProperty;
import hudson.model.JobPropertyDescriptor;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.acegisecurity.acls.sid.GrantedAuthoritySid;
import org.acegisecurity.acls.sid.PrincipalSid;
import org.acegisecurity.acls.sid.Sid;
import org.kohsuke.stapler.StaplerRequest;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
public class AuthorizationMatrixProperty extends JobProperty<Job<?, ?>> {
public static final JobPropertyDescriptor DESCRIPTOR = new DescriptorImpl();
private transient ACL acl = new AclImpl();
private boolean useProjectSecurity;
public boolean isUseProjectSecurity() {
return useProjectSecurity;
}
public void setUseProjectSecurity(boolean useProjectSecurity) {
this.useProjectSecurity = useProjectSecurity;
}
/**
* List up all permissions that are granted.
*
* Strings are either the granted authority or the principal, which is not
* distinguished.
*/
private final Map<Permission, Set<String>> grantedPermissions = new HashMap<Permission, Set<String>>();
private Set<String> sids = new HashSet<String>();
public Set<String> getGroups() {
return sids;
}
/**
* Returns all SIDs configured in this matrix, minus "anonymous"
*
* @return Always non-null.
*/
public List<String> getAllSIDs() {
Set<String> r = new HashSet<String>();
for (Set<String> set : grantedPermissions.values())
r.addAll(set);
r.remove("anonymous");
String[] data = r.toArray(new String[r.size()]);
Arrays.sort(data);
return Arrays.asList(data);
}
/**
* Adds to {@link #grantedPermissions}. Use of this method should be limited
* during construction, as this object itself is considered immutable once
* populated.
*/
protected void add(Permission p, String sid) {
Set<String> set = grantedPermissions.get(p);
if (set == null)
grantedPermissions.put(p, set = new HashSet<String>());
set.add(sid);
sids.add(sid);
}
@Override
public JobPropertyDescriptor getDescriptor() {
return DESCRIPTOR;
}
public static class DescriptorImpl extends JobPropertyDescriptor {
@Override
public JobProperty<?> newInstance(StaplerRequest req,
JSONObject formData) throws FormException {
boolean useProjectSecurity = formData.has("useProjectSecurity");
AuthorizationMatrixProperty amp = new AuthorizationMatrixProperty();
amp.setUseProjectSecurity(useProjectSecurity);
for (Map.Entry<String, Object> r : (Set<Map.Entry<String, Object>>) formData
.getJSONObject("data").entrySet()) {
String sid = r.getKey();
if (r.getValue() instanceof JSONObject) {
for (Map.Entry<String, Boolean> e : (Set<Map.Entry<String, Boolean>>) ((JSONObject) r
.getValue()).entrySet()) {
if (e.getValue()) {
Permission p = Permission.fromId(e.getKey());
amp.add(p, sid);
}
}
}
}
return amp;
}
protected DescriptorImpl() {
super(AuthorizationMatrixProperty.class);
}
@Override
public boolean isApplicable(Class<? extends Job> jobType) {
return true;
}
@Override
public String getDisplayName() {
return "Authorization Matrix";
}
public List<PermissionGroup> getAllGroups() {
return Collections.singletonList(PermissionGroup.get(Item.class));
}
}
private final class AclImpl extends SidACL {
protected Boolean hasPermission(Sid sid, Permission p) {
String s = toString(sid);
for (; p != null; p = p.impliedBy) {
Set<String> set = grantedPermissions.get(p);
if (set != null && set.contains(s))
return true;
}
return false;
}
protected Boolean _hasPermission(Authentication a, Permission permission) {
Boolean b = super._hasPermission(a, permission);
// permissions granted to anonymous users are granted to everyone
if (b == null)
b = hasPermission(ANONYMOUS, permission);
return b;
}
private String toString(Sid p) {
if (p instanceof GrantedAuthoritySid)
return ((GrantedAuthoritySid) p).getGrantedAuthority();
if (p instanceof PrincipalSid)
return ((PrincipalSid) p).getPrincipal();
// hmm...
return p.toString();
}
}
private Object readResolve() {
acl = new AclImpl();
return this;
}
public ACL getACL() {
return acl;
}
/**
* Checks if the given SID has the given permission.
*/
public boolean hasPermission(String sid, Permission p) {
for (; p != null; p = p.impliedBy) {
Set<String> set = grantedPermissions.get(p);
if (set != null && set.contains(sid))
return true;
}
return false;
}
/**
* Works like {@link #add(Permission, String)} but takes both parameters
* from a single string of the form <tt>PERMISSIONID:sid</tt>
*/
private void add(String shortForm) {
int idx = shortForm.indexOf(':');
add(Permission.fromId(shortForm.substring(0, idx)), shortForm
.substring(idx + 1));
}
/**
* Persist {@link ProjectMatrixAuthorizationStrategy} as a list of IDs that
* represent {@link ProjectMatrixAuthorizationStrategy#grantedPermissions}.
*/
public static final class ConverterImpl implements Converter {
public boolean canConvert(Class type) {
return type == AuthorizationMatrixProperty.class;
}
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
AuthorizationMatrixProperty amp = (AuthorizationMatrixProperty) source;
for (Entry<Permission, Set<String>> e : amp.grantedPermissions
.entrySet()) {
String p = e.getKey().getId();
for (String sid : e.getValue()) {
writer.startNode("permission");
context.convertAnother(p + ':' + sid);
writer.endNode();
}
}
}
public Object unmarshal(HierarchicalStreamReader reader,
final UnmarshallingContext context) {
AuthorizationMatrixProperty as = new AuthorizationMatrixProperty();
while (reader.hasMoreChildren()) {
reader.moveDown();
String id = (String) context.convertAnother(as, String.class);
as.add(id);
reader.moveUp();
}
return as;
}
}
}
......@@ -173,7 +173,8 @@ public abstract class AuthorizationStrategy implements Describable<Authorization
LIST.load(FullControlOnceLoggedInAuthorizationStrategy.class);
LIST.load(GlobalMatrixAuthorizationStrategy.class);
LIST.load(LegacyAuthorizationStrategy.class);
LIST.load(ProjectMatrixAuthorizationStrategy.class);
// can't do this in the constructor due to the initialization order
LIST.add(Unsecured.DESCRIPTOR);
}
......
package hudson.security;
import hudson.model.AbstractProject;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.View;
import hudson.util.FormFieldValidator;
import java.util.Collections;
import java.util.List;
import net.sf.json.JSONObject;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.acls.sid.GrantedAuthoritySid;
import org.kohsuke.stapler.StaplerRequest;
/**
* {@link AuthorizationStrategy} that grants control based on owned permissions.
*/
// TODO: ask Tom to explain what this class is.
public class ProjectBasedAuthorizationStrategy extends AuthorizationStrategy {
@Override
public ACL getRootACL() {
return ROOT_ACL;
}
@Override
public ACL getACL(AbstractProject<?, ?> project) {
SparseACL acl = new SparseACL(ROOT_ACL);
acl.add(new GrantedAuthoritySid(createItemAuthority(project)), Permission.FULL_CONTROL, true);
return acl;
}
@Override
public ACL getACL(Computer computer) {
SparseACL acl = new SparseACL(ROOT_ACL);
acl.add(ACL.ANONYMOUS, Hudson.ADMINISTER, false);
acl.add(ACL.EVERYONE, Hudson.ADMINISTER, true);
return acl;
}
@Override
public ACL getACL(View view) {
SparseACL acl = new SparseACL(ROOT_ACL);
acl.add(ACL.ANONYMOUS, Permission.CREATE, false);
acl.add(ACL.EVERYONE, Permission.CREATE, true);
return acl;
}
public List<String> getGroups() {
return Collections.emptyList();
}
public static final SparseACL ROOT_ACL = new SparseACL(null);
static {
ROOT_ACL.add(new GrantedAuthoritySid("admin"), Permission.FULL_CONTROL, true);
ROOT_ACL.add(ACL.ANONYMOUS,FormFieldValidator.CHECK,false);
ROOT_ACL.add(ACL.EVERYONE,FormFieldValidator.CHECK,true);
ROOT_ACL.add(ACL.ANONYMOUS, Permission.CREATE, false);
ROOT_ACL.add(ACL.EVERYONE, Permission.CREATE, true);
ROOT_ACL.add(ACL.EVERYONE,Permission.READ,true);
ROOT_ACL.add(ACL.EVERYONE,Permission.FULL_CONTROL,false);
}
public GrantedAuthority createItemAuthority(Item item) {
return new GrantedAuthorityImpl(item.getFullName());
}
public Descriptor<AuthorizationStrategy> getDescriptor() {
return DESCRIPTOR;
}
public static final Descriptor<AuthorizationStrategy> DESCRIPTOR = new Descriptor<AuthorizationStrategy>(ProjectBasedAuthorizationStrategy.class) {
public String getDisplayName() {
return "Project Based Access Control";
}
public AuthorizationStrategy newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return new ProjectBasedAuthorizationStrategy();
}
public String getHelpFile() {
return "/help/security/full-control-once-logged-in.html";
}
};
}
package hudson.security;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import hudson.model.AbstractProject;
import hudson.model.Descriptor;
import hudson.model.Job;
import hudson.model.JobProperty;
import hudson.model.Jobs;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.acegisecurity.acls.sid.GrantedAuthoritySid;
import org.acegisecurity.acls.sid.PrincipalSid;
import org.acegisecurity.acls.sid.Sid;
import org.kohsuke.stapler.StaplerRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Role-based authorization via a matrix.
*
* @author Kohsuke Kawaguchi
*/
public class ProjectMatrixAuthorizationStrategy extends AuthorizationStrategy {
protected transient ACL acl = new AclImpl();
/**
* List up all permissions that are granted.
*
* Strings are either the granted authority or the principal,
* which is not distinguished.
*/
private final Map<Permission,Set<String>> grantedPermissions = new HashMap<Permission, Set<String>>();
private final Set<String> sids = new HashSet<String>();
/**
* Adds to {@link #grantedPermissions}.
* Use of this method should be limited during construction,
* as this object itself is considered immutable once populated.
*/
protected void add(Permission p, String sid) {
Set<String> set = grantedPermissions.get(p);
if(set==null)
grantedPermissions.put(p,set = new HashSet<String>());
set.add(sid);
sids.add(sid);
}
/**
* Works like {@link #add(Permission, String)} but takes both parameters
* from a single string of the form <tt>PERMISSIONID:sid</tt>
*/
private void add(String shortForm) {
int idx = shortForm.indexOf(':');
add(Permission.fromId(shortForm.substring(0,idx)),shortForm.substring(idx+1));
}
@Override
public ACL getRootACL() {
return acl;
}
@Override
public ACL getACL(AbstractProject<?, ?> project) {
AuthorizationMatrixProperty amp = project.getProperty(AuthorizationMatrixProperty.class);
if (amp != null && amp.isUseProjectSecurity()) {
return amp.getACL();
} else {
return getRootACL();
}
}
public Set<String> getGroups() {
return sids;
}
private Object readResolve() {
acl = new AclImpl();
return this;
}
/**
* Checks if the given SID has the given permission.
*/
public boolean hasPermission(String sid, Permission p) {
for(; p!=null; p=p.impliedBy) {
Set<String> set = grantedPermissions.get(p);
if(set!=null && set.contains(sid))
return true;
}
return false;
}
/**
* Returns all SIDs configured in this matrix, minus "anonymous"
*
* @return
* Always non-null.
*/
public List<String> getAllSIDs() {
Set<String> r = new HashSet<String>();
for (Set<String> set : grantedPermissions.values())
r.addAll(set);
r.remove("anonymous");
String[] data = r.toArray(new String[r.size()]);
Arrays.sort(data);
return Arrays.asList(data);
}
protected final class AclImpl extends SidACL {
protected Boolean hasPermission(Sid p, Permission permission) {
if(ProjectMatrixAuthorizationStrategy.this.hasPermission(toString(p),permission))
return true;
return null;
}
protected Boolean _hasPermission(Authentication a, Permission permission) {
Boolean b = super._hasPermission(a,permission);
// permissions granted to anonymous users are granted to everyone
if(b==null) b=hasPermission(ANONYMOUS,permission);
return b;
}
private String toString(Sid p) {
if (p instanceof GrantedAuthoritySid)
return ((GrantedAuthoritySid) p).getGrantedAuthority();
if (p instanceof PrincipalSid)
return ((PrincipalSid) p).getPrincipal();
// hmm...
return p.toString();
}
}
public Descriptor<AuthorizationStrategy> getDescriptor() {
return DESCRIPTOR;
}
public static final Descriptor<AuthorizationStrategy> DESCRIPTOR = new DescriptorImpl();
/**
* Persist {@link ProjectMatrixAuthorizationStrategy} as a list of IDs that
* represent {@link ProjectMatrixAuthorizationStrategy#grantedPermissions}.
*/
public static final class ConverterImpl implements Converter {
public boolean canConvert(Class type) {
return type== ProjectMatrixAuthorizationStrategy.class;
}
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
ProjectMatrixAuthorizationStrategy strategy = (ProjectMatrixAuthorizationStrategy)source;
for (Entry<Permission, Set<String>> e : strategy.grantedPermissions.entrySet()) {
String p = e.getKey().getId();
for (String sid : e.getValue()) {
writer.startNode("permission");
context.convertAnother(p+':'+sid);
writer.endNode();
}
}
}
public Object unmarshal(HierarchicalStreamReader reader, final UnmarshallingContext context) {
ProjectMatrixAuthorizationStrategy as = new ProjectMatrixAuthorizationStrategy();
while (reader.hasMoreChildren()) {
reader.moveDown();
String id = (String)context.convertAnother(as,String.class);
as.add(id);
reader.moveUp();
}
return as;
}
}
static {
LIST.add(DESCRIPTOR);
Jobs.PROPERTIES.add(AuthorizationMatrixProperty.DESCRIPTOR);
}
public static final class DescriptorImpl extends Descriptor<AuthorizationStrategy> {
public DescriptorImpl() {
super(ProjectMatrixAuthorizationStrategy.class);
}
public String getDisplayName() {
return "Project-based Matrix Authorization Strategy";
}
public AuthorizationStrategy newInstance(StaplerRequest req, JSONObject formData) throws FormException {
ProjectMatrixAuthorizationStrategy gmas = new ProjectMatrixAuthorizationStrategy();
for(Map.Entry<String,JSONObject> r : (Set<Map.Entry<String,JSONObject>>)formData.getJSONObject("data").entrySet()) {
String sid = r.getKey();
for(Map.Entry<String,Boolean> e : (Set<Map.Entry<String,Boolean>>)r.getValue().entrySet()) {
if(e.getValue()) {
Permission p = Permission.fromId(e.getKey());
gmas.add(p,sid);
}
}
}
return gmas;
}
public String getHelpFile() {
return "/help/security/global-matrix.html";
}
public List<PermissionGroup> getAllGroups() {
List<PermissionGroup> groups = new ArrayList<PermissionGroup>(PermissionGroup.getAll());
groups.remove(PermissionGroup.get(Permission.class));
return groups;
}
}
}
......@@ -10,6 +10,7 @@ import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.model.ParametersAction;
import hudson.model.TaskListener;
import hudson.remoting.Callable;
import hudson.util.ArgumentListBuilder;
......@@ -105,6 +106,11 @@ public class Ant extends Builder {
public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
AbstractProject proj = build.getProject();
String targets = this.targets;
ParametersAction parameters = build.getAction(ParametersAction.class);
if (parameters != null)
targets = parameters.substitute(build,targets);
ArgumentListBuilder args = new ArgumentListBuilder();
String execName;
......
......@@ -15,6 +15,7 @@ import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.model.ParametersAction;
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
import hudson.util.ArgumentListBuilder;
......@@ -125,6 +126,11 @@ public class Maven extends Builder {
public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
AbstractProject proj = build.getProject();
String targets = this.targets;
ParametersAction parameters = build.getAction(ParametersAction.class);
if (parameters != null)
targets = parameters.substitute(build,targets);
int startIndex = 0;
int endIndex;
do {
......
......@@ -203,7 +203,7 @@ public abstract class Trigger<J extends Item> implements Describable<Trigger<?>>
* This timer is available for all the components inside Hudson to schedule
* some work.
*/
public static final Timer timer = new Timer("Hudson cron thread");
public static Timer timer;
public static void init() {
long MIN = 1000*60;
......
......@@ -16,14 +16,29 @@ import java.util.Collection;
*/
public class PluginServletFilter implements Filter {
public static CopyOnWriteList<Filter> LIST = new CopyOnWriteList<Filter>();
private static CopyOnWriteList<Filter> LIST = new CopyOnWriteList<Filter>();
private static FilterConfig filterConfig;
public PluginServletFilter() {
}
public void init(FilterConfig filterConfig) throws ServletException {
for (Filter f : LIST)
f.init(filterConfig);
this.filterConfig = filterConfig;
synchronized (LIST) {
for (Filter f : LIST) {
f.init(filterConfig);
}
}
}
public static void addFilter(Filter filter) throws ServletException {
synchronized (LIST) {
if (filterConfig != null) {
filter.init(filterConfig);
}
LIST.add(filter);
}
}
public void doFilter(ServletRequest request, ServletResponse response, final FilterChain chain) throws IOException, ServletException {
......
package hudson.util;
import hudson.model.Queue;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.ResourceList;
import java.io.IOException;
/**
* Convenient base class for {@link Queue.Task} decorators.
*
* @author Kohsuke Kawaguchi
*/
public abstract class QueueTaskFilter implements Queue.Task {
/**
* Delegation target.
*/
protected volatile Queue.Task delegate;
protected QueueTaskFilter(Queue.Task delegate) {
this.delegate = delegate;
}
protected QueueTaskFilter() {
}
public Label getAssignedLabel() {
return delegate.getAssignedLabel();
}
public Node getLastBuiltOn() {
return delegate.getLastBuiltOn();
}
public boolean isBuildBlocked() {
return delegate.isBuildBlocked();
}
public String getWhyBlocked() {
return delegate.getWhyBlocked();
}
public String getName() {
return delegate.getName();
}
public String getFullDisplayName() {
return delegate.getFullDisplayName();
}
public long getEstimatedDuration() {
return delegate.getEstimatedDuration();
}
public Queue.Executable createExecutable() throws IOException {
return delegate.createExecutable();
}
public void checkAbortPermission() {
delegate.checkAbortPermission();
}
public boolean hasAbortPermission() {
return delegate.hasAbortPermission();
}
public String getDisplayName() {
return delegate.getDisplayName();
}
public ResourceList getResourceList() {
return delegate.getResourceList();
}
// Queue.Task hashCode and equals need to be implemented to provide value equality semantics
public abstract int hashCode();
public abstract boolean equals(Object obj);
}
package hudson.util;
import java.util.Map;
import java.util.Collection;
/**
* Resolves variables to its value, while encapsulating
* how that resolution happens.
*
* @author Kohsuke Kawaguchi
*/
public interface VariableResolver<V> {
/**
* Receives a variable name and obtains the value associated with the name.
*
* <p>
* This can be implemented simply on top of a {@link Map} (see {@link ByMap}), or
* this can be used like an expression evaluator.
*
* @param name
* Name of the variable to be resolved.
* Never null, never empty. The name shouldn't include the syntactic
* marker of an expression. IOW, it should be "foo" but not "${foo}".
* A part of the goal of this design is to abstract away the expression
* marker syntax.
* @return
* Object referenced by the name.
* Null if not found.
*/
V resolve(String name);
/**
* Empty resolver that always returns null.
*/
public static final VariableResolver NONE = new VariableResolver() {
public Object resolve(String name) {
return null;
}
};
/**
* {@link VariableResolver} backed by a {@link Map}.
*/
public static final class ByMap<V> implements VariableResolver<V> {
private final Map<String,V> data;
public ByMap(Map<String, V> data) {
this.data = data;
}
public V resolve(String name) {
return data.get(name);
}
}
/**
* Union of multiple {@link VariableResolver}.
*/
public static final class Union<V> implements VariableResolver<V> {
private final VariableResolver<? extends V>[] resolvers;
public Union(VariableResolver<? extends V>... resolvers) {
this.resolvers = resolvers;
}
public Union(Collection<? extends VariableResolver<? extends V>> resolvers) {
this.resolvers = resolvers.toArray(new VariableResolver[resolvers.size()]);
}
public V resolve(String name) {
for (VariableResolver<? extends V> r : resolvers) {
V v = r.resolve(name);
if(v!=null) return v;
}
return null;
}
}
}
<!--
Side panel for the project view.
-->
<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" xmlns:i="jelly:fmt">
<j:jelly xmlns:j="jelly:core" xmlns:x="jelly:xml" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<l:header title="${it.name}">
<st:include page="rssHeader.jelly" />
<link rel="alternate" title="Hudson:${it.name} (changelog)" href="rssChangelog" type="application/rss+xml" />
......@@ -16,7 +16,8 @@
<l:task icon="images/24x24/folder.gif" href="${url}/ws/" title="${%Workspace}" permission="${it.WORKSPACE}" />
<j:if test="${it.configurable}">
<j:if test="${!it.disabled}">
<l:task icon="images/24x24/clock.gif" href="${url}/build?delay=0sec" title="${%Build Now}" onclick="return build(this)" permission="${it.BUILD}" />
<l:task icon="images/24x24/clock.gif" href="${url}/build?delay=0sec" title="${%Build Now}"
onclick="${h.ifThenElse(it.parameterized,null,'return build(this)')}" permission="${it.BUILD}" />
<script>
function build(a) {
new Ajax.Request(a.href,{method:"post"});
......
<!--
This Jelly view shows UI for configuring parameter definitions as a part of the job configuration.
-->
<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"
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
<f:textbox name="parameter.name" value="${instance.name}"/>
</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"
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
<l:layout title="${it.displayName}">
<st:include page="sidepanel.jelly" it="${it.build}" />
<l:main-panel>
<h1>${%Build} #${it.build.number}</h1>
<l:pane title="Parameters" width="3">
<j:forEach var="parameterValue" items="${it.parameters}">
<st:include it="${parameterValue}"
page="value.jelly" />
</j:forEach>
</l:pane>
</l:main-panel>
</l:layout>
</j:jelly>
<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"
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
<f:optionalBlock name="parameterized" title="${%This build is parameterized}"
checked="${instance!=null}" help="/help/project-config/parameterized-build.html">
<f:nested>
<f:hetero-list name="parameter" hasHeader="true"
descriptors="${h.getParameterDescriptors()}" items="${instance.parameterDefinitions}"
addCaption="${%Add Parameter}" />
</f:nested>
</f:optionalBlock>
</j:jelly>
\ No newline at end of file
<!--
This view is rendered as /hudson/job/XYZ/build
-->
<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"
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
<!--
send back 4xx code so that machine agents don't confuse this form with successful build triggering
405 is "Method Not Allowed" and this fits here because we need POST.
-->
<st:statusCode value="405" />
<l:layout title="${it.displayName}">
<st:include page="sidepanel.jelly" it="${it.project}" />
<l:main-panel>
<h1>${it.owner.pronoun} ${it.owner.displayName}</h1>
<p>${%description}</p>
<f:form method="post" action="build">
<j:forEach var="parameterDefinition" items="${it.parameterDefinitions}">
<st:include it="${parameterDefinition}"
page="${parameterDefinition.descriptor.valuePage}" />
</j:forEach>
<f:block>
<f:submit value="${%Build}" />
</f:block>
</f:form>
</l:main-panel>
</l:layout>
</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"
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
<f:entry title="Name">
<f:textbox name="parameter.name" value="${instance.name}" />
</f:entry>
<f:entry title="Default Value">
<f:textbox name="parameter.defaultValue" value="${instance.defaultValue}" />
</f:entry>
<f:entry title="Optional">
<f:checkbox name="parameter.optional" value="${instance.optional}" />
</f:entry>
</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"
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
<f:entry title="${it.name}">
<div name="parameter">
<input type="hidden" name="name" value="${it.name}" />
<f:textbox name="value" value="${it.defaultValue}" />
</div>
</f:entry>
</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"
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
<f:entry title="${it.name}">
<f:textbox name="value" value="${it.value}" readonly="true" />
</f:entry>
</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:optionalBlock xmlns:local="local" name="useProjectSecurity"
title="${%Enable project-based security}" checked="${instance.isUseProjectSecurity()}">
<f:block>
<j:set var="groups" value="${descriptor.allGroups}" />
<d:taglib uri="local">
<!-- generate one row for the sid name @sid -->
<d:tag name="row">
<td class="left-most">${title}</td>
<j:forEach var="g" items="${groups}">
<j:forEach var="p" items="${g.permissions}">
<td width="*">
<f:checkbox name="[${p.id}]"
checked="${instance.hasPermission(attrs.sid,p)}" />
</td>
</j:forEach>
</j:forEach>
<td class="stop">
<j:if test="${attrs.sid!='anonymous'}">
<a href="#">
<img alt="remove" src="${imagesURL}/16x16/stop.gif" />
</a>
</j:if>
</td>
</d:tag>
</d:taglib>
<link rel="stylesheet" href="${h.getViewResource(descriptor,'table.css')}"
type="text/css" />
<table id="global-matrix-authorization-strategy-table" class="center-align"
name="data" width="100%">
<!-- The first row will show grouping -->
<tr class="group-row">
<td rowspan="2" class="pane-header blank"> ${%User/group}
</td>
<j:forEach var="g" items="${groups}">
<td class="pane-header" colspan="${g.size()}">
${g.title}
</td>
</j:forEach>
<td rowspan="2" class="stop" />
</tr>
<!-- The second row for individual permission -->
<tr class="caption-row">
<j:forEach var="g" items="${groups}">
<j:forEach var="p" items="${g.permissions}">
<th class="pane">
${p.name}
</th>
</j:forEach>
</j:forEach>
</tr>
<j:forEach var="sid" items="${instance.allSIDs}">
<tr name="[${sid}]">
<local:row title="${sid}" sid="${sid}"/>
</tr>
</j:forEach>
<tr name="anonymous">
<local:row sid="anonymous" title="${%Anonymous}" />
</tr>
<!-- template row to be used for adding a new row -->
<j:set var="id" value="${h.generateId()}"/>
<tr id="${id}" style="display:none">
<local:row sid="${null}" />
</tr>
</table>
<div style="margin-top:0.5em; margin-left: 2em;">
${%User/group to add}:
<input type="text" id="${id}text" />
<input type="button" value="${%Add}" id="${id}button"/>
</div>
</f:block>
</f:optionalBlock>
<script>
(function() {
<!-- place master outside the DOM tree so that it won't creep into the submitted form -->
var master = document.getElementById('${id}');
var table = master.parentNode;
table.removeChild(master);
makeButton("${id}button", function (e) {
<!-- when 'add' is clicked... -->
var name = $$('${id}text').value;
if(name=="") {
alert("Please enter an user name or a group name");
return;
}
if(findElementsBySelector(table,"TR").find(function(n){return n.getAttribute("name")==name;})!=null) {
alert("Entry for '"+name+"' already exists");
return;
}
if(document.importNode!=null)
copy = document.importNode(master,true);
else
copy = master.cloneNode(true); <!-- for IE -->
copy.removeAttribute("id");
copy.removeAttribute("style");
copy.firstChild.innerHTML = name;
copy.setAttribute("name",'['+name+']');
table.appendChild(copy);
Behaviour.applySubtree(findAncestor(table,"TABLE"));
});
})();
Behaviour.register({
"#global-matrix-authorization-strategy-table TD.stop A" : function(e) {
e.onclick = function() {
var tr = findAncestor(this,"TR");
tr.parentNode.removeChild(tr);
return false;
}
e = null; <!-- avoid memory leak -->
}
});
</script>
</j:jelly>
\ No newline at end of file
#global-matrix-authorization-strategy-table {
border-collapse: collapse;
border-spacing: 0;
border: 1px solid #D3D7CF;
}
#global-matrix-authorization-strategy-table TH {
padding: 0.2em;
}
#global-matrix-authorization-strategy-table TD.blank {
vertical-align: middle;
padding: 0.2em;
}
#global-matrix-authorization-strategy-table .caption-row TH {
font-weight: bold;
}
#global-matrix-authorization-strategy-table TD {
border: 1px solid #D3D7CF;
}
#global-matrix-authorization-strategy-table TD.left-most {
text-align: left;
border-left: none;
}
#global-matrix-authorization-strategy-table TD.stop {
border-top: 1px solid white;
border-right: 1px solid white;
border-bottom: 1px solid white;
}
<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:block xmlns:local="local">
<j:set var="groups" value="${descriptor.allGroups}"/>
<d:taglib uri="local">
<!-- generate one row for the sid name @sid -->
<d:tag name="row">
<td class="left-most">${title}</td>
<j:forEach var="g" items="${groups}">
<j:forEach var="p" items="${g.permissions}">
<td width="*">
<f:checkbox name="[${p.id}]" checked="${instance.hasPermission(attrs.sid,p)}"/>
</td>
</j:forEach>
</j:forEach>
<td class="stop">
<j:if test="${attrs.sid!='anonymous'}">
<a href="#">
<img alt="remove" src="${imagesURL}/16x16/stop.gif"/>
</a>
</j:if>
</td>
</d:tag>
</d:taglib>
<link rel="stylesheet" href="${h.getViewResource(descriptor,'table.css')}" type="text/css" />
<table id="global-matrix-authorization-strategy-table" class="center-align" name="data" width="100%">
<!-- The first row will show grouping -->
<tr class="group-row">
<td rowspan="2" class="pane-header blank">
${%User/group}
</td>
<j:forEach var="g" items="${groups}">
<td class="pane-header" colspan="${g.size()}">
${g.title}
</td>
</j:forEach>
<td rowspan="2" class="stop" />
</tr>
<!-- The second row for individual permission -->
<tr class="caption-row">
<j:forEach var="g" items="${groups}">
<j:forEach var="p" items="${g.permissions}">
<th class="pane">
${p.name}
</th>
</j:forEach>
</j:forEach>
</tr>
<j:forEach var="sid" items="${instance.allSIDs}">
<tr name="[${sid}]">
<local:row title="${sid}" sid="${sid}"/>
</tr>
</j:forEach>
<tr name="anonymous">
<local:row sid="anonymous" title="${%Anonymous}" />
</tr>
<!-- template row to be used for adding a new row -->
<j:set var="id" value="${h.generateId()}"/>
<tr id="${id}" style="display:none">
<local:row sid="${null}" />
</tr>
</table>
<div style="margin-top:0.5em; margin-left: 2em;">
${%User/group to add}:
<input type="text" id="${id}text" />
<input type="button" value="${%Add}" id="${id}button"/>
</div>
<script>
(function() {
<!-- place master outside the DOM tree so that it won't creep into the submitted form -->
var master = document.getElementById('${id}');
var table = master.parentNode;
table.removeChild(master);
makeButton("${id}button", function (e) {
<!-- when 'add' is clicked... -->
var name = $$('${id}text').value;
if(name=="") {
alert("Please enter an user name or a group name");
return;
}
if(findElementsBySelector(table,"TR").find(function(n){return n.getAttribute("name")==name;})!=null) {
alert("Entry for '"+name+"' already exists");
return;
}
if(document.importNode!=null)
copy = document.importNode(master,true);
else
copy = master.cloneNode(true); <!-- for IE -->
copy.removeAttribute("id");
copy.removeAttribute("style");
copy.firstChild.innerHTML = name;
copy.setAttribute("name",'['+name+']');
table.appendChild(copy);
Behaviour.applySubtree(findAncestor(table,"TABLE"));
});
})();
Behaviour.register({
"#global-matrix-authorization-strategy-table TD.stop A" : function(e) {
e.onclick = function() {
var tr = findAncestor(this,"TR");
tr.parentNode.removeChild(tr);
return false;
}
e = null; <!-- avoid memory leak -->
}
});
</script>
</f:block>
</j:jelly>
\ No newline at end of file
#global-matrix-authorization-strategy-table {
border-collapse: collapse;
border-spacing: 0;
border: 1px solid #D3D7CF;
}
#global-matrix-authorization-strategy-table TH {
padding: 0.2em;
}
#global-matrix-authorization-strategy-table TD.blank {
vertical-align: middle;
padding: 0.2em;
}
#global-matrix-authorization-strategy-table .caption-row TH {
font-weight: bold;
}
#global-matrix-authorization-strategy-table TD {
border: 1px solid #D3D7CF;
}
#global-matrix-authorization-strategy-table TD.left-most {
text-align: left;
border-left: none;
}
#global-matrix-authorization-strategy-table TD.stop {
border-top: 1px solid white;
border-right: 1px solid white;
border-bottom: 1px solid white;
}
<div>
When you are using Hudson for various automations, it's sometimes convenient
to be able to "parameterize" a build, by requiring a set of user inputs to
be made available to the build process. For example, you might be setting up
an on-demand test job, where the user can submit a zip file of the binaries to be tested.
<p>
This section configures what parameters your build takes. Parameters are distinguished
by their names, and so you can have multiple parameters provided that they have different names.
<p>
See <a href="http://hudson.gotdns.com/wiki/display/HUDSON/Parameterized+Build">the Wiki topic</a>
for more discussions about this feature.
</div>
\ No newline at end of file
此差异由.gitattributes 抑制。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册