From aeef9c9475d63f7c24ff231b157b86f63d0c3015 Mon Sep 17 00:00:00 2001 From: George Aristy Date: Mon, 30 Apr 2018 17:21:15 -0400 Subject: [PATCH 1/2] (#83) Implemented Images.create() * Added helper class 'UncheckedUriBuilder' * Added new ctor to 'Response' for responses w/o payload --- .../java/com/amihaiemil/docker/Images.java | 19 ++++- .../java/com/amihaiemil/docker/RtImages.java | 35 ++++++++++ .../docker/UncheckedUriBuilder.java | 69 +++++++++++++++++++ .../amihaiemil/docker/RtImagesTestCase.java | 64 +++++++++++++++++ .../com/amihaiemil/docker/mock/Response.java | 10 +++ 5 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/amihaiemil/docker/UncheckedUriBuilder.java diff --git a/src/main/java/com/amihaiemil/docker/Images.java b/src/main/java/com/amihaiemil/docker/Images.java index 93416fd9..e2428d51 100644 --- a/src/main/java/com/amihaiemil/docker/Images.java +++ b/src/main/java/com/amihaiemil/docker/Images.java @@ -26,6 +26,7 @@ package com.amihaiemil.docker; import java.io.IOException; +import java.net.URL; /** * Images API. @@ -33,7 +34,7 @@ * @version $Id$ * @see Docker Images API * @since 0.0.1 - * @todo #71:30min Continue implementing the rest of the operations for the + * @todo #83:30min Keep implementing the rest of the operations for the * Images interface. See the docs referenced above for more details. */ public interface Images { @@ -48,4 +49,20 @@ public interface Images { * just iterate on 'docker.images()'. */ Iterable iterate() throws IOException, UnexpectedResponseException; + + /** + * Creates an image by pulling it from a registry. + * @param name Name of the image to pull. + * @param source The URL from which the image can be retrieved. + * @param repo Repository name for the image when it is pulled. + * @param tag Tag or digest for the image. + * @return This {@link Images}. + * @throws IOException If an I/O error occurs. + * @throws UnexpectedResponseException If the API responds with an + * unexpected status. + * @checkstyle ParameterNumber (4 lines) + */ + Images create( + final String name, final URL source, final String repo, final String tag + ) throws IOException, UnexpectedResponseException; } diff --git a/src/main/java/com/amihaiemil/docker/RtImages.java b/src/main/java/com/amihaiemil/docker/RtImages.java index ce48ef54..eff8a4f0 100644 --- a/src/main/java/com/amihaiemil/docker/RtImages.java +++ b/src/main/java/com/amihaiemil/docker/RtImages.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.net.URI; +import java.net.URL; import java.util.stream.Collectors; import javax.json.Json; import javax.json.JsonObject; @@ -34,6 +35,7 @@ import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; /** * Runtime {@link Images}. @@ -87,4 +89,37 @@ public Iterable iterate() throws IOException { get.releaseConnection(); } } + + // @todo #83:30min Several API calls required an authentication header as + // explained here: + // https://docs.docker.com/engine/api/v1.35/#section/Authentication + // (including Images.create()). Find a way to make a reusable object from + // that action and introduce it here. + // @checkstyle ParameterNumber (4 lines) + @Override + public Images create( + final String name, final URL source, final String repo, final String tag + ) throws IOException, UnexpectedResponseException { + final HttpPost create = new HttpPost( + new UncheckedUriBuilder(this.baseUri.toString().concat("/create")) + .addParameter("fromImage", name) + .addParameter("fromSrc", source.toString()) + .addParameter("repo", repo) + .addParameter("tag", tag) + .build() + ); + try { + final int status = this.client.execute(create) + .getStatusLine() + .getStatusCode(); + if (HttpStatus.SC_OK != status) { + throw new UnexpectedResponseException( + create.getURI().toString(), status, HttpStatus.SC_OK + ); + } + return this; + } finally { + create.releaseConnection(); + } + } } diff --git a/src/main/java/com/amihaiemil/docker/UncheckedUriBuilder.java b/src/main/java/com/amihaiemil/docker/UncheckedUriBuilder.java new file mode 100644 index 00000000..7757e70d --- /dev/null +++ b/src/main/java/com/amihaiemil/docker/UncheckedUriBuilder.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2018, Mihai Emil Andronache + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1)Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2)Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3)Neither the name of docker-java-api nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.amihaiemil.docker; + +import java.net.URI; +import java.net.URISyntaxException; +import org.apache.http.client.utils.URIBuilder; + +/** + * A {@link URIBuilder} that hides checked exceptions in the methods used + * throughout this library. Used under the assumption that the structure + * of URIs created using this class are valid. + * @author George Aristy (george.aristy@gmail.com) + */ +final class UncheckedUriBuilder extends URIBuilder { + /** + * Ctor. + * @param uri Base URI. + * @throws IllegalArgumentException From {@link URI#create(String)}. + * @throws NullPointerException From {@link URI#create(String)}. + */ + UncheckedUriBuilder( + final String uri + ) throws IllegalArgumentException, NullPointerException { + super(URI.create(uri)); + } + + @Override + public UncheckedUriBuilder addParameter( + final String name, final String value + ) { + super.addParameter(name, value); + return this; + } + + @Override + public URI build() { + try { + return super.build(); + } catch (final URISyntaxException ex) { + throw new IllegalStateException( + "Unexpected error while building a URI!", ex + ); + } + } +} diff --git a/src/test/java/com/amihaiemil/docker/RtImagesTestCase.java b/src/test/java/com/amihaiemil/docker/RtImagesTestCase.java index 3b09c0c7..b50abf59 100644 --- a/src/test/java/com/amihaiemil/docker/RtImagesTestCase.java +++ b/src/test/java/com/amihaiemil/docker/RtImagesTestCase.java @@ -26,8 +26,10 @@ package com.amihaiemil.docker; import com.amihaiemil.docker.mock.AssertRequest; +import com.amihaiemil.docker.mock.Condition; import com.amihaiemil.docker.mock.Response; import java.net.URI; +import java.net.URL; import java.util.concurrent.atomic.AtomicInteger; import javax.json.Json; import org.apache.http.HttpStatus; @@ -85,4 +87,66 @@ public void iterateFailsIfResponseIs500() throws Exception { URI.create("http://localhost") ).iterate(); } + + /** + * {@link RtImages#create(String, URL, String, String)} must construct the + * URL with parameters correctly. + *

+ * Notice the escaped characters for the 'fromSrc' parameter's value. + * @throws Exception If an error occurs. + */ + @Test + public void createSetsGivenParameters() throws Exception { + new RtImages( + new AssertRequest( + new Response(HttpStatus.SC_OK), + new Condition( + // @checkstyle LineLength (1 line) + "RtImages.create() failed to correctly build the request URI.", + req -> { + System.out.println(req.getRequestLine().getUri()); + return req.getRequestLine().getUri().endsWith( + // @checkstyle LineLength (1 line) + "/create?fromImage=testImage&fromSrc=http%3A%2F%2Fdocker.registry.com&repo=testRepo&tag=1.23" + ); + } + ) + ), + URI.create("http://localhost") + ) + .create( + "testImage", new URL("http://docker.registry.com"), + "testRepo", "1.23" + ); + } + + /** + * RtImages.create() must throw an {@link UnexpectedResponseException} + * if the docker API responds with status code 404. + * @throws Exception The UnexpectedResponseException. + */ + @Test(expected = UnexpectedResponseException.class) + public void createErrorOnStatus404() throws Exception { + new RtImages( + new AssertRequest( + new Response(HttpStatus.SC_NOT_FOUND) + ), + URI.create("http://localhost") + ).create("", new URL("http://registry.docker.com"), "", ""); + } + + /** + * RtImages.create() must throw an {@link UnexpectedResponseException} + * if the docker API responds with status code 500. + * @throws Exception The UnexpectedResponseException. + */ + @Test(expected = UnexpectedResponseException.class) + public void createErrorOnStatus500() throws Exception { + new RtImages( + new AssertRequest( + new Response(HttpStatus.SC_INTERNAL_SERVER_ERROR) + ), + URI.create("http://localhost") + ).create("", new URL("http://registry.docker.com"), "", ""); + } } diff --git a/src/test/java/com/amihaiemil/docker/mock/Response.java b/src/test/java/com/amihaiemil/docker/mock/Response.java index f4cec46b..05933722 100644 --- a/src/test/java/com/amihaiemil/docker/mock/Response.java +++ b/src/test/java/com/amihaiemil/docker/mock/Response.java @@ -65,6 +65,16 @@ public final class Response implements HttpResponse { */ private final HttpEntity payload; + /** + * Ctor. + *

+ * Response with no payload. + * @param status The {@link HttpStatus http status code} + */ + public Response(final int status) { + this(status, ""); + } + /** * Ctor. * From 9a55b9e658c80eeb5281a6f1ac7c7b06d4dcd18a Mon Sep 17 00:00:00 2001 From: George Aristy Date: Sun, 6 May 2018 08:48:52 -0400 Subject: [PATCH 2/2] (#83) Implemented Images.create() As per PR review: * Added missing @since and @version for UncheckedUriBuilder --- src/main/java/com/amihaiemil/docker/UncheckedUriBuilder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/amihaiemil/docker/UncheckedUriBuilder.java b/src/main/java/com/amihaiemil/docker/UncheckedUriBuilder.java index 7757e70d..9058d3fb 100644 --- a/src/main/java/com/amihaiemil/docker/UncheckedUriBuilder.java +++ b/src/main/java/com/amihaiemil/docker/UncheckedUriBuilder.java @@ -34,6 +34,8 @@ * throughout this library. Used under the assumption that the structure * of URIs created using this class are valid. * @author George Aristy (george.aristy@gmail.com) + * @version $Id$ + * @since 0.0.1 */ final class UncheckedUriBuilder extends URIBuilder { /**