Skip to content

Commit

Permalink
feature: Add 'cleanupRegexp' for matching image to stop/remove
Browse files Browse the repository at this point in the history
* Based on the work of fabric8io#900 (thanks !)
* Fixed conflicts
* Removed the top-level properties and just check whether the ImageConfiguration has the regular expression set.
* Not sure how useful `aliasRegex` is as I suppose that an alias is a fixed name without any version information and stable across releases. For now I removed it (and moved to a `cleanupRegexp` parameter in the image configuration which is the image regexp to use for stop/remove.
* Added some docs and streamlined the code a bit
  • Loading branch information
rhuss committed Jan 19, 2018
1 parent 4f9a721 commit c7d2cc9
Show file tree
Hide file tree
Showing 14 changed files with 181 additions and 28 deletions.
6 changes: 5 additions & 1 deletion src/main/asciidoc/inc/_docker-remove.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions src/main/asciidoc/inc/_docker-stop.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions src/main/asciidoc/inc/external/_property_configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
3 changes: 3 additions & 0 deletions src/main/asciidoc/inc/image/_configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<docker:stop>> and <<docker:remove>>. 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, *registry*>>
| Registry to use for this image. If the `name` already contains a registry this takes precedence. See <<registry,Registry handling>> for more details.

Expand Down
32 changes: 22 additions & 10 deletions src/main/java/io/fabric8/maven/docker/RemoveMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String> 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<String> 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;
Expand All @@ -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<String> getImageBuildTags(ImageConfiguration image){
private List<String> getImageBuildTags(ImageConfiguration image) {
return image.getBuildConfiguration() != null ?
image.getBuildConfiguration().getTags() :
Collections.<String>emptyList();
}

}
23 changes: 20 additions & 3 deletions src/main/java/io/fabric8/maven/docker/StopMojo.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -93,10 +94,26 @@ private List<Container> 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.<Container>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.<Container>emptyList();
// }
} else {
return queryService.getContainersForImage(image.getName());
if (image.getCleanupRegexp() != null) {
List<String> imageNames = queryService.findImageNamesByRegexp(image.getCleanupRegexp());
List<Container> containers = new ArrayList<>();
for(String imageName : imageNames) {
containers.addAll(queryService.getContainersForImage(imageName));
}
return containers;
}
else {
return queryService.getContainersForImage(image.getName());
}
}
}

Expand Down
17 changes: 17 additions & 0 deletions src/main/java/io/fabric8/maven/docker/access/DockerAccess.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ public interface DockerAccess {
*/
ExecDetails getExecContainer(String containerIdOrName) throws DockerAccessException;

/**
* Get a container
*
* @param regexContainerIdOrName container id or name
* @return <code>ContainerDetails<code> representing the container or null if none could be found
* @throws DockerAccessException if the container could not be inspected
*/
List<Container> findContainersByRegexp(String regexContainerIdOrName) throws DockerAccessException;

/**
* Check whether the given name exists as image at the docker daemon
*
Expand All @@ -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<String> findImageNamesByRegexp(String nameRegex) throws DockerAccessException;

/**
* Get the image id of a given name or <code>null</code> if no such image exists
*
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/io/fabric8/maven/docker/access/UrlBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -250,7 +251,7 @@ public LogGetHandle getLogAsync(String containerId, LogCallback callback) {
public List<Container> 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 {
Expand All @@ -265,7 +266,7 @@ public List<Container> 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));
}
}
Expand Down Expand Up @@ -295,6 +296,24 @@ public ExecDetails getExecContainer(String containerIdOrName) throws DockerAcces
}
}

@Override
public List<Container> findContainersByRegexp(String containerRegexp) throws DockerAccessException {
// All containers
List<Container> containers = getContainersForImage(null);
Pattern pattern = Pattern.compile(containerRegexp);
try {
List<Container> 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);
Expand Down Expand Up @@ -342,6 +361,40 @@ private HttpBodyAndStatus inspectImage(String name) throws DockerAccessException
}
}

@Override
public List<String> findImageNamesByRegexp(String nameRegexp) throws DockerAccessException {
String url = urlBuilder.listImages();
List<String> 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; i<imageDetails.length(); i++) {
JSONObject image = imageDetails.getJSONObject(i);
if(!image.has("RepoTags") || image.isNull("RepoTags")) {
log.debug("Invalid RepoTags found on %s", image.get("Id"));
continue;
}
JSONArray tags = image.getJSONArray("RepoTags");
for (int j=0; j<tags.length(); j++) {
String tag = tags.getString(j);
if (namePattern.matcher(tag).matches()) {
imageNames.add(tag);
}
}
}
return imageNames;

} catch (IOException e) {
throw new DockerAccessException(e, "Unable to find name by regex [%s]", nameRegexp);
}
}

@Override
public void removeContainer(String containerId, boolean removeVolumes)
throws DockerAccessException {
Expand Down Expand Up @@ -566,7 +619,7 @@ public void shutdown() {
}

ApacheHttpClientDelegate createHttpClient(ClientBuilder builder) throws IOException {
return createHttpClient(builder, true);
return createHttpClient(builder, true);
}

ApacheHttpClientDelegate createHttpClient(ClientBuilder builder, boolean pooled) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ public class ImageConfiguration implements StartOrderResolver.Resolvable, Serial
@Parameter
private String alias;

/**
* Regular expression used for matching containers and images when doing
* cleanup with <code>docker:stop</code> and <code>docker:remove</code>
*/
@Parameter
private String cleanupRegexp;

@Parameter
private RunImageConfiguration run;

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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() {
Expand All @@ -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;
Expand All @@ -205,10 +221,10 @@ public Builder registry(String registry) {
public ImageConfiguration build() {
return config;
}

public Builder watchConfig(WatchImageConfiguration watchConfig) {
config.watch = watchConfig;
return this;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public enum ConfigKey {
CAP_ADD,
CAP_DROP,
CLEANUP,
CLEANUP_REGEXP,
NOCACHE,
OPTIMISE,
CMD,
Expand Down
Loading

0 comments on commit c7d2cc9

Please sign in to comment.