diff --git a/.gitignore b/.gitignore index 4123b0d09..dc0e45487 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ target/ .classpath .project .settings +.*.md.html diff --git a/doc/changelog.md b/doc/changelog.md index 2b14ac1be..6d580c47c 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -1,5 +1,12 @@ # ChangeLog +* **0.13.3** + - Allow dangling images to be cleaned up after build (#20) + - Adapt order of WORKDIR and RUN when building images (#222) + - Allow 'build' and/or 'run' configuration to be skipped (#207) + - Refactored to use 'inspect' instead of 'list' for checking the existence of an image (#230) + - Refactored AppacheHttpClientDelegate to avoid leaking connections (#232) + * **0.13.2** - "run" directives can be added to the Dockerfile (#191) - Support user information in wait URL (#211) diff --git a/doc/contributing.md b/doc/contributing.md index 11c37137c..712f1d59f 100644 --- a/doc/contributing.md +++ b/doc/contributing.md @@ -68,8 +68,8 @@ which you can put into your `~/.gitconfig`: ```` When sending pull request we prefer that to be a single commit. So please squash your commits -with an interactive rebase before sending the pull request. And of course your pull request should be -rebased to the current master. All this is nicely explained [here](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request). +with an interactive rebase before sending the pull request. Also, your pull request should be +rebased to the current branch `integration`. All this is nicely explained [here](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request). Said all this, don't hesitate to ask when there are any problems or you have an issue with this process. diff --git a/doc/manual.md b/doc/manual.md index 49b6b28c2..afcc74516 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -236,30 +236,32 @@ of an image configuration. The available subelements are * **assembly** specifies the assembly configuration as described in [Build Assembly](#build-assembly) +* **cleanup** indicates if dangling (untagged) images should be cleaned up during each build. Default is `true` * **cmd** A command to execute by default (i.e. if no command is provided when a container for this image is started). See [Start-up Arguments](#start-up-arguments) for details. * **entrypoint** An entrypoint allows you to configure a container that will run as an executable. - See [Start-up Arguments](#start-up-arguments) for details. -* **workdir** the directory to change to when starting the container. + See [Start-up Arguments](#start-up-arguments) for details. * **env** holds environments as described in [Setting Environment Variables and Labels](#setting-environment-variables-and-labels). -* **labels** holds labels as described in - [Setting Environment Variables and Labels](#setting-environment-variables-and-labels). * **from** specifies the base image which should be used for this image. If not given this default to `busybox:latest` and is suitable for a pure data image. +* **labels** holds labels as described in + [Setting Environment Variables and Labels](#setting-environment-variables-and-labels). +* **maintainer** specifies the author (MAINTAINER) field for the generated image * **ports** describes the exports ports. It contains a list of `` elements, one for each port to expose. -* **volumes** contains a list of `volume` elements to create a container - volume. -* **tags** contains a list of additional `tag` elements with which an - image is to be tagged after the build. -* **maintainer** specifies the author (MAINTAINER) field for the generated image * **run** specifies commands to be run during the build process. It contains **run** elements - which are passed to bash. The run commands are inserted right after the assembly but before **workdir** in to the + which are passed to bash. The run commands are inserted right after the assembly and after **workdir** in to the Dockerfile. This tag is not to be confused with the `` section for this image which specifies the runtime behaviour when starting containers. +* **skip** if set to true disables building of the image. This config option is best used together with a maven property +* **tags** contains a list of additional `tag` elements with which an + image is to be tagged after the build. +* **volumes** contains a list of `volume` elements to create a container + volume. +* **workdir** the directory to change to when starting the container. From this configuration this Plugin creates an in-memory Dockerfile, copies over the assembled files and calls the Docker daemon via its @@ -539,6 +541,7 @@ The `` configuration knows the following sub elements: * **restartPolicy** (*v1.15*) specifies the container restart policy, see [below](#container-restart-policy) * **user** (*v1.11*) user used inside the container +* **skip** disable creating and starting of the container. This option is best used together with a configuration option. * **volumes** for bind configurtion of host directories and from other containers. See "[Volume binding] (#volume-binding)" for details. * **wait** specifies condition which must be fulfilled for the startup diff --git a/pom.xml b/pom.xml index 56807b370..e5c87564a 100644 --- a/pom.xml +++ b/pom.xml @@ -154,6 +154,13 @@ test + + org.mockito + mockito-all + 1.10.19 + test + + junit junit diff --git a/samples/data-jolokia-demo/pom.xml b/samples/data-jolokia-demo/pom.xml index 1de33f3aa..1c8932381 100644 --- a/samples/data-jolokia-demo/pom.xml +++ b/samples/data-jolokia-demo/pom.xml @@ -21,7 +21,7 @@ org.jolokia docker-jolokia-demo - 0.13.2 + 0.13.3-SNAPSHOT http://www.jolokia.org diff --git a/src/main/java/org/jolokia/docker/maven/AbstractBuildSupporMojo.java b/src/main/java/org/jolokia/docker/maven/AbstractBuildSupportMojo.java similarity index 78% rename from src/main/java/org/jolokia/docker/maven/AbstractBuildSupporMojo.java rename to src/main/java/org/jolokia/docker/maven/AbstractBuildSupportMojo.java index 93d6667a0..cdf077206 100644 --- a/src/main/java/org/jolokia/docker/maven/AbstractBuildSupporMojo.java +++ b/src/main/java/org/jolokia/docker/maven/AbstractBuildSupportMojo.java @@ -1,21 +1,4 @@ -package org.jolokia.docker.maven;/* - * - * Copyright 2014 Roland Huss - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.io.File; +package org.jolokia.docker.maven; import org.apache.maven.archiver.MavenArchiveConfiguration; import org.apache.maven.execution.MavenSession; @@ -33,7 +16,7 @@ * @author roland * @since 26/06/15 */ -abstract public class AbstractBuildSupporMojo extends AbstractDockerMojo { +abstract public class AbstractBuildSupportMojo extends AbstractDockerMojo { // ============================================================================================================== // Parameters required from Maven when building an assembly. They cannot be injected directly // into DockerAssemblyCreator. @@ -50,9 +33,7 @@ abstract public class AbstractBuildSupporMojo extends AbstractDockerMojo { /** @component */ private MavenReaderFilter mavenFilterReader; - /** @component */ - protected DockerAssemblyManager dockerAssemblyManager; - + /** * @parameter default-value="src/main/docker" property="docker.source.dir" */ @@ -76,10 +57,7 @@ protected void buildImage(DockerAccess dockerAccess, String imageName, ImageConf autoPullBaseImage(dockerAccess, imageConfig); MojoParameters params = createMojoParameters(); - File dockerArchive = dockerAssemblyManager.createDockerTarArchive(imageName, params, imageConfig.getBuildConfiguration()); - - dockerAccess.buildImage(imageName, dockerArchive); - log.info(imageConfig.getDescription() + ": Build image "); + serviceHub.getBuildService().buildImage(imageConfig, params); } private void autoPullBaseImage(DockerAccess dockerAccess, ImageConfiguration imageConfig) diff --git a/src/main/java/org/jolokia/docker/maven/AbstractDockerMojo.java b/src/main/java/org/jolokia/docker/maven/AbstractDockerMojo.java index ab004a183..4884f358a 100644 --- a/src/main/java/org/jolokia/docker/maven/AbstractDockerMojo.java +++ b/src/main/java/org/jolokia/docker/maven/AbstractDockerMojo.java @@ -239,7 +239,7 @@ private DockerAccess createDockerAccess(String baseUrl) throws MojoExecutionExce return client; } - catch (IOException | DockerAccessException e) { + catch (IOException e) { throw new MojoExecutionException("Cannot create docker access object ", e); } } diff --git a/src/main/java/org/jolokia/docker/maven/BuildMojo.java b/src/main/java/org/jolokia/docker/maven/BuildMojo.java index 8855d9e76..09932cab6 100644 --- a/src/main/java/org/jolokia/docker/maven/BuildMojo.java +++ b/src/main/java/org/jolokia/docker/maven/BuildMojo.java @@ -19,7 +19,7 @@ * @goal build * @phase install */ -public class BuildMojo extends AbstractBuildSupporMojo { +public class BuildMojo extends AbstractBuildSupportMojo { /** * @parameter default-value="false" property="docker.skipTags" @@ -31,16 +31,25 @@ protected void executeInternal(DockerAccess dockerAccess) throws DockerAccessExc for (ImageConfiguration imageConfig : getImages()) { BuildImageConfiguration buildConfig = imageConfig.getBuildConfiguration(); if (buildConfig != null) { - buildConfig.validate(); - String imageName = imageConfig.getName(); - buildImage(dockerAccess, imageName, imageConfig); - if (!skipTags) { - tagImage(imageName, imageConfig, dockerAccess); + if (buildConfig.skip()) { + log.info(imageConfig.getDescription() + ": Skipped building"); + } else { + buildImage(dockerAccess, imageConfig, buildConfig); } } } } + private void buildImage(DockerAccess dockerAccess, ImageConfiguration imageConfig, BuildImageConfiguration buildConfig) + throws MojoExecutionException, DockerAccessException { + buildConfig.validate(); + String imageName = imageConfig.getName(); + buildImage(dockerAccess, imageName, imageConfig); + if (!skipTags) { + tagImage(imageName, imageConfig, dockerAccess); + } + } + private void tagImage(String imageName, ImageConfiguration imageConfig, DockerAccess dockerAccess) throws DockerAccessException, MojoExecutionException { diff --git a/src/main/java/org/jolokia/docker/maven/WatchMojo.java b/src/main/java/org/jolokia/docker/maven/WatchMojo.java index fc5f2c524..423551b4e 100644 --- a/src/main/java/org/jolokia/docker/maven/WatchMojo.java +++ b/src/main/java/org/jolokia/docker/maven/WatchMojo.java @@ -22,15 +22,14 @@ import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugin.assembly.InvalidAssemblerConfigurationException; -import org.apache.maven.plugin.assembly.archive.ArchiveCreationException; -import org.apache.maven.plugin.assembly.format.AssemblyFormattingException; import org.codehaus.plexus.util.StringUtils; import org.jolokia.docker.maven.access.*; import org.jolokia.docker.maven.assembly.AssemblyFiles; import org.jolokia.docker.maven.config.*; -import org.jolokia.docker.maven.service.*; -import org.jolokia.docker.maven.util.*; +import org.jolokia.docker.maven.service.QueryService; +import org.jolokia.docker.maven.service.RunService; +import org.jolokia.docker.maven.util.MojoParameters; +import org.jolokia.docker.maven.util.StartOrderResolver; import static org.jolokia.docker.maven.config.WatchMode.both; @@ -50,7 +49,7 @@ * @author roland * @since 16/06/15 */ -public class WatchMojo extends AbstractBuildSupporMojo { +public class WatchMojo extends AbstractBuildSupportMojo { /** @parameter property = "docker.watchMode" default-value="both" **/ private WatchMode watchMode; @@ -137,7 +136,7 @@ private Runnable createBuildWatchTask(final DockerAccess docker, final ImageWatc throws MojoExecutionException { final ImageConfiguration imageConfig = watcher.getImageConfiguration(); final String name = imageConfig.getName(); - final AssemblyFiles files = getAssemblyFiles(name, imageConfig.getBuildConfiguration(), mojoParameters); + final AssemblyFiles files = serviceHub.getBuildService().getAssemblyFiles(name, imageConfig, mojoParameters); return new Runnable() { @Override public void run() { @@ -185,16 +184,6 @@ public void run() { }; } - private AssemblyFiles getAssemblyFiles(String name, BuildImageConfiguration buildConfiguration, MojoParameters mojoParameters) - throws MojoExecutionException { - try { - return dockerAssemblyManager.getAssemblyFiles(name, buildConfiguration, mojoParameters); - } catch (InvalidAssemblerConfigurationException | ArchiveCreationException | AssemblyFormattingException e) { - throw new MojoExecutionException("Cannot extract assembly files for image " + name + ": " + e,e); - } - } - - private void restartContainer(ImageWatcher watcher) throws DockerAccessException { // Stop old one RunService runService = serviceHub.getRunService(); diff --git a/src/main/java/org/jolokia/docker/maven/access/AuthConfig.java b/src/main/java/org/jolokia/docker/maven/access/AuthConfig.java index 231a4336c..858bb2a88 100644 --- a/src/main/java/org/jolokia/docker/maven/access/AuthConfig.java +++ b/src/main/java/org/jolokia/docker/maven/access/AuthConfig.java @@ -16,7 +16,7 @@ */ public class AuthConfig { - public final static AuthConfig EMPTY_AUTH_CONFIG = new AuthConfig("", "", "", ""); + public final static AuthConfig EMPTY_AUTH_CONFIG = new AuthConfig("", "", "", "", ""); private Map params; @@ -24,12 +24,13 @@ public AuthConfig(Map params) { this.params = params; } - public AuthConfig(String user, String password, String email,String auth) { + public AuthConfig(String user, String password, String email, String auth, String registry) { params = new HashMap<>(); putNonNull(params, "username", user); putNonNull(params, "password", password); putNonNull(params, "email", email); putNonNull(params, "auth", auth); + putNonNull(params, "serveraddress", registry); } public String toHeaderValue() { @@ -38,6 +39,7 @@ public String toHeaderValue() { add(ret,"password"); add(ret,"email"); add(ret,"auth"); + add(ret,"serveraddress"); try { return Base64.encodeBase64String(ret.toString().getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { diff --git a/src/main/java/org/jolokia/docker/maven/access/DockerAccess.java b/src/main/java/org/jolokia/docker/maven/access/DockerAccess.java index 94fa53ca3..bb6d62c4a 100644 --- a/src/main/java/org/jolokia/docker/maven/access/DockerAccess.java +++ b/src/main/java/org/jolokia/docker/maven/access/DockerAccess.java @@ -26,24 +26,30 @@ public interface DockerAccess { */ Container inspectContainer(String containerId) throws DockerAccessException; + /** + * Check whether the given name exists as image at the docker daemon + * + * @param name image name to check + * @return true if the image exists + */ + boolean hasImage(String name) throws DockerAccessException; /** - * List images - * - * @param args optional list images args - * @return list of Imageobjects - * @throws DockerAccessException if the images could not be listed + * Get the image id of a given name or null if no such image exists + * + * @param name name to lookup + * @return the image id or null */ - List listImages(ListArg... args) throws DockerAccessException; - + String getImageId(String name) throws DockerAccessException; + /** * List containers * - * @param args optional list containers args + * @param limit limit of containers to list * @return list of Container objects * @throws DockerAccessException if the containers could not be listed */ - List listContainers(ListArg... args) throws DockerAccessException; + List listContainers(int limit) throws DockerAccessException; /** * Create a container from the given image. @@ -139,9 +145,10 @@ public interface DockerAccess { * * @param image name of the image to build or null if none should be used * @param dockerArchive from which the docker image should be build + * @param forceRemove whether to remove intermediate containers * @throws DockerAccessException if docker host reports an error during building of an image */ - void buildImage(String image, File dockerArchive) throws DockerAccessException; + void buildImage(String image, File dockerArchive, boolean forceRemove) throws DockerAccessException; /** * Alias an image in the repository with a complete new name. (Note that this maps to a Docker Remote API 'tag' @@ -174,30 +181,4 @@ public interface DockerAccess { * cleaning up things. */ void shutdown(); - - class ListArg { - private final String key; - private final String value; - - private ListArg(String key, String value) { - this.key = key; - this.value = value; - } - - public String getKey() { - return key; - } - - public String getValue() { - return value; - } - - public static ListArg filter(String value) { - return new ListArg("filter", value); - } - - public static ListArg limit(int value) { - return new ListArg("limit", String.valueOf(value)); - } - } } diff --git a/src/main/java/org/jolokia/docker/maven/access/DockerAccessException.java b/src/main/java/org/jolokia/docker/maven/access/DockerAccessException.java index 896552048..a3367609f 100644 --- a/src/main/java/org/jolokia/docker/maven/access/DockerAccessException.java +++ b/src/main/java/org/jolokia/docker/maven/access/DockerAccessException.java @@ -1,12 +1,14 @@ package org.jolokia.docker.maven.access; +import java.io.IOException; + /** * Exception thrown if access to the docker host fails * * @author roland * @since 20.10.14 */ -public class DockerAccessException extends Exception { +public class DockerAccessException extends IOException { /** * Constructor diff --git a/src/main/java/org/jolokia/docker/maven/access/UrlBuilder.java b/src/main/java/org/jolokia/docker/maven/access/UrlBuilder.java index d45ea2407..455983b65 100644 --- a/src/main/java/org/jolokia/docker/maven/access/UrlBuilder.java +++ b/src/main/java/org/jolokia/docker/maven/access/UrlBuilder.java @@ -2,8 +2,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; import org.jolokia.docker.maven.util.ImageName; @@ -18,138 +17,79 @@ public UrlBuilder(String baseUrl, String apiVersion) { this.apiVersion = apiVersion; this.baseUrl = stripSlash(baseUrl); } - - public String buildImage(String tag, boolean forcerm, boolean pull) { - String url = createUrl("/build"); - - if (tag != null) { - url = addQueryParam(url, "t", tag); - } - - url = addQueryParam(url, "pull", pull); - url = addQueryParam(url, forcerm ? "forcerm" : "rm", true); - - return url; + + public String buildImage(String image, boolean forceRemove) { + return u("build") + .p("t",image) + .p(forceRemove ? "forcerm" : "rm", true) + .toString(); } public String inspectImage(String name) { - return createUrl(String.format("/images/%s/json", encode(name))); + return u("images/%s/json", name).toString(); } public String containerLogs(String containerId, boolean follow) { - String url = createUrl(String.format("/containers/%s/logs", containerId)); - url += "?stdout=1&stderr=1×tamps=1"; - url = addQueryParam(url, "follow", follow); - - return url; + return u("containers/%s/logs", containerId) + .p("stdout",true) + .p("stderr",true) + .p("timestamps", true) + .p("follow", follow) + .toString(); } public String createContainer(String name) { - String url = createUrl("/containers/create"); - url = addQueryParam(url, "name", name); - - return url; + return u("containers/create").p("name", name).toString(); } public String deleteImage(String name, boolean force) { - String url = createUrl(String.format("/images/%s", name)); - url = addQueryParam(url, "force", force); - - return url; + return u("images/%s", name).p("force", force).toString(); } - public DockerUrl inspectContainer(String containerId) { - return createDockerUrl(String.format("/containers/%s/json", encode(containerId))); + public String inspectContainer(String containerId) { + return u("containers/%s/json", containerId).toString(); } - public DockerUrl listContainers() { - return createDockerUrl(String.format("/containers/json")); - } - - public DockerUrl listImages() { - return createDockerUrl("/images/json"); - } - - public String listImages(ImageName name) { - return createUrl(String.format("/images/json?filter=%s", name.getNameWithoutTag())); + public String listContainers(int limit) { + return u("containers/json").p("limit", limit).toString(); } public String pullImage(ImageName name, String registry) { - String url = createUrl(String.format("/images/create?fromImage=%s", encode(name.getNameWithoutTag(registry)))); - url = addTagParam(url, name.getTag()); - - return url; + return u("images/create") + .p("fromImage", name.getNameWithoutTag(registry)) + .p("tag", name.getTag()).toString(); } public String pushImage(ImageName name, String registry) { - String url = createUrl(String.format("/images/%s/push", encode(name.getNameWithoutTag(registry)))); - url = addTagParam(url, name.getTag()); - // "force=1" helps Fedora/CentOs Docker variants to push to public registries - url = addForceParam(url,true); - return url; + return u("images/%s/push", name.getNameWithoutTag(registry)) + .p("tag", name.getTag()) + // "force=1" helps Fedora/CentOs Docker variants to push to public registries + .p("force", true) + .toString(); } public String removeContainer(String containerId, boolean removeVolumes) { - String url = createUrl(String.format("/containers/%s", encode(containerId))); - if (removeVolumes) { - url = addQueryParam(url, "v", "1"); - } - - return url; + return u("containers/%s", containerId).p("v", removeVolumes).toString(); } public String startContainer(String containerId) { - return createUrl(String.format("/containers/%s/start", encode(containerId))); + return u("containers/%s/start", containerId).toString(); } public String stopContainer(String containerId) { - return createUrl(String.format("/containers/%s/stop", encode(containerId))); + return u("containers/%s/stop", containerId).toString(); } public String tagContainer(ImageName source, ImageName target, boolean force) { - String url = createUrl(String.format("/images/%s/tag", encode(source.getFullName()))); - url = addRepositoryParam(url, target.getNameWithoutTag()); - url = addTagParam(url, target.getTag()); - if (force) { - url = addQueryParam(url, "force", "1"); - } - - return url; + return u("images/%s/tag", source.getFullName()) + .p("repo",target.getNameWithoutTag()) + .p("tag",target.getTag()) + .p("force",force) + .toString(); } // ============================================================================ - private String addQueryParam(String url, String param, boolean value) { - return addQueryParam(url, param, (value) ? "1" : "0"); - } - - private String addQueryParam(String url, String param, String value) { - if (value != null) { - return url + (url.contains("?") ? "&" : "?") + param + "=" + encode(value); - } - return url; - } - - private String addRepositoryParam(String url, String repository) { - return addQueryParam(url, "repo", repository); - } - - private String addTagParam(String url, String tag) { - return addQueryParam(url, "tag", tag); - } - - private String addForceParam(String url, boolean force) { - return addQueryParam(url, "force", force); - } - - private DockerUrl createDockerUrl(String path) { - return new DockerUrl(createUrl(path)); - } - - private String createUrl(String path) { - return String.format("%s/%s%s", baseUrl, apiVersion, path); - } - @SuppressWarnings("deprecation") private static String encode(String param) { try { @@ -168,34 +108,61 @@ private String stripSlash(String url) { } return ret; } - - public static class DockerUrl { - private final String url; - private final Map queryParams; - - DockerUrl(String url) { + + // Entry point for builder + private Builder u(String format, String ... args) { + return new Builder(createUrl(String.format(format,encodeArgs(args)))); + } + + private String[] encodeArgs(String[] args) { + String ret[] = new String[args.length]; + int i=0; + for (String arg : args) { + ret[i++] = encode(arg); + } + return ret; + } + + private String createUrl(String path) { + return String.format("%s/%s/%s", baseUrl, apiVersion, path); + } + + private static class Builder { + + Map queryParams = new HashMap<>(); + String url; + + public Builder(String url) { this.url = url; - this.queryParams = new LinkedHashMap<>(); } - - public void addQueryParam(String key, String value) { - queryParams.put(key, value); + + private Builder p(String key, String value) { + if (value != null) { + queryParams.put(key, value); + } + return this; + } + + private Builder p(String key, boolean value) { + return p(key,value ? "1" : "0"); + } + + private Builder p(String key, int value) { + return p(key,Integer.toString(value)); } - - @Override + public String toString() { - StringBuilder builder = new StringBuilder(url); - builder.append("?"); - + StringBuilder ret = new StringBuilder(url); + ret.append("?"); + int count = queryParams.size(); for (Map.Entry entry : queryParams.entrySet()) { - builder.append(entry.getKey()).append("=").append(encode(entry.getValue())); + ret.append(entry.getKey()).append("=").append(encode(entry.getValue())); if (--count > 0) { - builder.append("&"); + ret.append("&"); } } - - return builder.toString(); + return ret.toString(); } - } + } } diff --git a/src/main/java/org/jolokia/docker/maven/access/chunked/ChunkedResponseReader.java b/src/main/java/org/jolokia/docker/maven/access/chunked/ChunkedResponseReader.java index 5f8fd5b21..db1b5c3be 100644 --- a/src/main/java/org/jolokia/docker/maven/access/chunked/ChunkedResponseReader.java +++ b/src/main/java/org/jolokia/docker/maven/access/chunked/ChunkedResponseReader.java @@ -1,7 +1,5 @@ package org.jolokia.docker.maven.access.chunked; -import org.jolokia.docker.maven.access.DockerAccessException; - import java.io.IOException; import java.io.InputStream; @@ -15,7 +13,7 @@ public ChunkedResponseReader(InputStream stream, ChunkedResponseHandler this.handler = handler; } - public void process() throws IOException, DockerAccessException { + public void process() throws IOException { int len; int size = 8129; byte[] buf = new byte[size]; diff --git a/src/main/java/org/jolokia/docker/maven/access/hc/ApacheHttpClientDelegate.java b/src/main/java/org/jolokia/docker/maven/access/hc/ApacheHttpClientDelegate.java index 1a647951b..79727a79f 100644 --- a/src/main/java/org/jolokia/docker/maven/access/hc/ApacheHttpClientDelegate.java +++ b/src/main/java/org/jolokia/docker/maven/access/hc/ApacheHttpClientDelegate.java @@ -1,12 +1,19 @@ package org.jolokia.docker.maven.access.hc; -import java.io.*; +import com.google.common.net.MediaType; +import java.io.File; +import java.io.IOException; import java.nio.charset.Charset; import java.util.Map; import java.util.Map.Entry; - -import org.apache.http.*; -import org.apache.http.client.methods.*; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.FileEntity; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; @@ -15,121 +22,167 @@ public class ApacheHttpClientDelegate { - private static final String HEADER_ACCEPT = "Accept"; - private static final String HEADER_ACCEPT_ALL = "*/*"; + private final CloseableHttpClient httpClient; - private final CloseableHttpClient httpClient; + public ApacheHttpClientDelegate(CloseableHttpClient httpClient) { + this.httpClient = httpClient; + } - public ApacheHttpClientDelegate(CloseableHttpClient httpClient) { - this.httpClient = httpClient; - } + public int delete(String url, int... statusCodes) throws IOException { + return delete(url, new StatusCodeResponseHandler(), statusCodes); + } - public Result delete(String url, int statusCode, int... additional) throws IOException, HttpRequestException { - return parseResponse(httpClient.execute(newDelete(url)), statusCode, additional); - } + public static class StatusCodeResponseHandler implements ResponseHandler { - public Result get(String url, int statusCode, int... additional) throws IOException, HttpRequestException { - return parseResponse(httpClient.execute(newGet(url)), statusCode, additional); + @Override + public Integer handleResponse(HttpResponse response) + throws IOException { + return response.getStatusLine().getStatusCode(); + } + } + + public T delete(String url, ResponseHandler responseHandler, int... statusCodes) + throws IOException { + return httpClient.execute(newDelete(url), + new StatusCodeCheckerResponseHandler<>(responseHandler, + statusCodes)); + } + + public String get(String url, int... statusCodes) throws IOException { + return httpClient.execute(newGet(url), new StatusCodeCheckerResponseHandler<>( + new BodyResponseHandler(), statusCodes)); + } + + public T get(String url, ResponseHandler responseHandler, int... statusCodes) + throws IOException { + return httpClient + .execute(newGet(url), new StatusCodeCheckerResponseHandler<>(responseHandler, statusCodes)); + } + + public static class BodyResponseHandler implements ResponseHandler { + + @Override + public String handleResponse(HttpResponse response) + throws IOException { + return getResponseMessage(response); + } + } + + private static String getResponseMessage(HttpResponse response) throws IOException { + return (response.getEntity() == null) ? null + : EntityUtils.toString(response.getEntity()).trim(); + } + + public T post(String url, Object body, Map headers, + ResponseHandler responseHandler, int... statusCodes) throws IOException { + HttpUriRequest request = newPost(url, body); + for (Entry entry : headers.entrySet()) { + request.addHeader(entry.getKey(), entry.getValue()); } - public Result post(String url, Object body, Map headers, int statusCode, int... additional) throws IOException, - HttpRequestException { + return httpClient.execute(request, new StatusCodeCheckerResponseHandler<>(responseHandler, + statusCodes)); + } - HttpUriRequest request = newPost(url, body); - for (Entry entry : headers.entrySet()) { - request.addHeader(entry.getKey(), entry.getValue()); - } + public T post(String url, Object body, ResponseHandler responseHandler, + int... statusCodes) throws IOException { + return httpClient.execute(newPost(url, body), + new StatusCodeCheckerResponseHandler<>(responseHandler, + statusCodes)); + } - return parseResponse(httpClient.execute(request), statusCode, additional); - } + public int post(String url, Object body, + int... statusCodes) throws IOException { + return post(url, body, new StatusCodeResponseHandler(), statusCodes); + } - public Result post(String url, Object body, int statusCode, int... additional) throws IOException, HttpRequestException { - return parseResponse(httpClient.execute(newPost(url, body)), statusCode, additional); - } - - public CloseableHttpClient getHttpClient() { - return httpClient; - } + public CloseableHttpClient getHttpClient() { + return httpClient; + } - // ========================================================================================= + // ========================================================================================= - private HttpUriRequest addDefaultHeaders(HttpUriRequest req) { - req.addHeader(HEADER_ACCEPT, HEADER_ACCEPT_ALL); - req.addHeader("Content-Type", "application/json"); - return req; - } + private HttpUriRequest addDefaultHeaders(HttpUriRequest req) { + req.addHeader(HttpHeaders.ACCEPT, "*/*"); + req.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.JSON_UTF_8.toString()); + return req; + } + private HttpUriRequest newDelete(String url) { + return addDefaultHeaders(new HttpDelete(url)); + } - private HttpUriRequest newDelete(String url) { - return addDefaultHeaders(new HttpDelete(url)); - } + private HttpUriRequest newGet(String url) { + return addDefaultHeaders(new HttpGet(url)); + } + + private HttpUriRequest newPost(String url, Object body) { + HttpPost post = new HttpPost(url); - private HttpUriRequest newGet(String url) { - return addDefaultHeaders(new HttpGet(url)); + if (body != null) { + if (body instanceof File) { + post.setEntity(new FileEntity((File) body)); + } else { + post.setEntity(new StringEntity((String) body, Charset.defaultCharset())); + } } + return addDefaultHeaders(post); + } - private HttpUriRequest newPost(String url, Object body) { - HttpPost post = new HttpPost(url); + public static class StatusCodeCheckerResponseHandler implements ResponseHandler { - if (body != null) { - if (body instanceof File) { - post.setEntity(new FileEntity((File) body)); - } - else { - post.setEntity(new StringEntity((String) body, Charset.defaultCharset())); - } - } - return addDefaultHeaders(post); + private int[] statusCodes; + private ResponseHandler delegate; + + public StatusCodeCheckerResponseHandler(ResponseHandler delegate, int... statusCodes) { + this.statusCodes = statusCodes; + this.delegate = delegate; } - private Result parseResponse(HttpResponse response, int successCode, int... additional) throws HttpRequestException { - HttpEntity entity = response.getEntity(); + @Override + public T handleResponse(HttpResponse response) throws IOException { + StatusLine statusLine = response.getStatusLine(); + int statusCode = statusLine.getStatusCode(); + for (int code : statusCodes) { + if (statusCode == code) { + return delegate.handleResponse(response); + } + } - StatusLine statusLine = response.getStatusLine(); - int statusCode = statusLine.getStatusCode(); - String reason = statusLine.getReasonPhrase().trim(); + String reason = statusLine.getReasonPhrase().trim(); + throw new HttpRequestException(String.format("%s (%s: %d)", getResponseMessage(response), + reason, statusCode)); + } - if (statusCode == successCode) { - return new Result(statusCode, entity); - } + } - for (int code : additional) { - if (statusCode == code) { - return new Result(code, entity); - } - } + public static class BodyAndStatusResponseHandler implements ResponseHandler { - Result result = new Result(statusCode, entity); - throw new HttpRequestException(String.format("%s (%s: %d)", result.getMessage(), reason, statusCode)); + @Override + public HttpBodyAndStatus handleResponse(HttpResponse response) + throws IOException { + return new HttpBodyAndStatus(response.getStatusLine().getStatusCode(), + getResponseMessage(response)); } + } - public static class Result - { - private final int code; - private final HttpEntity entity; + public static class HttpBodyAndStatus { - public Result(int code, HttpEntity entity) - { - this.code = code; - this.entity = entity; - } + private final int statusCode; + private final String body; - public int getCode() { - return code; - } + public HttpBodyAndStatus(int statusCode, String body) { + this.statusCode = statusCode; + this.body = body; + } - public InputStream getInputStream() throws IOException { - return entity.getContent(); - } + public int getStatusCode() { + return statusCode; + } - public String getMessage() { - try { - return (entity == null) ? null : EntityUtils.toString(entity).trim(); - } - catch (IOException e) { - return "Unknown error - failed to read response content"; - } - } + public String getBody() { + return body; } + } } diff --git a/src/main/java/org/jolokia/docker/maven/access/hc/DockerAccessWithHcClient.java b/src/main/java/org/jolokia/docker/maven/access/hc/DockerAccessWithHcClient.java index 786fa92f9..dc07d2beb 100644 --- a/src/main/java/org/jolokia/docker/maven/access/hc/DockerAccessWithHcClient.java +++ b/src/main/java/org/jolokia/docker/maven/access/hc/DockerAccessWithHcClient.java @@ -4,12 +4,12 @@ import java.net.URI; import java.util.*; +import org.apache.http.HttpResponse; +import org.apache.http.client.ResponseHandler; import org.jolokia.docker.maven.access.*; -import org.jolokia.docker.maven.access.UrlBuilder.DockerUrl; import org.jolokia.docker.maven.access.chunked.*; -import org.jolokia.docker.maven.access.hc.http.*; +import org.jolokia.docker.maven.access.hc.http.HttpClientBuilder; import org.jolokia.docker.maven.access.hc.unix.UnixSocketClientBuilder; -import org.jolokia.docker.maven.access.hc.ApacheHttpClientDelegate.Result; import org.jolokia.docker.maven.access.log.*; import org.jolokia.docker.maven.model.*; import org.jolokia.docker.maven.util.ImageName; @@ -18,17 +18,19 @@ import org.json.JSONObject; import static java.net.HttpURLConnection.*; +import static org.jolokia.docker.maven.access.hc.ApacheHttpClientDelegate.*; /** - * Implementation using Apache HttpComponents for accessing remotely - * the docker host. - * - * The design goal here is to provide only the functionality required for this plugin in order to make - * it as robust as possible agains docker API changes (which happen quite frequently). That's also - * the reason, why no framework like JAX-RS or docker-java is used so that the dependencies are kept low. - * - * Of course, it's a bit more manual work, but it's worth the effort (as long as the Docker API functionality - * required is not to much). + * Implementation using Apache HttpComponents for accessing + * remotely the docker host. + *

