diff --git a/README.md b/README.md
index 039657807..d5052e6b9 100644
--- a/README.md
+++ b/README.md
@@ -51,23 +51,23 @@ For secure tls (https) communication:
DOCKER_CERT_PATH=/Users/marcus/.docker/machine/machines/docker-1.11.2
### Latest release version
-Supports a subset of the Docker Remote API [v1.23](https://github.com/docker/docker/blob/master/docs/reference/api/docker_remote_api_v1.23.md), Docker Server version 1.11.x
+Supports a subset of the Docker Remote API [v1.23](https://github.com/docker/docker/blob/master/docs/api/v1.23.md), Docker Server version 1.11.x
com.github.docker-java
docker-java
- 3.0.3
+ 3.0.6
### Latest development version
-Supports a subset of the Docker Remote API [v1.23](https://github.com/docker/docker/blob/master/docs/reference/api/docker_remote_api_v1.23.md), Docker Server version 1.11.x
+Supports a subset of the Docker Remote API [v1.23](https://github.com/docker/docker/blob/master/docs/api/v1.23.md), Docker Server version 1.11.x
You can find the latest development version including javadoc and source files on [Sonatypes OSS repository](https://oss.sonatype.org/content/groups/public/com/github/docker-java/docker-java/).
com.github.docker-java
docker-java
- 3.0.4-SNAPSHOT
+ 3.0.7-SNAPSHOT
diff --git a/src/main/java/com/github/dockerjava/api/model/Bind.java b/src/main/java/com/github/dockerjava/api/model/Bind.java
index 0995c87ac..9a7ebf18d 100644
--- a/src/main/java/com/github/dockerjava/api/model/Bind.java
+++ b/src/main/java/com/github/dockerjava/api/model/Bind.java
@@ -18,24 +18,48 @@ public class Bind implements Serializable {
private AccessMode accessMode;
+ /**
+ * @since {@link com.github.dockerjava.core.RemoteApiVersion#VERSION_1_23}
+ */
+ private Boolean noCopy;
+
/**
* @since {@link com.github.dockerjava.core.RemoteApiVersion#VERSION_1_17}
*/
private SELContext secMode;
+ /**
+ * @since {@link com.github.dockerjava.core.RemoteApiVersion#VERSION_1_22}
+ */
+ private PropagationMode propagationMode;
+
public Bind(String path, Volume volume) {
this(path, volume, AccessMode.DEFAULT, SELContext.DEFAULT);
}
+ public Bind(String path, Volume volume, Boolean noCopy) {
+ this(path, volume, AccessMode.DEFAULT, SELContext.DEFAULT, noCopy);
+ }
+
public Bind(String path, Volume volume, AccessMode accessMode) {
this(path, volume, accessMode, SELContext.DEFAULT);
}
public Bind(String path, Volume volume, AccessMode accessMode, SELContext secMode) {
+ this(path, volume, accessMode, secMode, null);
+ }
+
+ public Bind(String path, Volume volume, AccessMode accessMode, SELContext secMode, Boolean noCopy) {
+ this(path, volume, accessMode, secMode, noCopy, PropagationMode.DEFAULT_MODE);
+ }
+
+ public Bind(String path, Volume volume, AccessMode accessMode, SELContext secMode, Boolean noCopy, PropagationMode propagationMode) {
this.path = path;
this.volume = volume;
this.accessMode = accessMode;
this.secMode = secMode;
+ this.noCopy = noCopy;
+ this.propagationMode = propagationMode;
}
public String getPath() {
@@ -54,6 +78,14 @@ public SELContext getSecMode() {
return secMode;
}
+ public Boolean getNoCopy() {
+ return noCopy;
+ }
+
+ public PropagationMode getPropagationMode() {
+ return propagationMode;
+ }
+
/**
* Parses a bind mount specification to a {@link Bind}.
*
@@ -74,15 +106,25 @@ public static Bind parse(String serialized) {
String[] flags = parts[2].split(",");
AccessMode accessMode = AccessMode.DEFAULT;
SELContext seMode = SELContext.DEFAULT;
+ Boolean nocopy = null;
+ PropagationMode propagationMode = PropagationMode.DEFAULT_MODE;
for (String p : flags) {
if (p.length() == 2) {
accessMode = AccessMode.valueOf(p.toLowerCase());
+ } else if ("nocopy".equals(p)) {
+ nocopy = true;
+ } else if (PropagationMode.SHARED.toString().equals(p)) {
+ propagationMode = PropagationMode.SHARED;
+ } else if (PropagationMode.SLAVE.toString().equals(p)) {
+ propagationMode = PropagationMode.SLAVE;
+ } else if (PropagationMode.PRIVATE.toString().equals(p)) {
+ propagationMode = PropagationMode.PRIVATE;
} else {
seMode = SELContext.fromString(p);
}
}
- return new Bind(parts[0], new Volume(parts[1]), accessMode, seMode);
+ return new Bind(parts[0], new Volume(parts[1]), accessMode, seMode, nocopy, propagationMode);
}
default: {
throw new IllegalArgumentException();
@@ -102,6 +144,8 @@ public boolean equals(Object obj) {
.append(volume, other.getVolume())
.append(accessMode, other.getAccessMode())
.append(secMode, other.getSecMode())
+ .append(noCopy, other.getNoCopy())
+ .append(propagationMode, other.getPropagationMode())
.isEquals();
} else {
return super.equals(obj);
@@ -115,6 +159,8 @@ public int hashCode() {
.append(volume)
.append(accessMode)
.append(secMode)
+ .append(noCopy)
+ .append(propagationMode)
.toHashCode();
}
@@ -127,10 +173,12 @@ public int hashCode() {
*/
@Override
public String toString() {
- return String.format("%s:%s:%s%s",
+ return String.format("%s:%s:%s%s%s%s",
path,
volume.getPath(),
accessMode.toString(),
- secMode != SELContext.none ? "," + secMode.toString() : "");
+ secMode != SELContext.none ? "," + secMode.toString() : "",
+ noCopy != null ? ",nocopy" : "",
+ propagationMode != PropagationMode.DEFAULT_MODE ? "," + propagationMode.toString() : "");
}
}
diff --git a/src/main/java/com/github/dockerjava/api/model/PropagationMode.java b/src/main/java/com/github/dockerjava/api/model/PropagationMode.java
new file mode 100644
index 000000000..9be7d6e43
--- /dev/null
+++ b/src/main/java/com/github/dockerjava/api/model/PropagationMode.java
@@ -0,0 +1,50 @@
+package com.github.dockerjava.api.model;
+
+/**
+ * The propagation mode of a file system or file: shared, slave or private.
+ *
+ * @see https://github.com/docker/docker/pull/17034
+ * @since 1.22
+ */
+public enum PropagationMode {
+ /** default */
+ DEFAULT(""),
+
+ /** shared */
+ SHARED("shared"),
+
+ /** slave */
+ SLAVE("slave"),
+
+ /** private */
+ PRIVATE("private");
+
+ /**
+ * The default {@link PropagationMode}: {@link #DEFAULT}
+ */
+ public static final PropagationMode DEFAULT_MODE = DEFAULT;
+
+ private String value;
+
+ PropagationMode(String v) {
+ value = v;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ public static PropagationMode fromString(String v) {
+ switch (v) {
+ case "shared":
+ return SHARED;
+ case "slave":
+ return SLAVE;
+ case "private":
+ return PRIVATE;
+ default:
+ return DEFAULT;
+ }
+ }
+}
diff --git a/src/main/java/com/github/dockerjava/netty/NettyDockerCmdExecFactory.java b/src/main/java/com/github/dockerjava/netty/NettyDockerCmdExecFactory.java
index cead92e92..c91d4af21 100644
--- a/src/main/java/com/github/dockerjava/netty/NettyDockerCmdExecFactory.java
+++ b/src/main/java/com/github/dockerjava/netty/NettyDockerCmdExecFactory.java
@@ -108,6 +108,9 @@
import com.github.dockerjava.netty.exec.RenameContainerCmdExec;
import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelConfig;
+import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollDomainSocketChannel;
@@ -178,6 +181,8 @@ public DuplexChannel getChannel() {
}
};
+ private Integer connectTimeout = null;
+
@Override
public void init(DockerClientConfig dockerClientConfig) {
checkNotNull(dockerClientConfig, "config was not specified");
@@ -218,7 +223,15 @@ private class UnixDomainSocketInitializer implements NettyInitializer {
@Override
public EventLoopGroup init(Bootstrap bootstrap, DockerClientConfig dockerClientConfig) {
EventLoopGroup epollEventLoopGroup = new EpollEventLoopGroup(0, new DefaultThreadFactory(threadPrefix));
- bootstrap.group(epollEventLoopGroup).channel(EpollDomainSocketChannel.class)
+
+ ChannelFactory factory = new ChannelFactory() {
+ @Override
+ public EpollDomainSocketChannel newChannel() {
+ return configure(new EpollDomainSocketChannel());
+ }
+ };
+
+ bootstrap.group(epollEventLoopGroup).channelFactory(factory)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(final UnixChannel channel) throws Exception {
@@ -245,7 +258,14 @@ public EventLoopGroup init(Bootstrap bootstrap, final DockerClientConfig dockerC
Security.addProvider(new BouncyCastleProvider());
- bootstrap.group(nioEventLoopGroup).channel(NioSocketChannel.class)
+ ChannelFactory factory = new ChannelFactory() {
+ @Override
+ public NioSocketChannel newChannel() {
+ return configure(new NioSocketChannel());
+ }
+ };
+
+ bootstrap.group(nioEventLoopGroup).channelFactory(factory)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(final SocketChannel channel) throws Exception {
@@ -580,6 +600,24 @@ public void close() throws IOException {
eventLoopGroup.shutdownGracefully();
}
+ /**
+ * Configure connection timeout in milliseconds
+ */
+ public NettyDockerCmdExecFactory withConnectTimeout(Integer connectTimeout) {
+ this.connectTimeout = connectTimeout;
+ return this;
+ }
+
+ private T configure(T channel) {
+ ChannelConfig channelConfig = channel.config();
+
+ if (connectTimeout != null) {
+ channelConfig.setConnectTimeoutMillis(connectTimeout);
+ }
+
+ return channel;
+ }
+
private WebTarget getBaseResource() {
return new WebTarget(channelProvider);
}
diff --git a/src/test/java/com/github/dockerjava/api/model/BindTest.java b/src/test/java/com/github/dockerjava/api/model/BindTest.java
index 3204ee716..2e6fd8353 100644
--- a/src/test/java/com/github/dockerjava/api/model/BindTest.java
+++ b/src/test/java/com/github/dockerjava/api/model/BindTest.java
@@ -3,6 +3,7 @@
import static com.github.dockerjava.api.model.AccessMode.ro;
import static com.github.dockerjava.api.model.AccessMode.rw;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.Is.is;
import org.testng.annotations.Test;
@@ -16,6 +17,8 @@ public void parseUsingDefaultAccessMode() {
assertThat(bind.getVolume().getPath(), is("/container"));
assertThat(bind.getAccessMode(), is(AccessMode.DEFAULT));
assertThat(bind.getSecMode(), is(SELContext.none));
+ assertThat(bind.getNoCopy(), nullValue());
+ assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
}
@Test
@@ -25,6 +28,52 @@ public void parseReadWrite() {
assertThat(bind.getVolume().getPath(), is("/container"));
assertThat(bind.getAccessMode(), is(rw));
assertThat(bind.getSecMode(), is(SELContext.none));
+ assertThat(bind.getNoCopy(), nullValue());
+ assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
+ }
+
+ @Test
+ public void parseReadWriteNoCopy() {
+ Bind bind = Bind.parse("/host:/container:rw,nocopy");
+ assertThat(bind.getPath(), is("/host"));
+ assertThat(bind.getVolume().getPath(), is("/container"));
+ assertThat(bind.getAccessMode(), is(rw));
+ assertThat(bind.getSecMode(), is(SELContext.none));
+ assertThat(bind.getNoCopy(), is(true));
+ assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
+ }
+
+ @Test
+ public void parseReadWriteShared() {
+ Bind bind = Bind.parse("/host:/container:rw,shared");
+ assertThat(bind.getPath(), is("/host"));
+ assertThat(bind.getVolume().getPath(), is("/container"));
+ assertThat(bind.getAccessMode(), is(rw));
+ assertThat(bind.getSecMode(), is(SELContext.none));
+ assertThat(bind.getNoCopy(), nullValue());
+ assertThat(bind.getPropagationMode(), is(PropagationMode.SHARED));
+ }
+
+ @Test
+ public void parseReadWriteSlave() {
+ Bind bind = Bind.parse("/host:/container:rw,slave");
+ assertThat(bind.getPath(), is("/host"));
+ assertThat(bind.getVolume().getPath(), is("/container"));
+ assertThat(bind.getAccessMode(), is(rw));
+ assertThat(bind.getSecMode(), is(SELContext.none));
+ assertThat(bind.getNoCopy(), nullValue());
+ assertThat(bind.getPropagationMode(), is(PropagationMode.SLAVE));
+ }
+
+ @Test
+ public void parseReadWritePrivate() {
+ Bind bind = Bind.parse("/host:/container:rw,private");
+ assertThat(bind.getPath(), is("/host"));
+ assertThat(bind.getVolume().getPath(), is("/container"));
+ assertThat(bind.getAccessMode(), is(rw));
+ assertThat(bind.getSecMode(), is(SELContext.none));
+ assertThat(bind.getNoCopy(), nullValue());
+ assertThat(bind.getPropagationMode(), is(PropagationMode.PRIVATE));
}
@Test
@@ -34,6 +83,8 @@ public void parseReadOnly() {
assertThat(bind.getVolume().getPath(), is("/container"));
assertThat(bind.getAccessMode(), is(ro));
assertThat(bind.getSecMode(), is(SELContext.none));
+ assertThat(bind.getNoCopy(), nullValue());
+ assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
}
@Test
@@ -43,12 +94,16 @@ public void parseSELOnly() {
assertThat(bind.getVolume().getPath(), is("/container"));
assertThat(bind.getAccessMode(), is(AccessMode.DEFAULT));
assertThat(bind.getSecMode(), is(SELContext.single));
+ assertThat(bind.getNoCopy(), nullValue());
+ assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
bind = Bind.parse("/host:/container:z");
assertThat(bind.getPath(), is("/host"));
assertThat(bind.getVolume().getPath(), is("/container"));
assertThat(bind.getAccessMode(), is(AccessMode.DEFAULT));
assertThat(bind.getSecMode(), is(SELContext.shared));
+ assertThat(bind.getNoCopy(), nullValue());
+ assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
}
@Test
@@ -58,6 +113,8 @@ public void parseReadWriteSEL() {
assertThat(bind.getVolume().getPath(), is("/container"));
assertThat(bind.getAccessMode(), is(rw));
assertThat(bind.getSecMode(), is(SELContext.single));
+ assertThat(bind.getNoCopy(), nullValue());
+ assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
}
@Test
@@ -67,6 +124,8 @@ public void parseReadOnlySEL() {
assertThat(bind.getVolume().getPath(), is("/container"));
assertThat(bind.getAccessMode(), is(ro));
assertThat(bind.getSecMode(), is(SELContext.shared));
+ assertThat(bind.getNoCopy(), nullValue());
+ assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Error parsing Bind.*")
@@ -94,6 +153,26 @@ public void toStringReadWrite() {
assertThat(Bind.parse("/host:/container:rw").toString(), is("/host:/container:rw"));
}
+ @Test
+ public void toStringReadWriteNoCopy() {
+ assertThat(Bind.parse("/host:/container:rw,nocopy").toString(), is("/host:/container:rw,nocopy"));
+ }
+
+ @Test
+ public void toStringReadWriteShared() {
+ assertThat(Bind.parse("/host:/container:rw,shared").toString(), is("/host:/container:rw,shared"));
+ }
+
+ @Test
+ public void toStringReadWriteSlave() {
+ assertThat(Bind.parse("/host:/container:rw,slave").toString(), is("/host:/container:rw,slave"));
+ }
+
+ @Test
+ public void toStringReadWritePrivate() {
+ assertThat(Bind.parse("/host:/container:rw,private").toString(), is("/host:/container:rw,private"));
+ }
+
@Test
public void toStringDefaultAccessMode() {
assertThat(Bind.parse("/host:/container").toString(), is("/host:/container:rw"));
diff --git a/src/test/java/com/github/dockerjava/netty/exec/CreateContainerCmdExecTest.java b/src/test/java/com/github/dockerjava/netty/exec/CreateContainerCmdExecTest.java
index 3694ca7fb..aa97b8839 100644
--- a/src/test/java/com/github/dockerjava/netty/exec/CreateContainerCmdExecTest.java
+++ b/src/test/java/com/github/dockerjava/netty/exec/CreateContainerCmdExecTest.java
@@ -2,6 +2,7 @@
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.CreateNetworkResponse;
+import com.github.dockerjava.api.command.CreateVolumeResponse;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.exception.ConflictException;
import com.github.dockerjava.api.exception.DockerException;
@@ -15,6 +16,7 @@
import com.github.dockerjava.api.model.Network;
import com.github.dockerjava.api.model.Ports;
import com.github.dockerjava.api.model.Ports.Binding;
+import com.github.dockerjava.core.RemoteApiVersion;
import com.github.dockerjava.api.model.RestartPolicy;
import com.github.dockerjava.api.model.Ulimit;
import com.github.dockerjava.api.model.Volume;
@@ -23,6 +25,7 @@
import org.apache.commons.io.FileUtils;
import org.testng.ITestResult;
+import org.testng.SkipException;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeMethod;
@@ -40,6 +43,7 @@
import static com.github.dockerjava.api.model.Capability.MKNOD;
import static com.github.dockerjava.api.model.Capability.NET_ADMIN;
+import static com.github.dockerjava.utils.TestUtils.getVersion;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
@@ -138,6 +142,39 @@ public void createContainerWithReadOnlyVolume() throws DockerException {
// assertFalse(inspectContainerResponse.getMounts().get(0).getRW());
}
+ @Test
+ public void createContainerWithNoCopyVolumes() throws DockerException {
+ final RemoteApiVersion apiVersion = getVersion(dockerClient);
+
+ if (!apiVersion.isGreaterOrEqual(RemoteApiVersion.VERSION_1_23)) {
+ throw new SkipException("API version should be >= 1.23");
+ }
+
+ Volume volume1 = new Volume("/opt/webapp1");
+ String container1Name = UUID.randomUUID().toString();
+
+ CreateVolumeResponse volumeResponse = dockerClient.createVolumeCmd().withName("webapp1").exec();
+ assertThat(volumeResponse.getName(), equalTo("webapp1"));
+ assertThat(volumeResponse.getDriver(), equalTo("local"));
+ assertThat(volumeResponse.getMountpoint(), containsString("/webapp1/"));
+
+ Bind bind1 = new Bind("webapp1", volume1, true);
+
+ CreateContainerResponse container1 = dockerClient.createContainerCmd("busybox").withCmd("sleep", "9999")
+ .withName(container1Name)
+ .withBinds(bind1).exec();
+ LOG.info("Created container1 {}", container1.toString());
+
+ InspectContainerResponse inspectContainerResponse1 = dockerClient.inspectContainerCmd(container1.getId()).exec();
+
+ assertThat(Arrays.asList(inspectContainerResponse1.getHostConfig().getBinds()), contains(bind1));
+ assertThat(inspectContainerResponse1, mountedVolumes(contains(volume1)));
+
+ assertThat(inspectContainerResponse1.getMounts().get(0).getDestination(), equalTo(volume1));
+ assertThat(inspectContainerResponse1.getMounts().get(0).getMode(), equalTo("rw,nocopy"));
+ assertThat(inspectContainerResponse1.getMounts().get(0).getRW(), equalTo(true));
+ }
+
@Test
public void createContainerWithVolumesFrom() throws DockerException {