Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 51 additions & 3 deletions src/main/java/com/github/dockerjava/api/model/Bind.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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}.
*
Expand All @@ -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();
Expand All @@ -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);
Expand All @@ -115,6 +159,8 @@ public int hashCode() {
.append(volume)
.append(accessMode)
.append(secMode)
.append(noCopy)
.append(propagationMode)
.toHashCode();
}

Expand All @@ -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() : "");
}
}
50 changes: 50 additions & 0 deletions src/main/java/com/github/dockerjava/api/model/PropagationMode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.github.dockerjava.api.model;

/**
* The propagation mode of a file system or file: <code>shared</code>, <code>slave</code> or <code>private</code>.
*
* @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;
}
}
}
79 changes: 79 additions & 0 deletions src/test/java/com/github/dockerjava/api/model/BindTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.*")
Expand Down Expand Up @@ -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"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 {

Expand Down