diff --git a/src/main/asciidoc/inc/_docker-remove.adoc b/src/main/asciidoc/inc/_docker-remove.adoc index 4f9e0ac86..abb19450a 100644 --- a/src/main/asciidoc/inc/_docker-remove.adoc +++ b/src/main/asciidoc/inc/_docker-remove.adoc @@ -24,7 +24,11 @@ You can tune this by setting the property `removeMode` (property: `docker.remove | All data images, which are images without a run configuration. |=== -Previously, this could be tuned also by providing the property `removeAll` which indicates to remove all images managed by this build. Otherwise only data images were delete before 0.24.0. `removeAll` is deprecated and will be removed soone. Please use `removeMode` instead. +Previously, this could be tuned also by providing the property `removeAll` which indicates to remove all images managed by this build. Otherwise only data images were delete before 0.24.0. `removeAll` is deprecated and will be removed soon. Please use `removeMode` instead. + +The image configuration option `cleanupRegexp` can be used to further tune which images are actually removed. +If an image qualifies according to its `removeMode` and if `cleanupRegexp` is set to a regular pattern, then _all_ images which match this pattern will be removed. +This help in cleaning up images e.g. when you use the version number as part of the image name and update your project's version. As with the other goals, the configuration `image` can be used to tune the images to remove. All containers belonging to the images are removed as well as the all tags assigned to this image diff --git a/src/main/asciidoc/inc/_docker-stop.adoc b/src/main/asciidoc/inc/_docker-stop.adoc index 2010a68a3..5ad7d0c97 100644 --- a/src/main/asciidoc/inc/_docker-stop.adoc +++ b/src/main/asciidoc/inc/_docker-stop.adoc @@ -10,6 +10,9 @@ If called as a separate invocation, the plugin will stop and remove any containe In case the naming strategy for an image is `alias` (i.e. the container name is set to the given alias), then only the container with this alias is stopped. Other containers originating from the same image are not touched. +You can specify a `cleanupRegexp` in the image configuration, which will be used for the container lookup associated with images matching this pattern (if the naming strategy is not `alias`). +This help in stopping down running containers when you e.g. you use the project version as part of the image name and do a version upgrade. + It should be noted that any containers created prior to version `0.13.7` of the plugin may not be stopped correctly by the plugin because the label needed to tie the container to the project may not exist. Should this happen, you will need to use the Docker CLI to clean up the containers and/or use the `docker.allContainers` option listed below. For tuning what should happen when stopping there are four global parameters which are typically used as system properties: diff --git a/src/main/asciidoc/inc/external/_property_configuration.adoc b/src/main/asciidoc/inc/external/_property_configuration.adoc index b8d6e52d3..2f24d83f4 100644 --- a/src/main/asciidoc/inc/external/_property_configuration.adoc +++ b/src/main/asciidoc/inc/external/_property_configuration.adoc @@ -66,6 +66,9 @@ when a `docker.from` or a `docker.fromExt` is set. | *docker.cleanup* | Cleanup dangling (untagged) images after each build (including any containers created from them). Default is `try` (which wont fail the build if removing fails), other possible values are `none` (no cleanup) or `remove` (remove but fail if unsuccessful) +| *docker.cleanupRegexp* +| Use a regular expression to find images to be removed (docker:remover) or whose containers should be stopped (docker:stop) + | *docker.cmd* | Command to execute. This is used both when running a container and as default command when creating an image. diff --git a/src/main/asciidoc/inc/image/_configuration.adoc b/src/main/asciidoc/inc/image/_configuration.adoc index 1ace4fa95..974b6faf7 100644 --- a/src/main/asciidoc/inc/image/_configuration.adoc +++ b/src/main/asciidoc/inc/image/_configuration.adoc @@ -14,6 +14,9 @@ repository _name_. This can include registry and tag parts, but also placeholder identifying the image within this configuration. This is used when linking images together or for specifying it with the global *image* configuration element. +| *cleanupRegexp* +| A regular expression which can be used when doing <> and <>. It will stop containers belonging to images who's name match the given regular expression (docker:stop) and remove all images matching the regular expression (docker:remove) + | <> | Registry to use for this image. If the `name` already contains a registry this takes precedence. See <> for more details. diff --git a/src/main/java/io/fabric8/maven/docker/RemoveMojo.java b/src/main/java/io/fabric8/maven/docker/RemoveMojo.java index 9dbae2fd2..ce2570d49 100644 --- a/src/main/java/io/fabric8/maven/docker/RemoveMojo.java +++ b/src/main/java/io/fabric8/maven/docker/RemoveMojo.java @@ -20,13 +20,13 @@ import io.fabric8.maven.docker.service.QueryService; import io.fabric8.maven.docker.service.ServiceHub; +import java.util.List; + import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; -import java.util.ArrayList; import java.util.Collections; -import java.util.List; /** * Mojo for removing images. By default only data images are removed. Data images are @@ -56,19 +56,30 @@ public class RemoveMojo extends AbstractDockerMojo { @Override protected void executeInternal(ServiceHub hub) throws DockerAccessException { for (ImageConfiguration image : getResolvedImages()) { - String name = image.getName(); - if (imageShouldBeRemoved(image)) { - removeImage(hub, name); + List imagesToRemove = extractImagesToRemove(hub, image); + for (String name : imagesToRemove) { + removeImage(hub, name); - // Remove any tagged images - for (String tag: getImageBuildTags(image)){ - removeImage(hub, name + ":" + tag); + // Remove any tagged images + for (String tag : getImageBuildTags(image)) { + removeImage(hub, image.getName() + ":" + tag); + } } } } } + private List extractImagesToRemove(ServiceHub hub, ImageConfiguration image) throws DockerAccessException { + QueryService queryService = hub.getQueryService(); + if (image.getCleanupRegexp() != null) { + String nameRegex = image.getCleanupRegexp(); + return queryService.findImageNamesByRegexp(nameRegex); + } else { + return Collections.singletonList(image.getName()); + } + } + private boolean imageShouldBeRemoved(ImageConfiguration image) { if ("all".equalsIgnoreCase(removeMode)) { return true; @@ -92,15 +103,16 @@ private boolean imageShouldBeRemoved(ImageConfiguration image) { private void removeImage(ServiceHub hub, String name) throws DockerAccessException { QueryService queryService = hub.getQueryService(); if (queryService.hasImage(name)) { - if (hub.getDockerAccess().removeImage(name,true)) { + if (hub.getDockerAccess().removeImage(name, true)) { log.info("%s: Remove", name); } } } - private List getImageBuildTags(ImageConfiguration image){ + private List getImageBuildTags(ImageConfiguration image) { return image.getBuildConfiguration() != null ? image.getBuildConfiguration().getTags() : Collections.emptyList(); } + } diff --git a/src/main/java/io/fabric8/maven/docker/StopMojo.java b/src/main/java/io/fabric8/maven/docker/StopMojo.java index 51a7d9b6f..50ebbfe64 100644 --- a/src/main/java/io/fabric8/maven/docker/StopMojo.java +++ b/src/main/java/io/fabric8/maven/docker/StopMojo.java @@ -1,5 +1,6 @@ package io.fabric8.maven.docker; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -93,10 +94,26 @@ private List getContainersToStop(QueryService queryService, ImageConf RunImageConfiguration.NamingStrategy strategy = image.getRunConfiguration().getNamingStrategy(); if (strategy == RunImageConfiguration.NamingStrategy.alias) { - Container container = queryService.getContainer(image.getAlias()); - return container != null ? Collections.singletonList(container) : Collections.emptyList(); +// Disabled for now until its clear why a regexp stop on the alias might make sense +// if (image.getCleanupRegexp() != null) { +// return queryService.findContainersByRegexp(image.getCleanupRegexp()); +// } +// else { + Container container = queryService.getContainer(image.getAlias()); + return container != null ? Collections.singletonList(container) : Collections.emptyList(); +// } } else { - return queryService.getContainersForImage(image.getName()); + if (image.getCleanupRegexp() != null) { + List imageNames = queryService.findImageNamesByRegexp(image.getCleanupRegexp()); + List containers = new ArrayList<>(); + for(String imageName : imageNames) { + containers.addAll(queryService.getContainersForImage(imageName)); + } + return containers; + } + else { + return queryService.getContainersForImage(image.getName()); + } } } diff --git a/src/main/java/io/fabric8/maven/docker/access/DockerAccess.java b/src/main/java/io/fabric8/maven/docker/access/DockerAccess.java index 9fce39430..a85fe68ba 100644 --- a/src/main/java/io/fabric8/maven/docker/access/DockerAccess.java +++ b/src/main/java/io/fabric8/maven/docker/access/DockerAccess.java @@ -48,6 +48,15 @@ public interface DockerAccess { */ ExecDetails getExecContainer(String containerIdOrName) throws DockerAccessException; + /** + * Get a container + * + * @param regexContainerIdOrName container id or name + * @return ContainerDetails representing the container or null if none could be found + * @throws DockerAccessException if the container could not be inspected + */ + List findContainersByRegexp(String regexContainerIdOrName) throws DockerAccessException; + /** * Check whether the given name exists as image at the docker daemon * @@ -56,6 +65,14 @@ public interface DockerAccess { */ boolean hasImage(String name) throws DockerAccessException; + /** + * Check whether the given name exists as image at the docker daemon + * + * @param nameRegex image name to check + * @return true if the image exists + */ + List findImageNamesByRegexp(String nameRegex) throws DockerAccessException; + /** * Get the image id of a given name or null if no such image exists * diff --git a/src/main/java/io/fabric8/maven/docker/access/UrlBuilder.java b/src/main/java/io/fabric8/maven/docker/access/UrlBuilder.java index 9f772c1fd..89b45adfe 100644 --- a/src/main/java/io/fabric8/maven/docker/access/UrlBuilder.java +++ b/src/main/java/io/fabric8/maven/docker/access/UrlBuilder.java @@ -40,6 +40,11 @@ public String inspectImage(String name) { .build(); } + public String listImages() { + return u("images/json").p("all", true) + .build(); + } + public String containerLogs(String containerId, boolean follow) { return u("containers/%s/logs", containerId) .p("stdout",true) @@ -81,7 +86,7 @@ public String inspectExecContainer(String containerId) { } public String listContainers(String ... filter) { - Builder builder = u("containers/json"); + Builder builder = u("containers/json").p("all", true); addFilters(builder, filter); return builder.build(); } diff --git a/src/main/java/io/fabric8/maven/docker/access/hc/DockerAccessWithHcClient.java b/src/main/java/io/fabric8/maven/docker/access/hc/DockerAccessWithHcClient.java index f54b61b8e..fffb7a372 100644 --- a/src/main/java/io/fabric8/maven/docker/access/hc/DockerAccessWithHcClient.java +++ b/src/main/java/io/fabric8/maven/docker/access/hc/DockerAccessWithHcClient.java @@ -3,6 +3,7 @@ import java.io.*; import java.net.URI; import java.util.*; +import java.util.regex.Pattern; import io.fabric8.maven.docker.access.*; import io.fabric8.maven.docker.access.chunked.BuildJsonResponseHandler; @@ -85,7 +86,7 @@ public DockerAccessWithHcClient(String apiVersion, this.delegate = createHttpClient(new UnixSocketClientBuilder(uri.getPath(), maxConnections, log)); this.urlBuilder = new UrlBuilder(UNIX_URL, apiVersion); } else if (uri.getScheme().equalsIgnoreCase("npipe")) { - this.delegate = createHttpClient(new NamedPipeClientBuilder(uri.getPath(), maxConnections, log), false); + this.delegate = createHttpClient(new NamedPipeClientBuilder(uri.getPath(), maxConnections, log), false); this.urlBuilder = new UrlBuilder(NPIPE_URL, apiVersion); } else { this.delegate = createHttpClient(new HttpClientBuilder(isSSL(baseUrl) ? certPath : null, maxConnections)); @@ -250,7 +251,7 @@ public LogGetHandle getLogAsync(String containerId, LogCallback callback) { public List getContainersForImage(String image) throws DockerAccessException { String url; String serverApiVersion = getServerApiVersion(); - if (EnvUtil.greaterOrEqualsVersion(serverApiVersion, "1.23")) { + if (image != null && EnvUtil.greaterOrEqualsVersion(serverApiVersion, "1.23")) { // For Docker >= 1.11 we can use a new filter when listing containers url = urlBuilder.listContainers("ancestor",image); } else { @@ -265,7 +266,7 @@ public List getContainersForImage(String image) throws DockerAccessEx for (int i = 0; i < array.length(); i++) { JSONObject element = array.getJSONObject(i); - if (image.equals(element.getString("Image"))) { + if (image == null || image.equals(element.getString("Image"))) { containers.add(new ContainersListElement(element)); } } @@ -295,6 +296,24 @@ public ExecDetails getExecContainer(String containerIdOrName) throws DockerAcces } } + @Override + public List findContainersByRegexp(String containerRegexp) throws DockerAccessException { + // All containers + List containers = getContainersForImage(null); + Pattern pattern = Pattern.compile(containerRegexp); + try { + List ret = new ArrayList<>(); + for (Container container : containers) { + if (pattern.matcher(container.getName()).matches()){ + ret.add(container); + } + } + return ret; + } catch(Exception e) { + throw new DockerAccessException(e, "Unable to list container regular expression for [%s]", containerRegexp); + } + } + private HttpBodyAndStatus inspectContainer(String containerIdOrName) throws DockerAccessException { try { String url = urlBuilder.inspectContainer(containerIdOrName); @@ -342,6 +361,40 @@ private HttpBodyAndStatus inspectImage(String name) throws DockerAccessException } } + @Override + public List findImageNamesByRegexp(String nameRegexp) throws DockerAccessException { + String url = urlBuilder.listImages(); + List imageNames = new ArrayList<>(); + Pattern namePattern = Pattern.compile(nameRegexp); + + try { + HttpBodyAndStatus response = delegate.get(url, new BodyAndStatusResponseHandler(), HTTP_OK, HTTP_NOT_FOUND); + if (response.getStatusCode() == HTTP_NOT_FOUND) { + return imageNames; + } + JSONArray imageDetails = new JSONArray(response.getBody()); + + for (int i=0; idocker:stop and docker:remove + */ + @Parameter + private String cleanupRegexp; + @Parameter private RunImageConfiguration run; @@ -52,10 +59,14 @@ public void setName(String name) { } @Override - public String getAlias() { + public String getAlias() { return alias; } + public String getCleanupRegexp() { + return cleanupRegexp; + } + public RunImageConfiguration getRunConfiguration() { return (run == null) ? RunImageConfiguration.DEFAULT : run; } @@ -152,11 +163,11 @@ public String initAndValidate(ConfigHelper.NameFormatter nameFormatter, Logger l } return minimalApiVersion; } - // ========================================================================= - // Builder for image configurations + // Builder for image configurations public static class Builder { + private final ImageConfiguration config; public Builder() { @@ -182,6 +193,11 @@ public Builder alias(String alias) { return this; } + public Builder cleanupRegexp(String cleanupRegexp) { + config.cleanupRegexp = cleanupRegexp; + return this; + } + public Builder runConfig(RunImageConfiguration runConfig) { config.run = runConfig; return this; @@ -205,10 +221,10 @@ public Builder registry(String registry) { public ImageConfiguration build() { return config; } - public Builder watchConfig(WatchImageConfiguration watchConfig) { config.watch = watchConfig; return this; } + } } diff --git a/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java b/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java index ca6f5db4d..a9399c50f 100644 --- a/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java +++ b/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java @@ -40,6 +40,7 @@ public enum ConfigKey { CAP_ADD, CAP_DROP, CLEANUP, + CLEANUP_REGEXP, NOCACHE, OPTIMISE, CMD, diff --git a/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java b/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java index 113af5137..6a129065e 100644 --- a/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java +++ b/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java @@ -53,11 +53,12 @@ public List resolve(ImageConfiguration config, MavenProject String name = extractName(prefix, properties); String alias = withPrefix(prefix, ALIAS, properties); - + String cleanupRegexp = withPrefix(prefix, CLEANUP_REGEXP, properties); return Collections.singletonList( new ImageConfiguration.Builder() .name(name) .alias(alias != null ? alias : config.getAlias()) + .cleanupRegexp(cleanupRegexp != null ? cleanupRegexp : config.getCleanupRegexp()) .runConfig(run) .buildConfig(build) .watchConfig(watch) diff --git a/src/main/java/io/fabric8/maven/docker/service/QueryService.java b/src/main/java/io/fabric8/maven/docker/service/QueryService.java index 394b519df..6ce7fb726 100644 --- a/src/main/java/io/fabric8/maven/docker/service/QueryService.java +++ b/src/main/java/io/fabric8/maven/docker/service/QueryService.java @@ -8,8 +8,6 @@ import io.fabric8.maven.docker.model.Container; import io.fabric8.maven.docker.model.Network; import io.fabric8.maven.docker.access.DockerAccessException; -import io.fabric8.maven.docker.util.AutoPullMode; -import org.apache.maven.plugin.MojoExecutionException; /** * Query service for getting image and container information from the docker dameon @@ -43,6 +41,16 @@ public Container getMandatoryContainer(String containerIdOrName) throws DockerAc return container; } + /** + * Find containers by the given regex name + * @param nameRegex of container to lookup + * @return the List of containers found or empty List if no container is available. + * @throws DockerAccessException in case of an remote error + */ + public List findContainersByRegexp(final String nameRegex) throws DockerAccessException { + return docker.findContainersByRegexp(nameRegex); + } + /** * Get a container running for a given container name. * @param containerIdOrName name of container to lookup @@ -111,6 +119,16 @@ public String getImageId(String imageName) throws DockerAccessException { return docker.getImageId(imageName); } + /** + * Find images by the given regex name + * @param nameRegex regular expression to apply to the images + * @return the List of containers found or empty List if no container is available. + * @throws DockerAccessException in case of an remote error + */ + public List findImageNamesByRegexp(String nameRegex) throws DockerAccessException { + return docker.findImageNamesByRegexp(nameRegex); + } + /** * Get the id of the latest container started for an image * diff --git a/src/test/java/io/fabric8/maven/docker/UrlBuilderTest.java b/src/test/java/io/fabric8/maven/docker/UrlBuilderTest.java index 1bfe49d16..566da0a0c 100644 --- a/src/test/java/io/fabric8/maven/docker/UrlBuilderTest.java +++ b/src/test/java/io/fabric8/maven/docker/UrlBuilderTest.java @@ -89,8 +89,8 @@ public void getImage() throws URISyntaxException { public void listContainers() throws MalformedURLException, UnsupportedEncodingException, URISyntaxException { UrlBuilder builder = new UrlBuilder("","1.0"); - assertEquals(new URI("/1.0/containers/json"), new URI(builder.listContainers())); - assertEquals(new URI("/1.0/containers/json?filters=" + URLEncoder.encode("{\"ancestor\":[\"nginx\"]}","UTF8")), + assertEquals(new URI("/1.0/containers/json?all=1"), new URI(builder.listContainers())); + assertEquals(new URI("/1.0/containers/json?all=1&filters=" + URLEncoder.encode("{\"ancestor\":[\"nginx\"]}","UTF8")), new URI(builder.listContainers("ancestor", "nginx"))); try {