Repository: docker-java/docker-java
Pull request #148 (2015-02-08 08:13:11)

feat(save): add save image to docker client
add saveImageCmd which will return a InputStream.  the file is a tar file.
https://docs.docker.com/reference/api/docker_remote_api_v1.15/#image-tarball-format


Patch:
diff --git a/src/main/java/com/github/dockerjava/api/DockerClient.java b/src/main/java/com/github/dockerjava/api/DockerClient.java
index f7addfa2..c2b15cf3 100644
--- a/src/main/java/com/github/dockerjava/api/DockerClient.java
+++ b/src/main/java/com/github/dockerjava/api/DockerClient.java
@@ -39,6 +39,8 @@ public interface DockerClient extends Closeable {
 	public ListImagesCmd listImagesCmd();
 
 	public InspectImageCmd inspectImageCmd(String imageId);
+    
+    public SaveImageCmd saveImageCmd(String name);
 
 	/**
 	 * * CONTAINER API *
diff --git a/src/main/java/com/github/dockerjava/api/command/DockerCmdExecFactory.java b/src/main/java/com/github/dockerjava/api/command/DockerCmdExecFactory.java
index 73381d69..ca2bbcae 100644
--- a/src/main/java/com/github/dockerjava/api/command/DockerCmdExecFactory.java
+++ b/src/main/java/com/github/dockerjava/api/command/DockerCmdExecFactory.java
@@ -22,6 +22,8 @@ public interface DockerCmdExecFactory extends Closeable {
 	public PullImageCmd.Exec createPullImageCmdExec();
 
 	public PushImageCmd.Exec createPushImageCmdExec();
+    
+    public SaveImageCmd.Exec createSaveImageCmdExec();
 
 	public CreateImageCmd.Exec createCreateImageCmdExec();
 
diff --git a/src/main/java/com/github/dockerjava/api/command/SaveImageCmd.java b/src/main/java/com/github/dockerjava/api/command/SaveImageCmd.java
new file mode 100644
index 00000000..b8a5af4a
--- /dev/null
+++ b/src/main/java/com/github/dockerjava/api/command/SaveImageCmd.java
@@ -0,0 +1,39 @@
+package com.github.dockerjava.api.command;
+
+import com.github.dockerjava.api.NotFoundException;
+import com.github.dockerjava.api.model.AuthConfig;
+import com.github.dockerjava.api.model.PushEventStreamItem;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface SaveImageCmd extends DockerCmd<InputStream>{
+
+    public String getName();
+
+    public String getTag();
+
+    /**
+     * @param name The name, e.g. "alexec/busybox" or just "busybox" if you want to default. Not null.
+     */
+    public SaveImageCmd withName(String name);
+
+    /**
+     * @param tag The image's tag. Not null.
+     */
+    public SaveImageCmd withTag(String tag);
+
+    public AuthConfig getAuthConfig();
+
+    public SaveImageCmd withAuthConfig(AuthConfig authConfig);
+
+    /**
+     * @throws com.github.dockerjava.api.NotFoundException No such image
+     */
+    public InputStream exec() throws NotFoundException;
+
+    public static interface Exec extends DockerCmdExec<SaveImageCmd, InputStream> {
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/dockerjava/core/DockerClientImpl.java b/src/main/java/com/github/dockerjava/core/DockerClientImpl.java
index 5f0876bb..522afe3b 100644
--- a/src/main/java/com/github/dockerjava/core/DockerClientImpl.java
+++ b/src/main/java/com/github/dockerjava/core/DockerClientImpl.java
@@ -8,75 +8,9 @@ import java.io.IOException;
 import java.io.InputStream;
 
 import com.github.dockerjava.api.DockerClient;
-import com.github.dockerjava.api.command.AttachContainerCmd;
-import com.github.dockerjava.api.command.AuthCmd;
-import com.github.dockerjava.api.command.BuildImageCmd;
-import com.github.dockerjava.api.command.CommitCmd;
-import com.github.dockerjava.api.command.ContainerDiffCmd;
-import com.github.dockerjava.api.command.CopyFileFromContainerCmd;
-import com.github.dockerjava.api.command.CreateContainerCmd;
-import com.github.dockerjava.api.command.CreateImageCmd;
-import com.github.dockerjava.api.command.DockerCmdExecFactory;
-import com.github.dockerjava.api.command.EventCallback;
-import com.github.dockerjava.api.command.EventsCmd;
-import com.github.dockerjava.api.command.ExecCreateCmd;
-import com.github.dockerjava.api.command.ExecStartCmd;
-import com.github.dockerjava.api.command.InfoCmd;
-import com.github.dockerjava.api.command.InspectContainerCmd;
-import com.github.dockerjava.api.command.InspectImageCmd;
-import com.github.dockerjava.api.command.KillContainerCmd;
-import com.github.dockerjava.api.command.ListContainersCmd;
-import com.github.dockerjava.api.command.ListImagesCmd;
-import com.github.dockerjava.api.command.LogContainerCmd;
-import com.github.dockerjava.api.command.PauseContainerCmd;
-import com.github.dockerjava.api.command.PingCmd;
-import com.github.dockerjava.api.command.PullImageCmd;
-import com.github.dockerjava.api.command.PushImageCmd;
-import com.github.dockerjava.api.command.RemoveContainerCmd;
-import com.github.dockerjava.api.command.RemoveImageCmd;
-import com.github.dockerjava.api.command.RestartContainerCmd;
-import com.github.dockerjava.api.command.SearchImagesCmd;
-import com.github.dockerjava.api.command.StartContainerCmd;
-import com.github.dockerjava.api.command.StopContainerCmd;
-import com.github.dockerjava.api.command.TagImageCmd;
-import com.github.dockerjava.api.command.TopContainerCmd;
-import com.github.dockerjava.api.command.UnpauseContainerCmd;
-import com.github.dockerjava.api.command.VersionCmd;
-import com.github.dockerjava.api.command.WaitContainerCmd;
+import com.github.dockerjava.api.command.*;
 import com.github.dockerjava.api.model.AuthConfig;
-import com.github.dockerjava.core.command.AttachContainerCmdImpl;
-import com.github.dockerjava.core.command.AuthCmdImpl;
-import com.github.dockerjava.core.command.BuildImageCmdImpl;
-import com.github.dockerjava.core.command.CommitCmdImpl;
-import com.github.dockerjava.core.command.ContainerDiffCmdImpl;
-import com.github.dockerjava.core.command.CopyFileFromContainerCmdImpl;
-import com.github.dockerjava.core.command.CreateContainerCmdImpl;
-import com.github.dockerjava.core.command.CreateImageCmdImpl;
-import com.github.dockerjava.core.command.EventsCmdImpl;
-import com.github.dockerjava.core.command.ExecCreateCmdImpl;
-import com.github.dockerjava.core.command.ExecStartCmdImpl;
-import com.github.dockerjava.core.command.InfoCmdImpl;
-import com.github.dockerjava.core.command.InspectContainerCmdImpl;
-import com.github.dockerjava.core.command.InspectImageCmdImpl;
-import com.github.dockerjava.core.command.KillContainerCmdImpl;
-import com.github.dockerjava.core.command.ListContainersCmdImpl;
-import com.github.dockerjava.core.command.ListImagesCmdImpl;
-import com.github.dockerjava.core.command.LogContainerCmdImpl;
-import com.github.dockerjava.core.command.PauseContainerCmdImpl;
-import com.github.dockerjava.core.command.PingCmdImpl;
-import com.github.dockerjava.core.command.PullImageCmdImpl;
-import com.github.dockerjava.core.command.PushImageCmdImpl;
-import com.github.dockerjava.core.command.RemoveContainerCmdImpl;
-import com.github.dockerjava.core.command.RemoveImageCmdImpl;
-import com.github.dockerjava.core.command.RestartContainerCmdImpl;
-import com.github.dockerjava.core.command.SearchImagesCmdImpl;
-import com.github.dockerjava.core.command.StartContainerCmdImpl;
-import com.github.dockerjava.core.command.StopContainerCmdImpl;
-import com.github.dockerjava.core.command.TagImageCmdImpl;
-import com.github.dockerjava.core.command.TopContainerCmdImpl;
-import com.github.dockerjava.core.command.UnpauseContainerCmdImpl;
-import com.github.dockerjava.core.command.VersionCmdImpl;
-import com.github.dockerjava.core.command.WaitContainerCmdImpl;
+import com.github.dockerjava.core.command.*;
 
 /**
  * @author Konstantin Pelykh (kpelykh@gmail.com)
@@ -195,6 +129,13 @@ public class DockerClientImpl implements Closeable, DockerClient {
 		return new PushImageCmdImpl(getDockerCmdExecFactory()
 				.createPushImageCmdExec(), name).withAuthConfig(dockerClientConfig.effectiveAuthConfig(name));		
 	}
+    
+    @Override
+    public SaveImageCmd saveImageCmd(String name) {
+        return new SaveImageCmdImpl(getDockerCmdExecFactory()
+            .createSaveImageCmdExec(), name).withAuthConfig(dockerClientConfig.effectiveAuthConfig(name));
+        
+    }
 
 	@Override
 	public CreateImageCmd createImageCmd(String repository,
diff --git a/src/main/java/com/github/dockerjava/core/command/SaveImageCmdImpl.java b/src/main/java/com/github/dockerjava/core/command/SaveImageCmdImpl.java
new file mode 100644
index 00000000..85f7cfe6
--- /dev/null
+++ b/src/main/java/com/github/dockerjava/core/command/SaveImageCmdImpl.java
@@ -0,0 +1,63 @@
+package com.github.dockerjava.core.command;
+
+import com.github.dockerjava.api.NotFoundException;
+import com.github.dockerjava.api.command.SaveImageCmd;
+
+import java.io.InputStream;
+
+import static jersey.repackaged.com.google.common.base.Preconditions.checkNotNull;
+
+public class SaveImageCmdImpl extends AbstrAuthCfgDockerCmd<SaveImageCmd, InputStream> implements SaveImageCmd {
+    private String name;
+    private String tag;
+
+    public SaveImageCmdImpl(SaveImageCmd.Exec exec, String name) {
+        super(exec);
+        withName(name);
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getTag() {
+        return tag;
+    }
+
+    /**
+     * @param name The name, e.g. "alexec/busybox" or just "busybox" if you want to default. Not null.
+     */
+    @Override
+    public SaveImageCmd withName(String name) {
+        checkNotNull(name, "name was not specified");
+        this.name = name;
+        return this;
+    }
+
+    /**
+     * @param tag The image's tag. Can be null or empty.
+     */
+    @Override
+    public SaveImageCmd withTag(String tag) {
+        checkNotNull(tag, "tag was not specified");
+        this.tag = tag;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder("get ")
+                .append(name)
+                .toString();
+    }
+
+    /**
+     * @throws com.github.dockerjava.api.NotFoundException No such image
+     */
+    @Override
+    public InputStream exec() throws NotFoundException {
+        return super.exec();
+    }
+}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java b/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java
index cdb85117..4edba9fc 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java
@@ -5,6 +5,7 @@ import static jersey.repackaged.com.google.common.base.Preconditions.checkNotNul
 import java.io.IOException;
 import java.net.URI;
 
+import com.github.dockerjava.api.command.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -26,40 +27,6 @@ import org.glassfish.jersey.client.ClientProperties;
 
 import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
 import com.github.dockerjava.api.DockerClientException;
-import com.github.dockerjava.api.command.AttachContainerCmd;
-import com.github.dockerjava.api.command.AuthCmd;
-import com.github.dockerjava.api.command.BuildImageCmd;
-import com.github.dockerjava.api.command.CommitCmd;
-import com.github.dockerjava.api.command.ContainerDiffCmd;
-import com.github.dockerjava.api.command.CopyFileFromContainerCmd;
-import com.github.dockerjava.api.command.CreateContainerCmd;
-import com.github.dockerjava.api.command.CreateImageCmd;
-import com.github.dockerjava.api.command.DockerCmdExecFactory;
-import com.github.dockerjava.api.command.EventsCmd;
-import com.github.dockerjava.api.command.ExecCreateCmd;
-import com.github.dockerjava.api.command.ExecStartCmd;
-import com.github.dockerjava.api.command.InfoCmd;
-import com.github.dockerjava.api.command.InspectContainerCmd;
-import com.github.dockerjava.api.command.InspectImageCmd;
-import com.github.dockerjava.api.command.KillContainerCmd;
-import com.github.dockerjava.api.command.ListContainersCmd;
-import com.github.dockerjava.api.command.ListImagesCmd;
-import com.github.dockerjava.api.command.LogContainerCmd;
-import com.github.dockerjava.api.command.PauseContainerCmd;
-import com.github.dockerjava.api.command.PingCmd;
-import com.github.dockerjava.api.command.PullImageCmd;
-import com.github.dockerjava.api.command.PushImageCmd;
-import com.github.dockerjava.api.command.RemoveContainerCmd;
-import com.github.dockerjava.api.command.RemoveImageCmd;
-import com.github.dockerjava.api.command.RestartContainerCmd;
-import com.github.dockerjava.api.command.SearchImagesCmd;
-import com.github.dockerjava.api.command.StartContainerCmd;
-import com.github.dockerjava.api.command.StopContainerCmd;
-import com.github.dockerjava.api.command.TagImageCmd;
-import com.github.dockerjava.api.command.TopContainerCmd;
-import com.github.dockerjava.api.command.UnpauseContainerCmd;
-import com.github.dockerjava.api.command.VersionCmd;
-import com.github.dockerjava.api.command.WaitContainerCmd;
 import com.github.dockerjava.core.DockerClientConfig;
 import com.github.dockerjava.core.util.JsonClientFilter;
 import com.github.dockerjava.core.util.ResponseStatusExceptionFilter;
@@ -170,6 +137,9 @@ public class DockerCmdExecFactoryImpl implements DockerCmdExecFactory {
     public PushImageCmd.Exec createPushImageCmdExec() {
         return new PushImageCmdExec(getBaseResource());
     }
+    
+    @Override
+    public SaveImageCmd.Exec createSaveImageCmdExec() { return new SaveImageCmdExec(getBaseResource()); }
 
     @Override
     public CreateImageCmd.Exec createCreateImageCmdExec() {
diff --git a/src/main/java/com/github/dockerjava/jaxrs/SaveImageCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/SaveImageCmdExec.java
new file mode 100644
index 00000000..16ad0012
--- /dev/null
+++ b/src/main/java/com/github/dockerjava/jaxrs/SaveImageCmdExec.java
@@ -0,0 +1,44 @@
+package com.github.dockerjava.jaxrs;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.github.dockerjava.api.command.PushImageCmd;
+import com.github.dockerjava.api.command.SaveImageCmd;
+import com.github.dockerjava.api.model.AuthConfig;
+import com.github.dockerjava.api.model.PushEventStreamItem;
+import jersey.repackaged.com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+
+import static javax.ws.rs.client.Entity.entity;
+
+public class SaveImageCmdExec extends AbstrDockerCmdExec<SaveImageCmd, InputStream> implements SaveImageCmd.Exec {
+    private static final Logger LOGGER = LoggerFactory
+            .getLogger(PushImageCmdExec.class);
+
+    public SaveImageCmdExec(WebTarget baseResource) {
+        super(baseResource);
+    }
+
+    @Override
+    protected InputStream execute(SaveImageCmd command) {
+        WebTarget webResource = getBaseResource().path("/images/" + command.getName() + "/get")
+                .queryParam("tag", command.getTag());
+
+        final String registryAuth = registryAuth(command.getAuthConfig());
+        LOGGER.trace("GET: {}", webResource);
+        InputStream is =  webResource
+                .request()
+                .header("X-Registry-Auth", registryAuth)
+                .accept(MediaType.APPLICATION_JSON)
+                .get().readEntity(InputStream.class);
+
+        return is;
+    }
+}


Test patch:
diff --git a/src/test/java/com/github/dockerjava/core/TestDockerCmdExecFactory.java b/src/test/java/com/github/dockerjava/core/TestDockerCmdExecFactory.java
index 4fcf26b6..5e7f2974 100644
--- a/src/test/java/com/github/dockerjava/core/TestDockerCmdExecFactory.java
+++ b/src/test/java/com/github/dockerjava/core/TestDockerCmdExecFactory.java
@@ -141,6 +141,9 @@ public class TestDockerCmdExecFactory implements DockerCmdExecFactory {
 	public PushImageCmd.Exec createPushImageCmdExec() {
 		return delegate.createPushImageCmdExec();
 	}
+    
+    @Override
+    public SaveImageCmd.Exec createSaveImageCmdExec() { return delegate.createSaveImageCmdExec(); }
 
 	@Override
 	public SearchImagesCmd.Exec createSearchImagesCmdExec() {
diff --git a/src/test/java/com/github/dockerjava/core/command/SaveImageCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/SaveImageCmdImplTest.java
new file mode 100644
index 00000000..b62594be
--- /dev/null
+++ b/src/test/java/com/github/dockerjava/core/command/SaveImageCmdImplTest.java
@@ -0,0 +1,62 @@
+package com.github.dockerjava.core.command;
+
+import com.github.dockerjava.api.DockerException;
+import com.github.dockerjava.api.command.CreateContainerResponse;
+import com.github.dockerjava.client.AbstractDockerClientTest;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.ITestResult;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.isEmptyString;
+import static org.hamcrest.Matchers.not;
+
+@Test(groups = "integration")
+public class SaveImageCmdImplTest extends AbstractDockerClientTest {
+    public static final Logger LOG = LoggerFactory
+            .getLogger(PushImageCmdImplTest.class);
+
+    String username;
+
+    @BeforeTest
+    public void beforeTest() throws DockerException {
+        super.beforeTest();
+    }
+    @AfterTest
+    public void afterTest() {
+        super.afterTest();
+    }
+
+    @BeforeMethod
+    public void beforeMethod(Method method) {
+        super.beforeMethod(method);
+    }
+
+    @AfterMethod
+    public void afterMethod(ITestResult result) {
+        super.afterMethod(result);
+    }
+
+    @Test
+    public void saveImage() throws Exception {
+
+        InputStream image = IOUtils.toBufferedInputStream(dockerClient
+                .saveImageCmd("busybox").exec());
+        assertThat(image.available(), greaterThan(0));
+
+    }
+
+}