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"); + } }