Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for pattern matching in docker:stop and docker:remove #1215

Merged
merged 3 commits into from
Aug 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/main/asciidoc/inc/_docker-remove.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[[docker:remove]]
== *docker:remove*

This goal can be used to clean up images and containers. By default all images with a build configuration are removed.
This goal can be used to clean up images. By default all images with a build configuration are removed.
You can tune this by setting the property `removeMode` (property: `docker.removeMode`) to one of the following values:

.removeMode Values
Expand All @@ -14,13 +14,13 @@ You can tune this by setting the property `removeMode` (property: `docker.remove
| All images with a <<build-configuration,build configuration>>

| `run`
| All images with a <<start-configuration,run configuration>>
| All images without a <<start-configuration,build configuration>>

| `all`
| All configured images

| `data`
| All data images, which are images without a run configuration.
| All data images, which are images without a <<start-configuration,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.
Expand All @@ -42,4 +42,9 @@ Considering three images 'db','tomcat' and 'data' where 'data' is the only image
| *skipTag*
| If set to `true` this plugin won't remove any tags
| `docker.skip.tag`

| *removeNamePattern*
| If a list of <<name-patterns, name patterns>> is provided, any images matching the patterns will be removed,
independently of whether there is an <<image-configuration, image configuration>> marked for removal.
| `docker.removeNamePattern`
|===
10 changes: 9 additions & 1 deletion src/main/asciidoc/inc/_docker-stop.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ In case the naming strategy for an image is `alias` (i.e. the container name is
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:
`allContainers`, `keepContainer`, `keepRunning` and `removeVolumes`.

.Stop configuration
[cols="1,5,1"]
Expand All @@ -24,7 +25,8 @@ For tuning what should happen when stopping there are four global parameters whi
| `docker.allContainers`

| <<container-name, *containerNamePattern*>>
| Default pattern for naming all containers when they are created. See <<container-name, Container Names>> for details.
| Default pattern that <<{plugin}:start>> uses for naming containers when they are created. See <<container-name, Container Names>> for details.
This should match the setting for <<{plugin}:start>> goals if the goals are configured in separate executions.
| `docker.containerNamePattern`

| *keepContainer*
Expand All @@ -38,6 +40,12 @@ For tuning what should happen when stopping there are four global parameters whi
| *removeVolumes*
| If set to `true` will remove any volumes associated to the container as well. This option will be ignored if either `keepContainer` or `keepRunning` are true.
| `docker.removeVolumes`

| *stopNamePattern*
| If a list of <<name-patterns, name patterns>> is provided, any containers matching the patterns will be stopped and
removed (depending on the values of `keepContainer` and `keepRunning`),
independently of whether there is an <<image-configuration, image configuration>>.
| `docker.stopNamePattern`
|===

.Example
Expand Down
13 changes: 2 additions & 11 deletions src/main/asciidoc/inc/build/_configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,8 @@ A provided `<from>` takes precedence over the name given here. This tag is usefu
| *imagePullPolicy*
| Specific pull policy for the base image. This overwrites any global pull policy. See the globale configuration option <<image-pull-policy, imagePullPolicy>> for the possible values and the default.

| *loadNamePattern*
a| Scan the images in the archive specified in `dockerArchive` and match the associated repository and tag information against this pattern. When a matching repository and tag is found, create a tag linking the `name` for this image to the repository and tag that matched the pattern.

The wildcards are:

* `?` matches a single character
* `*` matches within one component, where components are separated by slashes, or the final colon that separates the repository from the tag
* `**` matches multiple components, stopping at the final colon
* `**/` matches multiple components, but must stop at a slash, or the final colon

When matching multiple components, `$$**/$$` is likely to be more useful than `$$**$$`. The pattern `$$**image-name:*$$` will match `my-group/my-image-name:some-tag`, whereas `$$**/image-name:*$$` will not, because the wildcard has to stop at a slash. Note that `$$**/image-name:*$$` will also match 'image-name:some-tag', since the `$$**/$$` wildcard can be empty.
| [[build-config-load-name-pattern]]*loadNamePattern*
a| Scan the archive specified in `dockerArchive` and find the actual repository and tag in the archive that matches this <<name-patterns, name pattern>>. After loading the archive, link the <<config-image, image name>> configured in the POM to the repository and tag matched in the archive.

| <<misc-env, *labels*>>
| Labels as described in <<misc-env,Setting Environment Variables and Labels>>.
Expand Down
9 changes: 9 additions & 0 deletions src/main/asciidoc/inc/image/_configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,13 @@ created and run when <<{plugin}:start>> is called. If this image is only used a
| <<external-configuration, *external*>>
| Specification of external configuration as an alternative to this XML based configuration with `<run>` and `<build>`. It contains a `<type>` element specifying the handler for getting the configuration. See <<external-configuration,External configuration>> for details.
endif::[]

| *removeNamePattern*
| When this image is to be removed by <<{plugin}:remove>>, use this <<name-patterns, pattern list>> to find images to
remove rather than just using the name.

| *stopNamePattern*
| When containers associated with this image will be stopped by <<{plugin}:stop>>, use this <<name-patterns, pattern list>>
to find containers to remove rather than just using the associated container name.

|===
101 changes: 101 additions & 0 deletions src/main/asciidoc/inc/image/_naming.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,104 @@ Similar, when stopping containers with `mvn docker:stop` then only the container
However, if you don't use an index via `%i` then _all_ containers started with `docker:start` are stopped.
Use `mvn docker:stop -Ddocker.allContainers` to also stop every container named via a `%i` pattern.
endif::[]

[[name-patterns]]
## Name Patterns

Goals that need to refer to images or containers where the name of the image or container is not fixed may support name
patterns for matching. Patterns can use an Ant-like syntax or Java regular expressions.

### Ant-like Name Patterns
Ant path matching patterns that operate on path names use the convention that a `$$*$$` matches within a single path
component, while `$$**$$` can match multiple components.

Adapting this style to image names requires some tweaks since image names may include registry information, a path-like
repository name and a tag. Consider the following image names:

* `alpine:latest`
* `fluent/fluentd:edge`
* `quay.io/operator-framework/helm-operator:v0.9.0`
* `company.local:5000/division/project/artifact:version`

Unlike in Ant matching of file system paths, the `:` is an important marker, but only at the end where it separates the
version from the repository. Also, patterns that match repository names need to anticipate that there may be a registry
name at the beginning if the image has been tagged for pushing to a registry.

Taking this into account, the name pattern wildcards are:

* `?` matches a single character
* `*` matches zero or more characters, up to the next slash or the tag separator
* `**` matches zero or more characters, up to the tag separator
* `**/` matches zero or more characters, up to the tag separator, and ensures that if any characters are matched, the
final character matched is a slash

#### Examples of Ant-like Name Patterns

[cols="1,3,3"]
|===
| Pattern | Matches | Does Not Match

| `$$**tomcat:jdk-11*$$`
a|
* megacorp/tomcat:jdk-11-alpine
* megacorp.com:5000/megacorp/project-x-tomcat:jdk-11

a|
* megacorp/tomcat__-operator__:jdk-11
* megacorp/project-x-tomcat:__jdk-9-alpine__

| `$$**/megacorp/tomcat:*alpine$$`
a|
* megacorp/tomcat:alpine
* megacorp.com:5000/megacorp/tomcat:jdk-11-alpine

a|
* megacorp/tomcat:__jdk-11__
* megacorp.com:5000/__ultra__megacorp/tomcat:jdk-11-alpine

| `$$megacorp/*-operator:*$$`
a|
* megacorp/tomcat-operator:alpine
* megacorp/mysql-operator:latest

a|
* megacorp/__tomcat__:jdk-11
* __megacorp.com:5000/__megacorp/tomcat-operator:alpine

|===

### Java Regular Expression Patterns

To indicate that a name pattern is a Java regular expression, prefix the regular expression with `$$%regex[$$` and
suffix with `$$]$$`.

#### Examples of Java Regular Expression Patterns

[cols="1,3,3"]
|===
| Pattern | Matches | Does Not Match

| `$$%regex[j(dk\|re)-11]$$`
a|
* megacorp/tomcat:jdk-11-alpine
* openjdk-11:latest

a|
* openjdk:11-alpine

| `$$%regex[tomcat]$$`
a|
* megacorp/tomcat:alpine
* megacorp.com:5000/tomcat-projects/project-x:latest

a|
* megacorp/topcat:edge

|===

[[name-pattern-lists]]
### Name Pattern Lists

In goals such as <<{plugin}:stop>> and <<{plugin}:remove>> where multiple patterns are supported, separate patterns
with commas.

85 changes: 78 additions & 7 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,19 @@
import io.fabric8.maven.docker.service.QueryService;
import io.fabric8.maven.docker.service.ServiceHub;
import io.fabric8.maven.docker.util.ImageName;
import io.fabric8.maven.docker.util.NamePatternUtil;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
* Mojo for removing images. By default only data images are removed. Data images are
Expand Down Expand Up @@ -58,23 +64,31 @@ public class RemoveMojo extends AbstractDockerMojo {
*/
@Parameter(property = "docker.skip.tag", defaultValue = "false")
private boolean skipTag;


@Parameter(property = "docker.removeNamePattern")
private String removeNamePattern;

@Override
protected void executeInternal(ServiceHub hub) throws DockerAccessException {
protected void executeInternal(ServiceHub hub) throws DockerAccessException, MojoExecutionException {
for (ImageConfiguration image : getResolvedImages()) {
String name = image.getName();

if (imageShouldBeRemoved(image)) {
removeImage(hub, name);
for(String name : getImageNamesToRemoveForImage(hub, image)) {
removeImage(hub, name);
}

if(!skipTag) {
// Remove any tagged images
for (String tag: getImageBuildTags(image)){
removeImage(hub, new ImageName(name, tag).getFullName());
removeImage(hub, new ImageName(image.getName(), tag).getFullName());
}
}
}
}

// If a global pattern option is provided, process it after per-image patterns
for(String name : getImageNamesToRemoveForMojo(hub)) {
removeImage(hub, name);
}
}

private boolean imageShouldBeRemoved(ImageConfiguration image) {
Expand All @@ -85,7 +99,7 @@ private boolean imageShouldBeRemoved(ImageConfiguration image) {
return image.getBuildConfiguration() != null;
}
if ("run".equalsIgnoreCase(removeMode)) {
return image.getRegistry() != null;
return image.getBuildConfiguration() == null;
}
if ("data".equalsIgnoreCase(removeMode)) {
return image.isDataImage();
Expand All @@ -97,6 +111,63 @@ private boolean imageShouldBeRemoved(ImageConfiguration image) {
return image.getBuildConfiguration() != null;
}

private Collection<String> getImageNamesMatchingPattern(ServiceHub hub, Matcher imageNameMatcher)
throws MojoExecutionException, DockerAccessException {
return hub.getQueryService().listImages(false)
.stream()
.flatMap(image -> image.getRepoTags().stream())
.filter(repoTag -> imageNameMatcher.reset(repoTag).matches())
.collect(Collectors.toList());
}

private Collection<String> getImageNamesToRemoveForMojo(ServiceHub hub)
throws MojoExecutionException, DockerAccessException {
if(removeNamePattern != null) {
Matcher imageNameMatcher = getImageNameMatcher(removeNamePattern);

if(imageNameMatcher == null) {
log.warn("There are no image name patterns in removeNamePattern for docker:remove");
return Collections.emptyList();
}

return getImageNamesMatchingPattern(hub, imageNameMatcher);
}

return Collections.emptyList();
}

private Collection<String> getImageNamesToRemoveForImage(ServiceHub hub, ImageConfiguration imageConfiguration)
throws MojoExecutionException, DockerAccessException {

if(imageConfiguration.getRemoveNamePattern() != null) {
Matcher imageNameMatcher = getImageNameMatcher(imageConfiguration.getRemoveNamePattern());

if(imageNameMatcher == null) {
log.warn("There are no image name patterns in removeNamePattern for image %s: no images will be removed", imageConfiguration.getName());
return Collections.emptyList();
}

return getImageNamesMatchingPattern(hub, imageNameMatcher);
}

return Collections.singleton(imageConfiguration.getName());
}

private Matcher getImageNameMatcher(String removeNamePattern) throws MojoExecutionException {
try {
String imageNameRegex = NamePatternUtil.convertNamePatternList(removeNamePattern, NamePatternUtil.IMAGE_FIELD, true);
if(imageNameRegex == null) {
log.debug("No image name patterns in removeNamePattern %s", removeNamePattern);
return null;
} else {
log.debug("Converted removeNamePattern %s into image name regular expression %s", removeNamePattern, imageNameRegex);
return Pattern.compile(imageNameRegex).matcher("");
}
} catch(IllegalArgumentException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}

private void removeImage(ServiceHub hub, String name) throws DockerAccessException {
QueryService queryService = hub.getQueryService();
if (queryService.hasImage(name)) {
Expand Down
Loading