+ * The design goal here is to provide only the functionality required for this plugin in order to + * make it as robust as possible agains docker API changes (which happen quite frequently). That's + * also the reason, why no framework like JAX-RS or docker-java is used so that the dependencies are + * kept low. + *

+ * Of course, it's a bit more manual work, but it's worth the effort (as long as the Docker API + * functionality required is not to much). * * @author roland * @since 26.03.14 @@ -46,191 +48,222 @@ public class DockerAccessWithHcClient implements DockerAccess { /** * Create a new access for the given URL - * @param baseUrl base URL for accessing the docker Daemon - * @param certPath used to build up a keystore with the given keys and certificates found in this directory - * @param log a log handler for printing out logging information + * + * @param baseUrl base URL for accessing the docker Daemon + * @param certPath used to build up a keystore with the given keys and certificates found in this + * directory + * @param log a log handler for printing out logging information */ - public DockerAccessWithHcClient(String apiVersion, String baseUrl, String certPath, Logger log) throws IOException { + public DockerAccessWithHcClient(String apiVersion, String baseUrl, String certPath, Logger log) + throws IOException { this.log = log; URI uri = URI.create(baseUrl); if (uri.getScheme().equalsIgnoreCase("unix")) { - this.delegate = new ApacheHttpClientDelegate(new UnixSocketClientBuilder().build(uri.getPath())); - this.urlBuilder = new UrlBuilder(DUMMY_BASE_URL,apiVersion); + this.delegate = + new ApacheHttpClientDelegate(new UnixSocketClientBuilder().build(uri.getPath())); + this.urlBuilder = new UrlBuilder(DUMMY_BASE_URL, apiVersion); } else { - this.delegate = new ApacheHttpClientDelegate(new HttpClientBuilder(isSSL(baseUrl) ? certPath : null).build()); + this.delegate = + new ApacheHttpClientDelegate( + new HttpClientBuilder(isSSL(baseUrl) ? certPath : null).build()); this.urlBuilder = new UrlBuilder(baseUrl, apiVersion); } } @Override - public String createContainer(ContainerCreateConfig containerConfig, String containerName) throws DockerAccessException { + public String createContainer(ContainerCreateConfig containerConfig, String containerName) + throws DockerAccessException { String createJson = containerConfig.toJson(); log.debug("Container create config: " + createJson); try { - String response = post(urlBuilder.createContainer(containerName), createJson, HTTP_CREATED).getMessage(); + String url = urlBuilder.createContainer(containerName); + String response = + delegate.post(url, createJson, new BodyResponseHandler(), HTTP_CREATED); JSONObject json = new JSONObject(response); logWarnings(json); // only need first 12 to id a container return json.getString("Id").substring(0, 12); - } - catch (HttpRequestException e) { + } catch (IOException e) { log.error(e.getMessage()); - throw new DockerAccessException("Unable to create container for [%s]", containerConfig.getImageName()); + throw new DockerAccessException("Unable to create container for [%s]", + containerConfig.getImageName()); } } @Override public void startContainer(String containerId) throws DockerAccessException { try { - post(urlBuilder.startContainer(containerId), null, HTTP_NO_CONTENT, HTTP_OK); - } catch (HttpRequestException e) { + String url = urlBuilder.startContainer(containerId); + delegate.post(url, null, HTTP_NO_CONTENT, HTTP_OK); + } catch (IOException e) { log.error(e.getMessage()); - throw new DockerAccessException(String.format("Unable to start container id [%s]", containerId)); + throw new DockerAccessException("Unable to start container id [%s]", containerId); } } @Override public void stopContainer(String containerId) throws DockerAccessException { try { - post(urlBuilder.stopContainer(containerId), null, HTTP_NO_CONTENT, HTTP_NOT_MODIFIED); - } catch (HttpRequestException e) { + String url = urlBuilder.stopContainer(containerId); + delegate.post(url, HTTP_NO_CONTENT, HTTP_NOT_MODIFIED); + } catch (IOException e) { log.error(e.getMessage()); - throw new DockerAccessException(String.format("Unable to stop container id [%s]", containerId)); + throw new DockerAccessException("Unable to stop container id [%s]", containerId); } } @Override - public void buildImage(String image, File dockerArchive) throws DockerAccessException { - // auto-pull not supported in v1.15, which is currently the default - String buildUrl = urlBuilder.buildImage(image, false, false); - + public void buildImage(String image, File dockerArchive, boolean forceRemove) + throws DockerAccessException { try { - Result result = post(buildUrl, dockerArchive, HTTP_OK); - processChunkedResponse(result, createBuildResponseHandler()); - } catch (HttpRequestException e) { + String url = urlBuilder.buildImage(image, forceRemove); + delegate.post(url, dockerArchive, createBuildResponseHandler(), HTTP_OK); + } catch (IOException e) { log.error(e.getMessage()); - throw new DockerAccessException(String.format("Unable to build image [%s]", image)); + throw new DockerAccessException("Unable to build image [%s]", image); } } @Override - public Map queryContainerPortMapping(String containerId) throws DockerAccessException { + public Map queryContainerPortMapping(String containerId) + throws DockerAccessException { try { - String response = get(urlBuilder.inspectContainer(containerId), HTTP_OK).getMessage(); + String url = urlBuilder.inspectContainer(containerId); + String response = delegate.get(url, HTTP_OK); JSONObject json = new JSONObject(response); return extractPorts(json); - } catch (HttpRequestException e) { - throw new DockerAccessException("Unable to query port mappings for container [%s]", containerId); + } catch (IOException e) { + throw new DockerAccessException("Unable to query port mappings for container [%s]", + containerId); } } @Override public void getLogSync(String containerId, LogCallback callback) { - LogRequestor extractor = new LogRequestor(delegate.getHttpClient(), urlBuilder, containerId, callback); + LogRequestor + extractor = + new LogRequestor(delegate.getHttpClient(), urlBuilder, containerId, callback); extractor.fetchLogs(); } @Override public LogGetHandle getLogAsync(String containerId, LogCallback callback) { - LogRequestor extractor = new LogRequestor(delegate.getHttpClient(), urlBuilder, containerId, callback); + LogRequestor + extractor = + new LogRequestor(delegate.getHttpClient(), urlBuilder, containerId, callback); extractor.start(); return extractor; } - + @Override public Container inspectContainer(String containerId) throws DockerAccessException { try { - String response = get(urlBuilder.inspectContainer(containerId), HTTP_OK).getMessage(); + String url = urlBuilder.inspectContainer(containerId); + String response = delegate.get(url, HTTP_OK); return new ContainerDetails(new JSONObject(response)); - } catch (HttpRequestException e) { + } catch (IOException e) { log.error(e.getMessage()); throw new DockerAccessException("Unable to retrieve container name for [%s]", containerId); } - } - + } + @Override - public List listContainers(ListArg... args) throws DockerAccessException { - DockerUrl url = buildDockerUrl(urlBuilder.listContainers(), args); + public List listContainers(int limit) throws DockerAccessException { + String url = urlBuilder.listContainers(limit); try { - String response = get(url, HTTP_OK).getMessage(); + String response = delegate.get(url, HTTP_OK); JSONArray array = new JSONArray(response); List containers = new ArrayList<>(array.length()); - + for (int i = 0; i < array.length(); i++) { containers.add(new ContainersListElement(array.getJSONObject(i))); } - + return containers; - } - catch (HttpRequestException e) { + } catch (IOException e) { throw new DockerAccessException(e.getMessage()); } } - - @Override - public List listImages(ListArg... args) throws DockerAccessException { - DockerUrl url = buildDockerUrl(urlBuilder.listImages(), args); + public boolean hasImage(String name) throws DockerAccessException { + String url = urlBuilder.inspectImage(name); try { - String response = get(url, HTTP_OK).getMessage(); - JSONArray array = new JSONArray(response); - List images = new ArrayList<>(array.length()); - - for (int i = 0; i < array.length(); i++) { - images.add(new Image(array.getJSONObject(i))); - } - - return images; + return delegate.get(url, new StatusCodeResponseHandler(), HTTP_OK, HTTP_NOT_FOUND) == HTTP_OK; + } catch (IOException e) { + log.error(e.getMessage()); + throw new DockerAccessException("Unable to check image [%s]", name); } - catch (HttpRequestException e) { + } + + @Override + public String getImageId(String name) throws DockerAccessException { + HttpBodyAndStatus response = inspectImage(name); + if (response.getStatusCode() == HTTP_NOT_FOUND) { + return null; + } + JSONObject imageDetails = new JSONObject(response.getBody()); + return imageDetails.getString("Id"); + } + + private HttpBodyAndStatus inspectImage(String name) throws DockerAccessException { + String url = urlBuilder.inspectImage(name); + try { + return delegate.get(url, new BodyAndStatusResponseHandler(), HTTP_OK, HTTP_NOT_FOUND); + } catch (IOException e) { log.error(e.getMessage()); - throw new DockerAccessException("Unable to list images"); + throw new DockerAccessException("Unable to inspect image [%s]", name); } - } + } @Override - public void removeContainer(String containerId, boolean removeVolumes) throws DockerAccessException { + public void removeContainer(String containerId, boolean removeVolumes) + throws DockerAccessException { try { - delete(urlBuilder.removeContainer(containerId, removeVolumes), HTTP_NO_CONTENT); - } catch (HttpRequestException e) { + String url = urlBuilder.removeContainer(containerId, removeVolumes); + delegate.delete(url, HTTP_NO_CONTENT); + } catch (IOException e) { log.error(e.getMessage()); throw new DockerAccessException("Unable to remove container [%s]", containerId); } } @Override - public void pullImage(String image, AuthConfig authConfig, String registry) throws DockerAccessException { + public void pullImage(String image, AuthConfig authConfig, String registry) + throws DockerAccessException { ImageName name = new ImageName(image); String pullUrl = urlBuilder.pullImage(name, registry); try { - Result result = post(pullUrl, null, authConfig, HTTP_OK); - processChunkedResponse(result, createPullOrPushResponseHandler()); - } catch (HttpRequestException e) { + delegate.post(pullUrl, null, createAuthHeader(authConfig), + createPullOrPushResponseHandler(), HTTP_OK); + } catch (IOException e) { log.error(e.getMessage()); throw new DockerAccessException("Unable to pull \"" + image + "\"" + - (registry != null ? " from registry \"" + registry + "\"" : "") + - " : " + e); + (registry != null ? " from registry \"" + registry + "\"" + : "") + + " : " + e); } } @Override - public void pushImage(String image, AuthConfig authConfig, String registry) throws DockerAccessException { + public void pushImage(String image, AuthConfig authConfig, String registry) + throws DockerAccessException { ImageName name = new ImageName(image); String pushUrl = urlBuilder.pushImage(name, registry); String temporaryImage = tagTemporaryImage(name, registry); try { - Result result = post(pushUrl, null, authConfig, HTTP_OK); - processChunkedResponse(result, createPullOrPushResponseHandler()); - } catch (HttpRequestException e) { + delegate.post(pushUrl, null, createAuthHeader(authConfig), + createPullOrPushResponseHandler(), HTTP_OK); + } catch (IOException e) { log.error(e.getMessage()); - throw new DockerAccessException(e,"Unable to push \"" + image + "\"" + - (registry != null ? " to registry \"" + registry + "\"" : "") + - " : " + e); + throw new DockerAccessException(e, "Unable to push \"" + image + "\"" + + (registry != null ? " to registry \"" + registry + "\"" + : "") + + " : " + e); } finally { if (temporaryImage != null) { removeImage(temporaryImage); @@ -239,28 +272,32 @@ public void pushImage(String image, AuthConfig authConfig, String registry) thro } @Override - public void tag(String sourceImage, String targetImage, boolean force) throws DockerAccessException { + public void tag(String sourceImage, String targetImage, boolean force) + throws DockerAccessException { ImageName source = new ImageName(sourceImage); ImageName target = new ImageName(targetImage); try { - post(urlBuilder.tagContainer(source, target, force), null, HTTP_CREATED); - } catch (HttpRequestException e) { + String url = urlBuilder.tagContainer(source, target, force); + delegate.post(url, null, HTTP_CREATED); + } catch (IOException e) { log.error(e.getMessage()); - throw new DockerAccessException("Unable to add tag [%s] to image [%s]", targetImage, sourceImage); + throw new DockerAccessException("Unable to add tag [%s] to image [%s]", targetImage, + sourceImage); } } @Override - public boolean removeImage(String image, boolean ... forceOpt) throws DockerAccessException { + public boolean removeImage(String image, boolean... forceOpt) throws DockerAccessException { boolean force = forceOpt != null && forceOpt.length > 0 && forceOpt[0]; try { - Result result = delete(urlBuilder.deleteImage(image, force), HTTP_OK, HTTP_NOT_FOUND); + String url = urlBuilder.deleteImage(image, force); + HttpBodyAndStatus response = delegate.delete(url, new BodyAndStatusResponseHandler(),HTTP_OK, HTTP_NOT_FOUND); if (log.isDebugEnabled()) { - logRemoveResponse(new JSONArray(result.getMessage())); + logRemoveResponse(new JSONArray(response.getBody())); } - return result.getCode() == HTTP_OK; - } catch (HttpRequestException e) { + return response.getStatusCode() == HTTP_OK; + } catch (IOException e) { log.error(e.getMessage()); throw new DockerAccessException("Unable to remove image [%s]", image); } @@ -269,19 +306,42 @@ public boolean removeImage(String image, boolean ... forceOpt) throws DockerAcce // --------------- // Lifecycle methods not needed here @Override - public void start() {} + public void start() { + } @Override - public void shutdown() {} + public void shutdown() { + } // visible for testing? - private BuildResponseHandler createBuildResponseHandler() { - return new BuildResponseHandler(log); + private HcChunckedResponseHandlerWrapper createBuildResponseHandler() { + return new HcChunckedResponseHandlerWrapper(log, new BuildResponseHandler(log)); + } + + private static class HcChunckedResponseHandlerWrapper implements ResponseHandler { + + private ChunkedResponseHandler handler; + private Logger log; + + public HcChunckedResponseHandlerWrapper(Logger log, + ChunkedResponseHandler handler) { + this.log = log; + this.handler = handler; + } + + @Override + public Object handleResponse(HttpResponse response) throws IOException { + try (InputStream stream = response.getEntity().getContent()) { + // Parse text as json + new ChunkedResponseReader(stream, new TextToJsonBridgeCallback(log, handler)).process(); + } + return null; + } } // visible for testing? - private PullOrPushResponseHandler createPullOrPushResponseHandler() { - return new PullOrPushResponseHandler(log); + private HcChunckedResponseHandlerWrapper createPullOrPushResponseHandler() { + return new HcChunckedResponseHandlerWrapper(log, new PullOrPushResponseHandler(log)); } private Map createAuthHeader(AuthConfig authConfig) { @@ -293,8 +353,9 @@ private Map createAuthHeader(AuthConfig authConfig) { private String tagTemporaryImage(ImageName name, String registry) throws DockerAccessException { String targetImage = name.getFullName(registry); - if (!name.hasRegistry() && registry != null && !isDefaultRegistry(registry) && !hasImage(targetImage)) { - tag(name.getFullName(null), targetImage,false); + if (!name.hasRegistry() && registry != null && !isDefaultRegistry(registry) && !hasImage( + targetImage)) { + tag(name.getFullName(null), targetImage, false); return targetImage; } return null; @@ -306,51 +367,9 @@ private boolean isDefaultRegistry(String registry) { "registry.hub.docker.com".equalsIgnoreCase(registry); } - // =========================================================================================================== - - private Result delete(String url, int statusCode, int... additional) throws HttpRequestException, DockerAccessException { - try { - return delegate.delete(url, statusCode, additional); - } catch (IOException e) { - throw new DockerAccessException(e, "Communication error with the docker daemon"); - } - } - - private Result get(DockerUrl url, int statusCode, int... additional) throws HttpRequestException, DockerAccessException { - try { - return delegate.get(url.toString(), statusCode, additional); - } catch (IOException e) { - throw new DockerAccessException(e, "Communication error with the docker daemon"); - } - } - - private Result post(String url, Object body, AuthConfig authConfig, int statusCode) throws HttpRequestException, DockerAccessException { - try { - return delegate.post(url, body, createAuthHeader(authConfig), statusCode); - } catch (IOException e) { - throw new DockerAccessException(e, "communication error occurred with the docker daemon"); - } - } - - private Result post(String url, Object body, int statusCode, int... additional) throws HttpRequestException, DockerAccessException { - try { - return delegate.post(url, body, statusCode, additional); - } catch (IOException e) { - throw new DockerAccessException(e, "communication error occurred with the docker daemon"); - } - } - // =========================================================================================================== // Preparation for performing requests - private DockerUrl buildDockerUrl(DockerUrl url, ListArg... args) { - for (ListArg arg : args) { - url.addQueryParam(arg.getKey(), arg.getValue()); - } - - return url; - } - private Map extractPorts(JSONObject info) { JSONObject networkSettings = info.getJSONObject("NetworkSettings"); if (networkSettings != null) { @@ -374,7 +393,8 @@ private Map createPortMapping(JSONObject ports) { return portMapping; } - private void parseHostSpecsAndUpdateMapping(Map portMapping, JSONArray hostSpecs, String portSpec) { + private void parseHostSpecsAndUpdateMapping(Map portMapping, JSONArray hostSpecs, + String portSpec) { if (hostSpecs != null && hostSpecs.length() > 0) { // We take only the first JSONObject hostSpec = hostSpecs.getJSONObject(0); @@ -385,7 +405,8 @@ private void parseHostSpecsAndUpdateMapping(Map portMapping, JS } } - private void parsePortSpecAndUpdateMapping(Map portMapping, Object hostPort, String portSpec) { + private void parsePortSpecAndUpdateMapping(Map portMapping, Object hostPort, + String portSpec) { try { Integer hostP = (Integer.parseInt(hostPort.toString())); int idx = portSpec.indexOf('/'); @@ -401,17 +422,8 @@ private void parsePortSpecAndUpdateMapping(Map portMapping, Obj portMapping.put(Integer.parseInt(portSpec) + "/tcp", hostP); } } catch (NumberFormatException exp) { - log.warn("Cannot parse " + hostPort + " or " + portSpec + " as a port number. Ignoring in mapping"); - } - } - - private void processChunkedResponse(Result result, ChunkedResponseHandler handler) throws DockerAccessException { - try (InputStream stream = result.getInputStream()) { - // Parse text as json - new ChunkedResponseReader(stream, new TextToJsonBridgeCallback(log, handler)).process(); - } - catch (IOException e) { - throw new DockerAccessException(e, "Cannot process chunk response: " + e); + log.warn("Cannot parse " + hostPort + " or " + portSpec + + " as a port number. Ignoring in mapping"); } } @@ -437,9 +449,6 @@ private void logRemoveResponse(JSONArray logElements) { private boolean isSSL(String url) { return url != null && url.toLowerCase().startsWith("https"); - } - - private boolean hasImage(String image) throws DockerAccessException { - return !listImages(ListArg.filter(image)).isEmpty(); } + } diff --git a/src/main/java/org/jolokia/docker/maven/access/hc/http/HttpRequestException.java b/src/main/java/org/jolokia/docker/maven/access/hc/http/HttpRequestException.java index 9a123a761..c5ef74817 100644 --- a/src/main/java/org/jolokia/docker/maven/access/hc/http/HttpRequestException.java +++ b/src/main/java/org/jolokia/docker/maven/access/hc/http/HttpRequestException.java @@ -1,6 +1,8 @@ package org.jolokia.docker.maven.access.hc.http; -public class HttpRequestException extends Exception { +import java.io.IOException; + +public class HttpRequestException extends IOException { public HttpRequestException(String message) { super(message); diff --git a/src/main/java/org/jolokia/docker/maven/assembly/DockerFileBuilder.java b/src/main/java/org/jolokia/docker/maven/assembly/DockerFileBuilder.java index 948a20c59..705c757b0 100644 --- a/src/main/java/org/jolokia/docker/maven/assembly/DockerFileBuilder.java +++ b/src/main/java/org/jolokia/docker/maven/assembly/DockerFileBuilder.java @@ -93,8 +93,8 @@ public String content() throws IllegalArgumentException { addVolumes(b); addEntries(b); - addRun(b); addWorkdir(b); + addRun(b); addCmd(b); addEntryPoint(b); diff --git a/src/main/java/org/jolokia/docker/maven/config/BuildImageConfiguration.java b/src/main/java/org/jolokia/docker/maven/config/BuildImageConfiguration.java index db7d91d6b..c4b18d1cf 100644 --- a/src/main/java/org/jolokia/docker/maven/config/BuildImageConfiguration.java +++ b/src/main/java/org/jolokia/docker/maven/config/BuildImageConfiguration.java @@ -37,6 +37,10 @@ public class BuildImageConfiguration { */ private List runCmds; + /** + * @parameter default-value="false" + */ + private boolean cleanup = false; /** * @paramter @@ -84,6 +88,11 @@ public class BuildImageConfiguration { */ private AssemblyConfiguration assembly; + /** + * @parameter + */ + private boolean skip = false; + public BuildImageConfiguration() {} public String getFrom() { @@ -134,6 +143,14 @@ public Arguments getCmd() { public String getCommand() { return command; } + + public boolean cleanup() { + return cleanup; + } + + public boolean skip() { + return skip; + } public Arguments getEntryPoint() { return entryPoint; @@ -178,7 +195,7 @@ public Builder ports(List ports) { public Builder runCmds(List theCmds) { if (config.runCmds == null) { - config.runCmds = new ArrayList(); + config.runCmds = new ArrayList<>(); } else config.runCmds = theCmds; @@ -212,6 +229,13 @@ public Builder cmd(String cmd) { config.cmd.setShell(cmd); return this; } + + public Builder cleanup(String cleanup) { + if (cleanup != null) { + config.cleanup = Boolean.valueOf(cleanup); + } + return this; + } public Builder entryPoint(String entryPoint) { if (config.entryPoint == null) { @@ -220,6 +244,13 @@ public Builder entryPoint(String entryPoint) { config.entryPoint.setShell(entryPoint); return this; } + + public Builder skip(String skip) { + if (skip != null) { + config.skip = Boolean.valueOf(skip); + } + return this; + } public BuildImageConfiguration build() { return config; diff --git a/src/main/java/org/jolokia/docker/maven/config/ImageConfiguration.java b/src/main/java/org/jolokia/docker/maven/config/ImageConfiguration.java index 7f9b6365a..0ba38637a 100644 --- a/src/main/java/org/jolokia/docker/maven/config/ImageConfiguration.java +++ b/src/main/java/org/jolokia/docker/maven/config/ImageConfiguration.java @@ -46,22 +46,10 @@ public class ImageConfiguration implements StartOrderResolver.Resolvable { * @parameter */ private String registry; - + // Used for injection public ImageConfiguration() {} - - // For builder - private ImageConfiguration(String name, String alias, - RunImageConfiguration run, BuildImageConfiguration build,WatchImageConfiguration watch, - Map external) { - this.name = name; - this.alias = alias; - this.run = run; - this.build = build; - this.watch = watch; - this.external = external; - } - + @Override public String getName() { return name; @@ -123,10 +111,9 @@ public boolean isDataImage() { // is a data image or not on its own. return getRunConfiguration() == null; } - + public String getDescription() { - return "[" + name + "]" + - (alias != null ? " \"" + alias + "\"" : ""); + return String.format("[%s] %s", name, (alias != null ? "\"" + alias + "\"" : "")); } public String getRegistry() { @@ -135,55 +122,46 @@ public String getRegistry() { @Override public String toString() { - return "ImageConfiguration{" + - "name='" + name + '\'' + - ", alias='" + alias + '\'' + - '}'; + return String.format("ImageConfiguration {name='%s', alias='%s'}", name, alias); } // ========================================================================= // Builder for image configurations public static class Builder { - - String name,alias; - RunImageConfiguration runConfig; - BuildImageConfiguration buildConfig; - WatchImageConfiguration watchConfig; - - Map externalConfig; + private ImageConfiguration config = new ImageConfiguration(); public Builder name(String name) { - this.name = name; + config.name = name; return this; } public Builder alias(String alias) { - this.alias = alias; + config.alias = alias; return this; } public Builder runConfig(RunImageConfiguration runConfig) { - this.runConfig = runConfig; + config.run = runConfig; return this; } public Builder buildConfig(BuildImageConfiguration buildConfig) { - this.buildConfig = buildConfig; + config.build = buildConfig; return this; } public Builder externalConfig(Map externalConfig) { - this.externalConfig = externalConfig; + config.external = externalConfig; return this; } - + public ImageConfiguration build() { - return new ImageConfiguration(name,alias,runConfig,buildConfig,watchConfig, externalConfig); + return config; } public Builder watchConfig(WatchImageConfiguration watchConfig) { - this.watchConfig = watchConfig; + config.watch = watchConfig; return this; } } diff --git a/src/main/java/org/jolokia/docker/maven/config/RunImageConfiguration.java b/src/main/java/org/jolokia/docker/maven/config/RunImageConfiguration.java index 28d378eca..5ce8aa8fb 100644 --- a/src/main/java/org/jolokia/docker/maven/config/RunImageConfiguration.java +++ b/src/main/java/org/jolokia/docker/maven/config/RunImageConfiguration.java @@ -152,12 +152,17 @@ public enum NamingStrategy { * @parameter */ private LogConfiguration log; - + /** * @parameter */ private RestartPolicy restartPolicy; + /** + * @parameter + */ + private boolean skip = false; + public RunImageConfiguration() { } public Map getEnv() { @@ -259,6 +264,10 @@ public Boolean getPrivileged() { public RestartPolicy getRestartPolicy() { return (restartPolicy == null) ? RestartPolicy.DEFAULT : restartPolicy; } + + public boolean skip() { + return skip; + } // ====================================================================================== @@ -392,6 +401,13 @@ public Builder restartPolicy(RestartPolicy restartPolicy) { return this; } + public Builder skip(String skip) { + if (skip != null) { + config.skip = Boolean.valueOf(skip); + } + return this; + } + public RunImageConfiguration build() { return config; } diff --git a/src/main/java/org/jolokia/docker/maven/config/handler/property/ConfigKey.java b/src/main/java/org/jolokia/docker/maven/config/handler/property/ConfigKey.java index 25673a27d..1ab10420c 100644 --- a/src/main/java/org/jolokia/docker/maven/config/handler/property/ConfigKey.java +++ b/src/main/java/org/jolokia/docker/maven/config/handler/property/ConfigKey.java @@ -35,6 +35,7 @@ public enum ConfigKey { BIND, CAP_ADD, CAP_DROP, + CLEANUP, CMD, DOMAINNAME, DNS, @@ -58,6 +59,8 @@ public enum ConfigKey { REGISTRY, RESTART_POLICY_NAME("restartPolicy.name"), RESTART_POLICY_RETRY("restartPolicy.retry"), + SKIP_BUILD("skip.build"), + SKIP_RUN("skip.run"), USER, VOLUMES, TAGS, diff --git a/src/main/java/org/jolokia/docker/maven/config/handler/property/PropertyConfigHandler.java b/src/main/java/org/jolokia/docker/maven/config/handler/property/PropertyConfigHandler.java index f73e548b6..76f90e34f 100644 --- a/src/main/java/org/jolokia/docker/maven/config/handler/property/PropertyConfigHandler.java +++ b/src/main/java/org/jolokia/docker/maven/config/handler/property/PropertyConfigHandler.java @@ -59,6 +59,7 @@ public List resolve(ImageConfiguration config, Properties pr private BuildImageConfiguration extractBuildConfiguration(String prefix, Properties properties) { return new BuildImageConfiguration.Builder() .cmd(withPrefix(prefix, CMD, properties)) + .cleanup(withPrefix(prefix, CLEANUP, properties)) .entryPoint(withPrefix(prefix, ENTRYPOINT, properties)) .assembly(extractAssembly(prefix, properties)) .env(mapWithPrefix(prefix, ENV, properties)) @@ -71,6 +72,7 @@ private BuildImageConfiguration extractBuildConfiguration(String prefix, Propert .tags(listWithPrefix(prefix, TAGS, properties)) .maintainer(withPrefix(prefix, MAINTAINER, properties)) .workdir(withPrefix(prefix, WORKDIR, properties)) + .skip(withPrefix(prefix, ConfigKey.SKIP_BUILD, properties)) .build(); } @@ -101,6 +103,7 @@ private RunImageConfiguration extractRunConfiguration(String prefix, Properties .workingDir(withPrefix(prefix, WORKING_DIR, properties)) .wait(extractWaitConfig(prefix, properties)) .volumes(extractVolumeConfig(prefix, properties)) + .skip(withPrefix(prefix, ConfigKey.SKIP_RUN, properties)) .build(); } diff --git a/src/main/java/org/jolokia/docker/maven/model/ContainerDetails.java b/src/main/java/org/jolokia/docker/maven/model/ContainerDetails.java index 8eb2296a1..e8e95148a 100644 --- a/src/main/java/org/jolokia/docker/maven/model/ContainerDetails.java +++ b/src/main/java/org/jolokia/docker/maven/model/ContainerDetails.java @@ -14,6 +14,7 @@ public ContainerDetails(JSONObject json) { this.json = json; } + @Override public String getName() { String name = json.getString("Name"); @@ -32,7 +33,8 @@ public long getCreated() { @Override public String getId() { - return json.getString("Id"); + // only need first 12 to id a container + return json.getString("Id").substring(0, 12); } @Override @@ -41,6 +43,7 @@ public String getImage() { return json.getJSONObject("Config").getString("Image"); } + @Override public boolean isRunning() { JSONObject state = json.getJSONObject("State"); return state.getBoolean("Running"); diff --git a/src/main/java/org/jolokia/docker/maven/model/ContainersListElement.java b/src/main/java/org/jolokia/docker/maven/model/ContainersListElement.java index 65dd2108b..51314741e 100644 --- a/src/main/java/org/jolokia/docker/maven/model/ContainersListElement.java +++ b/src/main/java/org/jolokia/docker/maven/model/ContainersListElement.java @@ -11,6 +11,7 @@ public ContainersListElement(JSONObject json) { this.json = json; } + @Override public String getName() { if (json.has("Names")) { JSONArray names = json.getJSONArray("Names"); @@ -30,12 +31,15 @@ public String getName() { } } + @Override public long getCreated() { return json.getLong("Created"); } + @Override public String getId() { - return json.getString("Id"); + // only need first 12 to id a container + return json.getString("Id").substring(0, 12); } @Override @@ -43,6 +47,7 @@ public String getImage() { return json.getString("Image"); } + @Override public boolean isRunning() { String status = json.getString("Status"); return status.toLowerCase().contains("up"); diff --git a/src/main/java/org/jolokia/docker/maven/model/Image.java b/src/main/java/org/jolokia/docker/maven/model/Image.java deleted file mode 100644 index 548af8e65..000000000 --- a/src/main/java/org/jolokia/docker/maven/model/Image.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.jolokia.docker.maven.model; - -import java.util.ArrayList; -import java.util.List; - -import edu.emory.mathcs.backport.java.util.Collections; -import org.json.JSONArray; -import org.json.JSONObject; - -/** - * Wrapper around a JSON return object representing an image - * - */ -public class Image { - - private final JSONObject json; - - /** - * Constructor wrapping the JSON image object - * - * @param json initial json response - */ - public Image(JSONObject json) { - this.json = json; - } - - /** - * - * @return the image id - */ - public String getId() { - return json.getString("Id"); - } - - /** - * Get all repo tags as an unmodifiable list - * - * @return repo tags - */ - public List getRepoTags() { - JSONArray array = json.getJSONArray("RepoTags"); - List tags = new ArrayList<>(array.length()); - - for (int i = 0; i < array.length(); i++) { - tags.add(array.getString(i)); - } - - return Collections.unmodifiableList(tags); - } - - -} diff --git a/src/main/java/org/jolokia/docker/maven/service/BuildService.java b/src/main/java/org/jolokia/docker/maven/service/BuildService.java new file mode 100644 index 000000000..71472ff61 --- /dev/null +++ b/src/main/java/org/jolokia/docker/maven/service/BuildService.java @@ -0,0 +1,88 @@ +package org.jolokia.docker.maven.service; + +import java.io.File; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.assembly.InvalidAssemblerConfigurationException; +import org.apache.maven.plugin.assembly.archive.ArchiveCreationException; +import org.apache.maven.plugin.assembly.format.AssemblyFormattingException; +import org.jolokia.docker.maven.access.DockerAccess; +import org.jolokia.docker.maven.access.DockerAccessException; +import org.jolokia.docker.maven.assembly.AssemblyFiles; +import org.jolokia.docker.maven.assembly.DockerAssemblyManager; +import org.jolokia.docker.maven.config.BuildImageConfiguration; +import org.jolokia.docker.maven.config.ImageConfiguration; +import org.jolokia.docker.maven.util.Logger; +import org.jolokia.docker.maven.util.MojoParameters; + +public class BuildService { + + private final DockerAccess docker; + private final QueryService queryService; + private final Logger log; + + private DockerAssemblyManager dockerAssemblyManager; + + BuildService(DockerAccess docker, QueryService queryService, DockerAssemblyManager dockerAssemblyManager, Logger log) { + this.docker = docker; + this.queryService = queryService; + this.dockerAssemblyManager = dockerAssemblyManager; + this.log = log; + } + + /** + * Build an image + * + * @param imageConfig the image configuration + * @param params mojo params for the project + * @throws DockerAccessException + * @throws MojoExecutionException + */ + public void buildImage(ImageConfiguration imageConfig, MojoParameters params) + throws DockerAccessException, MojoExecutionException { + + String imageName = imageConfig.getName(); + BuildImageConfiguration buildConfig = imageConfig.getBuildConfiguration(); + + String oldImageId = null; + + if (buildConfig.cleanup()) { + oldImageId = queryService.getImageId(imageName); + } + + // auto is now supported by docker, consider switching? + String newImageId = buildImage(imageName, buildConfig, params); + log.info(imageConfig.getDescription() + ": Built image " + newImageId); + + if (removeOldImage(oldImageId, newImageId)) { + docker.removeImage(oldImageId); + log.info(imageConfig.getDescription() + ": Removed image " + oldImageId); + } + } + + public AssemblyFiles getAssemblyFiles(String name, ImageConfiguration imageConfig, MojoParameters mojoParameters) + throws MojoExecutionException { + + try { + return dockerAssemblyManager.getAssemblyFiles(name, imageConfig.getBuildConfiguration(), mojoParameters); + } catch (InvalidAssemblerConfigurationException | ArchiveCreationException | AssemblyFormattingException e) { + throw new MojoExecutionException("Cannot extract assembly files for image " + name + ": " + e, e); + } + } + + private String buildImage(String imageName, BuildImageConfiguration buildConfig, MojoParameters mojoParameters) + throws DockerAccessException, MojoExecutionException { + + File dockerArchive = createArchive(imageName, buildConfig, mojoParameters); + docker.buildImage(imageName, dockerArchive, buildConfig.cleanup()); + return queryService.getImageId(imageName); + } + + private File createArchive(String imageName, BuildImageConfiguration buildConfig, MojoParameters params) throws MojoExecutionException { + return dockerAssemblyManager.createDockerTarArchive(imageName, params, buildConfig); + } + + private boolean removeOldImage(String oldImageId, String newImageId) { + return oldImageId != null && !oldImageId.equals(newImageId); + } +} diff --git a/src/main/java/org/jolokia/docker/maven/service/QueryService.java b/src/main/java/org/jolokia/docker/maven/service/QueryService.java index 4d0285cf6..aac4330d2 100644 --- a/src/main/java/org/jolokia/docker/maven/service/QueryService.java +++ b/src/main/java/org/jolokia/docker/maven/service/QueryService.java @@ -5,11 +5,10 @@ import org.apache.maven.plugin.MojoExecutionException; import org.jolokia.docker.maven.access.DockerAccess; -import org.jolokia.docker.maven.access.DockerAccess.ListArg; import org.jolokia.docker.maven.access.DockerAccessException; import org.jolokia.docker.maven.model.Container; -import org.jolokia.docker.maven.model.Image; -import org.jolokia.docker.maven.util.*; +import org.jolokia.docker.maven.util.AutoPullMode; +import org.jolokia.docker.maven.util.Logger; /** * Query service for getting image and container information from the docker dameon @@ -18,7 +17,7 @@ public class QueryService { // Default limit when listing containers - private static final ListArg CONTAINER_LIMIT = ListArg.limit(100); + private static final int CONTAINER_LIMIT = 100; // Access to docker daemon & logger private DockerAccess docker; @@ -90,27 +89,15 @@ public List getContainersForImage(final String image) throws DockerAc return ret; } - /** - * Get an image with the given name. - * - * @param image image name to lookup - * @return the image found - * @throws DockerAccessException - */ - public Image getImage(String image) throws DockerAccessException { - String name = new ImageName(image).getNameWithoutTag(); - return docker.listImages(ListArg.filter(name)).get(0); - } - /** * Finds the id of an image. * - * @param image name of the image. + * @param imageName name of the image. * @return the id of the image * @throws DockerAccessException if the request fails */ - public String getImageId(String image) throws DockerAccessException { - return getImage(image).getId(); + public String getImageId(String imageName) throws DockerAccessException { + return docker.getImageId(imageName); } /** @@ -157,17 +144,7 @@ public boolean hasContainer(String containerName) throws DockerAccessException { * @throws DockerAccessException if the request fails */ public boolean hasImage(String name) throws DockerAccessException { - ImageName imageName = new ImageName(name); - - String fullName = imageName.getFullName(); - String nameWithoutTag = imageName.getNameWithoutTag(); - - for (Image image : docker.listImages(ListArg.filter(nameWithoutTag))) { - if (image.getRepoTags().contains(fullName)) { - return true; - } - } - return false; + return docker.hasImage(name); } /** @@ -194,7 +171,7 @@ public boolean imageRequiresAutoPull(String mode, String imageName, boolean alwa } else { if (!hasImage(imageName)) { throw new MojoExecutionException( - String.format("No image '%s' found, Please enable 'autoPull' or pull image '%s'yourself (docker pull %s)", + String.format("No image '%s' found, Please enable 'autoPull' or pull image '%s' yourself (docker pull %s)", imageName, imageName, imageName)); } return false; @@ -208,6 +185,6 @@ private boolean alwaysPull(AutoPullMode autoPullMode, boolean always) { // Check if an image is not loaded but should be pulled private boolean pullIfNotPresent(AutoPullMode autoPull, String name) throws DockerAccessException { - return autoPull.doPullIfNotPresent() & !hasImage(name); + return autoPull.doPullIfNotPresent() && !hasImage(name); } } diff --git a/src/main/java/org/jolokia/docker/maven/service/RunService.java b/src/main/java/org/jolokia/docker/maven/service/RunService.java index 84d3f5a58..50cc9181e 100644 --- a/src/main/java/org/jolokia/docker/maven/service/RunService.java +++ b/src/main/java/org/jolokia/docker/maven/service/RunService.java @@ -180,7 +180,11 @@ public void run() { private List convertToResolvables(List images) { List ret = new ArrayList<>(); for (ImageConfiguration config : images) { - ret.add(config); + if (config.getRunConfiguration().skip()) { + log.info(config.getDescription() + ": Skipped running"); + } else { + ret.add(config); + } } return ret; } @@ -291,7 +295,7 @@ private String findContainerId(String imageNameOrAlias, boolean checkAllContaine } private void startContainer(ImageConfiguration imageConfig, String id) throws DockerAccessException { - log.info(imageConfig.getDescription() + ": Start container " + id.substring(0, 12)); + log.info(imageConfig.getDescription() + ": Start container " + id); docker.startContainer(id); tracker.registerShutdownAction(id, imageConfig); } diff --git a/src/main/java/org/jolokia/docker/maven/service/ServiceHub.java b/src/main/java/org/jolokia/docker/maven/service/ServiceHub.java index 80da21844..3b5e09998 100644 --- a/src/main/java/org/jolokia/docker/maven/service/ServiceHub.java +++ b/src/main/java/org/jolokia/docker/maven/service/ServiceHub.java @@ -4,6 +4,7 @@ import org.apache.maven.plugin.BuildPluginManager; import org.apache.maven.project.MavenProject; import org.jolokia.docker.maven.access.DockerAccess; +import org.jolokia.docker.maven.assembly.DockerAssemblyManager; import org.jolokia.docker.maven.util.Logger; /** @@ -18,16 +19,20 @@ public class ServiceHub { /** @component **/ protected BuildPluginManager pluginManager; + /** @component */ + protected DockerAssemblyManager dockerAssemblyManager; + /** @component */ protected MavenProject project; /** @component */ protected MavenSession session; - // Services managed by thus hub + // Services managed by this hub private MojoExecutionService mojoExecutionService; private QueryService queryService; private RunService runService; + private BuildService buildService; // initialization flag preventing multiple initializations private boolean initDone = false; @@ -44,6 +49,7 @@ public synchronized void init(DockerAccess dockerAccess, Logger log) { mojoExecutionService = new MojoExecutionService(project, session, pluginManager); queryService = new QueryService(dockerAccess, log); runService = new RunService(dockerAccess, queryService, containerTracker, log); + buildService = new BuildService(dockerAccess, queryService, dockerAssemblyManager, log); initDone = true; } } @@ -58,6 +64,11 @@ public MojoExecutionService getMojoExecutionService() { return mojoExecutionService; } + public BuildService getBuildService() { + checkInitialization(); + return buildService; + } + /** * Get the query service for obtaining information about containers and images * diff --git a/src/main/java/org/jolokia/docker/maven/util/AuthConfigFactory.java b/src/main/java/org/jolokia/docker/maven/util/AuthConfigFactory.java index 92366d4cd..1ff528cbf 100644 --- a/src/main/java/org/jolokia/docker/maven/util/AuthConfigFactory.java +++ b/src/main/java/org/jolokia/docker/maven/util/AuthConfigFactory.java @@ -72,17 +72,18 @@ public AuthConfigFactory(PlexusContainer container) { public AuthConfig createAuthConfig(Map authConfig, Settings settings, String user, String registry) throws MojoExecutionException { Properties props = System.getProperties(); if (props.containsKey(DOCKER_USERNAME) || props.containsKey(DOCKER_PASSWORD)) { - return getAuthConfigFromProperties(props); + return getAuthConfigFromProperties(props, registry); } if (authConfig != null) { - return getAuthConfigFromPluginConfiguration(authConfig); + return getAuthConfigFromPluginConfiguration(authConfig,registry); } return getAuthConfigFromSettings(settings,user,registry); } // =================================================================================================== - private AuthConfig getAuthConfigFromProperties(Properties props) throws MojoExecutionException { + private AuthConfig getAuthConfigFromProperties(Properties props, String registry) throws + MojoExecutionException { if (!props.containsKey(DOCKER_USERNAME)) { throw new MojoExecutionException("No " + DOCKER_USERNAME + " given when using authentication"); } @@ -92,10 +93,10 @@ private AuthConfig getAuthConfigFromProperties(Properties props) throws MojoExec return new AuthConfig(props.getProperty(DOCKER_USERNAME), decrypt(props.getProperty(DOCKER_PASSWORD)), props.getProperty(DOCKER_EMAIL), - props.getProperty(DOCKER_AUTH)); + props.getProperty(DOCKER_AUTH), registry); } - private AuthConfig getAuthConfigFromPluginConfiguration(Map authConfig) throws MojoExecutionException { + private AuthConfig getAuthConfigFromPluginConfiguration(Map authConfig, String registry) throws MojoExecutionException { for (String key : new String[] { "username", "password"}) { if (!authConfig.containsKey(key)) { throw new MojoExecutionException("No '" + key + "' given while using in configuration"); @@ -103,6 +104,7 @@ private AuthConfig getAuthConfigFromPluginConfiguration(Map authConfig) throws M } Map cloneConfig = new HashMap(authConfig); cloneConfig.put("password",decrypt(cloneConfig.get("password"))); + cloneConfig.put("serveraddress",registry); return new AuthConfig(cloneConfig); } @@ -117,10 +119,10 @@ private AuthConfig getAuthConfigFromSettings(Settings settings, String user, Str } found = checkForServer(server, id, registry, user); if (found != null) { - return createAuthConfigFromServer(found); + return createAuthConfigFromServer(found, registry); } } - return defaultServer != null ? createAuthConfigFromServer(defaultServer) : null; + return defaultServer != null ? createAuthConfigFromServer(defaultServer, registry) : null; } private Server checkForServer(Server server, String id, String registry, String user) { @@ -147,12 +149,13 @@ private String decrypt(String password) throws MojoExecutionException { } } - private AuthConfig createAuthConfigFromServer(Server server) throws MojoExecutionException { + private AuthConfig createAuthConfigFromServer(Server server, String registry) throws MojoExecutionException { return new AuthConfig( server.getUsername(), decrypt(server.getPassword()), extractFromServerConfiguration(server.getConfiguration(), "email"), - extractFromServerConfiguration(server.getConfiguration(), "auth") + extractFromServerConfiguration(server.getConfiguration(), "auth"), + registry ); } diff --git a/src/main/resources/META-INF/plexus/components.xml b/src/main/resources/META-INF/plexus/components.xml index 79894861f..d03e867c4 100644 --- a/src/main/resources/META-INF/plexus/components.xml +++ b/src/main/resources/META-INF/plexus/components.xml @@ -24,12 +24,6 @@ org.jolokia.docker.maven.service.ServiceHub org.jolokia.docker.maven.service.ServiceHub singleton - - - - org.jolokia.docker.maven.service.MojoExecutionService - org.jolokia.docker.maven.service.MojoExecutionService - singleton org.apache.maven.execution.MavenSession @@ -40,6 +34,9 @@ org.apache.maven.project.MavenProject + + org.jolokia.docker.maven.assembly.DockerAssemblyManager + diff --git a/src/test/java/integration/DockerAccessIT.java b/src/test/java/integration/DockerAccessIT.java index d52995458..9d8ac71b7 100644 --- a/src/test/java/integration/DockerAccessIT.java +++ b/src/test/java/integration/DockerAccessIT.java @@ -9,7 +9,6 @@ import org.apache.maven.plugin.logging.SystemStreamLog; import org.jolokia.docker.maven.AbstractDockerMojo; import org.jolokia.docker.maven.access.*; -import org.jolokia.docker.maven.access.DockerAccess.ListArg; import org.jolokia.docker.maven.access.hc.DockerAccessWithHcClient; import org.jolokia.docker.maven.util.*; import org.junit.*; @@ -47,7 +46,7 @@ public void setup() throws DockerAccessException { @Test public void testBuildImage() throws DockerAccessException { File file = new File("src/test/resources/integration/busybox-test.tar"); - dockerClient.buildImage(IMAGE_TAG, file); + dockerClient.buildImage(IMAGE_TAG, file, false); assertTrue(hasImage(IMAGE_TAG)); testRemoveImage(IMAGE_TAG); @@ -129,6 +128,6 @@ private void testTagImage() throws DockerAccessException { } private boolean hasImage(String image) throws DockerAccessException { - return !dockerClient.listImages(ListArg.filter(image)).isEmpty(); + return dockerClient.hasImage(image); } } diff --git a/src/test/java/org/jolokia/docker/maven/config/handler/property/PropertyConfigHandlerTest.java b/src/test/java/org/jolokia/docker/maven/config/handler/property/PropertyConfigHandlerTest.java index 0e3bd659c..213374a94 100644 --- a/src/test/java/org/jolokia/docker/maven/config/handler/property/PropertyConfigHandlerTest.java +++ b/src/test/java/org/jolokia/docker/maven/config/handler/property/PropertyConfigHandlerTest.java @@ -40,7 +40,23 @@ public void setUp() throws Exception { configHandler = new PropertyConfigHandler(); imageConfiguration = new ImageConfiguration.Builder().build(); } + + @Test + public void testSkipBuild() { + assertFalse(resolveExternalImageConfig(getSkipTestData(ConfigKey.SKIP_BUILD, false)).getBuildConfiguration().skip()); + assertTrue(resolveExternalImageConfig(getSkipTestData(ConfigKey.SKIP_BUILD, true)).getBuildConfiguration().skip()); + + assertFalse(resolveExternalImageConfig(new String[] { k(NAME), "image"}).getBuildConfiguration().skip()); + } + @Test + public void testSkipRun() { + assertFalse(resolveExternalImageConfig(getSkipTestData(ConfigKey.SKIP_RUN, false)).getRunConfiguration().skip()); + assertTrue(resolveExternalImageConfig(getSkipTestData(ConfigKey.SKIP_RUN, true)).getRunConfiguration().skip()); + + assertFalse(resolveExternalImageConfig(new String[] { k(NAME), "image"}).getRunConfiguration().skip()); + } + @Test public void testType() throws Exception { assertNotNull(configHandler.getType()); @@ -121,6 +137,14 @@ public void testNamingScheme() throws Exception { assertEquals(NamingStrategy.alias, config.getRunConfiguration().getNamingStrategy()); } + @Test + public void testNoCleanup() throws Exception { + String[] testData = new String[] { k(NAME), "image", k(CLEANUP), "false" }; + + ImageConfiguration config = resolveExternalImageConfig(testData); + assertEquals(false, config.getBuildConfiguration().cleanup()); + } + @Test public void testNoAssembly() throws Exception { Properties props = props(k(NAME), "image"); @@ -153,6 +177,7 @@ private ImageConfiguration resolveExternalImageConfig(String[] testData) { } private void validateBuildConfiguration(BuildImageConfiguration buildConfig) { + assertEquals(false, buildConfig.cleanup()); assertEquals("command.sh", buildConfig.getCmd().getShell()); assertEquals("image", buildConfig.getFrom()); assertEquals(a("8080"), buildConfig.getPorts()); @@ -282,6 +307,10 @@ private String[] getTestData() { k(WORKING_DIR), "foo" }; } + + private String[] getSkipTestData(ConfigKey key, boolean value) { + return new String[] { k(NAME), "image", k(key), String.valueOf(value) }; + } private String k(ConfigKey from) { return from.asPropertyKey(); diff --git a/src/test/java/org/jolokia/docker/maven/model/ContainerTest.java b/src/test/java/org/jolokia/docker/maven/model/ContainerTest.java index a6548e1f2..078e13469 100644 --- a/src/test/java/org/jolokia/docker/maven/model/ContainerTest.java +++ b/src/test/java/org/jolokia/docker/maven/model/ContainerTest.java @@ -33,13 +33,13 @@ public class ContainerTest { public void details() throws Exception { JSONObject data = new JSONObject(); data.put("Created", "2015-01-06T15:47:31.485331387Z"); - data.put("Id", "1234AF"); + data.put("Id", "1234AF1234AF"); data.put("Name", "/milkman-kindness"); data.put("Config", new JSONObject("{ 'Image': '9876CE'}")); data.put("State", new JSONObject("{'Running' : true }")); Container cont = new ContainerDetails(data); assertEquals(1420559251485L, cont.getCreated()); - assertEquals("1234AF", cont.getId()); + assertEquals("1234AF1234AF", cont.getId()); assertEquals("milkman-kindness", cont.getName()); assertEquals("9876CE",cont.getImage()); assertTrue(cont.isRunning()); @@ -49,12 +49,12 @@ public void details() throws Exception { public void listElement() throws Exception { JSONObject data = new JSONObject(); data.put("Created",1420559251485L); - data.put("Id", "1234AF"); + data.put("Id", "1234AF1234AF"); data.put("Image", "9876CE"); data.put("Status", "Up 16 seconds"); Container cont = new ContainersListElement(data); assertEquals(1420559251485L, cont.getCreated()); - assertEquals("1234AF", cont.getId()); + assertEquals("1234AF1234AF", cont.getId()); assertEquals("9876CE", cont.getImage()); assertTrue(cont.isRunning()); diff --git a/src/test/java/org/jolokia/docker/maven/service/BuildServiceTest.java b/src/test/java/org/jolokia/docker/maven/service/BuildServiceTest.java new file mode 100644 index 000000000..34cb3cc85 --- /dev/null +++ b/src/test/java/org/jolokia/docker/maven/service/BuildServiceTest.java @@ -0,0 +1,134 @@ +package org.jolokia.docker.maven.service; + +import java.io.File; + +import org.apache.maven.plugin.MojoExecutionException; +import org.jolokia.docker.maven.access.DockerAccess; +import org.jolokia.docker.maven.access.DockerAccessException; +import org.jolokia.docker.maven.assembly.DockerAssemblyManager; +import org.jolokia.docker.maven.config.BuildImageConfiguration; +import org.jolokia.docker.maven.config.ImageConfiguration; +import org.jolokia.docker.maven.util.Logger; +import org.jolokia.docker.maven.util.MojoParameters; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.*; + +public class BuildServiceTest { + + private static final String NEW_IMAGE_ID = "efg789efg789"; + private static final String OLD_IMAGE_ID = "abc123abc123"; + + private BuildService buildService; + + @Mock + private DockerAccess docker; + + @Mock + private DockerAssemblyManager dockerAssemblyManager; + + private ImageConfiguration imageConfig; + + @Mock + private Logger log; + + private String oldImageId; + + @Mock + private MojoParameters params; + + @Mock + private QueryService queryService; + + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + + when(dockerAssemblyManager.createDockerTarArchive(anyString(), any(MojoParameters.class), any(BuildImageConfiguration.class))) + .thenReturn(null); + + buildService = new BuildService(docker, queryService, dockerAssemblyManager, log); + } + + @Test + public void testBuildImageWithCleanup() throws Exception { + givenAnImageConfiguration(true); + givenImageIds(OLD_IMAGE_ID, NEW_IMAGE_ID); + whenBuildImage(true); + thenImageIsBuilt(); + thenOldImageIsRemoved(); + } + + @Test + public void testBuildImageWithNoCleanup() throws Exception { + givenAnImageConfiguration(false); + givenImageIds(OLD_IMAGE_ID, NEW_IMAGE_ID); + whenBuildImage(false); + thenImageIsBuilt(); + thenOldImageIsNotRemoved(); + } + + @Test + public void testCleanupCachedImage() throws Exception { + givenAnImageConfiguration(true); + givenImageIds(OLD_IMAGE_ID, OLD_IMAGE_ID); + whenBuildImage(true); + thenImageIsBuilt(); + thenOldImageIsNotRemoved(); + } + + @Test + public void testCleanupNoExistingImage() throws Exception { + givenAnImageConfiguration(true); + givenImageIds(null, NEW_IMAGE_ID); + whenBuildImage(true); + thenImageIsBuilt(); + thenOldImageIsNotRemoved(); + } + + private void givenAnImageConfiguration(Boolean cleanup) { + BuildImageConfiguration buildConfig = new BuildImageConfiguration.Builder() + .cleanup(cleanup.toString()) + .build(); + + imageConfig = new ImageConfiguration.Builder() + .name("build-image") + .alias("build-alias") + .buildConfig(buildConfig) + .build(); + } + + private void givenImageIds(String oldImageId, String newImageId) throws DockerAccessException { + this.oldImageId = oldImageId; + when(queryService.getImageId(imageConfig.getName())).thenReturn(oldImageId).thenReturn(newImageId); + } + + private void thenImageIsBuilt() throws DockerAccessException { + verify(docker).buildImage(eq(imageConfig.getName()), (File) eq(null), anyBoolean()); + } + + private void thenOldImageIsNotRemoved() throws DockerAccessException { + verify(docker, never()).removeImage(oldImageId); + } + + private void thenOldImageIsRemoved() throws DockerAccessException { + verify(docker).removeImage(oldImageId); + } + + private void whenBuildImage(boolean cleanup) throws DockerAccessException, MojoExecutionException { + doNothing().when(docker).buildImage(eq(imageConfig.getName()), (File) isNull(), anyBoolean()); + + if (cleanup) { + when(docker.removeImage(oldImageId)).thenReturn(true); + } + + buildService.buildImage(imageConfig, params); + } +} diff --git a/src/test/java/org/jolokia/docker/maven/util/AuthConfigTest.java b/src/test/java/org/jolokia/docker/maven/util/AuthConfigTest.java index 64af2af88..88ce16f9e 100644 --- a/src/test/java/org/jolokia/docker/maven/util/AuthConfigTest.java +++ b/src/test/java/org/jolokia/docker/maven/util/AuthConfigTest.java @@ -1,15 +1,15 @@ package org.jolokia.docker.maven.util; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + import java.util.HashMap; import java.util.Map; - import org.apache.commons.codec.binary.Base64; import org.jolokia.docker.maven.access.AuthConfig; import org.json.JSONObject; import org.junit.Test; -import static org.junit.Assert.*; - /** * @author roland * @since 30.07.14 @@ -23,13 +23,15 @@ public void simpleConstructor() { map.put("username","roland"); map.put("password","secret"); map.put("email","roland@jolokia.org"); + map.put("serveraddress","private.registry.com"); AuthConfig config = new AuthConfig(map); check(config); } @Test public void mapConstructor() { - AuthConfig config = new AuthConfig("roland","secret","roland@jolokia.org",null); + AuthConfig config = new AuthConfig("roland","secret","roland@jolokia.org",null, + "private.registry.com"); check(config); } @@ -39,6 +41,7 @@ private void check(AuthConfig config) { assertEquals("roland",data.getString("username")); assertEquals("secret",data.getString("password")); assertEquals("roland@jolokia.org",data.getString("email")); + assertEquals("private.registry.com",data.getString("serveraddress")); assertFalse(data.has("auth")); } } diff --git a/src/test/resources/docker/Dockerfile.test b/src/test/resources/docker/Dockerfile.test index 21d146178..df1c66f65 100644 --- a/src/test/resources/docker/Dockerfile.test +++ b/src/test/resources/docker/Dockerfile.test @@ -5,7 +5,7 @@ LABEL com.acme.foobar="How are \"you\" ?" EXPOSE 8080 VOLUME ["/vol1"] COPY /src /export/dest +WORKDIR /tmp RUN echo something RUN echo second -WORKDIR /tmp CMD ["c1","c2"]