From 1b7278f1fe93b0a28788b3d65588abde0deb9bf3 Mon Sep 17 00:00:00 2001 From: Michael Lux Date: Mon, 17 Feb 2020 14:23:53 +0100 Subject: [PATCH 1/3] Implemented more API complete container listing --- .../com/amihaiemil/docker/Containers.java | 18 +- .../amihaiemil/docker/ListedContainers.java | 162 ++++++++++++++++++ .../com/amihaiemil/docker/RtContainers.java | 53 ++---- .../java/com/amihaiemil/docker/RtDocker.java | 2 +- .../docker/RtContainersTestCase.java | 38 ++-- 5 files changed, 217 insertions(+), 56 deletions(-) create mode 100644 src/main/java/com/amihaiemil/docker/ListedContainers.java diff --git a/src/main/java/com/amihaiemil/docker/Containers.java b/src/main/java/com/amihaiemil/docker/Containers.java index fe1c5a68..98eebf92 100644 --- a/src/main/java/com/amihaiemil/docker/Containers.java +++ b/src/main/java/com/amihaiemil/docker/Containers.java @@ -28,6 +28,7 @@ import javax.json.JsonObject; import java.io.IOException; import java.util.Iterator; +import java.util.Map; /** * Containers API. This is also an Iterable over the running containers. @@ -75,10 +76,25 @@ Container create( Container create(final JsonObject container) throws IOException; /** - * Return all the Containers, not only the running ones. + * Return all Containers, not only the running ones. * @return Iterator over all the containers. */ Iterator all(); + + /** + * Whether to request the size of containers (fields SizeRw and SizeRootFs). + * @param withSize Return the size of containers (SizeRw and SizeRootFs). + * @return Containers with modified size flag. + */ + Containers withSize(final boolean withSize); + + /** + * Filter these Containers. + * @param filters Filters to apply. + * @return Filtered containers. + * @see Docker API Docs + */ + Containers filter(Map> filters); /** * Return the Docker engine where these Containers came from. diff --git a/src/main/java/com/amihaiemil/docker/ListedContainers.java b/src/main/java/com/amihaiemil/docker/ListedContainers.java new file mode 100644 index 00000000..158e43fe --- /dev/null +++ b/src/main/java/com/amihaiemil/docker/ListedContainers.java @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2018-2019, 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 org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.URIBuilder; + +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Listed containers, which may have filters applied. + * @author Michael Lux (michi.lux@gmail.com) + * @version $Id$ + * @since 0.0.11 + */ +final class ListedContainers extends RtContainers { + /** + * Container filters. + */ + private final Map> filters; + /** + * Whether to request the size of containers (fields SizeRw and SizeRootFs). + */ + private final boolean withSize; + + /** + * Ctor. + * @param client The http client. + * @param uri The URI for this Containers API. + * @param dkr The docker entry point. + * @checkstyle ParameterNumber (2 lines) + */ + ListedContainers(final HttpClient client, final URI uri, final Docker dkr) { + this(client, uri, dkr, Collections.emptyMap(), false); + } + + /** + * Ctor. + * @param client The http client. + * @param uri The URI for this Containers API. + * @param dkr The docker entry point. + * @param filters Container filter + * @param withSize Size query flag + * @checkstyle ParameterNumber (2 lines) + */ + ListedContainers( + final HttpClient client, final URI uri, + final Docker dkr, final Map> filters, + final boolean withSize + ) { + super(client, uri, dkr); + this.filters = filters; + this.withSize = withSize; + } + + @Override + public Iterator iterator() { + final URIBuilder uriBuilder = new UncheckedUriBuilder( + super.baseUri().toString().concat("/json") + ); + if (this.withSize) { + uriBuilder.addParameter("size", "true"); + } + final FilteredUriBuilder uri = new FilteredUriBuilder( + uriBuilder, + this.filters); + + return new ResourcesIterator<>( + super.client(), + new HttpGet(uri.build()), + json -> new RtContainer( + json, + super.client(), + URI.create( + super.baseUri().toString() + "/" + json.getString("Id") + ), + super.docker() + ) + ); + } + + @Override + public Containers withSize(final boolean newWithSize) { + return new ListedContainers( + super.client(), + this.baseUri(), + this.docker(), + this.filters, + newWithSize + ); + } + + @Override + public Iterator all() { + final URIBuilder uriBuilder = new UncheckedUriBuilder( + super.baseUri().toString().concat("/json") + ); + uriBuilder.addParameter("all", "true"); + if (this.withSize) { + uriBuilder.addParameter("size", "true"); + } + final FilteredUriBuilder uri = new FilteredUriBuilder( + uriBuilder, + this.filters); + + return new ResourcesIterator<>( + super.client(), + new HttpGet(uri.build()), + json -> new RtContainer( + json, + super.client(), + URI.create( + super.baseUri().toString() + "/" + json.getString("Id") + ), + super.docker() + ) + ); + } + + @Override + public Containers filter(final Map> newFilters) { + final Map> merged = new HashMap<>( + this.filters + ); + merged.putAll(newFilters); + return new ListedContainers( + super.client(), + this.baseUri(), + this.docker(), + merged, + this.withSize + ); + } +} diff --git a/src/main/java/com/amihaiemil/docker/RtContainers.java b/src/main/java/com/amihaiemil/docker/RtContainers.java index 4f1e5898..029fee1f 100644 --- a/src/main/java/com/amihaiemil/docker/RtContainers.java +++ b/src/main/java/com/amihaiemil/docker/RtContainers.java @@ -27,7 +27,6 @@ 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; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHeader; @@ -35,7 +34,6 @@ import javax.json.JsonObject; import java.io.IOException; import java.net.URI; -import java.util.Iterator; /** * Containers API. @@ -43,7 +41,7 @@ * @version $Id$ * @since 0.0.1 */ -final class RtContainers implements Containers { +abstract class RtContainers implements Containers { /** * Apache HttpClient which sends the requests. @@ -138,41 +136,26 @@ public Container create( post.releaseConnection(); } } - - @Override - public Iterator all() { - return new ResourcesIterator<>( - this.client, - new HttpGet(this.baseUri.toString().concat("/json?all=true")), - json -> new RtContainer( - json, - this.client, - URI.create( - this.baseUri.toString() + "/" + json.getString("Id") - ), - this.docker - ) - ); - } - - @Override - public Iterator iterator() { - return new ResourcesIterator<>( - this.client, - new HttpGet(this.baseUri.toString().concat("/json")), - json -> new RtContainer( - json, - this.client, - URI.create( - this.baseUri.toString() + "/" + json.getString("Id") - ), - this.docker - ) - ); - } @Override public Docker docker() { return this.docker; } + + + /** + * Get the (protected) HttpClient for subclasses. + * @return HttpClient. + */ + HttpClient client() { + return this.client; + } + + /** + * Get the (protected) base URI for subclasses. + * @return URI. + */ + URI baseUri() { + return this.baseUri; + } } diff --git a/src/main/java/com/amihaiemil/docker/RtDocker.java b/src/main/java/com/amihaiemil/docker/RtDocker.java index 53462cd2..66591065 100644 --- a/src/main/java/com/amihaiemil/docker/RtDocker.java +++ b/src/main/java/com/amihaiemil/docker/RtDocker.java @@ -87,7 +87,7 @@ public Reader events() throws IOException, UnexpectedResponseException { @Override public final Containers containers() { - return new RtContainers( + return new ListedContainers( this.client, URI.create(this.baseUri.toString() + "/containers"), this diff --git a/src/test/java/com/amihaiemil/docker/RtContainersTestCase.java b/src/test/java/com/amihaiemil/docker/RtContainersTestCase.java index 0d1ffd27..0a7de5bd 100644 --- a/src/test/java/com/amihaiemil/docker/RtContainersTestCase.java +++ b/src/test/java/com/amihaiemil/docker/RtContainersTestCase.java @@ -56,7 +56,7 @@ public final class RtContainersTestCase { public void returnsDocker() { final Docker parent = Mockito.mock(Docker.class); MatcherAssert.assertThat( - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_OK, @@ -78,7 +78,7 @@ public void returnsDocker() { @Test public void returnsAllContainers() throws Exception { final AtomicInteger count = new AtomicInteger(); - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_OK, @@ -115,7 +115,7 @@ public void returnsAllContainers() throws Exception { @Test public void iteratesRunningContainers() throws Exception { final AtomicInteger count = new AtomicInteger(); - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_OK, @@ -149,7 +149,7 @@ public void iteratesRunningContainers() throws Exception { @Test public void iteratesZeroContainers() throws Exception { final AtomicInteger count = new AtomicInteger(); - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_OK, @@ -169,7 +169,7 @@ public void iteratesZeroContainers() throws Exception { */ @Test(expected = UnexpectedResponseException.class) public void iterateFailsIfResponseIs500() throws Exception { - new RtContainers( + new ListedContainers( new AssertRequest( new Response(HttpStatus.SC_INTERNAL_SERVER_ERROR) ), @@ -184,7 +184,7 @@ public void iterateFailsIfResponseIs500() throws Exception { */ @Test(expected = UnexpectedResponseException.class) public void iterateFailsIfResponseIs400() throws Exception { - new RtContainers( + new ListedContainers( new AssertRequest( new Response(HttpStatus.SC_BAD_REQUEST) ), @@ -200,7 +200,7 @@ public void iterateFailsIfResponseIs400() throws Exception { @Test public void createsContainerOk() throws Exception { - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_CREATED, @@ -236,7 +236,7 @@ public void createsContainerOk() @Test public void returnsCreatedContainer() throws Exception { MatcherAssert.assertThat( - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_CREATED, @@ -256,7 +256,7 @@ public void returnsCreatedContainer() throws Exception { */ @Test(expected = UnexpectedResponseException.class) public void createsWith400() throws IOException { - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_BAD_REQUEST @@ -273,7 +273,7 @@ public void createsWith400() throws IOException { */ @Test(expected = UnexpectedResponseException.class) public void createsWith404() throws IOException { - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_NOT_FOUND @@ -290,7 +290,7 @@ public void createsWith404() throws IOException { */ @Test(expected = UnexpectedResponseException.class) public void createsWith406() throws IOException { - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_NOT_ACCEPTABLE @@ -307,7 +307,7 @@ public void createsWith406() throws IOException { */ @Test(expected = UnexpectedResponseException.class) public void createsWithConflict() throws IOException { - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_CONFLICT @@ -324,7 +324,7 @@ public void createsWithConflict() throws IOException { */ @Test(expected = UnexpectedResponseException.class) public void createsWithServerErrpr() throws IOException { - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_INTERNAL_SERVER_ERROR @@ -343,7 +343,7 @@ public void createsWithServerErrpr() throws IOException { */ @Test public void createsWithImageName() throws Exception { - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_CREATED, @@ -372,7 +372,7 @@ public void createsWithPayloadForCreateJson() throws Exception { .add("Entrypoint", "script.sh") .add("StopSignal", "SIGTERM") .build(); - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_CREATED, @@ -410,7 +410,7 @@ public void createsWithPayloadForCreateNameAndJson() throws Exception { .add("Entrypoint", "script.sh") .add("StopSignal", "SIGTERM") .build(); - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_CREATED, @@ -443,7 +443,7 @@ public void createsWithPayloadForCreateNameAndJson() throws Exception { */ @Test public void createEscapesNameParameter() throws Exception { - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_CREATED, @@ -467,7 +467,7 @@ public void createEscapesNameParameter() throws Exception { @Test public void createsContainerWithGivenParameters() throws Exception { MatcherAssert.assertThat( - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_CREATED, @@ -491,7 +491,7 @@ public void createsContainerWithGivenParameters() throws Exception { @Test public void createsContainerWithId() throws Exception { MatcherAssert.assertThat( - new RtContainers( + new ListedContainers( new AssertRequest( new Response( HttpStatus.SC_CREATED, From 899c08c1707fcd781c662a4ea38c1c0744882c01 Mon Sep 17 00:00:00 2001 From: Michael Lux Date: Thu, 20 Feb 2020 17:46:01 +0100 Subject: [PATCH 2/3] Added unit test for ListedContainers --- .../docker/ListedContainersTestCase.java | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 src/test/java/com/amihaiemil/docker/ListedContainersTestCase.java diff --git a/src/test/java/com/amihaiemil/docker/ListedContainersTestCase.java b/src/test/java/com/amihaiemil/docker/ListedContainersTestCase.java new file mode 100644 index 00000000..f05c761c --- /dev/null +++ b/src/test/java/com/amihaiemil/docker/ListedContainersTestCase.java @@ -0,0 +1,184 @@ +/** + * Copyright (c) 2018-2019, 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 com.amihaiemil.docker.mock.AssertRequest; +import com.amihaiemil.docker.mock.Condition; +import com.amihaiemil.docker.mock.Response; +import org.apache.http.HttpStatus; +import org.apache.http.NameValuePair; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Test; + +import java.util.*; + +/** + * Unit tests for {@link ListedContainers}. + * @author Michael Lux (michi.lux@gmail.com) + * @version $Id$ + * @since 0.0.11 + */ +public final class ListedContainersTestCase { + + /** + * {@link ListedImages} can iterate over them without filters. + */ + @Test + public void iterateAll() { + Docker docker = new LocalDocker( + new AssertRequest( + new Response( + HttpStatus.SC_OK, + "[{\"Id\": \"abc1\"}, {\"Id\":\"cde2\"}]" + ), + new Condition( + "iterate() must send a GET request", + req -> "GET".equals(req.getRequestLine().getMethod()) + ), + new Condition( + "URI path must end with '/containers/json'", + req -> new UncheckedUriBuilder( + req.getRequestLine().getUri() + ).getPath().endsWith("/containers/json") + ), + new Condition( + "URI query param must be 'all=true'", + req -> { + NameValuePair queryParam = new UncheckedUriBuilder( + req.getRequestLine().getUri() + ).getQueryParams().get(0); + return "all".equals(queryParam.getName()) + && "true".equals(queryParam.getValue()); + } + ) + ), + "v1.35"); + final Iterator all = docker.containers().all(); + MatcherAssert.assertThat( + all.next().getString("Id"), + Matchers.equalTo("abc1") + ); + MatcherAssert.assertThat( + all.next().getString("Id"), + Matchers.equalTo("cde2") + ); + MatcherAssert.assertThat( + all.hasNext(), + Matchers.equalTo(false) + ); + } + + /** + * {@link ListedImages} can include filters in request to fetch images. + */ + @Test + public void includeFiltersInRequest() { + final Map> filterMap = new HashMap<>(); + filterMap.put( + "label", + Arrays.asList( + "maintainer=john@doe.org", + "randomLabel=test" + ) + ); + new LocalDocker( + new AssertRequest( + new Response( + HttpStatus.SC_OK, + "[]" + ), + new Condition( + "Query parameters must include the filters provided", + req -> { + final List params = + new UncheckedUriBuilder( + req.getRequestLine().getUri() + ).getQueryParams(); + NameValuePair filtersPair = params.get(0); + String filters = filtersPair.getValue(); + // @checkstyle BooleanExpressionComplexity (5 lines) + return params.size() == 1 + && "filters".equals(filtersPair.getName()) + && filters.contains("label") + && filters.contains("\"maintainer=john@doe.org\"") + && filters.contains("\"randomLabel=test\""); + } + ) + ), + "v1.35").containers().filter(filterMap).iterator(); + } + + /** + * {@link ListedImages} can include filters added in filter(), in addition + * to those provided via ctor, in request to fetch images. + */ + @Test + public void includeAddedFiltersInRequest() { + final Map> initial = new HashMap<>(); + initial.put( + "label", + Arrays.asList( + "maintainer=john@doe.org", + "randomLabel=test" + ) + ); + final Map> added = new HashMap<>(); + added.put("dangling", Collections.singletonList("true")); + new LocalDocker( + new AssertRequest( + new Response( + HttpStatus.SC_OK, + "[]" + ), + new Condition( + "Query parameters must include all filters provided, " + + "plus size set to true", + req -> { + final List params = + new UncheckedUriBuilder( + req.getRequestLine().getUri() + ).getQueryParams(); + NameValuePair sizePair = params.get(0); + NameValuePair filtersPair = params.get(1); + String filters = filtersPair.getValue(); + // @checkstyle BooleanExpressionComplexity (8 lines) + return params.size() == 2 + && "size".equals(sizePair.getName()) + && "true".equals(sizePair.getValue()) + && "filters".equals(filtersPair.getName()) + && filters.contains("label") + && filters.contains("\"maintainer=john@doe.org\"") + && filters.contains("\"randomLabel=test\"") + && filters.contains("\"dangling\":[\"true\"]"); + } + ) + ), + "v1.35" + ).containers().filter(initial).filter(added).withSize(true).iterator(); + } + +} From cfc428f99d94cf31700336d38d59e2ce5ea63d96 Mon Sep 17 00:00:00 2001 From: Michael Lux Date: Thu, 20 Feb 2020 18:31:28 +0100 Subject: [PATCH 3/3] Got last LOC (all containers incl. size) covered --- .../docker/ListedContainersTestCase.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/amihaiemil/docker/ListedContainersTestCase.java b/src/test/java/com/amihaiemil/docker/ListedContainersTestCase.java index f05c761c..29b8d3f7 100644 --- a/src/test/java/com/amihaiemil/docker/ListedContainersTestCase.java +++ b/src/test/java/com/amihaiemil/docker/ListedContainersTestCase.java @@ -45,7 +45,7 @@ public final class ListedContainersTestCase { /** - * {@link ListedImages} can iterate over them without filters. + * {@link ListedImages} can iterate over all containers with size. */ @Test public void iterateAll() { @@ -66,7 +66,7 @@ public void iterateAll() { ).getPath().endsWith("/containers/json") ), new Condition( - "URI query param must be 'all=true'", + "URI query param must contain all=true", req -> { NameValuePair queryParam = new UncheckedUriBuilder( req.getRequestLine().getUri() @@ -74,10 +74,21 @@ public void iterateAll() { return "all".equals(queryParam.getName()) && "true".equals(queryParam.getValue()); } + ), + new Condition( + "URI query param must contain size=true", + req -> { + NameValuePair queryParam = new UncheckedUriBuilder( + req.getRequestLine().getUri() + ).getQueryParams().get(1); + return "size".equals(queryParam.getName()) + && "true".equals(queryParam.getValue()); + } ) ), "v1.35"); - final Iterator all = docker.containers().all(); + final Iterator all = docker.containers() + .withSize(true).all(); MatcherAssert.assertThat( all.next().getString("Id"), Matchers.equalTo("abc1")