Repository: docker-java/docker-java
Pull request #137 (2015-01-28 22:57:43)

Multiple volumesFrom option when creating a container


Patch:
diff --git a/src/main/java/com/github/dockerjava/api/command/CreateContainerCmd.java b/src/main/java/com/github/dockerjava/api/command/CreateContainerCmd.java
index 2eeda23e..8d290125 100644
--- a/src/main/java/com/github/dockerjava/api/command/CreateContainerCmd.java
+++ b/src/main/java/com/github/dockerjava/api/command/CreateContainerCmd.java
@@ -6,6 +6,7 @@ import com.github.dockerjava.api.model.Capability;
 import com.github.dockerjava.api.model.ExposedPort;
 import com.github.dockerjava.api.model.HostConfig;
 import com.github.dockerjava.api.model.Volume;
+import com.github.dockerjava.api.model.VolumeFrom;
 
 public interface CreateContainerCmd extends DockerCmd<CreateContainerResponse>{
 
@@ -93,9 +94,9 @@ public interface CreateContainerCmd extends DockerCmd<CreateContainerResponse>{
 
 	public CreateContainerCmd withVolumes(Volume... volumes);
 
-	public String[] getVolumesFrom();
+	public VolumeFrom[] getVolumesFrom();
 
-	public CreateContainerCmd withVolumesFrom(String... volumesFrom);
+	public CreateContainerCmd withVolumesFrom(VolumeFrom... volumesFrom);
 	
 	public HostConfig getHostConfig();
 	
diff --git a/src/main/java/com/github/dockerjava/api/model/AccessMode.java b/src/main/java/com/github/dockerjava/api/model/AccessMode.java
index e0106536..a1f53f57 100644
--- a/src/main/java/com/github/dockerjava/api/model/AccessMode.java
+++ b/src/main/java/com/github/dockerjava/api/model/AccessMode.java
@@ -16,4 +16,5 @@ public enum AccessMode {
 	 */
 	public static final AccessMode DEFAULT = rw;
 
+	
 }
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 2a838c27..eb0b8fd4 100644
--- a/src/main/java/com/github/dockerjava/api/model/Bind.java
+++ b/src/main/java/com/github/dockerjava/api/model/Bind.java
@@ -1,7 +1,6 @@
 package com.github.dockerjava.api.model;
 
-import static com.github.dockerjava.api.model.AccessMode.ro;
-import static com.github.dockerjava.api.model.AccessMode.rw;
+
 
 import org.apache.commons.lang.builder.EqualsBuilder;
 import org.apache.commons.lang.builder.HashCodeBuilder;
@@ -29,14 +28,6 @@ public class Bind {
 		this.accessMode = accessMode;
 	}
 
-	/**
-	 * @deprecated use {@link #Bind(String, Volume, AccessMode)}
-	 */
-	@Deprecated
-	public Bind(String path, Volume volume, boolean readOnly) {
-		this(path, volume, readOnly ? ro : rw);
-	}
-
 	public String getPath() {
 		return path;
 	}
@@ -49,13 +40,6 @@ public class Bind {
 		return accessMode;
 	}
 
-	/**
-	 * @deprecated use {@link #getAccessMode()}
-	 */
-	@Deprecated
-	public boolean isReadOnly() {
-		return ro.equals(accessMode);
-	}
 
 	/**
 	 * Parses a bind mount specification to a {@link Bind}.
diff --git a/src/main/java/com/github/dockerjava/api/model/HostConfig.java b/src/main/java/com/github/dockerjava/api/model/HostConfig.java
index 3d186501..dd25e79b 100644
--- a/src/main/java/com/github/dockerjava/api/model/HostConfig.java
+++ b/src/main/java/com/github/dockerjava/api/model/HostConfig.java
@@ -33,7 +33,7 @@ public class HostConfig {
 	private String[] dnsSearch;
 
 	@JsonProperty("VolumesFrom")
-	private String[] volumesFrom;
+	private VolumesFrom volumesFrom;
 
 	@JsonProperty("ContainerIDFile")
 	private String containerIDFile;
@@ -57,7 +57,7 @@ public class HostConfig {
 	}
 
 	public HostConfig(String[] binds, Links links, LxcConf[] lxcConf, Ports portBindings, boolean publishAllPorts,
-			boolean privileged, String[] dns, String[] dnsSearch, String[] volumesFrom, String containerIDFile,
+			boolean privileged, String[] dns, String[] dnsSearch, VolumesFrom volumesFrom, String containerIDFile,
 			Capability[] capAdd, Capability[] capDrop, RestartPolicy restartPolicy, String networkMode, Device[] devices) {
 		this.binds = binds;
 		this.links = links;
@@ -100,7 +100,7 @@ public class HostConfig {
 		return dns;
 	}
 
-	public String[] getVolumesFrom() {
+	public VolumesFrom getVolumesFrom() {
 		return volumesFrom;
 	}
 
@@ -168,7 +168,7 @@ public class HostConfig {
 		this.dnsSearch = dnsSearch;
 	}
 
-	public void setVolumesFrom(String[] volumesFrom) {
+	public void setVolumesFrom(VolumesFrom volumesFrom) {
 		this.volumesFrom = volumesFrom;
 	}
 
diff --git a/src/main/java/com/github/dockerjava/api/model/Volume.java b/src/main/java/com/github/dockerjava/api/model/Volume.java
index 1a189014..3ec5e24c 100644
--- a/src/main/java/com/github/dockerjava/api/model/Volume.java
+++ b/src/main/java/com/github/dockerjava/api/model/Volume.java
@@ -30,18 +30,23 @@ public class Volume {
 
 	private String path;
 	
-	private boolean readWrite = true;
+	private AccessMode accessMode = AccessMode.rw;
 
 	public Volume(String path) {
 		this.path = path;
 	}
+	
+	public Volume(String path, AccessMode accessMode) {
+		this.path = path;
+		this.accessMode = accessMode;
+	}
 
 	public String getPath() {
 		return path;
 	}
 	
-	public boolean isReadWrite() {
-		return readWrite;
+	public AccessMode getAccessMode() {
+		return accessMode;
 	}
 
 	public static Volume parse(String serialized) {
@@ -64,7 +69,7 @@ public class Volume {
 	public boolean equals(Object obj) {
 		if (obj instanceof Volume) {
 			Volume other = (Volume) obj;
-			return new EqualsBuilder().append(path, other.getPath()).append(readWrite, other.isReadWrite())
+			return new EqualsBuilder().append(path, other.getPath()).append(accessMode, other.getAccessMode())
 					.isEquals();
 		} else
 			return super.equals(obj);
@@ -72,7 +77,7 @@ public class Volume {
 
 	@Override
 	public int hashCode() {
-		return new HashCodeBuilder().append(path).append(readWrite).toHashCode();
+		return new HashCodeBuilder().append(path).append(accessMode).toHashCode();
 	}
 	
 	public static class Serializer extends JsonSerializer<Volume> {
@@ -84,7 +89,7 @@ public class Volume {
 
 			jsonGen.writeStartObject();
 			jsonGen.writeFieldName(volume.getPath());
-			jsonGen.writeString(Boolean.toString(volume.isReadWrite()));
+			jsonGen.writeString(Boolean.toString(volume.getAccessMode().equals(AccessMode.rw) ? true: false));
 			jsonGen.writeEndObject();
 		}
 
diff --git a/src/main/java/com/github/dockerjava/api/model/VolumeFrom.java b/src/main/java/com/github/dockerjava/api/model/VolumeFrom.java
new file mode 100644
index 00000000..8554c2f6
--- /dev/null
+++ b/src/main/java/com/github/dockerjava/api/model/VolumeFrom.java
@@ -0,0 +1,87 @@
+package com.github.dockerjava.api.model;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+public class VolumeFrom {
+
+	private String container;
+
+	private AccessMode accessMode;
+
+	public VolumeFrom(String container) {
+		this(container, AccessMode.DEFAULT);
+	}
+
+	public VolumeFrom(String container, AccessMode accessMode) {
+		this.container = container;
+		this.accessMode = accessMode;
+	}
+	
+	public String getContainer() {
+		return container;
+	}
+	
+	public AccessMode getAccessMode() {
+		return accessMode;
+	}
+
+
+	/**
+	 * Parses a volume from specification to a {@link VolumeFrom}.
+	 * 
+	 * @param serialized the specification, e.g. <code>container:ro</code>
+	 * @return a {@link VolumeFrom} matching the specification
+	 * @throws IllegalArgumentException if the specification cannot be parsed
+	 */
+	public static VolumeFrom parse(String serialized) {
+		try {
+			String[] parts = serialized.split(":");
+			switch (parts.length) {
+			case 1: {
+				return new VolumeFrom(parts[0]);
+			}
+			case 2: {
+				return new VolumeFrom(parts[0], AccessMode.valueOf(parts[1]));
+			}
+			
+			default: {
+				throw new IllegalArgumentException();
+			}
+			}
+		} catch (Exception e) {
+			throw new IllegalArgumentException("Error parsing Bind '" + serialized
+					+ "'");
+		}
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (obj instanceof VolumeFrom) {
+			VolumeFrom other = (VolumeFrom) obj;
+			return new EqualsBuilder().append(container, other.getContainer())
+					.append(accessMode, other.getAccessMode()).isEquals();
+		} else
+			return super.equals(obj);
+	}
+
+	@Override
+	public int hashCode() {
+		return new HashCodeBuilder().append(container)
+				.append(accessMode).toHashCode();
+	}
+
+	/**
+	 * Returns a string representation of this {@link VolumeFrom} suitable
+	 * for inclusion in a JSON message.
+	 * The format is <code>&lt;container&gt;:&lt;access mode&gt;</code>,
+	 * like the argument in {@link #parse(String)}.
+	 * 
+	 * @return a string representation of this {@link VolumeFrom}
+	 */
+	@Override
+	public String toString() {
+		return container + ":" + accessMode.toString();
+	}
+
+}
diff --git a/src/main/java/com/github/dockerjava/api/model/Volumes.java b/src/main/java/com/github/dockerjava/api/model/Volumes.java
index b8a9315b..69753d6a 100644
--- a/src/main/java/com/github/dockerjava/api/model/Volumes.java
+++ b/src/main/java/com/github/dockerjava/api/model/Volumes.java
@@ -48,7 +48,7 @@ public class Volumes {
 			jsonGen.writeStartObject();
 			for (Volume volume : volumes.getVolumes()) {
 				jsonGen.writeFieldName(volume.getPath());
-				jsonGen.writeString(Boolean.toString(volume.isReadWrite()));
+				jsonGen.writeString(Boolean.toString(volume.getAccessMode().equals(AccessMode.rw) ? true: false));
 			}
 			jsonGen.writeEndObject();
 		}
diff --git a/src/main/java/com/github/dockerjava/api/model/VolumesFrom.java b/src/main/java/com/github/dockerjava/api/model/VolumesFrom.java
new file mode 100644
index 00000000..a16e0c67
--- /dev/null
+++ b/src/main/java/com/github/dockerjava/api/model/VolumesFrom.java
@@ -0,0 +1,69 @@
+package com.github.dockerjava.api.model;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.ObjectCodec;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+@JsonSerialize(using = VolumesFrom.Serializer.class)
+@JsonDeserialize(using = VolumesFrom.Deserializer.class)
+public class VolumesFrom {
+
+	private VolumeFrom[] volumesFrom;
+
+	public VolumesFrom(VolumeFrom... volumesFrom) {
+		this.volumesFrom = volumesFrom;
+	}
+
+	public VolumeFrom[] getVolumesFrom() {
+		return volumesFrom;
+	}
+
+	public static class Serializer extends JsonSerializer<VolumesFrom> {
+
+		@Override
+		public void serialize(VolumesFrom volumesFrom, JsonGenerator jsonGen,
+				SerializerProvider serProvider) throws IOException,
+				JsonProcessingException {
+
+			//
+			jsonGen.writeStartArray();
+			for (VolumeFrom bind : volumesFrom.getVolumesFrom()) {
+				jsonGen.writeString(bind.toString());
+			}
+			jsonGen.writeEndArray();
+			//
+		}
+
+	}
+
+	public static class Deserializer extends JsonDeserializer<VolumesFrom> {
+		@Override
+		public VolumesFrom deserialize(JsonParser jsonParser,
+				DeserializationContext deserializationContext)
+				throws IOException, JsonProcessingException {
+
+			List<VolumeFrom> volumesFrom = new ArrayList<VolumeFrom>();
+			ObjectCodec oc = jsonParser.getCodec();
+			JsonNode node = oc.readTree(jsonParser);
+			for (Iterator<JsonNode> it = node.iterator(); it.hasNext();) {
+				JsonNode field = it.next();
+				volumesFrom.add(VolumeFrom.parse(field.asText()));
+			}
+			return new VolumesFrom(volumesFrom.toArray(new VolumeFrom[0]));
+		}
+	}
+
+}
diff --git a/src/main/java/com/github/dockerjava/core/command/CreateContainerCmdImpl.java b/src/main/java/com/github/dockerjava/core/command/CreateContainerCmdImpl.java
index ea6d721e..991ecc8e 100644
--- a/src/main/java/com/github/dockerjava/core/command/CreateContainerCmdImpl.java
+++ b/src/main/java/com/github/dockerjava/core/command/CreateContainerCmdImpl.java
@@ -15,7 +15,9 @@ import com.github.dockerjava.api.model.ExposedPort;
 import com.github.dockerjava.api.model.ExposedPorts;
 import com.github.dockerjava.api.model.HostConfig;
 import com.github.dockerjava.api.model.Volume;
+import com.github.dockerjava.api.model.VolumeFrom;
 import com.github.dockerjava.api.model.Volumes;
+import com.github.dockerjava.api.model.VolumesFrom;
 
 
 /**
@@ -303,13 +305,13 @@ public class CreateContainerCmdImpl extends AbstrDockerCmd<CreateContainerCmd, C
     }
 
     @Override
-	public String[] getVolumesFrom() {
-        return hostConfig.getVolumesFrom();
+	public VolumeFrom[] getVolumesFrom() {
+        return hostConfig.getVolumesFrom() == null ? new VolumeFrom[0] : hostConfig.getVolumesFrom().getVolumesFrom();
     }
 
     @Override
-	public CreateContainerCmdImpl withVolumesFrom(String... volumesFrom) {
-        this.hostConfig.setVolumesFrom(volumesFrom);
+	public CreateContainerCmdImpl withVolumesFrom(VolumeFrom... volumesFrom) {
+        this.hostConfig.setVolumesFrom(new VolumesFrom(volumesFrom));
         return this;
     }
 


Test patch:
diff --git a/src/test/java/com/github/dockerjava/api/model/VolumeFrom_SerializingTest.java b/src/test/java/com/github/dockerjava/api/model/VolumeFrom_SerializingTest.java
new file mode 100644
index 00000000..37a3e37a
--- /dev/null
+++ b/src/test/java/com/github/dockerjava/api/model/VolumeFrom_SerializingTest.java
@@ -0,0 +1,33 @@
+package com.github.dockerjava.api.model;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.dockerjava.api.model.Ports.Binding;
+
+public class VolumeFrom_SerializingTest {
+	private final ObjectMapper objectMapper = new ObjectMapper();
+	private final String json = 
+			"[\"container1:ro\",\"container2:rw\"]";
+	@Test
+	public void deserializing() throws Exception {
+		VolumesFrom volumesFrom = objectMapper.readValue(json, VolumesFrom.class);
+		
+		VolumesFrom expected = new VolumesFrom(new VolumeFrom("container1", AccessMode.ro), new VolumeFrom("container2", AccessMode.rw));
+		
+		assertEquals(volumesFrom.getVolumesFrom(), expected.getVolumesFrom());
+	}
+	
+	@Test
+	public void serializing() throws Exception {
+		VolumesFrom volumesFrom = new VolumesFrom(new VolumeFrom("container1", AccessMode.ro), new VolumeFrom("container2", AccessMode.rw));
+		
+		assertEquals(objectMapper.writeValueAsString(volumesFrom), json);
+	}
+	
+	
+}
diff --git a/src/test/java/com/github/dockerjava/core/command/CreateContainerCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/CreateContainerCmdImplTest.java
index 2b879a9a..71358ff5 100644
--- a/src/test/java/com/github/dockerjava/core/command/CreateContainerCmdImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/command/CreateContainerCmdImplTest.java
@@ -32,6 +32,7 @@ import com.github.dockerjava.api.model.HostConfig;
 import com.github.dockerjava.api.model.Link;
 import com.github.dockerjava.api.model.Links;
 import com.github.dockerjava.api.model.Volume;
+import com.github.dockerjava.api.model.VolumeFrom;
 import com.github.dockerjava.client.AbstractDockerClientTest;
 
 @Test(groups = "integration")
@@ -125,14 +126,14 @@ public class CreateContainerCmdImplTest extends AbstractDockerClientTest {
 		// create a second container with volumes from first container
 		CreateContainerResponse container2 = dockerClient
 				.createContainerCmd("busybox").withCmd("sleep", "9999")
-				.withVolumesFrom(container1Name).exec();
+				.withVolumesFrom(new VolumeFrom(container1Name)).exec();
 		LOG.info("Created container2 {}", container2.toString());
 
 		InspectContainerResponse inspectContainerResponse2 = dockerClient
 				.inspectContainerCmd(container2.getId()).exec();
 
 		// No volumes are created, the information is just stored in .HostConfig.VolumesFrom
-		assertThat(inspectContainerResponse2.getHostConfig().getVolumesFrom(), hasItemInArray(container1Name));
+		assertThat(inspectContainerResponse2.getHostConfig().getVolumesFrom().getVolumesFrom(), hasItemInArray(new VolumeFrom(container1Name)));
 
 		// To ensure that the information stored in VolumesFrom really is considered
 		// when starting the container, we start it and verify that it has the same 
@@ -144,7 +145,7 @@ public class CreateContainerCmdImplTest extends AbstractDockerClientTest {
 		
 		inspectContainerResponse2 = dockerClient.inspectContainerCmd(container2.getId()).exec();
 
-		assertThat(inspectContainerResponse2.getHostConfig().getVolumesFrom(), hasItemInArray(container1Name));
+		assertThat(inspectContainerResponse2.getHostConfig().getVolumesFrom().getVolumesFrom(), hasItemInArray(new VolumeFrom(container1Name)));
 		assertContainerHasVolumes(inspectContainerResponse2, volume1, volume2);
 	}