@@ -1,257 +1,44 @@
package com.nirima.jenkins.plugins.docker;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.PushImageCmd;
import com.github.dockerjava.api.exception.DockerException;
import com.github.dockerjava.api.exception.NotFoundException;
import com.github.dockerjava.api.model.Identifier;
import com.github.dockerjava.api.model.PushResponseItem;
import com.github.dockerjava.core.NameParser;
import com.github.dockerjava.core.command.PushImageResultCallback;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.nirima.jenkins.plugins.docker.action.DockerBuildAction;
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.Queue;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.queue.CauseOfBlockage;
import hudson.slaves.AbstractCloudSlave;
import hudson.slaves.Cloud;
import hudson.model.Slave;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.NodeProperty;
import hudson.slaves.RetentionStrategy;
import io.jenkins.docker.DockerTransientNode;
import io.jenkins.docker.client.DockerAPI;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.docker.commons.credentials.DockerRegistryEndpoint;
import org.jenkinsci.plugins.tokenmacro.TokenMacro;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.nirima.jenkins.plugins.docker.utils.LogUtils.printResponseItemToListener;
import static org.apache.commons.lang.StringUtils.isEmpty;

/**
* @deprecated use {@link DockerTransientNode}
*/
@Deprecated
public class DockerSlave extends Slave {

public class DockerSlave extends AbstractCloudSlave {
private static final Logger LOGGER = Logger.getLogger(DockerSlave.class.getName());
private transient DockerTemplate dockerTemplate;

public DockerTemplate dockerTemplate;
private transient String containerId;

// remember container id
@CheckForNull private String containerId;
private transient String cloudId;

// remember cloud name
@CheckForNull private String cloudId;
private transient DockerAPI dockerAPI;

private transient Run theRun;
private DockerAPI dockerAPI;

public DockerSlave(DockerTemplate dockerTemplate, String containerId,
String name, String nodeDescription,
String remoteFS, int numExecutors, Mode mode,
String labelString, ComputerLauncher launcher,
RetentionStrategy retentionStrategy,
List<? extends NodeProperty<?>> nodeProperties)
throws Descriptor.FormException, IOException {
super(name, nodeDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, nodeProperties);
Preconditions.checkNotNull(dockerTemplate);
Preconditions.checkNotNull(containerId);

setDockerTemplate(dockerTemplate);
this.containerId = containerId;
}

@Deprecated
public DockerSlave(String slaveName, String nodeDescription, ComputerLauncher launcher, String containerId,
DockerTemplate dockerTemplate, String cloudId)
throws IOException, Descriptor.FormException {
super(slaveName,
nodeDescription, //description
dockerTemplate.getRemoteFs(),
dockerTemplate.getNumExecutors(),
dockerTemplate.getMode(),
dockerTemplate.getLabelString(),
launcher,
dockerTemplate.getRetentionStrategyCopy(),
dockerTemplate.getNodeProperties()
);
setContainerId(containerId);
setDockerTemplate(dockerTemplate);
setCloudId(cloudId);
}

public DockerSlave(DockerCloud cloud, DockerTemplate template, ComputerLauncher launcher) throws IOException, Descriptor.FormException {
super(
cloud.getDisplayName() + '-' + Long.toHexString(System.nanoTime()),
"Docker Node [" + template.getDockerTemplateBase().getImage() + " on "+ cloud.getDisplayName() + "]",
template.getRemoteFs(),
template.getNumExecutors(),
template.getMode(),
template.getLabelString(),
launcher,
template.getRetentionStrategyCopy(),
template.getNodeProperties()
);
this.cloudId = cloud.getDisplayName();
this.dockerAPI = cloud.getDockerApi();
this.dockerTemplate = template;
}

public String getContainerId() {
return containerId;
}

public void setContainerId(String containerId) {
this.containerId = containerId;
}

public String getCloudId() {
return cloudId;
}

public void setCloudId(String cloudId) {
this.cloudId = cloudId;
}

public void setDockerAPI(DockerAPI dockerAPI) {
this.dockerAPI = dockerAPI;
}

public DockerAPI getDockerAPI() {
return dockerAPI;
}

public DockerTemplate getDockerTemplate() {
return dockerTemplate;
}

public void setDockerTemplate(DockerTemplate dockerTemplate) {
this.dockerTemplate = dockerTemplate;
}

public DockerCloud getCloud() {
final Cloud cloud = Jenkins.getInstance().getCloud(getCloudId());

if (cloud == null) {
throw new RuntimeException("Docker template " + dockerTemplate + " has no assigned Cloud.");
}

if (cloud.getClass() != DockerCloud.class) {
throw new RuntimeException("Assigned cloud is not DockerCloud");
}

return (DockerCloud) cloud;
private DockerSlave(@Nonnull String name, String remoteFS, ComputerLauncher launcher) throws Descriptor.FormException, IOException {
super(name, remoteFS, launcher);
}

@Override
public DockerComputer getComputer() {
return (DockerComputer) super.getComputer();
}

@Override
public String getDisplayName() {
return name;
}

public void setRun(Run run) {
this.theRun = run;
}

@Override
public DockerComputer createComputer() {
return new DockerComputer(this);
}

@Override
public CauseOfBlockage canTake(Queue.BuildableItem item) {
if (item.task instanceof Queue.FlyweightTask) {
return new CauseOfBlockage() {
public String getShortDescription() {
return "Don't run FlyweightTask on Docker node";
}
};
}
return super.canTake(item);
}

public boolean containerExistsInCloud() {
protected Object readResolve() {
try {
DockerClient client = getClient();
client.inspectContainerCmd(containerId).exec();
return true;
} catch (Exception ex) {
return false;
return new DockerTransientNode(containerId, dockerTemplate.remoteFs, getLauncher());
} catch (Descriptor.FormException | IOException e) {
throw new RuntimeException("Failed to migrate DockerSlave", e);
}
}

@Override
protected void _terminate(final TaskListener listener) throws IOException, InterruptedException {
try {
toComputer().disconnect(new DockerOfflineCause());
LOGGER.log(Level.INFO, "Disconnected computer");
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Can't disconnect", e);
}

if (containerId != null) {
Computer.threadPoolForRemoting.submit(new Runnable() {

@Override
public void run() {

try {
DockerClient client = getClient();
client.stopContainerCmd(getContainerId())
.withTimeout(10)
.exec();
LOGGER.log(Level.INFO, "Stopped container {0}", getContainerId());
} catch(NotFoundException e) {
LOGGER.log(Level.INFO, "Container already removed {0}", getContainerId());
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Failed to stop instance " + getContainerId() + " for slave " + name + " due to exception", ex.getMessage());
LOGGER.log(Level.SEVERE, "Causing exception for failure on stopping the instance was", ex);
}

// If the run was OK, then do any tagging here
if (theRun != null) {
try {
slaveShutdown(listener);
LOGGER.log(Level.INFO, "Shutdowned slave for {0}", getContainerId());
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Failure to slaveShutdown instance " + getContainerId() + " for slave " + name, e);
LOGGER.log(Level.SEVERE, "Causing exception for failure on slaveShutdown was", e);
}
}

try {
DockerClient client = getClient();
client.removeContainerCmd(containerId)
.withRemoveVolumes(getDockerTemplate().isRemoveVolumes())
.exec();

LOGGER.log(Level.INFO, "Removed container {0}", getContainerId());
} catch (NotFoundException e) {
LOGGER.log(Level.INFO, "Container already gone.");
}catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Failed to remove instance " + getContainerId() + " for slave " + name + " due to exception: " + ex.getMessage());
LOGGER.log(Level.SEVERE, "Causing exception for failre on removing instance was", ex);
}
}
});

} else {
LOGGER.log(Level.SEVERE, "ContainerId is absent, no way to remove/stop container");
}
}

/* FIXME better move this to a io.jenkins.docker.DockerTransientNode.Callback
private void slaveShutdown(final TaskListener listener) throws DockerException, IOException {
// The slave has stopped. Should we commit / tag / push ?
@@ -340,18 +127,6 @@ private String getAdditionalTag(TaskListener listener) {
return tagToken;
}
/**
* Add a built on docker action.
*/
private void addJenkinsAction(String tag_image) throws IOException {
theRun.addAction(new DockerBuildAction(getCloud().getDockerHost().getUri(), containerId, tag_image, dockerTemplate.remoteFsMapping));
theRun.save();
}

public DockerClient getClient() {
return getCloud().getClient();
}

private DockerJobProperty getJobProperty() {
try {
@@ -366,27 +141,14 @@ private DockerJobProperty getJobProperty() {
return new DockerJobProperty(false, null, false, true, null);
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("name", name)
.add("containerId", containerId)
.add("template", dockerTemplate)
.toString();
}

@Extension
public static final class DescriptorImpl extends SlaveDescriptor {

@Override
public String getDisplayName() {
return "Docker Slave";
}

@Override
public boolean isInstantiable() {
return false;
}
/ **
* Add a built on docker action.
* /
private void addJenkinsAction(String tag_image) throws IOException {
theRun.addAction(new DockerBuildAction(getCloud().getDockerHost().getUri(), containerId, tag_image, dockerTemplate.remoteFsMapping));
theRun.save();
}
*/

}
@@ -18,13 +18,16 @@
import hudson.model.Descriptor;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.Slave;
import hudson.model.TaskListener;
import hudson.model.labels.LabelAtom;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
import hudson.slaves.RetentionStrategy;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
import io.jenkins.docker.DockerTransientNode;
import io.jenkins.docker.client.DockerAPI;
import io.jenkins.docker.connector.DockerComputerConnector;
import jenkins.model.Jenkins;
@@ -267,14 +270,7 @@ public RetentionStrategy getRetentionStrategy() {
return retentionStrategy;
}

/**
* tmp fix for terminating boolean caching
*/
public RetentionStrategy getRetentionStrategyCopy() {
if (retentionStrategy instanceof DockerOnceRetentionStrategy) {
DockerOnceRetentionStrategy onceRetention = (DockerOnceRetentionStrategy) retentionStrategy;
return new DockerOnceRetentionStrategy(onceRetention.getIdleMinutes());
}
return retentionStrategy;
}

@@ -453,7 +449,7 @@ void pullImage(DockerAPI api) throws IOException, InterruptedException {
}

@Restricted(NoExternalUse.class)
public DockerSlave provisionFromTemplate(TaskListener listener, DockerAPI api) throws IOException, Descriptor.FormException, InterruptedException {
public Node provisionNode(TaskListener listener, DockerAPI api) throws IOException, Descriptor.FormException, InterruptedException {

final DockerClient client = api.getClient();
final DockerComputerConnector connector = getConnector();
@@ -479,20 +475,16 @@ public DockerSlave provisionFromTemplate(TaskListener listener, DockerAPI api) t
throw e;
}

DockerSlave slave = new DockerSlave(this, containerId,
"docker-" + containerId.substring(0,12),
"Docker Agent [" + getImage() + " on "+ api.getDockerHost().getUri() + "]",
getRemoteFs(),
getNumExecutors(),
getMode(),
getLabelString(),
connector.launch(api, containerId, this, listener),
getRetentionStrategyCopy(),
getNodeProperties());

slave.setContainerId(containerId);
slave.setDockerAPI(api);
return slave;
final ComputerLauncher launcher = connector.createLauncher(api, containerId, this, listener);
DockerTransientNode node = new DockerTransientNode(containerId, remoteFs, launcher);
node.setNodeDescription("Docker Agent [" + getImage() + " on "+ api.getDockerHost().getUri() + "]");
node.setMode(mode);
node.setLabelString(labelString);
node.setRetentionStrategy(retentionStrategy);
node.setNodeProperties(nodeProperties);
node.setRemoveVolumes(removeVolumes);
node.setDockerAPI(api);
return node;
}

@Extension
@@ -510,7 +502,7 @@ public FormValidation doCheckNumExecutors(@QueryParameter int numExecutors) {
* Get a list of all {@link NodePropertyDescriptor}s we can use to define DockerSlave NodeProperties.
*/
public List<NodePropertyDescriptor> getNodePropertyDescriptors() {
DockerSlave.DescriptorImpl descriptor = (DockerSlave.DescriptorImpl) Jenkins.getInstance().getDescriptorOrDie(DockerSlave.class);
Slave.SlaveDescriptor descriptor = (Slave.SlaveDescriptor) Jenkins.getInstance().getDescriptorOrDie(Slave.class);
final List<NodePropertyDescriptor> descriptors = descriptor.nodePropertyDescriptors(null);

final Iterator<NodePropertyDescriptor> iterator = descriptors.iterator();
@@ -1,21 +1,14 @@
package com.nirima.jenkins.plugins.docker.builder;

import com.google.common.base.Strings;
import com.nirima.jenkins.plugins.docker.DockerCloud;
import com.nirima.jenkins.plugins.docker.utils.JenkinsUtils;
import hudson.Launcher;
import hudson.model.Build;
import hudson.model.Run;
import com.google.common.base.Strings;

import com.github.dockerjava.api.DockerClient;
import com.nirima.jenkins.plugins.docker.DockerCloud;
import com.nirima.jenkins.plugins.docker.DockerSlave;
import hudson.model.AbstractBuild;
import hudson.model.Node;
import jenkins.model.Jenkins;

import com.google.common.base.Optional;

import javax.annotation.Nonnull;
import java.util.Optional;


/**
@@ -12,7 +12,6 @@
import com.github.dockerjava.core.command.PushImageResultCallback;
import com.github.dockerjava.core.dockerfile.Dockerfile;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.nirima.jenkins.plugins.docker.DockerCloud;
@@ -55,6 +54,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;

import static com.nirima.jenkins.plugins.docker.utils.LogUtils.printResponseItemToListener;
@@ -1,20 +1,20 @@
package com.nirima.jenkins.plugins.docker.strategy;

import io.jenkins.docker.DockerComputer;
import hudson.Extension;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.Executor;
import hudson.model.ExecutorListener;
import hudson.model.Queue;
import hudson.slaves.AbstractCloudComputer;
import hudson.slaves.AbstractCloudSlave;
import hudson.slaves.CloudRetentionStrategy;
import hudson.slaves.EphemeralNode;
import hudson.slaves.RetentionStrategy;
import io.jenkins.docker.DockerTransientNode;
import org.jenkinsci.plugins.durabletask.executors.ContinuableExecutable;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.IOException;
import javax.annotation.Nonnull;
import java.util.logging.Level;
import java.util.logging.Logger;

@@ -27,7 +27,7 @@
* Retention strategy that allows a cloud slave to run only a single build before disconnecting.
* A {@link ContinuableExecutable} does not trigger termination.
*/
public class DockerOnceRetentionStrategy extends CloudRetentionStrategy implements ExecutorListener {
public class DockerOnceRetentionStrategy extends RetentionStrategy<DockerComputer> implements ExecutorListener {

private static final Logger LOGGER = Logger.getLogger(DockerOnceRetentionStrategy.class.getName());

@@ -41,7 +41,6 @@ public class DockerOnceRetentionStrategy extends CloudRetentionStrategy implemen
*/
@DataBoundConstructor
public DockerOnceRetentionStrategy(int idleMinutes) {
super(idleMinutes);
this.timeout = idleMinutes;
}

@@ -50,14 +49,14 @@ public int getIdleMinutes() {
}

@Override
public long check(final AbstractCloudComputer c) {
public long check(@Nonnull DockerComputer c) {
// When the slave is idle we should disable accepting tasks and check to see if it is already trying to
// terminate. If it's not already trying to terminate then lets terminate manually.
if (c.isIdle() && !disabled) {
if (c.isIdle()) {
final long idleMilliseconds = System.currentTimeMillis() - c.getIdleStartMilliseconds();
if (idleMilliseconds > MINUTES.toMillis(timeout)) {
LOGGER.log(Level.FINE, "Disconnecting {0}", c.getName());
done(c);
done(c, null);
}
}

@@ -66,11 +65,11 @@ public long check(final AbstractCloudComputer c) {
}

@Override
public void start(AbstractCloudComputer c) {
public void start(DockerComputer c) {
if (c.getNode() instanceof EphemeralNode) {
throw new IllegalStateException("May not use OnceRetentionStrategy on an EphemeralNode: " + c);
}
super.start(c);
c.connect(true);
}

@Override
@@ -88,44 +87,31 @@ public void taskCompletedWithProblems(Executor executor, Queue.Task task, long d
}

private void done(Executor executor) {
final AbstractCloudComputer<?> c = (AbstractCloudComputer) executor.getOwner();
final DockerComputer c = (DockerComputer) executor.getOwner();
Queue.Executable exec = executor.getCurrentExecutable();
if (exec instanceof ContinuableExecutable && ((ContinuableExecutable) exec).willContinue()) {
LOGGER.log(Level.FINE, "not terminating {0} because {1} says it will be continued", new Object[]{c.getName(), exec});
return;
}
LOGGER.log(Level.FINE, "terminating {0} since {1} seems to be finished", new Object[]{c.getName(), exec});
done(c);
done(c, exec);
}

private void done(final AbstractCloudComputer<?> c) {
private void done(final DockerComputer c, Queue.Executable exec) {
c.setAcceptingTasks(false); // just in case
synchronized (this) {
if (terminating) {
return;
}
terminating = true;
}
Computer.threadPoolForRemoting.submit(new Runnable() {
@Override
public void run() {
Queue.withLock( new Runnable() {
@Override
public void run() {
try {
AbstractCloudSlave node = c.getNode();
if (node != null) {
node.terminate();
}
} catch (InterruptedException | IOException e) {
LOGGER.log(Level.WARNING, "Failed to terminate " + c.getName(), e);
synchronized (DockerOnceRetentionStrategy.this) {
terminating = false;
}
}
}
});
}
Computer.threadPoolForRemoting.submit(() -> {
Queue.withLock( () -> {
DockerTransientNode node = c.getNode();
if (node != null) {
node.terminate(c.getListener(), exec);
}
});
});
}

@@ -5,6 +5,7 @@
import hudson.model.AbstractBuild;
import hudson.model.Node;
import hudson.model.TaskListener;
import io.jenkins.docker.DockerTransientNode;
import org.jenkinsci.plugins.tokenmacro.DataBoundTokenMacro;
import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException;

@@ -18,9 +19,8 @@ public class DockerHostTokenMacro extends DataBoundTokenMacro {
@Override
public String evaluate(AbstractBuild<?, ?> abstractBuild, TaskListener taskListener, String s) throws MacroEvaluationException, IOException, InterruptedException {
Node node = abstractBuild.getBuiltOn();
if( node instanceof DockerSlave) {
DockerSlave dockerSlave = (DockerSlave)node;
return dockerSlave.getContainerId();
if( node instanceof DockerTransientNode) {
return ((DockerTransientNode) node).getContainerId();
}

return null;
@@ -1,24 +1,9 @@
package com.nirima.jenkins.plugins.docker.utils;

import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.Ports;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.nirima.jenkins.plugins.docker.DockerCloud;
import com.nirima.jenkins.plugins.docker.DockerSlave;

import org.jenkinsci.main.modules.instance_identity.InstanceIdentity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Map;

import javax.annotation.Nullable;

import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
@@ -27,11 +12,17 @@
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
import hudson.slaves.Cloud;
import io.jenkins.docker.DockerTransientNode;
import jenkins.model.Jenkins;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import org.jenkinsci.main.modules.instance_identity.InstanceIdentity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Optional;

/**
* Utilities to fetch things out of jenkins environment.
@@ -46,12 +37,11 @@ public class JenkinsUtils {
public static Optional<DockerCloud> getCloudForBuild(AbstractBuild build) {

Node node = build.getBuiltOn();
if (node instanceof DockerSlave) {
DockerSlave slave = (DockerSlave) node;
return Optional.of(slave.getCloud());
if (node instanceof DockerTransientNode) {
return Optional.of(((DockerTransientNode) node).getCloud());
}

return Optional.absent();
return Optional.empty();
}

/**
@@ -62,13 +52,12 @@ public static Optional<DockerCloud> getCloudForChannel(VirtualChannel channel) {
if( channel instanceof Channel) {
Channel c = (Channel)channel;
Node node = Jenkins.getInstance().getNode( c.getName() );
if (node instanceof DockerSlave) {
DockerSlave slave = (DockerSlave) node;
return Optional.of(slave.getCloud());
if (node instanceof DockerTransientNode) {
return Optional.of(((DockerTransientNode) node).getCloud());
}
}

return Optional.absent();
return Optional.empty();
}

public static Optional<DockerCloud> getCloudThatWeBuiltOn(Run<?,?> build, Launcher launcher) {
@@ -0,0 +1,48 @@
package io.jenkins.docker;

import com.google.common.base.Objects;
import com.nirima.jenkins.plugins.docker.DockerCloud;
import com.nirima.jenkins.plugins.docker.DockerSlave;
import hudson.slaves.SlaveComputer;

import javax.annotation.CheckForNull;
import java.util.logging.Logger;

/**
* Represents remote (running) container
*
* @author magnayn
*/
public class DockerComputer extends SlaveComputer {
private static final Logger LOGGER = Logger.getLogger(DockerComputer.class.getName());

public DockerComputer(DockerTransientNode node) {
super(node);
}

public DockerCloud getCloud() {
return getNode().getCloud();
}

@CheckForNull
@Override
public DockerTransientNode getNode() {
return (DockerTransientNode) super.getNode();
}

public String getContainerId() {
return getNode().getContainerId();
}

public String getCloudId() {
return getNode().getCloudId();
}

@Override
public String toString() {
return Objects.toStringHelper(this)
.add("name", super.getName())
.add("slave", getNode())
.toString();
}
}
@@ -0,0 +1,154 @@
package io.jenkins.docker;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.exception.NotFoundException;
import com.nirima.jenkins.plugins.docker.DockerCloud;
import com.nirima.jenkins.plugins.docker.DockerOfflineCause;
import com.nirima.jenkins.plugins.docker.strategy.DockerOnceRetentionStrategy;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.Queue;
import hudson.model.Slave;
import hudson.model.TaskListener;
import hudson.slaves.Cloud;
import hudson.slaves.ComputerLauncher;
import io.jenkins.docker.client.DockerAPI;
import jenkins.model.Jenkins;

import javax.annotation.CheckForNull;
import java.io.IOException;
import java.io.Serializable;

/**
* A {@link Slave} node designed to be used only once for a build.
*
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
*/
public class DockerTransientNode extends Slave {

private final String containerId;

private DockerAPI dockerAPI;

private boolean removeVolumes;

private String cloudId;

private Callback callback;

public DockerTransientNode(String containerId, String remoteFS, ComputerLauncher launcher) throws Descriptor.FormException, IOException {
super("docker-" + containerId.substring(0,12), remoteFS, launcher);
setNumExecutors(1);
setMode(Mode.EXCLUSIVE);
setRetentionStrategy(new DockerOnceRetentionStrategy(10));
this.containerId = containerId;
}

public String getContainerId() {
return containerId;
}

public void setDockerAPI(DockerAPI dockerAPI) {
this.dockerAPI = dockerAPI;
}

public boolean isRemoveVolumes() {
return removeVolumes;
}

public void setRemoveVolumes(boolean removeVolumes) {
this.removeVolumes = removeVolumes;
}

public String getCloudId() {
return cloudId;
}

public void setCloudId(String cloudId) {
this.cloudId = cloudId;
}

public void setCallback(Callback callback) {
this.callback = callback;
}



@Override
public DockerComputer createComputer() {
return new DockerComputer(this);
}

public void terminate(TaskListener listener, Queue.Executable exec) {
try {
toComputer().disconnect(new DockerOfflineCause());
listener.getLogger().println("Disconnected computer");
} catch (Exception e) {
listener.error("Can't disconnect", e);
}

if (containerId != null) {
Computer.threadPoolForRemoting.submit(() -> {

DockerClient client = dockerAPI.getClient();

try {
client.stopContainerCmd(containerId)
.withTimeout(10)
.exec();
listener.getLogger().println("Stopped container "+ containerId);
} catch(NotFoundException e) {
listener.getLogger().println("Container already removed " + containerId);
} catch (Exception ex) {
listener.error("Failed to stop instance " + getContainerId() + " for slave " + name + " due to exception", ex.getMessage());
listener.error("Causing exception for failure on stopping the instance was", ex);
}

callback.onCompletion(this, exec);

try {
client.removeContainerCmd(containerId)
.withRemoveVolumes(removeVolumes)
.exec();

listener.getLogger().println("Removed container " + containerId);
} catch (NotFoundException e) {
listener.getLogger().println("Container already gone.");
} catch (Exception ex) {
listener.error("Failed to remove instance " + getContainerId() + " for slave " + name + " due to exception: " + ex.getMessage());
listener.error("Causing exception for failre on removing instance was", ex);
}
});

} else {
listener.error("ContainerId is absent, no way to remove/stop container");
}
}

public DockerCloud getCloud() {
if (cloudId == null) return null;
final Cloud cloud = Jenkins.getInstance().getCloud(cloudId);

if (cloud == null) {
throw new RuntimeException("Failed to retrieve Cloud " + cloudId);
}

if (cloud.getClass() != DockerCloud.class) {
throw new RuntimeException(cloudId + " is not DockerCloud");
}

return (DockerCloud) cloud;
}

public static interface Callback extends Serializable {

/**
* Callback to get notified as container has been stopped and will be removed.
* The {@link Queue.Executable} assigned to this {@link DockerTransientNode} si also passed, but may be
* <code>null</code> as something went wrong during provisioning.
*/
void onCompletion(DockerTransientNode dockerTransientNode, @CheckForNull Queue.Executable exec);
}


}
@@ -63,7 +63,7 @@ public void afterContainerStarted(DockerAPI api, DockerTemplate template, String
}

@Override
protected ComputerLauncher launch(DockerAPI api, DockerTemplate template, InspectContainerResponse inspect, TaskListener listener) throws IOException, InterruptedException {
protected ComputerLauncher createLauncher(DockerAPI api, DockerTemplate template, InspectContainerResponse inspect, TaskListener listener) throws IOException, InterruptedException {
return new DockerAttachLauncher(api, inspect.getId(), user, template.remoteFs);
}

@@ -84,7 +84,7 @@ protected String injectRemotingJar(String containerId, String workdir, DockerCli
return workdir + '/' + remoting.getName();
}

public final ComputerLauncher launch(DockerAPI api, @Nonnull String containerId, DockerTemplate template, TaskListener listener) throws IOException, InterruptedException {
public final ComputerLauncher createLauncher(DockerAPI api, @Nonnull String containerId, DockerTemplate template, TaskListener listener) throws IOException, InterruptedException {

final InspectContainerResponse inspect = api.getClient().inspectContainerCmd(containerId).exec();
final Boolean running = inspect.getState().getRunning();
@@ -93,13 +93,13 @@ public final ComputerLauncher launch(DockerAPI api, @Nonnull String containerId,
throw new IOException("Container is not running.");
}

return launch(api, template, inspect, listener);
return createLauncher(api, template, inspect, listener);
}

/**
* Create a Launcher to create an Agent with this container. Can assume container has been created by this
* DockerAgentConnector so adequate setup did take place.
*/
protected abstract ComputerLauncher launch(DockerAPI api, DockerTemplate template, InspectContainerResponse inspect, TaskListener listener) throws IOException, InterruptedException;
protected abstract ComputerLauncher createLauncher(DockerAPI api, DockerTemplate template, InspectContainerResponse inspect, TaskListener listener) throws IOException, InterruptedException;

}
@@ -56,7 +56,7 @@ public JNLPLauncher getJnlpLauncher() {


@Override
protected ComputerLauncher launch(final DockerAPI api, final DockerTemplate template, final InspectContainerResponse inspect, TaskListener listener) throws IOException, InterruptedException {
protected ComputerLauncher createLauncher(final DockerAPI api, final DockerTemplate template, final InspectContainerResponse inspect, TaskListener listener) throws IOException, InterruptedException {
return new DelegatingComputerLauncher(new JNLPLauncher()) {

@Override
@@ -186,7 +186,7 @@ public void beforeContainerStarted(DockerAPI api, DockerTemplate template, Strin
}

@Override
protected ComputerLauncher launch(DockerAPI api, DockerTemplate template, InspectContainerResponse inspect, TaskListener listener) throws IOException, InterruptedException {
protected ComputerLauncher createLauncher(DockerAPI api, DockerTemplate template, InspectContainerResponse inspect, TaskListener listener) throws IOException, InterruptedException {
if ("exited".equals(inspect.getState().getStatus())) {
// Something went wrong
// FIXME report error "somewhere" visible to end user.
@@ -1,11 +1,11 @@
package io.jenkins.docker.pipeline;

import com.nirima.jenkins.plugins.docker.DockerSlave;
import com.nirima.jenkins.plugins.docker.DockerTemplate;
import com.nirima.jenkins.plugins.docker.DockerTemplateBase;
import hudson.FilePath;
import hudson.model.Node;
import hudson.model.TaskListener;
import io.jenkins.docker.DockerTransientNode;
import io.jenkins.docker.client.DockerAPI;
import io.jenkins.docker.connector.DockerComputerAttachConnector;
import jenkins.model.Jenkins;
@@ -54,7 +54,7 @@ public boolean start() throws Exception {
listener.getLogger().println("Launching new docker node based on " + image);

final DockerAPI api = new DockerAPI(new DockerServerEndpoint(dockerHost, credentialsId));
final DockerSlave slave = t.provisionFromTemplate(listener, api);
final Node slave = t.provisionNode(listener, api);

Jenkins.getInstance().addNode(slave);

@@ -82,17 +82,17 @@ private static class Callback extends BodyExecutionCallback.TailCall {

private final String nodeName;

public Callback(DockerSlave node) {
public Callback(Node node) {
this.nodeName = node.getNodeName();
}

@Override
protected void finished(StepContext context) throws Exception {
final DockerSlave node = (DockerSlave) Jenkins.getInstance().getNode(nodeName);
final DockerTransientNode node = (DockerTransientNode) Jenkins.getInstance().getNode(nodeName);
if (node != null) {
TaskListener listener = context.get(TaskListener.class);
listener.getLogger().println("Waiting for node to be online ...");
node.terminate();
node.terminate(listener, null);
Jenkins.getInstance().removeNode(node);
}
}