diff --git a/src/main/java/com/github/dockerjava/api/command/BuildImageCmd.java b/src/main/java/com/github/dockerjava/api/command/BuildImageCmd.java index 7c2deb63a..8a1e62115 100644 --- a/src/main/java/com/github/dockerjava/api/command/BuildImageCmd.java +++ b/src/main/java/com/github/dockerjava/api/command/BuildImageCmd.java @@ -1,5 +1,9 @@ package com.github.dockerjava.api.command; +import com.github.dockerjava.api.model.EventStreamItem; +import com.github.dockerjava.api.model.PushEventStreamItem; + +import java.io.IOException; import java.io.InputStream; /** @@ -9,7 +13,7 @@ * TODO: http://docs.docker.com/reference/builder/#dockerignore * */ -public interface BuildImageCmd extends DockerCmd{ +public interface BuildImageCmd extends DockerCmd{ public BuildImageCmd withTag(String tag); @@ -37,7 +41,10 @@ public interface BuildImageCmd extends DockerCmd{ public BuildImageCmd withQuiet(boolean quiet); - public static interface Exec extends DockerCmdExec { + public static interface Exec extends DockerCmdExec { } + public static abstract class Response extends InputStream { + public abstract Iterable getItems() throws IOException; + } } \ No newline at end of file diff --git a/src/main/java/com/github/dockerjava/api/command/PushImageCmd.java b/src/main/java/com/github/dockerjava/api/command/PushImageCmd.java index f50beb01e..80e16c6d9 100644 --- a/src/main/java/com/github/dockerjava/api/command/PushImageCmd.java +++ b/src/main/java/com/github/dockerjava/api/command/PushImageCmd.java @@ -1,16 +1,18 @@ package com.github.dockerjava.api.command; +import java.io.IOException; import java.io.InputStream; import com.github.dockerjava.api.NotFoundException; import com.github.dockerjava.api.model.AuthConfig; +import com.github.dockerjava.api.model.PushEventStreamItem; /** * Push the latest image to the repository. * * @param name The name, e.g. "alexec/busybox" or just "busybox" if you want to default. Not null. */ -public interface PushImageCmd extends DockerCmd{ +public interface PushImageCmd extends DockerCmd{ public String getName(); @@ -33,10 +35,13 @@ public interface PushImageCmd extends DockerCmd{ /** * @throws NotFoundException No such image */ - @Override - public InputStream exec() throws NotFoundException; + public Response exec() throws NotFoundException; - public static interface Exec extends DockerCmdExec { + public static interface Exec extends DockerCmdExec { + } + + public static abstract class Response extends InputStream { + public abstract Iterable getItems() throws IOException; } } \ No newline at end of file diff --git a/src/main/java/com/github/dockerjava/api/model/EventStreamItem.java b/src/main/java/com/github/dockerjava/api/model/EventStreamItem.java new file mode 100644 index 000000000..6e6ce035a --- /dev/null +++ b/src/main/java/com/github/dockerjava/api/model/EventStreamItem.java @@ -0,0 +1,64 @@ +package com.github.dockerjava.api.model; + + + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; + +import jersey.repackaged.com.google.common.base.Objects; + +/** + * Represents an event stream + */ +@JsonIgnoreProperties(ignoreUnknown=true) +public class EventStreamItem implements Serializable { + + @JsonProperty("stream") + private String stream; + + // {"error":"Error...", "errorDetail":{"code": 123, "message": "Error..."}} + @JsonProperty("error") + private String error; + + @JsonProperty("errorDetail") + private ErrorDetail errorDetail; + + public String getStream() { + return stream; + } + + public String getError() { + return error; + } + + public ErrorDetail getErrorDetail() { + return errorDetail; + } + + @JsonIgnoreProperties(ignoreUnknown=true) + public static class ErrorDetail implements Serializable { + @JsonProperty("code") + String code; + @JsonProperty("message") + String message; + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("code", code) + .add("message", message) + .toString(); + } + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("stream", stream) + .add("error", error) + .add("errorDetail", errorDetail) + .toString(); + } +} diff --git a/src/main/java/com/github/dockerjava/api/model/PushEventStreamItem.java b/src/main/java/com/github/dockerjava/api/model/PushEventStreamItem.java new file mode 100644 index 000000000..f5e845b29 --- /dev/null +++ b/src/main/java/com/github/dockerjava/api/model/PushEventStreamItem.java @@ -0,0 +1,58 @@ +package com.github.dockerjava.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; + +import jersey.repackaged.com.google.common.base.Objects; + +/** + * Represents an item returned from push + */ +@JsonIgnoreProperties(ignoreUnknown=true) +public class PushEventStreamItem implements Serializable { + + @JsonProperty("status") + private String status; + + @JsonProperty("progress") + private String progress; + + @JsonProperty("progressDetail") + private ProgressDetail progressDetail; + + + public String getStatus() { + return status; + } + + public String getProgress() { + return progress; + } + + public ProgressDetail getProgressDetail() { + return progressDetail; + } + + @JsonIgnoreProperties(ignoreUnknown=true) + public static class ProgressDetail implements Serializable { + @JsonProperty("current") + int current; + + + @Override + public String toString() { + return "current " + current; + } + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("status", status) + .add("progress", progress) + .add("progressDetail", progressDetail) + .toString(); + } +} diff --git a/src/main/java/com/github/dockerjava/core/command/BuildImageCmdImpl.java b/src/main/java/com/github/dockerjava/core/command/BuildImageCmdImpl.java index 0bdb6db36..468dac7e7 100644 --- a/src/main/java/com/github/dockerjava/core/command/BuildImageCmdImpl.java +++ b/src/main/java/com/github/dockerjava/core/command/BuildImageCmdImpl.java @@ -32,8 +32,7 @@ * Build an image from Dockerfile. * */ -public class BuildImageCmdImpl extends - AbstrDockerCmd implements BuildImageCmd { +public class BuildImageCmdImpl extends AbstrDockerCmd implements BuildImageCmd { private static final Pattern ADD_OR_COPY_PATTERN = Pattern .compile("^(ADD|COPY)\\s+(.*)\\s+(.*)$"); diff --git a/src/main/java/com/github/dockerjava/core/command/PushImageCmdImpl.java b/src/main/java/com/github/dockerjava/core/command/PushImageCmdImpl.java index 137c008a3..1914f91dd 100644 --- a/src/main/java/com/github/dockerjava/core/command/PushImageCmdImpl.java +++ b/src/main/java/com/github/dockerjava/core/command/PushImageCmdImpl.java @@ -2,8 +2,6 @@ import static jersey.repackaged.com.google.common.base.Preconditions.checkNotNull; -import java.io.InputStream; - import com.github.dockerjava.api.NotFoundException; import com.github.dockerjava.api.command.PushImageCmd; @@ -12,7 +10,7 @@ * * @param name The name, e.g. "alexec/busybox" or just "busybox" if you want to default. Not null. */ -public class PushImageCmdImpl extends AbstrAuthCfgDockerCmd implements PushImageCmd { +public class PushImageCmdImpl extends AbstrAuthCfgDockerCmd implements PushImageCmd { private String name; private String tag; @@ -63,7 +61,7 @@ public String toString() { * @throws NotFoundException No such image */ @Override - public InputStream exec() throws NotFoundException { + public Response exec() throws NotFoundException { return super.exec(); } } diff --git a/src/main/java/com/github/dockerjava/jaxrs/BuildImageCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/BuildImageCmdExec.java index 85c5f18c2..faf3d9f23 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/BuildImageCmdExec.java +++ b/src/main/java/com/github/dockerjava/jaxrs/BuildImageCmdExec.java @@ -1,8 +1,12 @@ package com.github.dockerjava.jaxrs; + + import static javax.ws.rs.client.Entity.entity; +import java.io.IOException; import java.io.InputStream; +import java.util.Iterator; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; @@ -13,9 +17,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import com.github.dockerjava.api.command.BuildImageCmd; +import com.github.dockerjava.api.command.PushImageCmd; +import com.github.dockerjava.api.model.EventStreamItem; +import com.github.dockerjava.api.model.PushEventStreamItem; + +import jersey.repackaged.com.google.common.collect.ImmutableList; -public class BuildImageCmdExec extends AbstrDockerCmdExec implements BuildImageCmd.Exec { +public class BuildImageCmdExec extends AbstrDockerCmdExec implements BuildImageCmd.Exec { private static final Logger LOGGER = LoggerFactory .getLogger(BuildImageCmdExec.class); @@ -25,7 +36,7 @@ public BuildImageCmdExec(WebTarget baseResource) { } @Override - protected InputStream execute(BuildImageCmd command) { + protected ResponseImpl execute(BuildImageCmd command) { WebTarget webResource = getBaseResource().path("/build"); if(command.getTag() != null) { @@ -45,12 +56,38 @@ protected InputStream execute(BuildImageCmd command) { webResource.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.CHUNKED); webResource.property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024*1024); - LOGGER.debug("POST: {}", webResource); - return webResource - .request() - .accept(MediaType.TEXT_PLAIN) - .post(entity(command.getTarInputStream(), "application/tar"), Response.class).readEntity(InputStream.class); + + LOGGER.debug("POST: {}", webResource); + InputStream is = webResource + .request() + .accept(MediaType.TEXT_PLAIN) + .post(entity(command.getTarInputStream(), "application/tar"), Response.class).readEntity(InputStream.class); + + return new ResponseImpl(is); } + public static class ResponseImpl extends BuildImageCmd.Response { + + private final InputStream proxy; + + public ResponseImpl(InputStream proxy) { + this.proxy = proxy; + } + + @Override + public Iterable getItems() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + // we'll be reading instances of MyBean + ObjectReader reader = mapper.reader(EventStreamItem.class); + // and then do other configuration, if any, and read: + Iterator items = reader.readValues(proxy); + + return ImmutableList.copyOf(items); + } + @Override + public int read() throws IOException { + return proxy.read(); + } + } } diff --git a/src/main/java/com/github/dockerjava/jaxrs/PushImageCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/PushImageCmdExec.java index b970d42fc..152f31a3b 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/PushImageCmdExec.java +++ b/src/main/java/com/github/dockerjava/jaxrs/PushImageCmdExec.java @@ -1,6 +1,12 @@ package com.github.dockerjava.jaxrs; + + +import static javax.ws.rs.client.Entity.entity; + +import java.io.IOException; import java.io.InputStream; +import java.util.Iterator; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; @@ -8,37 +14,72 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +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.PushImageCmd.Response; import com.github.dockerjava.api.model.AuthConfig; +import com.github.dockerjava.api.model.PushEventStreamItem; -public class PushImageCmdExec extends - AbstrDockerCmdExec implements - PushImageCmd.Exec { +// Shaded, but imported +import jersey.repackaged.com.google.common.collect.ImmutableList; +public class PushImageCmdExec extends AbstrDockerCmdExec implements PushImageCmd.Exec { + private static final Logger LOGGER = LoggerFactory .getLogger(PushImageCmdExec.class); - + public PushImageCmdExec(WebTarget baseResource) { super(baseResource); } @Override - protected InputStream execute(PushImageCmd command) { - WebTarget webResource = getBaseResource().path( - "/images/" + name(command) + "/push").queryParam("tag", - command.getTag()); + protected ResponseImpl execute(PushImageCmd command) { + WebTarget webResource = getBaseResource().path("/images/" + name(command) + "/push") + .queryParam("tag", command.getTag()); final String registryAuth = registryAuth(command.getAuthConfig()); LOGGER.trace("POST: {}", webResource); - return webResource.request().header("X-Registry-Auth", registryAuth) - .accept(MediaType.APPLICATION_JSON).post(null) - .readEntity(InputStream.class); - } + InputStream is = webResource + .request() + .header("X-Registry-Auth", registryAuth) + .accept(MediaType.APPLICATION_JSON) + .post( + entity(Response.class, MediaType.APPLICATION_JSON)).readEntity( + InputStream.class); + return new ResponseImpl(is); + } + private String name(PushImageCmd command) { String name = command.getName(); AuthConfig authConfig = command.getAuthConfig(); return name.contains("/") ? name : authConfig.getUsername(); } + + public static class ResponseImpl extends Response { + + private final InputStream proxy; + + ResponseImpl(InputStream proxy) { + this.proxy = proxy; + } + + @Override + public Iterable getItems() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + // we'll be reading instances of MyBean + ObjectReader reader = mapper.reader(PushEventStreamItem.class); + // and then do other configuration, if any, and read: + Iterator items = reader.readValues(proxy); + + return ImmutableList.copyOf(items); + } + + @Override + public int read() throws IOException { + return proxy.read(); + } + } } diff --git a/src/test/java/com/github/dockerjava/core/TestDockerCmdExecFactory.java b/src/test/java/com/github/dockerjava/core/TestDockerCmdExecFactory.java index c63667a0d..4fcf26b6a 100644 --- a/src/test/java/com/github/dockerjava/core/TestDockerCmdExecFactory.java +++ b/src/test/java/com/github/dockerjava/core/TestDockerCmdExecFactory.java @@ -8,6 +8,7 @@ import com.github.dockerjava.api.command.*; import com.github.dockerjava.api.command.AuthCmd.Exec; +import com.github.dockerjava.jaxrs.BuildImageCmdExec; /** * Special {@link DockerCmdExecFactory} implementation that collects container and image creations @@ -92,18 +93,18 @@ public Void exec(RemoveImageCmd command) { public BuildImageCmd.Exec createBuildImageCmdExec() { return new BuildImageCmd.Exec() { @Override - public InputStream exec(BuildImageCmd command) { + public BuildImageCmd.Response exec(BuildImageCmd command) { // can't detect image id here so tagging it String tag = command.getTag(); if(tag == null || "".equals(tag.trim())) { tag = "" + new SecureRandom().nextInt(Integer.MAX_VALUE); command.withTag(tag); } - InputStream inputStream = delegate.createBuildImageCmdExec().exec(command); + InputStream inputStream = delegate.createBuildImageCmdExec().exec(command); imageNames.add(tag); - return inputStream; + return new BuildImageCmdExec.ResponseImpl(inputStream); } - }; + }; } @Override diff --git a/src/test/java/com/github/dockerjava/core/command/BuildImageCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/BuildImageCmdImplTest.java index 3692f56d1..8be2f8f8e 100644 --- a/src/test/java/com/github/dockerjava/core/command/BuildImageCmdImplTest.java +++ b/src/test/java/com/github/dockerjava/core/command/BuildImageCmdImplTest.java @@ -27,8 +27,10 @@ import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.command.InspectContainerResponse; import com.github.dockerjava.api.command.InspectImageResponse; +import com.github.dockerjava.api.model.EventStreamItem; import com.github.dockerjava.client.AbstractDockerClientTest; + @Test(groups = "integration") public class BuildImageCmdImplTest extends AbstractDockerClientTest { @@ -170,18 +172,24 @@ public void testDockerIgnore() throws DockerException, } @Test - public void testNetCatDockerfileBuilder() throws InterruptedException { + public void testNetCatDockerfileBuilder() throws InterruptedException, IOException { File baseDir = new File(Thread.currentThread().getContextClassLoader() .getResource("netcat").getFile()); - InputStream response = dockerClient.buildImageCmd(baseDir).withNoCache().exec(); + Iterable response = dockerClient.buildImageCmd(baseDir).withNoCache().exec().getItems(); - String fullLog = asString(response); + String imageId = null; + + for(EventStreamItem item : response) { + String text = item.getStream(); + if( text.startsWith("Successfully built ")) { + imageId = StringUtils.substringBetween(text, + "Successfully built ", "\n").trim(); + } + } - assertThat(fullLog, containsString("Successfully built")); + assertNotNull(imageId, "Not successful in build"); - String imageId = StringUtils.substringBetween(fullLog, - "Successfully built ", "\\n\"}").trim(); InspectImageResponse inspectImageResponse = dockerClient .inspectImageCmd(imageId).exec();