diff --git a/src/main/java/com/amihaiemil/docker/Image.java b/src/main/java/com/amihaiemil/docker/Image.java
index 329f4f4f..b57a086e 100644
--- a/src/main/java/com/amihaiemil/docker/Image.java
+++ b/src/main/java/com/amihaiemil/docker/Image.java
@@ -34,9 +34,6 @@
* @version $Id$
* @see Docker Images API
* @since 0.0.1
- * @todo #96:30min Finish implementing the operations that affect a single
- * docker image (I think `tag` is the only one remaining). See the link
- * referenced above.
*/
public interface Image {
@@ -58,11 +55,25 @@ public interface Image {
Images history();
/**
- * The parent {@link Images}.
+ * Remove an image, along with any untagged parent images that were
+ * referenced by that image.
* @throws IOException If something goes wrong.
* @throws UnexpectedResponseException If the status response is not
* the expected one (200 OK).
* @see Remove an image
*/
void delete() throws IOException, UnexpectedResponseException;
+
+ /**
+ * Tags this image so that it becomes part of a repository.
+ * @param repo The repository to tag in. Eg.: "someuser/someimage"
+ * @param name The name of the new tag.
+ * @throws IOException If something goes wrong.
+ * @throws UnexpectedResponseException If the status response is not
+ * the expected one.
+ * @see Tag an image
+ */
+ void tag(
+ String repo, String name
+ ) throws IOException, UnexpectedResponseException;
}
diff --git a/src/main/java/com/amihaiemil/docker/RtImage.java b/src/main/java/com/amihaiemil/docker/RtImage.java
index b2bda47b..e2f2dcc6 100644
--- a/src/main/java/com/amihaiemil/docker/RtImage.java
+++ b/src/main/java/com/amihaiemil/docker/RtImage.java
@@ -31,6 +31,7 @@
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpPost;
/**
* Runtime {@link Image}.
@@ -88,4 +89,25 @@ public void delete() throws IOException, UnexpectedResponseException {
delete.releaseConnection();
}
}
+
+ @Override
+ public void tag(
+ final String repo, final String name
+ ) throws IOException, UnexpectedResponseException {
+ final HttpPost tag = new HttpPost(
+ new UncheckedUriBuilder(
+ this.baseUri.toString() + "/tag"
+ ).addParameter("repo", repo)
+ .addParameter("tag", name)
+ .build()
+ );
+ try {
+ this.client.execute(
+ tag,
+ new MatchStatus(tag.getURI(), HttpStatus.SC_CREATED)
+ );
+ } finally {
+ tag.releaseConnection();
+ }
+ }
}
diff --git a/src/test/java/com/amihaiemil/docker/RtImageTestCase.java b/src/test/java/com/amihaiemil/docker/RtImageTestCase.java
index 0673871b..19c1dd12 100644
--- a/src/test/java/com/amihaiemil/docker/RtImageTestCase.java
+++ b/src/test/java/com/amihaiemil/docker/RtImageTestCase.java
@@ -180,4 +180,90 @@ public void deleteErrorOn500() throws Exception {
URI.create("http://localhost/images/test")
).delete();
}
+
+ /**
+ * RtImage.tag() must send a POST request to the image's URL.
+ *
+ * Note the escaped forward slash in the query parameters.
+ * @throws Exception If something goes wrong.
+ */
+ @Test
+ public void tagsOk() throws Exception {
+ new RtImage(
+ new AssertRequest(
+ new Response(HttpStatus.SC_CREATED),
+ new Condition(
+ "RtImage.tag() must send a POST request",
+ req -> "POST".equals(req.getRequestLine().getMethod())
+ ),
+ new Condition(
+ "RtImage.tag() not building the URL correctly",
+ req -> req.getRequestLine().getUri().endsWith(
+ "/images/123/tag?repo=myrepo%2Fmyimage&tag=mytag"
+ )
+ )
+ ),
+ URI.create("http://localhost/images/123")
+ ).tag("myrepo/myimage", "mytag");
+ }
+
+ /**
+ * RtImage.tag() must throw UnexpectedResponseException if docker responds
+ * with 400.
+ * @throws Exception The UnexpectedResponseException.
+ */
+ @Test(expected = UnexpectedResponseException.class)
+ public void tagErrorIfResponseIs400() throws Exception {
+ new RtImage(
+ new AssertRequest(
+ new Response(HttpStatus.SC_BAD_REQUEST)
+ ),
+ URI.create("https://localhost")
+ ).tag("myrepo/myimage", "mytag");
+ }
+
+ /**
+ * RtImage.tag() must throw UnexpectedResponseException if docker responds
+ * with 404.
+ * @throws Exception The UnexpectedResponseException.
+ */
+ @Test(expected = UnexpectedResponseException.class)
+ public void tagErrorIfResponseIs404() throws Exception {
+ new RtImage(
+ new AssertRequest(
+ new Response(HttpStatus.SC_NOT_FOUND)
+ ),
+ URI.create("https://localhost")
+ ).tag("myrepo/myimage", "mytag");
+ }
+
+ /**
+ * RtImage.tag() must throw UnexpectedResponseException if docker responds
+ * with 409.
+ * @throws Exception The UnexpectedResponseException.
+ */
+ @Test(expected = UnexpectedResponseException.class)
+ public void tagErrorIfResponseIs409() throws Exception {
+ new RtImage(
+ new AssertRequest(
+ new Response(HttpStatus.SC_CONFLICT)
+ ),
+ URI.create("https://localhost")
+ ).tag("myrepo/myimage", "mytag");
+ }
+
+ /**
+ * RtImage.tag() must throw UnexpectedResponseException if docker responds
+ * with 500.
+ * @throws Exception The UnexpectedResponseException.
+ */
+ @Test(expected = UnexpectedResponseException.class)
+ public void tagErrorIfResponseIs500() throws Exception {
+ new RtImage(
+ new AssertRequest(
+ new Response(HttpStatus.SC_INTERNAL_SERVER_ERROR)
+ ),
+ URI.create("https://localhost")
+ ).tag("myrepo/myimage", "mytag");
+ }
}