diff --git a/src/main/java/com/amihaiemil/docker/Plugin.java b/src/main/java/com/amihaiemil/docker/Plugin.java
index 7adddfd4..151f085a 100644
--- a/src/main/java/com/amihaiemil/docker/Plugin.java
+++ b/src/main/java/com/amihaiemil/docker/Plugin.java
@@ -34,9 +34,10 @@
* A docker plugin.
* @author Boris Kuzmic (boris.kuzmic@gmail.com)
* @see Docker Plugin API
- * @todo #266:30min Continue implementing rest of the Plugin methods besides
- * enable and disable. More information about API methods can be found at:
- * https://docs.docker.com/engine/api/v1.35/#tag/Plugin
+ * @todo #266:30min Implement Plugin#configure method. The tests are already
+ * coded, so after the implementation just remove the ignore annotation from
+ * these tests. More information about Configure API method can be found at:
+ * https://docs.docker.com/engine/api/v1.35/#operation/PluginSet
* @since 0.0.7
*/
public interface Plugin extends JsonObject {
diff --git a/src/main/java/com/amihaiemil/docker/RtPlugin.java b/src/main/java/com/amihaiemil/docker/RtPlugin.java
index 5ba9aa42..78f83132 100644
--- a/src/main/java/com/amihaiemil/docker/RtPlugin.java
+++ b/src/main/java/com/amihaiemil/docker/RtPlugin.java
@@ -33,6 +33,8 @@
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
/**
* Runtime {@link Plugin}.
@@ -122,24 +124,47 @@ public void disable() throws IOException, UnexpectedResponseException {
@Override
public void upgrade(final String remote, final JsonArray properties)
throws IOException, UnexpectedResponseException {
- throw new UnsupportedOperationException(
- String.join(" ",
- "RtPlugin.upgrade() is not yet implemented.",
- "If you can contribute please",
- "do it here: https://www.github.com/amihaiemil/docker-java-api"
- )
- );
+ final HttpPost upgrade =
+ new HttpPost(
+ new UncheckedUriBuilder(this.uri.toString().concat("/upgrade"))
+ .addParameter("remote", remote)
+ .build()
+ );
+ try {
+ upgrade.setEntity(
+ new StringEntity(
+ properties.toString(), ContentType.APPLICATION_JSON
+ )
+ );
+ this.client.execute(
+ upgrade,
+ new MatchStatus(
+ upgrade.getURI(),
+ HttpStatus.SC_NO_CONTENT
+ )
+ );
+ } finally {
+ upgrade.releaseConnection();
+ }
}
@Override
public void push() throws IOException, UnexpectedResponseException {
- throw new UnsupportedOperationException(
- String.join(" ",
- "RtPlugin.push() is not yet implemented.",
- "If you can contribute please",
- "do it here: https://www.github.com/amihaiemil/docker-java-api"
- )
- );
+ final HttpPost push =
+ new HttpPost(
+ String.format("%s/%s", this.uri.toString(), "push")
+ );
+ try {
+ this.client.execute(
+ push,
+ new MatchStatus(
+ push.getURI(),
+ HttpStatus.SC_OK
+ )
+ );
+ } finally {
+ push.releaseConnection();
+ }
}
@Override
diff --git a/src/test/java/com/amihaiemil/docker/RtPluginTestCase.java b/src/test/java/com/amihaiemil/docker/RtPluginTestCase.java
index edb859a3..da2e09aa 100644
--- a/src/test/java/com/amihaiemil/docker/RtPluginTestCase.java
+++ b/src/test/java/com/amihaiemil/docker/RtPluginTestCase.java
@@ -25,16 +25,25 @@
*/
package com.amihaiemil.docker;
+import com.amihaiemil.docker.mock.ArrayPayloadOf;
import com.amihaiemil.docker.mock.AssertRequest;
import com.amihaiemil.docker.mock.Condition;
import com.amihaiemil.docker.mock.Response;
+import java.io.IOException;
import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
import javax.json.Json;
+import javax.json.JsonArray;
import javax.json.JsonObject;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpRequest;
import org.apache.http.HttpStatus;
+import org.apache.http.util.EntityUtils;
import org.hamcrest.MatcherAssert;
import org.hamcrest.collection.IsCollectionWithSize;
import org.hamcrest.core.IsEqual;
+import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
@@ -43,6 +52,8 @@
*
* @author Boris Kuzmic (boris.kuzmic@gmail.com)
* @since 0.0.8
+ * @todo #266:30min Extract method stringPayloadOf to class to reuse in
+ * other test cases (like in RtPluginsTestCase.createOk() method)
* @checkstyle MethodName (500 lines)
*/
public final class RtPluginTestCase {
@@ -153,7 +164,7 @@ public void enablesItself() throws Exception {
}
/**
- * RtPluginenable() must throw UnexpectedResponseException
+ * RtPlugin enable() must throw UnexpectedResponseException
* if service responds with 404.
* @throws Exception If something goes wrong.
*/
@@ -255,4 +266,281 @@ public void failsToDisableItselfWhenNotInstalled() throws Exception {
);
plugin.disable();
}
+
+ /**
+ * RtPlugin upgrade with properties ok.
+ * @throws Exception If something goes wrong.
+ */
+ @Test
+ public void upgradeOk() throws Exception {
+ new ListedPlugins(
+ new AssertRequest(
+ new Response(
+ HttpStatus.SC_NO_CONTENT
+ )
+ ),
+ URI.create("http://localhost/plugins"),
+ DOCKER
+ ).pullAndInstall(
+ "vieus/sshfs",
+ "sshfs",
+ Json.createArrayBuilder().add(
+ Json.createObjectBuilder()
+ .add("Name", "network")
+ .add("Description", "")
+ .add("Value", "host")
+ ).build()
+ );
+ final JsonArray properties = Json.createArrayBuilder().add(
+ Json.createObjectBuilder()
+ .add("Name", "mount")
+ .add("Description", "")
+ .add("Value", "/data")
+ ).build();
+ final Plugin plugin = new RtPlugin(
+ Json.createObjectBuilder().build(),
+ new AssertRequest(
+ new Response(
+ HttpStatus.SC_NO_CONTENT
+ ),
+ new Condition(
+ "Method should be a POST",
+ req -> "POST".equals(req.getRequestLine().getMethod())
+ ),
+ new Condition(
+ "Resource path must be /{name}/upgrade?remote=test",
+ req -> req.getRequestLine().getUri()
+ .endsWith("/sshfs/upgrade?remote=test")
+ ),
+ new Condition(
+ "upgrade() must send JsonArray request body",
+ req -> {
+ JsonObject payload =
+ new ArrayPayloadOf(req).next();
+ return "mount".equals(payload.getString("Name"))
+ && "/data".equals(payload.getString("Value"));
+ }
+ )
+ ),
+ URI.create("http://localhost/plugins/sshfs"),
+ DOCKER
+ );
+ plugin.upgrade("test", properties);
+ }
+
+ /**
+ * RtPlugin upgrade throws UnexpectedResponseException if service
+ * responds with 404.
+ * @throws Exception If something goes wrong.
+ */
+ @Test(expected = UnexpectedResponseException.class)
+ public void upgradeFailsPluginNotInstalled() throws Exception {
+ final Plugin plugin = new RtPlugin(
+ Json.createObjectBuilder().build(),
+ new AssertRequest(
+ new Response(
+ HttpStatus.SC_NOT_FOUND
+ ),
+ new Condition(
+ "Method should be a POST",
+ req -> "POST".equals(req.getRequestLine().getMethod())
+ ),
+ new Condition(
+ "Resource path must be /{name}/upgrade?remote=test",
+ req -> req.getRequestLine().getUri()
+ .endsWith("/sshfs/upgrade?remote=test")
+ )
+ ),
+ URI.create("http://localhost/plugins/sshfs"),
+ DOCKER
+ );
+ final JsonArray properties = Json.createArrayBuilder().add(
+ Json.createObjectBuilder()
+ .add("Name", "mount")
+ .add("Description", "")
+ .add("Value", "/data")
+ ).build();
+ plugin.upgrade("test", properties);
+ }
+
+ /**
+ * RtPlugin push to repository ok.
+ * @throws Exception If something goes wrong.
+ */
+ @Test
+ public void pushOk() throws Exception {
+ new ListedPlugins(
+ new AssertRequest(
+ new Response(
+ HttpStatus.SC_NO_CONTENT
+ )
+ ),
+ URI.create("http://localhost/plugins"),
+ DOCKER
+ ).pullAndInstall(
+ "vieus/sshfs",
+ "sshfs",
+ Json.createArrayBuilder().add(
+ Json.createObjectBuilder()
+ .add("Name", "network")
+ .add("Description", "")
+ .add("Value", "host")
+ ).build()
+ );
+ final Plugin plugin = new RtPlugin(
+ Json.createObjectBuilder().build(),
+ new AssertRequest(
+ new Response(
+ HttpStatus.SC_OK
+ ),
+ new Condition(
+ "Method should be a POST",
+ req -> "POST".equals(req.getRequestLine().getMethod())
+ ),
+ new Condition(
+ "Resource path must be /{name}/push",
+ req -> req.getRequestLine().getUri()
+ .endsWith("/sshfs/push")
+ )
+ ),
+ URI.create("http://localhost/plugins/sshfs"),
+ DOCKER
+ );
+ plugin.push();
+ }
+
+ /**
+ * RtPlugin push throws UnexpectedResponseException if service
+ * responds with 404.
+ * @throws Exception If something goes wrong.
+ */
+ @Test(expected = UnexpectedResponseException.class)
+ public void pushFailsPluginNotInstalled() throws Exception {
+ final Plugin plugin = new RtPlugin(
+ Json.createObjectBuilder().build(),
+ new AssertRequest(
+ new Response(
+ HttpStatus.SC_NOT_FOUND
+ ),
+ new Condition(
+ "Method should be a POST",
+ req -> "POST".equals(req.getRequestLine().getMethod())
+ ),
+ new Condition(
+ "Resource path must be /{name}/push",
+ req -> req.getRequestLine().getUri()
+ .endsWith("/sshfs/push")
+ )
+ ),
+ URI.create("http://localhost/plugins/sshfs"),
+ DOCKER
+ );
+ plugin.push();
+ }
+
+ /**
+ * RtPlugin configure plugin.
+ * @throws Exception If something goes wrong.
+ */
+ @Ignore
+ @Test
+ public void configureOk() throws Exception {
+ new ListedPlugins(
+ new AssertRequest(
+ new Response(
+ HttpStatus.SC_NO_CONTENT
+ )
+ ),
+ URI.create("http://localhost/plugins"),
+ DOCKER
+ ).pullAndInstall(
+ "vieus/sshfs",
+ "sshfs",
+ Json.createArrayBuilder().add(
+ Json.createObjectBuilder()
+ .add("Name", "network")
+ .add("Description", "")
+ .add("Value", "host")
+ ).build()
+ );
+ final Map options = new HashMap<>();
+ options.put("DEBUG", "1");
+ final Plugin plugin = new RtPlugin(
+ Json.createObjectBuilder().build(),
+ new AssertRequest(
+ new Response(
+ HttpStatus.SC_NO_CONTENT
+ ),
+ new Condition(
+ "Method should be a POST",
+ req -> "POST".equals(req.getRequestLine().getMethod())
+ ),
+ new Condition(
+ "Resource path must be /{name}/set",
+ req -> req.getRequestLine().getUri()
+ .endsWith("/sshfs/set")
+ ),
+ new Condition(
+ "configure() must send String Array as request body",
+ req -> "[\"DEBUG=1\"]".equals(this.stringPayloadOf(req))
+ )
+ ),
+ URI.create("http://localhost/plugins/sshfs"),
+ DOCKER
+ );
+ plugin.configure(options);
+ }
+
+ /**
+ * RtPlugin configure throws UnexpectedResponseException if service
+ * responds with 404.
+ * @throws Exception If something goes wrong.
+ */
+ @Ignore
+ @Test(expected = UnexpectedResponseException.class)
+ public void configureFailsPluginNotInstalled() throws Exception {
+ final Plugin plugin = new RtPlugin(
+ Json.createObjectBuilder().build(),
+ new AssertRequest(
+ new Response(
+ HttpStatus.SC_NOT_FOUND
+ ),
+ new Condition(
+ "Method should be a POST",
+ req -> "POST".equals(req.getRequestLine().getMethod())
+ ),
+ new Condition(
+ "Resource path must be /{name}/set",
+ req -> req.getRequestLine().getUri()
+ .endsWith("/sshfs/set")
+ )
+ ),
+ URI.create("http://localhost/plugins/sshfs"),
+ DOCKER
+ );
+ plugin.configure(new HashMap<>());
+ }
+
+ /**
+ * Extracts request payload as String.
+ * @param request Http Request.
+ * @return Payload as String.
+ */
+ private String stringPayloadOf(final HttpRequest request) {
+ try {
+ final String payload;
+ if (request instanceof HttpEntityEnclosingRequest) {
+ payload = EntityUtils.toString(
+ ((HttpEntityEnclosingRequest) request).getEntity()
+ );
+ } else {
+ payload = "";
+ }
+ return payload;
+ } catch (final IOException ex) {
+ throw new IllegalStateException(
+ "Cannot read request payload", ex
+ );
+ }
+ }
}
diff --git a/src/test/java/com/amihaiemil/docker/RtPluginsTestCase.java b/src/test/java/com/amihaiemil/docker/RtPluginsTestCase.java
index a8109a9d..9614f140 100644
--- a/src/test/java/com/amihaiemil/docker/RtPluginsTestCase.java
+++ b/src/test/java/com/amihaiemil/docker/RtPluginsTestCase.java
@@ -25,6 +25,7 @@
*/
package com.amihaiemil.docker;
+import com.amihaiemil.docker.mock.ArrayPayloadOf;
import com.amihaiemil.docker.mock.AssertRequest;
import com.amihaiemil.docker.mock.Condition;
import com.amihaiemil.docker.mock.Response;
@@ -137,7 +138,7 @@ public void pullAndInstallOk() throws Exception {
"pullAndInstall() must send Json body request",
req -> {
JsonObject payload =
- this.arrayPayloadOf(req).getJsonObject(0);
+ new ArrayPayloadOf(req).next();
return "network".equals(payload.getString("Name"))
&& "host".equals(payload.getString("Value"));
}
@@ -232,28 +233,4 @@ private String stringPayloadOf(final HttpRequest request) {
}
}
- /**
- * Extracts request payload as JsonArray.
- * @param request Http Request.
- * @return Payload as Json array.
- */
- private JsonArray arrayPayloadOf(final HttpRequest request) {
- try {
- final JsonArray body;
- if (request instanceof HttpEntityEnclosingRequest) {
- body = Json.createReader(
- ((HttpEntityEnclosingRequest) request).getEntity()
- .getContent()
- ).readArray();
- } else {
- body = Json.createArrayBuilder().build();
- }
- return body;
- } catch (final IOException ex) {
- throw new IllegalStateException(
- "Cannot read request payload", ex
- );
- }
- }
-
}
diff --git a/src/test/java/com/amihaiemil/docker/mock/ArrayPayloadOf.java b/src/test/java/com/amihaiemil/docker/mock/ArrayPayloadOf.java
new file mode 100644
index 00000000..a93ce09d
--- /dev/null
+++ b/src/test/java/com/amihaiemil/docker/mock/ArrayPayloadOf.java
@@ -0,0 +1,81 @@
+/**
+ * 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.mock;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpRequest;
+
+/**
+ * Json Array payload of an HttpRequest.
+ *
+ * @author Boris Kuzmic (boris.kuzmic@gmail.com)
+ * @since 0.0.8
+ */
+public final class ArrayPayloadOf implements Iterator {
+
+ /**
+ * List of JsonObjects.
+ */
+ private final Iterator resources;
+
+ /**
+ * Ctor.
+ *
+ * @param request The http request
+ * @throws IllegalStateException if the request's payload cannot be read
+ */
+ public ArrayPayloadOf(final HttpRequest request) {
+ try (JsonReader reader = Json.createReader(
+ ((HttpEntityEnclosingRequest) request).getEntity().getContent())) {
+ if (request instanceof HttpEntityEnclosingRequest) {
+ this.resources =
+ reader.readArray().getValuesAs(JsonObject.class).iterator();
+ } else {
+ this.resources = new ArrayList().iterator();
+ }
+ } catch (final IOException ex) {
+ throw new IllegalStateException(
+ "Cannot read request payload", ex
+ );
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return this.resources.hasNext();
+ }
+
+ @Override
+ public JsonObject next() {
+ return this.resources.next();
+ }
+}