提交 4e24f702 编写于 作者: J Jesse Glick

Merge branch 'master' into SCM-Job

......@@ -54,6 +54,19 @@ Upcoming changes</a>
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class=bug>
Fixed <code>NullPointerException</code> caused by the uninitialized <code>ProcessStarter</code> environment in build wrappers
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-20559">issue 20559</a>)
<li class=rfe>
API changes allowing to create nested launchers (<code>DecoratedLauncher</code>)
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19454">issue 19454</a>)
</ul>
</div><!--=TRUNK-END=-->
<!-- these changes are controlled by the release process. DO NOT MODIFY -->
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.567>What's new in 1.567</a> <!--=DATE=--></h3>
<ul class=image>
<li class="bug">
Fixed a reference counting bug in the remoting layer.
......@@ -82,6 +95,10 @@ Upcoming changes</a>
<li class="bug">
Process the items hierarchy when displaying the Show Poll Thread Count option
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-22934">issue 22934</a>)
<li class="bug">
Compressed output was turned on even before Access Denied errors were shown for disallowed Remote API requests, yielding a confusing error.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17374">issue 17374</a>)
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18116">issue 18116</a>)
<li class="bug">
Properly close input streams in <code>FileParameterValue</code>
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-22693">issue 22693</a>)
......@@ -113,11 +130,8 @@ Upcoming changes</a>
Added an option to archive artifacts only when the build is successful
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-22699">issue 22699</a>)
</ul>
</div><!--=TRUNK-END=-->
<!-- these changes are controlled by the release process. DO NOT MODIFY -->
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.566>What's new in 1.566</a> <!--=DATE=--></h3>
</div><!--=END=-->
<h3><a name=v1.566>What's new in 1.566</a> (2014/06/01)</h3>
<ul class=image>
<li class="rfe">
Configurable case sensitivity mode for user IDs.
......@@ -132,7 +146,6 @@ Upcoming changes</a>
Jenkins cannot restart Windows service
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-22685">issue 22685</a>)
</ul>
</div><!--=END=-->
<h3><a name=v1.565>What's new in 1.565</a> (2014/05/26)</h3>
<ul class=image>
<li class="bug">
......
......@@ -5,7 +5,7 @@
<parent>
<artifactId>pom</artifactId>
<groupId>org.jenkins-ci.main</groupId>
<version>1.567-SNAPSHOT</version>
<version>1.568-SNAPSHOT</version>
</parent>
<artifactId>cli</artifactId>
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.567-SNAPSHOT</version>
<version>1.568-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
......@@ -300,8 +300,13 @@ public abstract class Launcher {
return this;
}
/**
* Gets a list of environment variables to be set.
* Returns an empty array if envs field has not been initialized.
* @return If initialized, returns a copy of internal envs array. Otherwise - a new empty array.
*/
public String[] envs() {
return envs.clone();
return envs != null ? envs.clone() : new String[0];
}
/**
......@@ -972,6 +977,88 @@ public abstract class Launcher {
}
}
}
/**
* A launcher which delegates to a provided inner launcher.
* Allows subclasses to only implement methods they want to override.
* Originally, this launcher has been implemented in
* <a href="https://wiki.jenkins-ci.org/display/JENKINS/Custom+Tools+Plugin">
* Custom Tools Plugin</a>.
*
* @author rcampbell
* @author Oleg Nenashev, Synopsys Inc.
* @since TODO: define version
*/
public static class DecoratedLauncher extends Launcher {
private Launcher inner = null;
public DecoratedLauncher(Launcher inner) {
super(inner);
this.inner = inner;
}
@Override
public Proc launch(ProcStarter starter) throws IOException {
return inner.launch(starter);
}
@Override
public Channel launchChannel(String[] cmd, OutputStream out,
FilePath workDir, Map<String, String> envVars) throws IOException,
InterruptedException {
return inner.launchChannel(cmd, out, workDir, envVars);
}
@Override
public void kill(Map<String, String> modelEnvVars) throws IOException,
InterruptedException {
inner.kill(modelEnvVars);
}
@Override
public boolean isUnix() {
return inner.isUnix();
}
@Override
public Proc launch(String[] cmd, boolean[] mask, String[] env, InputStream in, OutputStream out, FilePath workDir) throws IOException {
return inner.launch(cmd, mask, env, in, out, workDir);
}
@Override
public Computer getComputer() {
return inner.getComputer();
}
@Override
public TaskListener getListener() {
return inner.getListener();
}
@Override
public String toString() {
return super.toString() + "; decorates " + inner.toString();
}
@Override
public VirtualChannel getChannel() {
return inner.getChannel();
}
@Override
public Proc launch(String[] cmd, String[] env, InputStream in, OutputStream out, FilePath workDir) throws IOException {
return inner.launch(cmd, env, in, out, workDir);
}
/**
* Gets nested launcher.
* @return Inner launcher
*/
public Launcher getInner() {
return inner;
}
}
public static class IOTriplet implements Serializable {
InputStream stdout,stderr;
......
此差异已折叠。
......@@ -122,6 +122,9 @@ public class InstallToolCommand extends CLICommand {
throw new AbortException(b.getFullDisplayName()+" is not building");
Node node = exec.getOwner().getNode();
if (node == null) {
throw new AbortException("The node " + exec.getOwner().getDisplayName() + " has been deleted");
}
t = t.translate(node, EnvVars.getRemote(checkChannel()), new StreamTaskListener(stderr));
stdout.println(t.getHome());
......
......@@ -84,9 +84,11 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.kohsuke.stapler.interceptor.RequirePOST;
import static java.util.logging.Level.WARNING;
import jenkins.model.lazy.BuildReference;
import jenkins.model.lazy.LazyBuildMixIn;
......@@ -435,9 +437,20 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
/**
* Returns the current {@link Node} on which we are building.
* @return Returns the current {@link Node}
* @throws IllegalStateException if that cannot be determined
*/
protected final Node getCurrentNode() {
return Executor.currentExecutor().getOwner().getNode();
protected final @Nonnull Node getCurrentNode() throws IllegalStateException {
Executor exec = Executor.currentExecutor();
if (exec == null) {
throw new IllegalStateException("not being called from an executor thread");
}
Computer c = exec.getOwner();
Node node = c.getNode();
if (node == null) {
throw new IllegalStateException("no longer a configured node for " + c.getName());
}
return node;
}
public Launcher getLauncher() {
......@@ -456,7 +469,7 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
* @param wsl
* Passed in for the convenience. The returned path must be registered to this object.
*/
protected Lease decideWorkspace(Node n, WorkspaceList wsl) throws InterruptedException, IOException {
protected Lease decideWorkspace(@Nonnull Node n, WorkspaceList wsl) throws InterruptedException, IOException {
String customWorkspace = getProject().getCustomWorkspace();
if (customWorkspace != null) {
// we allow custom workspaces to be concurrently used between jobs.
......@@ -466,8 +479,9 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
return wsl.allocate(n.getWorkspaceFor((TopLevelItem)getProject()), getBuild());
}
public Result run(BuildListener listener) throws Exception {
Node node = getCurrentNode();
public Result run(@Nonnull BuildListener listener) throws Exception {
final Node node = getCurrentNode();
assert builtOn==null;
builtOn = node.getNodeName();
hudsonVersion = Jenkins.VERSION;
......@@ -555,8 +569,10 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
* @param listener
* Always non-null. Connected to the main build output.
*/
protected Launcher createLauncher(BuildListener listener) throws IOException, InterruptedException {
Launcher l = getCurrentNode().createLauncher(listener);
@Nonnull
protected Launcher createLauncher(@Nonnull BuildListener listener) throws IOException, InterruptedException {
final Node currentNode = getCurrentNode();
Launcher l = currentNode.createLauncher(listener);
if (project instanceof BuildableItemWithBuildWrappers) {
BuildableItemWithBuildWrappers biwbw = (BuildableItemWithBuildWrappers) project;
......@@ -580,7 +596,7 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
}
}
for (NodeProperty nodeProperty: Computer.currentComputer().getNode().getNodeProperties()) {
for (NodeProperty nodeProperty: currentNode.getNodeProperties()) {
Environment environment = nodeProperty.setUp(AbstractBuild.this, l, listener);
if (environment != null) {
buildEnvironments.add(environment);
......
......@@ -165,9 +165,10 @@ public abstract class AbstractCIBase extends Node implements ItemGroup<TopLevelI
synchronized(updateComputerLock) {// just so that we don't have two code updating computer list at the same time
Map<String,Computer> byName = new HashMap<String,Computer>();
for (Computer c : computers.values()) {
if(c.getNode()==null)
Node node = c.getNode();
if (node == null)
continue; // this computer is gone
byName.put(c.getNode().getNodeName(),c);
byName.put(node.getNodeName(),c);
}
Set<Computer> old = new HashSet<Computer>(computers.values());
......
......@@ -154,16 +154,21 @@ public class Api extends AbstractModelObject {
throw new IOException("Failed to do XPath/wrapper handling. Turn on FINER logging to view XML.",e);
}
if (isSimpleOutput(result) && !permit(req)) {
// simple output prohibited
rsp.sendError(HttpURLConnection.HTTP_FORBIDDEN, "primitive XPath result sets forbidden; implement jenkins.security.SecureRequester");
return;
}
// switch to gzipped output
OutputStream o = rsp.getCompressedOutputStream(req);
try {
if (result instanceof CharacterData || result instanceof String || result instanceof Number || result instanceof Boolean) {
if (permit(req)) {
rsp.setContentType("text/plain;charset=UTF-8");
String text = result instanceof CharacterData ? ((CharacterData) result).getText() : result.toString();
o.write(text.getBytes("UTF-8"));
} else {
rsp.sendError(HttpURLConnection.HTTP_FORBIDDEN, "primitive XPath result sets forbidden; implement jenkins.security.SecureRequester");
}
if (isSimpleOutput(result)) {
// simple output allowed
rsp.setContentType("text/plain;charset=UTF-8");
String text = result instanceof CharacterData ? ((CharacterData) result).getText() : result.toString();
o.write(text.getBytes("UTF-8"));
return;
}
......@@ -175,6 +180,10 @@ public class Api extends AbstractModelObject {
}
}
private boolean isSimpleOutput(Object result) {
return result instanceof CharacterData || result instanceof String || result instanceof Number || result instanceof Boolean;
}
/**
* Generate schema.
*/
......
......@@ -42,6 +42,7 @@ import java.util.logging.Logger;
import java.util.logging.Level;
import static hudson.model.Result.FAILURE;
import javax.annotation.Nonnull;
/**
* A build of a {@link Project}.
......@@ -135,7 +136,7 @@ public abstract class Build <P extends Project<P,B>,B extends Build<P,B>>
deprecated class here.
*/
protected Result doRun(BuildListener listener) throws Exception {
protected Result doRun(@Nonnull BuildListener listener) throws Exception {
if(!preBuild(listener,project.getBuilders()))
return FAILURE;
if(!preBuild(listener,project.getPublishersList()))
......@@ -178,7 +179,7 @@ public abstract class Build <P extends Project<P,B>,B extends Build<P,B>>
return r;
}
public void post2(BuildListener listener) throws IOException, InterruptedException {
public void post2(@Nonnull BuildListener listener) throws IOException, InterruptedException {
if (!performAllBuildSteps(listener, project.getPublishersList(), true))
setResult(FAILURE);
if (!performAllBuildSteps(listener, project.getProperties(), true))
......@@ -186,14 +187,14 @@ public abstract class Build <P extends Project<P,B>,B extends Build<P,B>>
}
@Override
public void cleanUp(BuildListener listener) throws Exception {
public void cleanUp(@Nonnull BuildListener listener) throws Exception {
// at this point it's too late to mark the build as a failure, so ignore return value.
performAllBuildSteps(listener, project.getPublishersList(), false);
performAllBuildSteps(listener, project.getProperties(), false);
super.cleanUp(listener);
}
private boolean build(BuildListener listener, Collection<Builder> steps) throws IOException, InterruptedException {
private boolean build(@Nonnull BuildListener listener, @Nonnull Collection<Builder> steps) throws IOException, InterruptedException {
for( BuildStep bs : steps ) {
if(!perform(bs,listener)) {
LOGGER.log(Level.FINE, "{0} : {1} failed", new Object[] {Build.this, bs});
......
......@@ -204,7 +204,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
/**
* This is where the log from the remote agent goes.
*
* The method also creates a log directory if required.
* @see #relocateOldLogs()
*/
protected File getLogFile() {
......@@ -1196,7 +1196,11 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
// read
checkPermission(EXTENDED_READ);
rsp.setContentType("application/xml");
Jenkins.XSTREAM2.toXMLUTF8(getNode(), rsp.getOutputStream());
Node node = getNode();
if (node == null) {
throw HttpResponses.notFound();
}
Jenkins.XSTREAM2.toXMLUTF8(node, rsp.getOutputStream());
return;
}
if (req.getMethod().equals("POST")) {
......@@ -1218,7 +1222,8 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
// replace the old Node object by the new one
synchronized (app) {
List<Node> nodes = new ArrayList<Node>(app.getNodes());
int i = nodes.indexOf(getNode());
Node node = getNode();
int i = (node != null) ? nodes.indexOf(node) : -1;
if(i<0) {
throw new IOException("This slave appears to be removed while you were editing the configuration");
}
......@@ -1348,7 +1353,12 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
if (m.matches()) {
File newLocation = new File(dir, "logs/slaves/" + m.group(1) + "/slave.log" + Util.fixNull(m.group(2)));
newLocation.getParentFile().mkdirs();
f.renameTo(newLocation);
boolean relocationSuccessfull=f.renameTo(newLocation);
if (relocationSuccessfull) { // The operation will fail if mkdir fails
LOGGER.log(Level.INFO, "Relocated log file {0} to {1}",new Object[] {f.getPath(),newLocation.getPath()});
} else {
LOGGER.log(Level.WARNING, "Cannot relocate log file {0} to {1}",new Object[] {f.getPath(),newLocation.getPath()});
}
} else {
assert false;
}
......
......@@ -59,6 +59,7 @@ import java.util.logging.Logger;
import static hudson.model.queue.Executables.*;
import static java.util.logging.Level.*;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
......@@ -70,7 +71,7 @@ import javax.annotation.CheckForNull;
*/
@ExportedBean
public class Executor extends Thread implements ModelObject {
protected final Computer owner;
protected final @Nonnull Computer owner;
private final Queue queue;
private long startTime;
......@@ -111,7 +112,7 @@ public class Executor extends Thread implements ModelObject {
*/
private final List<CauseOfInterruption> causes = new Vector<CauseOfInterruption>();
public Executor(Computer owner, int n) {
public Executor(@Nonnull Computer owner, int n) {
super("Executor #"+n+" for "+owner.getDisplayName());
this.owner = owner;
this.queue = Jenkins.getInstance().getQueue();
......@@ -534,7 +535,7 @@ public class Executor extends Thread implements ModelObject {
return e!=null && Tasks.getOwnerTaskOf(getParentOf(e)).hasAbortPermission();
}
public Computer getOwner() {
public @Nonnull Computer getOwner() {
return owner;
}
......
......@@ -202,7 +202,7 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
// At startup, we need to restore any previously in-effect temp offline cause.
// We wait until the computer is started rather than getting the data to it sooner
// so that the normal computer start up processing works as expected.
if (node.temporaryOfflineCause != null && node.temporaryOfflineCause != c.getOfflineCause()) {
if (node!= null && node.temporaryOfflineCause != null && node.temporaryOfflineCause != c.getOfflineCause()) {
c.setTemporarilyOffline(true, node.temporaryOfflineCause);
}
}
......
......@@ -283,6 +283,7 @@ public class Queue extends ResourceController implements Saveable {
return workUnit == null && !executor.getOwner().isOffline() && executor.getOwner().isAcceptingTasks();
}
@CheckForNull
public Node getNode() {
return executor.getOwner().getNode();
}
......@@ -739,6 +740,8 @@ public class Queue extends ResourceController implements Saveable {
private void _getBuildableItems(Computer c, ItemList<BuildableItem> col, List<BuildableItem> result) {
Node node = c.getNode();
if (node == null) // Deleted computers cannot take build items...
return;
for (BuildableItem p : col.values()) {
if (node.canTake(p) == null)
result.add(p);
......
......@@ -26,6 +26,7 @@ package hudson.node_monitors;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.remoting.Callable;
import jenkins.model.Jenkins;
import hudson.node_monitors.DiskSpaceMonitorDescriptor.DiskSpace;
......@@ -66,7 +67,10 @@ public class DiskSpaceMonitor extends AbstractDiskSpaceMonitor {
@Override
protected Callable<DiskSpace, IOException> createCallable(Computer c) {
FilePath p = c.getNode().getRootPath();
Node node = c.getNode();
if (node == null) return null;
FilePath p = node.getRootPath();
if(p==null) return null;
return p.asCallableWith(new GetUsableSpace());
......
......@@ -27,6 +27,7 @@ import hudson.Extension;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.remoting.Callable;
import jenkins.model.Jenkins;
import hudson.node_monitors.DiskSpaceMonitorDescriptor.DiskSpace;
......@@ -67,7 +68,10 @@ public class TemporarySpaceMonitor extends AbstractDiskSpaceMonitor {
@Override
protected Callable<DiskSpace,IOException> createCallable(Computer c) {
FilePath p = c.getNode().getRootPath();
Node node = c.getNode();
if (node == null) return null;
FilePath p = node.getRootPath();
if(p==null) return null;
return p.asCallableWith(new GetTempSpace());
......
......@@ -23,6 +23,7 @@
*/
package hudson.security;
import javax.annotation.Nonnull;
import jenkins.security.NonSerializableSecurityContext;
import jenkins.model.Jenkins;
import org.acegisecurity.AccessDeniedException;
......@@ -48,7 +49,7 @@ public abstract class ACL {
* @throws AccessDeniedException
* if the user doesn't have the permission.
*/
public final void checkPermission(Permission p) {
public final void checkPermission(@Nonnull Permission p) {
Authentication a = Jenkins.getAuthentication();
if(!hasPermission(a,p))
throw new AccessDeniedException2(a,p);
......@@ -60,7 +61,7 @@ public abstract class ACL {
* @return false
* if the user doesn't have the permission.
*/
public final boolean hasPermission(Permission p) {
public final boolean hasPermission(@Nonnull Permission p) {
return hasPermission(Jenkins.getAuthentication(),p);
}
......@@ -71,7 +72,7 @@ public abstract class ACL {
* Note that {@link #SYSTEM} can be passed in as the authentication parameter,
* in which case you should probably just assume it has every permission.
*/
public abstract boolean hasPermission(Authentication a, Permission permission);
public abstract boolean hasPermission(@Nonnull Authentication a, @Nonnull Permission permission);
//
// Sid constants
......@@ -124,7 +125,7 @@ public abstract class ACL {
* because the same {@link SecurityContext} object is reused for all the concurrent requests from the same session.
* @since 1.462
*/
public static SecurityContext impersonate(Authentication auth) {
public static @Nonnull SecurityContext impersonate(@Nonnull Authentication auth) {
SecurityContext old = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(new NonSerializableSecurityContext(auth));
return old;
......@@ -136,7 +137,7 @@ public abstract class ACL {
* @param body an action to run with this alternate authentication in effect
* @since 1.509
*/
public static void impersonate(Authentication auth, Runnable body) {
public static void impersonate(@Nonnull Authentication auth, @Nonnull Runnable body) {
SecurityContext old = impersonate(auth);
try {
body.run();
......
......@@ -23,6 +23,7 @@
*/
package hudson.security;
import javax.annotation.Nonnull;
import org.acegisecurity.AccessDeniedException;
/**
......@@ -36,16 +37,16 @@ public interface AccessControlled {
*
* @return never null.
*/
ACL getACL();
@Nonnull ACL getACL();
/**
* Convenient short-cut for {@code getACL().checkPermission(permission)}
*/
void checkPermission(Permission permission) throws AccessDeniedException;
void checkPermission(@Nonnull Permission permission) throws AccessDeniedException;
/**
* Convenient short-cut for {@code getACL().hasPermission(permission)}
*/
boolean hasPermission(Permission permission);
boolean hasPermission(@Nonnull Permission permission);
}
......@@ -33,6 +33,7 @@ import hudson.util.DescriptorList;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
......@@ -68,18 +69,18 @@ public abstract class AuthorizationStrategy extends AbstractDescribableImpl<Auth
* <p>
* IOW, this ACL will have the ultimate say on the access control.
*/
public abstract ACL getRootACL();
public abstract @Nonnull ACL getRootACL();
/**
* @deprecated since 1.277
* Override {@link #getACL(Job)} instead.
*/
@Deprecated
public ACL getACL(AbstractProject<?,?> project) {
public @Nonnull ACL getACL(@Nonnull AbstractProject<?,?> project) {
return getACL((Job)project);
}
public ACL getACL(Job<?,?> project) {
public @Nonnull ACL getACL(@Nonnull Job<?,?> project) {
return getRootACL();
}
......@@ -93,7 +94,7 @@ public abstract class AuthorizationStrategy extends AbstractDescribableImpl<Auth
*
* @since 1.220
*/
public ACL getACL(final View item) {
public @Nonnull ACL getACL(final @Nonnull View item) {
return new ACL() {
@Override
public boolean hasPermission(Authentication a, Permission permission) {
......@@ -118,7 +119,7 @@ public abstract class AuthorizationStrategy extends AbstractDescribableImpl<Auth
*
* @since 1.220
*/
public ACL getACL(AbstractItem item) {
public @Nonnull ACL getACL(@Nonnull AbstractItem item) {
return getRootACL();
}
......@@ -131,7 +132,7 @@ public abstract class AuthorizationStrategy extends AbstractDescribableImpl<Auth
*
* @since 1.221
*/
public ACL getACL(User user) {
public @Nonnull ACL getACL(@Nonnull User user) {
return getRootACL();
}
......@@ -144,7 +145,7 @@ public abstract class AuthorizationStrategy extends AbstractDescribableImpl<Auth
*
* @since 1.220
*/
public ACL getACL(Computer computer) {
public @Nonnull ACL getACL(@Nonnull Computer computer) {
return getACL(computer.getNode());
}
......@@ -157,11 +158,11 @@ public abstract class AuthorizationStrategy extends AbstractDescribableImpl<Auth
*
* @since 1.252
*/
public ACL getACL(Cloud cloud) {
public @Nonnull ACL getACL(@Nonnull Cloud cloud) {
return getRootACL();
}
public ACL getACL(Node node) {
public @Nonnull ACL getACL(@Nonnull Node node) {
return getRootACL();
}
......@@ -179,12 +180,12 @@ public abstract class AuthorizationStrategy extends AbstractDescribableImpl<Auth
* @return
* never null.
*/
public abstract Collection<String> getGroups();
public abstract @Nonnull Collection<String> getGroups();
/**
* Returns all the registered {@link AuthorizationStrategy} descriptors.
*/
public static DescriptorExtensionList<AuthorizationStrategy,Descriptor<AuthorizationStrategy>> all() {
public static @Nonnull DescriptorExtensionList<AuthorizationStrategy,Descriptor<AuthorizationStrategy>> all() {
return Jenkins.getInstance().<AuthorizationStrategy,Descriptor<AuthorizationStrategy>>getDescriptorList(AuthorizationStrategy.class);
}
......@@ -214,15 +215,17 @@ public abstract class AuthorizationStrategy extends AbstractDescribableImpl<Auth
}
@Override
public ACL getRootACL() {
public @Nonnull ACL getRootACL() {
return UNSECURED_ACL;
}
public Collection<String> getGroups() {
@Override
public @Nonnull Collection<String> getGroups() {
return Collections.emptySet();
}
private static final ACL UNSECURED_ACL = new ACL() {
@Override
public boolean hasPermission(Authentication a, Permission permission) {
return true;
}
......@@ -230,12 +233,13 @@ public abstract class AuthorizationStrategy extends AbstractDescribableImpl<Auth
@Extension
public static final class DescriptorImpl extends Descriptor<AuthorizationStrategy> {
@Override
public String getDisplayName() {
return Messages.AuthorizationStrategy_DisplayName();
}
@Override
public AuthorizationStrategy newInstance(StaplerRequest req, JSONObject formData) throws FormException {
public @Nonnull AuthorizationStrategy newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return UNSECURED;
}
......
......@@ -34,6 +34,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.jvnet.localizer.Localizable;
......@@ -58,14 +59,14 @@ public final class Permission {
*/
// break eclipse compilation
//Override
public int compare(Permission one, Permission two) {
public int compare(@Nonnull Permission one, @Nonnull Permission two) {
return one.getId().compareTo(two.getId());
}
};
public final Class owner;
public final @Nonnull Class owner;
public final PermissionGroup group;
public final @Nonnull PermissionGroup group;
/**
* Human readable ID of the permission.
......@@ -76,7 +77,7 @@ public final class Permission {
* <p>
* The expected naming convention is something like "BrowseWorkspace".
*/
public final String name;
public final @Nonnull String name;
/**
* Human-readable description of this permission.
......@@ -86,7 +87,7 @@ public final class Permission {
* <p>
* If null, there will be no description text.
*/
public final Localizable description;
public final @CheckForNull Localizable description;
/**
* Bundled {@link Permission} that also implies this permission.
......@@ -101,7 +102,7 @@ public final class Permission {
* such broad permission bundle is good enough, and those few
* that need finer control can do so.
*/
public final Permission impliedBy;
public final @CheckForNull Permission impliedBy;
/**
* Whether this permission is available for use.
......@@ -118,7 +119,7 @@ public final class Permission {
/**
* Scopes that this permission is directly contained by.
*/
private final Set<PermissionScope> scopes;
private final @Nonnull Set<PermissionScope> scopes;
/**
* Defines a new permission.
......@@ -144,7 +145,9 @@ public final class Permission {
* @param impliedBy
* See {@link #impliedBy}.
*/
public Permission(PermissionGroup group, String name, Localizable description, Permission impliedBy, boolean enable, PermissionScope[] scopes) {
public Permission(@Nonnull PermissionGroup group, @Nonnull String name,
@CheckForNull Localizable description, @CheckForNull Permission impliedBy, boolean enable,
@Nonnull PermissionScope[] scopes) {
if(!JSONUtils.isJavaIdentifier(name))
throw new IllegalArgumentException(name+" is not a Java identifier");
this.owner = group.owner;
......@@ -159,7 +162,8 @@ public final class Permission {
ALL.add(this);
}
public Permission(PermissionGroup group, String name, Localizable description, Permission impliedBy, PermissionScope scope) {
public Permission(@Nonnull PermissionGroup group, @Nonnull String name,
@CheckForNull Localizable description, @CheckForNull Permission impliedBy, @Nonnull PermissionScope scope) {
this(group,name,description,impliedBy,true,new PermissionScope[]{scope});
assert scope!=null;
}
......@@ -168,7 +172,7 @@ public final class Permission {
* @deprecated as of 1.421
* Use {@link #Permission(PermissionGroup, String, Localizable, Permission, boolean, PermissionScope[])}
*/
public Permission(PermissionGroup group, String name, Localizable description, Permission impliedBy, boolean enable) {
public Permission(@Nonnull PermissionGroup group, @Nonnull String name, @CheckForNull Localizable description, @CheckForNull Permission impliedBy, boolean enable) {
this(group,name,description,impliedBy,enable,new PermissionScope[]{PermissionScope.JENKINS});
}
......@@ -176,7 +180,7 @@ public final class Permission {
* @deprecated as of 1.421
* Use {@link #Permission(PermissionGroup, String, Localizable, Permission, PermissionScope)}
*/
public Permission(PermissionGroup group, String name, Localizable description, Permission impliedBy) {
public Permission(@Nonnull PermissionGroup group, @Nonnull String name, @CheckForNull Localizable description, @CheckForNull Permission impliedBy) {
this(group, name, description, impliedBy, PermissionScope.JENKINS);
}
......@@ -184,18 +188,18 @@ public final class Permission {
* @deprecated since 1.257.
* Use {@link #Permission(PermissionGroup, String, Localizable, Permission)}
*/
public Permission(PermissionGroup group, String name, Permission impliedBy) {
public Permission(@Nonnull PermissionGroup group, @Nonnull String name, @CheckForNull Permission impliedBy) {
this(group,name,null,impliedBy);
}
private Permission(PermissionGroup group, String name) {
private Permission(@Nonnull PermissionGroup group, @Nonnull String name) {
this(group,name,null,null);
}
/**
* Checks if this permission is contained in the specified scope, (either directly or indirectly.)
*/
public boolean isContainedBy(PermissionScope s) {
public boolean isContainedBy(@Nonnull PermissionScope s) {
for (PermissionScope c : scopes) {
if (c.isContainedBy(s))
return true;
......@@ -210,10 +214,10 @@ public final class Permission {
*
* <p>
* This string representation is suitable for persistence.
*
* @return ID with the following format: <i>permissionClass.permissionName</i>
* @see #fromId(String)
*/
public String getId() {
public @Nonnull String getId() {
return owner.getName()+'.'+name;
}
......@@ -224,7 +228,7 @@ public final class Permission {
* null if the conversion failed.
* @see #getId()
*/
public static @CheckForNull Permission fromId(String id) {
public static @CheckForNull Permission fromId(@Nonnull String id) {
int idx = id.lastIndexOf('.');
if(idx<0) return null;
......@@ -257,7 +261,7 @@ public final class Permission {
* @return
* always non-null. Read-only.
*/
public static List<Permission> getAll() {
public static @Nonnull List<Permission> getAll() {
return ALL_VIEW;
}
......
......@@ -29,6 +29,7 @@ import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import java.io.IOException;
import javax.annotation.CheckForNull;
/**
* Partial implementation of {@link Computer} to be used in conjunction with
......@@ -42,6 +43,7 @@ public class AbstractCloudComputer<T extends AbstractCloudSlave> extends SlaveCo
super(slave);
}
@CheckForNull
@Override
public T getNode() {
return (T) super.getNode();
......@@ -54,7 +56,10 @@ public class AbstractCloudComputer<T extends AbstractCloudSlave> extends SlaveCo
public HttpResponse doDoDelete() throws IOException {
checkPermission(DELETE);
try {
getNode().terminate();
T node = getNode();
if (node != null) { // No need to terminate nodes again
node.terminate();
}
return new HttpRedirect("..");
} catch (InterruptedException e) {
return HttpResponses.error(500,e);
......
......@@ -23,10 +23,12 @@
*/
package hudson.slaves;
import hudson.model.Node;
import java.io.IOException;
import java.util.logging.Logger;
import static hudson.util.TimeUnit2.*;
import java.util.logging.Level;
import static java.util.logging.Level.*;
/**
......@@ -43,13 +45,15 @@ public class CloudRetentionStrategy extends RetentionStrategy<AbstractCloudCompu
this.idleMinutes = idleMinutes;
}
@Override
public synchronized long check(AbstractCloudComputer c) {
if (c.isIdle() && !disabled) {
AbstractCloudSlave computerNode = c.getNode();
if (c.isIdle() && !disabled && computerNode != null) {
final long idleMilliseconds = System.currentTimeMillis() - c.getIdleStartMilliseconds();
if (idleMilliseconds > MINUTES.toMillis(idleMinutes)) {
LOGGER.info("Disconnecting "+c.getName());
LOGGER.log(Level.INFO, "Disconnecting {0}",c.getName());
try {
c.getNode().terminate();
computerNode.terminate();
} catch (InterruptedException e) {
LOGGER.log(WARNING,"Failed to terminate "+c.getName(),e);
} catch (IOException e) {
......
......@@ -23,10 +23,12 @@
*/
package hudson.slaves;
import hudson.AbortException;
import hudson.EnvVars;
import hudson.Util;
import hudson.Extension;
import hudson.model.Descriptor;
import hudson.model.Slave;
import jenkins.model.Jenkins;
import hudson.model.TaskListener;
import hudson.remoting.Channel;
......@@ -87,6 +89,11 @@ public class CommandLauncher extends ComputerLauncher {
EnvVars _cookie = null;
Process _proc = null;
try {
Slave node = computer.getNode();
if (node == null) {
throw new AbortException("Cannot launch commands on deleted nodes");
}
listener.getLogger().println(hudson.model.Messages.Slave_Launching(getTimestamp()));
if(getCommand().trim().length()==0) {
listener.getLogger().println(Messages.CommandLauncher_NoLaunchCommand());
......@@ -96,8 +103,8 @@ public class CommandLauncher extends ComputerLauncher {
ProcessBuilder pb = new ProcessBuilder(Util.tokenize(getCommand()));
final EnvVars cookie = _cookie = EnvVars.createCookie();
pb.environment().putAll(cookie);
pb.environment().put("WORKSPACE", computer.getNode().getRemoteFS()); //path for local slave log
pb.environment().putAll(cookie);
pb.environment().put("WORKSPACE", node.getRemoteFS()); //path for local slave log
{// system defined variables
String rootUrl = Jenkins.getInstance().getRootUrl();
......
......@@ -24,6 +24,7 @@
package hudson.slaves;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.Node;
import hudson.model.EnvironmentSpecific;
import hudson.model.TaskListener;
......@@ -44,5 +45,5 @@ public interface NodeSpecific<T extends NodeSpecific<T>> {
/**
* Returns a specialized copy of T for functioning in the given node.
*/
T forNode(Node node, TaskListener log) throws IOException, InterruptedException;
T forNode(@NonNull Node node, TaskListener log) throws IOException, InterruptedException;
}
......@@ -193,6 +193,7 @@ public abstract class RetentionStrategy<T extends Computer> extends AbstractDesc
return idleDelay;
}
@Override
public synchronized long check(SlaveComputer c) {
if (c.isOffline() && c.isLaunchSupported()) {
final HashMap<Computer, Integer> availableComputers = new HashMap<Computer, Integer>();
......@@ -211,7 +212,8 @@ public abstract class RetentionStrategy<T extends Computer> extends AbstractDesc
// assume the answer is no until we can find such an executor
boolean needExecutor = true;
for (Computer o : Collections.unmodifiableSet(availableComputers.keySet())) {
if (o.getNode().canTake(item) == null) {
Node otherNode = o.getNode();
if (otherNode != null && otherNode.canTake(item) == null) {
needExecutor = false;
final int availableExecutors = availableComputers.remove(o);
if (availableExecutors > 1) {
......@@ -224,7 +226,8 @@ public abstract class RetentionStrategy<T extends Computer> extends AbstractDesc
}
// this 'item' cannot be built by any of the existing idle nodes, but it can be built by 'c'
if (needExecutor && c.getNode().canTake(item) == null) {
Node checkedNode = c.getNode();
if (needExecutor && checkedNode != null && checkedNode.canTake(item) == null) {
demandMilliseconds = System.currentTimeMillis() - item.buildableStartMilliseconds;
needComputer = demandMilliseconds > inDemandDelay * 1000 * 60 /*MINS->MILLIS*/;
break;
......@@ -251,6 +254,7 @@ public abstract class RetentionStrategy<T extends Computer> extends AbstractDesc
@Extension
public static class DescriptorImpl extends Descriptor<RetentionStrategy<?>> {
@Override
public String getDisplayName() {
return Messages.RetentionStrategy_Demand_displayName();
}
......
......@@ -78,8 +78,11 @@ import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import jenkins.model.Jenkins;
import static hudson.slaves.SlaveComputer.LogHolder.*;
/**
* {@link Computer} for {@link Slave}s.
*
......@@ -168,6 +171,7 @@ public class SlaveComputer extends Computer {
return isUnix;
}
@CheckForNull
@Override
public Slave getNode() {
Node node = super.getNode();
......@@ -418,7 +422,7 @@ public class SlaveComputer extends Computer {
}
/**
* Sets up the connection through an exsting channel.
* Sets up the connection through an existing channel.
*
* @since 1.444
*/
......@@ -454,10 +458,15 @@ public class SlaveComputer extends Computer {
String defaultCharsetName = channel.call(new DetectDefaultCharset());
String remoteFs = getNode().getRemoteFS();
Slave node = getNode();
if (node == null) { // Node has been disabled/removed during the connection
throw new IOException("Node "+nodeName+" has been deleted during the channel setup");
}
String remoteFs = node.getRemoteFS();
if(_isUnix && !remoteFs.contains("/") && remoteFs.contains("\\"))
log.println("WARNING: "+remoteFs+" looks suspiciously like Windows path. Maybe you meant "+remoteFs.replace('\\','/')+"?");
FilePath root = new FilePath(channel,getNode().getRemoteFS());
FilePath root = new FilePath(channel,remoteFs);
// reference counting problem is known to happen, such as JENKINS-9017, and so as a preventive measure
// we pin the base classloader so that it'll never get GCed. When this classloader gets released,
......
......@@ -247,11 +247,9 @@ public class BuildTrigger extends Recorder implements DependencyDeclarer {
logger.println(Messages.BuildTrigger_Disabled(ModelHyperlinkNote.encodeTo(p)));
continue;
}
// this is not completely accurate, as a new build might be triggered
// between these calls
boolean scheduled = p.scheduleBuild(p.getQuietPeriod(), new UpstreamCause((Run)build), buildActions.toArray(new Action[buildActions.size()]));
if (Jenkins.getInstance().getItemByFullName(p.getFullName()) == p) {
String name = ModelHyperlinkNote.encodeTo(p) + " #" + p.getNextBuildNumber();
String name = ModelHyperlinkNote.encodeTo(p);
if (scheduled) {
logger.println(Messages.BuildTrigger_Triggering(name));
} else {
......
......@@ -41,6 +41,7 @@ import java.util.List;
import com.thoughtworks.xstream.annotations.XStreamSerializable;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
/**
......@@ -163,7 +164,7 @@ public abstract class ToolInstallation extends AbstractDescribableImpl<ToolInsta
* @see EnvironmentSpecific
* @since 1.460
*/
public ToolInstallation translate(Node node, EnvVars envs, TaskListener listener) throws IOException, InterruptedException {
public ToolInstallation translate(@Nonnull Node node, EnvVars envs, TaskListener listener) throws IOException, InterruptedException {
ToolInstallation t = this;
if (t instanceof NodeSpecific) {
NodeSpecific n = (NodeSpecific) t;
......
......@@ -210,10 +210,11 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
public void onDeleted(Run run) {
Job<?, ?> j = run.getParent();
for (PeepholePermalink pp : Util.filter(j.getPermalinks(), PeepholePermalink.class)) {
if (pp.apply(run)) {
if (pp.resolve(j)==run) {
pp.updateCache(j,pp.find(run.getPreviousBuild()));
}
if (pp.resolve(j)==run) {
Run<?,?> r = pp.find(run.getPreviousBuild());
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Updating "+pp.getPermalinkFile(j).getName()+" permalink from deleted "+run.getNumber()+" to "+(r == null ? -1 : r.getNumber()));
pp.updateCache(j,r);
}
}
}
......@@ -227,8 +228,11 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
for (PeepholePermalink pp : Util.filter(j.getPermalinks(), PeepholePermalink.class)) {
if (pp.apply(run)) {
Run<?, ?> cur = pp.resolve(j);
if (cur==null || cur.getNumber()<run.getNumber())
if (cur==null || cur.getNumber()<run.getNumber()) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Updating "+pp.getPermalinkFile(j).getName()+" permalink to completed "+run.getNumber());
pp.updateCache(j,run);
}
}
}
}
......
jenkins (1.566) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
-- Kohsuke Kawaguchi <kk@kohsuke.org> Mon, 02 Jun 2014 08:18:10 -0700
jenkins (1.565) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
......
......@@ -11,7 +11,7 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<name>Jenkins plugin POM</name>
<version>1.567-SNAPSHOT</version>
<version>1.568-SNAPSHOT</version>
<packaging>pom</packaging>
<!--
......@@ -39,19 +39,19 @@
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-war</artifactId>
<type>war</type>
<version>1.567-SNAPSHOT</version>
<version>1.568-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-core</artifactId>
<version>1.567-SNAPSHOT</version>
<version>1.568-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-test-harness</artifactId>
<version>1.567-SNAPSHOT</version>
<version>1.568-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<!--
......
......@@ -33,7 +33,7 @@ THE SOFTWARE.
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.567-SNAPSHOT</version>
<version>1.568-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Jenkins main module</name>
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<artifactId>pom</artifactId>
<groupId>org.jenkins-ci.main</groupId>
<version>1.567-SNAPSHOT</version>
<version>1.568-SNAPSHOT</version>
</parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-test-harness</artifactId>
......@@ -103,9 +103,9 @@ THE SOFTWARE.
<version>6.1.26</version>
</dependency>
<dependency>
<groupId>org.jvnet.hudson</groupId>
<groupId>org.jenkins-ci</groupId>
<artifactId>test-annotations</artifactId>
<version>1.0</version>
<version>1.1</version>
<scope>compile</scope>
<!-- in this module we need this as a compile scope, whereas in the parent it's test -->
</dependency>
......
/*
* The MIT License
*
* Copyright 2013 Oleg Nenashev <nenashev@synopsys.com>, Synopsys Inc.
*
* 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.
*/
package hudson;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.FreeStyleProject;
import hudson.model.Run;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrapperDescriptor;
import java.io.IOException;
import org.jvnet.hudson.test.Bug;
import hudson.Launcher.DecoratedLauncher;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
/**
* Contains tests for {@link ProcStarter} class.
* @author Oleg Nenashev <nenashev@synopsys.com>, Synopsys Inc.
* @since TODO: define a version
*/
public class ProcStarterTest {
@Rule
public JenkinsRule rule = new JenkinsRule();
@Test
@Bug(20559)
public void testNonInitializedEnvsNPE() throws Exception {
// Create nodes and other test stuff
rule.hudson.setNumExecutors(0);
rule.createSlave();
// Create a job with test build wrappers
FreeStyleProject project = rule.createFreeStyleProject();
project.getBuildWrappersList().add(new DecoratedWrapper());
project.getBuildWrappersList().add(new EchoWrapper());
// Run the build. If NPE occurs, the test will fail
rule.buildAndAssertSuccess(project);
}
/**
* A stub descriptor for {@link BuildWrapper}s.
*/
public abstract static class TestWrapperDescriptor extends BuildWrapperDescriptor {
@Override
public boolean isApplicable(AbstractProject<?, ?> ap) {
return true;
}
@Override
public String getDisplayName() {
return "testStub";
}
}
/**
* A wrapper, which contains a nested launch.
*/
public static class EchoWrapper extends BuildWrapper {
@Override
public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
Launcher.ProcStarter starter = launcher.launch().cmds("echo", "Hello");
starter.start();
starter.join();
return new Environment() {
};
}
@Extension
public static class DescriptorImpl extends TestWrapperDescriptor {
}
};
/**
* A wrapper, which decorates launchers.
*/
public static class DecoratedWrapper extends BuildWrapper {
@Override
public Launcher decorateLauncher(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException, Run.RunnerAbortedException {
final BuildListener l = listener;
return new DecoratedLauncher(launcher) {
@Override
public Proc launch(Launcher.ProcStarter starter) throws IOException {
String[] envs = starter.envs(); // Finally, call envs()
l.getLogger().println("[DecoratedWrapper]: Number of environment variables is "+envs.length); // Fail on null
return super.launch(starter);
}
};
}
@Override
public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
return new Environment() {
};
}
@Extension
public static class DescriptorImpl extends TestWrapperDescriptor {
}
};
}
......@@ -28,7 +28,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.567-SNAPSHOT</version>
<version>1.568-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册