diff --git a/doc/changelog.md b/doc/changelog.md index 282748a5c..5030afef7 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -8,8 +8,10 @@ - Fix for hanging wait on log (#904) - Fix for credential helper which do not return a version (#896) - Also remove tagged images when calling `docker:remove` (#193) - - Introduce a `removeMode` for selecting the images to remove - + - Introduced a `removeMode` for selecting the images to remove + - Introduced a `breakOnError`` for the `postStart` and `preStop` hooks in the + wait configuration (#914) + Please note that `autoPullMode` is deprecated now and the behaviour of the `autoPullMode == always` has been changed slightly so that now, it really always pulls the image from the registry. Also `removeAll` for `docker:remove` is deprecated in favor of `removeMode` (and the default mode has changed slightly). Please refer to the documentation for more information. * **0.23.0** (2017-11-04) diff --git a/src/main/asciidoc/inc/external/_property_configuration.adoc b/src/main/asciidoc/inc/external/_property_configuration.adoc index 4a0a7cf3f..b8d6e52d3 100644 --- a/src/main/asciidoc/inc/external/_property_configuration.adoc +++ b/src/main/asciidoc/inc/external/_property_configuration.adoc @@ -249,6 +249,9 @@ when a `docker.from` or a `docker.fromExt` is set. | *docker.wait.exec.preStop* | Command to execute before command stops. +| *docker.wait.exec.breakOnError* +| If set to "true" then stop the build if the a `postStart` or `preStop` command failed + | *docker.wait.shutdown* | Time in milliseconds to wait between stopping a container and removing it. diff --git a/src/main/asciidoc/inc/start/_wait.adoc b/src/main/asciidoc/inc/start/_wait.adoc index 79388d131..7eb040446 100644 --- a/src/main/asciidoc/inc/start/_wait.adoc +++ b/src/main/asciidoc/inc/start/_wait.adoc @@ -34,6 +34,7 @@ a| Commands to execute during specified lifecycle of the container. It knows the * *postStart* Command to run after the above wait criteria has been met * *preStop* Command to run before the container is stopped. +* *breakOnError* If set to `true` then break the build if a `postStart` or `preStop` command exits with an return code other than 0, otherwise only print an error message. | *tcp* a| TCP port check which periodically polls given tcp ports. It knows the following sub-elements: diff --git a/src/main/java/io/fabric8/maven/docker/AbstractDockerMojo.java b/src/main/java/io/fabric8/maven/docker/AbstractDockerMojo.java index bcca72ae2..06c25d379 100644 --- a/src/main/java/io/fabric8/maven/docker/AbstractDockerMojo.java +++ b/src/main/java/io/fabric8/maven/docker/AbstractDockerMojo.java @@ -7,6 +7,7 @@ import io.fabric8.maven.docker.access.DockerAccess; import io.fabric8.maven.docker.access.DockerAccessException; +import io.fabric8.maven.docker.access.ExecException; import io.fabric8.maven.docker.config.ConfigHelper; import io.fabric8.maven.docker.config.DockerMachineConfiguration; import io.fabric8.maven.docker.config.ImageConfiguration; @@ -218,7 +219,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { } ServiceHub serviceHub = serviceHubFactory.createServiceHub(project, session, access, log, logSpecFactory); executeInternal(serviceHub); - } catch (DockerAccessException exp) { + } catch (DockerAccessException | ExecException exp) { logException(exp); throw new MojoExecutionException(log.errorMessage(exp.getMessage()), exp); } catch (MojoExecutionException exp) { @@ -340,7 +341,7 @@ protected boolean isDockerAccessRequired() { * @param serviceHub context for accessing backends */ protected abstract void executeInternal(ServiceHub serviceHub) - throws DockerAccessException, MojoExecutionException; + throws DockerAccessException, ExecException, MojoExecutionException; // ============================================================================================= diff --git a/src/main/java/io/fabric8/maven/docker/StartMojo.java b/src/main/java/io/fabric8/maven/docker/StartMojo.java index d54d21284..2588ca71a 100644 --- a/src/main/java/io/fabric8/maven/docker/StartMojo.java +++ b/src/main/java/io/fabric8/maven/docker/StartMojo.java @@ -14,6 +14,7 @@ import com.google.common.util.concurrent.MoreExecutors; import io.fabric8.maven.docker.access.DockerAccessException; +import io.fabric8.maven.docker.access.ExecException; import io.fabric8.maven.docker.access.PortMapping; import io.fabric8.maven.docker.config.*; import io.fabric8.maven.docker.log.LogDispatcher; @@ -91,6 +92,7 @@ private StartedContainer(ImageConfiguration imageConfig, String containerId) { */ @Override public synchronized void executeInternal(final ServiceHub hub) throws DockerAccessException, + ExecException, MojoExecutionException { if (skipRun) { return; @@ -203,12 +205,14 @@ private void shutdownExecutorService(ExecutorService executorService) { } } - private void rethrowCause(ExecutionException e) throws IOException, InterruptedException { + private void rethrowCause(ExecutionException e) throws IOException, InterruptedException, ExecException { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof IOException) { throw (IOException) cause; + } else if (cause instanceof ExecException) { + throw (ExecException) cause; } else if (cause instanceof InterruptedException) { throw (InterruptedException) cause; } else { @@ -254,7 +258,15 @@ public StartedContainer call() throws Exception { hub.getWaitService().wait(image, projProperties, containerId); WaitConfiguration waitConfig = runConfig.getWaitConfiguration(); if (waitConfig != null && waitConfig.getExec() != null && waitConfig.getExec().getPostStart() != null) { - runService.execInContainer(containerId, waitConfig.getExec().getPostStart(), image); + try { + runService.execInContainer(containerId, waitConfig.getExec().getPostStart(), image); + } catch (ExecException exp) { + if (waitConfig.getExec().isBreakOnError()) { + throw exp; + } else { + log.warn("Cannot run postStart: %s", exp.getMessage()); + } + } } return new StartedContainer(image, containerId); diff --git a/src/main/java/io/fabric8/maven/docker/StopMojo.java b/src/main/java/io/fabric8/maven/docker/StopMojo.java index 969231ab5..51a7d9b6f 100644 --- a/src/main/java/io/fabric8/maven/docker/StopMojo.java +++ b/src/main/java/io/fabric8/maven/docker/StopMojo.java @@ -8,6 +8,7 @@ import java.util.Set; import io.fabric8.maven.docker.access.DockerAccessException; +import io.fabric8.maven.docker.access.ExecException; import io.fabric8.maven.docker.config.ImageConfiguration; import io.fabric8.maven.docker.config.NetworkConfig; import io.fabric8.maven.docker.config.RunImageConfiguration; @@ -55,9 +56,8 @@ public class StopMojo extends AbstractDockerMojo { @Parameter( property = "docker.sledgeHammer", defaultValue = "false" ) private boolean sledgeHammer; - @Override - protected void executeInternal(ServiceHub hub) throws MojoExecutionException, DockerAccessException { + protected void executeInternal(ServiceHub hub) throws MojoExecutionException, DockerAccessException, ExecException { QueryService queryService = hub.getQueryService(); RunService runService = hub.getRunService(); @@ -76,7 +76,7 @@ protected void executeInternal(ServiceHub hub) throws MojoExecutionException, Do dispatcher.untrackAllContainerLogs(); } - private void stopContainers(QueryService queryService, RunService runService, PomLabel pomLabel) throws DockerAccessException { + private void stopContainers(QueryService queryService, RunService runService, PomLabel pomLabel) throws DockerAccessException, ExecException { Collection networksToRemove = getNetworksToRemove(queryService, pomLabel); for (ImageConfiguration image : getResolvedImages()) { for (Container container : getContainersToStop(queryService, image)) { 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 cd4c88a2a..9fce39430 100644 --- a/src/main/java/io/fabric8/maven/docker/access/DockerAccess.java +++ b/src/main/java/io/fabric8/maven/docker/access/DockerAccess.java @@ -9,7 +9,8 @@ import io.fabric8.maven.docker.config.Arguments; import io.fabric8.maven.docker.log.LogOutputSpec; import io.fabric8.maven.docker.model.Container; -import io.fabric8.maven.docker.model.InspectedContainer; +import io.fabric8.maven.docker.model.ContainerDetails; +import io.fabric8.maven.docker.model.ExecDetails; import io.fabric8.maven.docker.model.Network; /** @@ -36,7 +37,16 @@ public interface DockerAccess { * @return ContainerDetails representing the container or null if none could be found * @throws DockerAccessException if the container could not be inspected */ - InspectedContainer getContainer(String containerIdOrName) throws DockerAccessException; + ContainerDetails getContainer(String containerIdOrName) throws DockerAccessException; + + /** + * Get an exec container which is the result of executing a command in a running container. + * + * @param containerIdOrName exec container id or name + * @return ExecDetails representing the container or null if none could be found + * @throws DockerAccessException if the container could not be inspected + */ + ExecDetails getExecContainer(String containerIdOrName) throws DockerAccessException; /** * Check whether the given name exists as image at the docker daemon @@ -65,8 +75,9 @@ public interface DockerAccess { List getContainersForImage(String image) throws DockerAccessException; /** - * Starts a previously set up exec instance id. - * this API sets up an interactive session with the exec command. Output is streamed to the log. + * Starts a previously set up exec instance (via {@link #createExecContainer(String, Arguments)} container + * this API sets up a session with the exec command. Output is streamed to the log. This methods + * returns only when the exec command has finished (i.e this method calls the command in a non-detached mode). * * @param containerId id of the exec container * @param outputSpec how to print out the output of the command diff --git a/src/main/java/io/fabric8/maven/docker/access/ExecException.java b/src/main/java/io/fabric8/maven/docker/access/ExecException.java new file mode 100644 index 000000000..5d7771f42 --- /dev/null +++ b/src/main/java/io/fabric8/maven/docker/access/ExecException.java @@ -0,0 +1,25 @@ +package io.fabric8.maven.docker.access; + +import java.util.Arrays; + +import io.fabric8.maven.docker.model.ContainerDetails; +import io.fabric8.maven.docker.model.ExecDetails; + +/** + * Exception thrown when the execution of an exec container failed + * + * @author roland + * @since 18.01.18 + */ +public class ExecException extends Exception { + public ExecException(ExecDetails details, ContainerDetails container) { + super(String.format( + "Executing '%s' with args '%s' inside container '%s' [%s](%s) resulted in a non-zero exit code: %d", + details.getEntryPoint(), + Arrays.toString(details.getArguments()), + container.getName(), + container.getImage(), + container.getId(), + details.getExitCode())); + } +} 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 df137a9a5..9f772c1fd 100644 --- a/src/main/java/io/fabric8/maven/docker/access/UrlBuilder.java +++ b/src/main/java/io/fabric8/maven/docker/access/UrlBuilder.java @@ -75,6 +75,11 @@ public String inspectContainer(String containerId) { .build(); } + public String inspectExecContainer(String containerId) { + return u("exec/%s/json", containerId) + .build(); + } + public String listContainers(String ... filter) { Builder builder = u("containers/json"); addFilters(builder, filter); 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 960291d2c..f54b61b8e 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 @@ -276,7 +276,7 @@ public List getContainersForImage(String image) throws DockerAccessEx } @Override - public InspectedContainer getContainer(String containerIdOrName) throws DockerAccessException { + public ContainerDetails getContainer(String containerIdOrName) throws DockerAccessException { HttpBodyAndStatus response = inspectContainer(containerIdOrName); if (response.getStatusCode() == HTTP_NOT_FOUND) { return null; @@ -285,6 +285,16 @@ public InspectedContainer getContainer(String containerIdOrName) throws DockerAc } } + @Override + public ExecDetails getExecContainer(String containerIdOrName) throws DockerAccessException { + HttpBodyAndStatus response = inspectExecContainer(containerIdOrName); + if (response.getStatusCode() == HTTP_NOT_FOUND) { + return null; + } else { + return new ExecDetails(new JSONObject(response.getBody())); + } + } + private HttpBodyAndStatus inspectContainer(String containerIdOrName) throws DockerAccessException { try { String url = urlBuilder.inspectContainer(containerIdOrName); @@ -294,6 +304,15 @@ private HttpBodyAndStatus inspectContainer(String containerIdOrName) throws Dock } } + private HttpBodyAndStatus inspectExecContainer(String containerIdOrName) throws DockerAccessException { + try { + String url = urlBuilder.inspectExecContainer(containerIdOrName); + return delegate.get(url, new BodyAndStatusResponseHandler(), HTTP_OK, HTTP_NOT_FOUND); + } catch (IOException e) { + throw new DockerAccessException(e, "Unable to retrieve container name for [%s]", containerIdOrName); + } + } + @Override public boolean hasImage(String name) throws DockerAccessException { String url = urlBuilder.inspectImage(name); diff --git a/src/main/java/io/fabric8/maven/docker/config/WaitConfiguration.java b/src/main/java/io/fabric8/maven/docker/config/WaitConfiguration.java index a943de0c1..85a54566f 100644 --- a/src/main/java/io/fabric8/maven/docker/config/WaitConfiguration.java +++ b/src/main/java/io/fabric8/maven/docker/config/WaitConfiguration.java @@ -119,6 +119,7 @@ public static class Builder { private String tcpHost; private TcpConfigMode tcpMode; private Integer exit; + private Boolean breakOnError = false; public Builder time(int time) { this.time = time; @@ -185,7 +186,7 @@ public Builder tcpMode(String tcpMode) { public WaitConfiguration build() { return new WaitConfiguration(time, - postStart != null || preStop != null ? new ExecConfiguration(postStart, preStop) : null, + postStart != null || preStop != null ? new ExecConfiguration(postStart, preStop, breakOnError != null ? breakOnError : false) : null, url != null ? new HttpConfiguration(url,method,status) : null, tcpPorts != null ? new TcpConfiguration(tcpMode, tcpHost, tcpPorts) : null, healthy, @@ -204,6 +205,11 @@ public Builder postStart(String command) { this.postStart = command; return this; } + + public Builder breakOnError(Boolean stop) { + this.breakOnError = stop; + return this; + } } public static class ExecConfiguration implements Serializable { @@ -213,11 +219,15 @@ public static class ExecConfiguration implements Serializable { @Parameter private String preStop; + @Parameter + private boolean breakOnError; + public ExecConfiguration() {} - public ExecConfiguration(String postStart, String preStop) { + public ExecConfiguration(String postStart, String preStop, boolean breakOnError) { this.postStart = postStart; this.preStop = preStop; + this.breakOnError = breakOnError; } public String getPostStart() { @@ -227,6 +237,10 @@ public String getPostStart() { public String getPreStop() { return preStop; } + + public boolean isBreakOnError() { + return breakOnError; + } } public static class HttpConfiguration implements Serializable { 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 a44766ec4..ca6f5db4d 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 @@ -86,8 +86,6 @@ public enum ConfigKey { NETWORK_ALIAS("network.alias"), PORT_PROPERTY_FILE, PORTS, - POST_START("wait.exec.postStart"), - PRE_STOP("wait.exec.preStop"), PRIVILEGED, REGISTRY, RESTART_POLICY_NAME("restartPolicy.name"), @@ -110,6 +108,9 @@ public enum ConfigKey { WAIT_HTTP_METHOD("wait.http.method"), WAIT_HTTP_STATUS("wait.http.status"), WAIT_KILL("wait.kill"), + WAIT_EXEC_POST_START("wait.exec.postStart"), + WAIT_EXEC_PRE_STOP("wait.exec.preStop"), + WAIT_EXEC_BREAK_ON_ERROR("wait.exec.breakOnError"), WAIT_EXIT("wait.exit"), WAIT_SHUTDOWN("wait.shutdown"), WAIT_TCP_MODE("wait.tcp.mode"), 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 6bec83fdc..113af5137 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 @@ -245,8 +245,9 @@ private WaitConfiguration extractWaitConfig(String prefix, Properties properties return new WaitConfiguration.Builder() .time(asInt(withPrefix(prefix, WAIT_TIME,properties))) .url(url) - .preStop(withPrefix(prefix, PRE_STOP, properties)) - .postStart(withPrefix(prefix, POST_START, properties)) + .preStop(withPrefix(prefix, WAIT_EXEC_PRE_STOP, properties)) + .postStart(withPrefix(prefix, WAIT_EXEC_POST_START, properties)) + .breakOnError(booleanWithPrefix(prefix, WAIT_EXEC_BREAK_ON_ERROR, properties)) .method(withPrefix(prefix, WAIT_HTTP_METHOD, properties)) .status(withPrefix(prefix, WAIT_HTTP_STATUS, properties)) .log(withPrefix(prefix, WAIT_LOG, properties)) diff --git a/src/main/java/io/fabric8/maven/docker/model/ContainerDetails.java b/src/main/java/io/fabric8/maven/docker/model/ContainerDetails.java index 518a13492..fa0470528 100644 --- a/src/main/java/io/fabric8/maven/docker/model/ContainerDetails.java +++ b/src/main/java/io/fabric8/maven/docker/model/ContainerDetails.java @@ -7,7 +7,7 @@ import java.util.*; -public class ContainerDetails implements InspectedContainer { +public class ContainerDetails implements Container { static final String CONFIG = "Config"; static final String CREATED = "Created"; @@ -136,14 +136,12 @@ public Integer getExitCode() { return state.getInt(EXIT_CODE); } - @Override public boolean isHealthy() { final JSONObject state = json.getJSONObject(STATE); // always indicate healthy for docker hosts that do not support health checks. return !state.has(HEALTH) || HEALTH_STATUS_HEALTHY.equals(state.getJSONObject(HEALTH).getString(STATUS)); } - @Override public String getHealthcheck() { if (!json.getJSONObject(CONFIG).has(HEALTHCHECK) || !json.getJSONObject(CONFIG).getJSONObject(HEALTHCHECK).has(TEST)) { diff --git a/src/main/java/io/fabric8/maven/docker/model/ExecDetails.java b/src/main/java/io/fabric8/maven/docker/model/ExecDetails.java new file mode 100644 index 000000000..b9cb9fa02 --- /dev/null +++ b/src/main/java/io/fabric8/maven/docker/model/ExecDetails.java @@ -0,0 +1,60 @@ +package io.fabric8.maven.docker.model; + +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * Model class holding details of the result of an exec command on a running container. + */ +public class ExecDetails { + private static final String EXIT_CODE = "ExitCode"; + private static final String RUNNING = "Running"; + private static final String ENTRY_POINT = "entrypoint"; + private static final String ARGUMENTS = "arguments"; + + private static final String PROCESS_CONFIG = "ProcessConfig"; + + private final JSONObject json; + + public ExecDetails(JSONObject json) { + this.json = json; + } + + public boolean isRunning() { + return json.getBoolean(RUNNING); + } + + public Integer getExitCode() { + if (isRunning()) { + return null; + } + return json.getInt(EXIT_CODE); + } + + public String getEntryPoint() { + if (!json.has(PROCESS_CONFIG)) { + return null; + } + JSONObject processConfig = json.getJSONObject(PROCESS_CONFIG); + if (!processConfig.has(ENTRY_POINT)) { + return null; + } + return processConfig.getString(ENTRY_POINT); + } + + public String[] getArguments() { + if (!json.has(PROCESS_CONFIG)) { + return null; + } + JSONObject processConfig = json.getJSONObject(PROCESS_CONFIG); + if (!processConfig.has(ARGUMENTS)) { + return null; + } + JSONArray arguments = processConfig.getJSONArray(ARGUMENTS); + String[] result = new String[arguments.length()]; + for (int i = 0; i < arguments.length(); i++) { + result[i] = arguments.getString(i); + } + return result; + } +} diff --git a/src/main/java/io/fabric8/maven/docker/model/InspectedContainer.java b/src/main/java/io/fabric8/maven/docker/model/InspectedContainer.java deleted file mode 100644 index 39eea529a..000000000 --- a/src/main/java/io/fabric8/maven/docker/model/InspectedContainer.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.fabric8.maven.docker.model; -/* - * - * 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. - */ - -/** - * Interface representing an inspected container - * - * @author daniel - * @since 17/02/15 - */ -public interface InspectedContainer extends Container { - - /** - * The Health Status of this container (if applicable). - * @return {@code false} if the container has a configured healthcheck and has not the - * Health Status "healthy". Returns {@code true} otherwise. - */ - boolean isHealthy(); - - /** - * @return the docker healthcheck command that is configured for this container. - */ - String getHealthcheck(); - -} diff --git a/src/main/java/io/fabric8/maven/docker/service/ContainerTracker.java b/src/main/java/io/fabric8/maven/docker/service/ContainerTracker.java index fdef8743c..3b94e0ac2 100644 --- a/src/main/java/io/fabric8/maven/docker/service/ContainerTracker.java +++ b/src/main/java/io/fabric8/maven/docker/service/ContainerTracker.java @@ -188,8 +188,10 @@ static class ContainerShutdownDescriptor { // How long to wait after stop to kill container (in seconds) private final int killGracePeriod; - // Command to call before stopping container + + // Command to call before stopping container and whether to stop the build private String preStop; + private boolean breakOnError = false; ContainerShutdownDescriptor(ImageConfiguration imageConfig, String containerId) { this.imageConfig = imageConfig; @@ -201,6 +203,7 @@ static class ContainerShutdownDescriptor { this.killGracePeriod = waitConfig != null ? waitConfig.getKill() : 0; if (waitConfig != null && waitConfig.getExec() != null) { this.preStop = waitConfig.getExec().getPreStop(); + this.breakOnError = waitConfig.getExec().isBreakOnError(); } } @@ -232,6 +235,10 @@ public String getPreStop() { return preStop; } + public boolean isBreakOnError() { + return breakOnError; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/io/fabric8/maven/docker/service/RunService.java b/src/main/java/io/fabric8/maven/docker/service/RunService.java index 714ea6f69..86da86fda 100644 --- a/src/main/java/io/fabric8/maven/docker/service/RunService.java +++ b/src/main/java/io/fabric8/maven/docker/service/RunService.java @@ -22,7 +22,10 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; +import io.fabric8.maven.docker.access.ExecException; import io.fabric8.maven.docker.model.Container; +import io.fabric8.maven.docker.model.ContainerDetails; +import io.fabric8.maven.docker.model.ExecDetails; import io.fabric8.maven.docker.log.LogOutputSpecFactory; import io.fabric8.maven.docker.access.*; import io.fabric8.maven.docker.config.*; @@ -77,11 +80,18 @@ public RunService(DockerAccess docker, * @throws DockerAccessException if access to the docker backend fails */ public String execInContainer(String containerId, String command, ImageConfiguration imageConfiguration) - throws DockerAccessException { + throws DockerAccessException, ExecException { Arguments arguments = new Arguments(); arguments.setExec(Arrays.asList(EnvUtil.splitOnSpaceWithEscape(command))); String execContainerId = docker.createExecContainer(containerId, arguments); docker.startExecContainer(execContainerId, logConfig.createSpec(containerId, imageConfiguration)); + + ExecDetails execContainer = docker.getExecContainer(execContainerId); + Integer exitCode = execContainer.getExitCode(); + if (exitCode != null && exitCode != 0) { + ContainerDetails container = docker.getContainer(containerId); + throw new ExecException(execContainer, container); + } return execContainerId; } @@ -127,7 +137,7 @@ public void stopContainer(String containerId, ImageConfiguration imageConfig, boolean keepContainer, boolean removeVolumes) - throws DockerAccessException { + throws DockerAccessException, ExecException { ContainerTracker.ContainerShutdownDescriptor descriptor = new ContainerTracker.ContainerShutdownDescriptor(imageConfig, containerId); shutdown(descriptor, keepContainer, removeVolumes); @@ -145,7 +155,7 @@ public void stopContainer(String containerId, public void stopPreviouslyStartedContainer(String containerId, boolean keepContainer, boolean removeVolumes) - throws DockerAccessException { + throws DockerAccessException, ExecException { ContainerTracker.ContainerShutdownDescriptor descriptor = tracker.removeContainer(containerId); if (descriptor != null) { shutdown(descriptor, keepContainer, removeVolumes); @@ -154,16 +164,15 @@ public void stopPreviouslyStartedContainer(String containerId, /** * Stop all registered container - * @param keepContainer whether to keep container or to remove them after stoppings + * @param keepContainer whether to keep container or to remove them after stopping * @param removeVolumes whether to remove volumes after stopping - * * @throws DockerAccessException if during stopping of a container sth fails */ public void stopStartedContainers(boolean keepContainer, boolean removeVolumes, boolean removeCustomNetworks, PomLabel pomLabel) - throws DockerAccessException { + throws DockerAccessException, ExecException { Set networksToRemove = new HashSet<>(); for (ContainerTracker.ContainerShutdownDescriptor descriptor : tracker.removeShutdownDescriptors(pomLabel)) { collectCustomNetworks(networksToRemove, descriptor, removeCustomNetworks); @@ -222,7 +231,7 @@ public void addShutdownHookForStoppingContainers(final boolean keepContainer, fi public void run() { try { stopStartedContainers(keepContainer, removeVolumes, removeCustomNetworks, null); - } catch (DockerAccessException e) { + } catch (DockerAccessException | ExecException e) { log.error("Error while stopping containers: %s", e.getMessage()); } } @@ -412,7 +421,7 @@ private void updateMappedPortsAndAddresses(String containerId, PortMapping mappe } private void shutdown(ContainerTracker.ContainerShutdownDescriptor descriptor, boolean keepContainer, boolean removeVolumes) - throws DockerAccessException { + throws DockerAccessException, ExecException { String containerId = descriptor.getContainerId(); if (descriptor.getPreStop() != null) { @@ -420,6 +429,12 @@ private void shutdown(ContainerTracker.ContainerShutdownDescriptor descriptor, b execInContainer(containerId, descriptor.getPreStop(), descriptor.getImageConfiguration()); } catch (DockerAccessException e) { log.error("%s", e.getMessage()); + } catch (ExecException e) { + if (descriptor.isBreakOnError()) { + throw e; + } else { + log.warn("Cannot run preStop: %s", e.getMessage()); + } } } diff --git a/src/main/java/io/fabric8/maven/docker/service/WatchService.java b/src/main/java/io/fabric8/maven/docker/service/WatchService.java index 0a4e78b9e..40c1de25e 100644 --- a/src/main/java/io/fabric8/maven/docker/service/WatchService.java +++ b/src/main/java/io/fabric8/maven/docker/service/WatchService.java @@ -12,6 +12,7 @@ import io.fabric8.maven.docker.access.DockerAccess; import io.fabric8.maven.docker.access.DockerAccessException; +import io.fabric8.maven.docker.access.ExecException; import io.fabric8.maven.docker.access.PortMapping; import io.fabric8.maven.docker.assembly.AssemblyFiles; import io.fabric8.maven.docker.config.ImageConfiguration; @@ -131,16 +132,16 @@ public void run() { imageConfig.getName(), mojoParameters); dockerAccess.copyArchive(watcher.getContainerId(), changedFilesArchive, containerBaseDir); callPostExec(watcher); - } catch (MojoExecutionException | IOException e) { + } catch (MojoExecutionException | IOException | ExecException e) { log.error("%s: Error when copying files to container %s: %s", - imageConfig.getDescription(), watcher.getContainerId(), e.getMessage()); + imageConfig.getDescription(), watcher.getContainerId(), e.getMessage()); } } } }; } - private void callPostExec(ImageWatcher watcher) throws DockerAccessException { + private void callPostExec(ImageWatcher watcher) throws DockerAccessException, ExecException { if (watcher.getPostExec() != null) { String containerId = watcher.getContainerId(); runService.execInContainer(containerId, watcher.getPostExec(), watcher.getImageConfiguration()); diff --git a/src/main/java/io/fabric8/maven/docker/wait/HealthCheckChecker.java b/src/main/java/io/fabric8/maven/docker/wait/HealthCheckChecker.java index 5ee94998f..51bf5d3dd 100644 --- a/src/main/java/io/fabric8/maven/docker/wait/HealthCheckChecker.java +++ b/src/main/java/io/fabric8/maven/docker/wait/HealthCheckChecker.java @@ -2,7 +2,7 @@ import io.fabric8.maven.docker.access.DockerAccess; import io.fabric8.maven.docker.access.DockerAccessException; -import io.fabric8.maven.docker.model.InspectedContainer; +import io.fabric8.maven.docker.model.ContainerDetails; import io.fabric8.maven.docker.util.Logger; /** @@ -28,7 +28,7 @@ public HealthCheckChecker(DockerAccess docker, String containerId, String imageC @Override public boolean check() { try { - final InspectedContainer container = docker.getContainer(containerId); + final ContainerDetails container = docker.getContainer(containerId); if (container == null) { log.debug("HealthWaitChecker: Container %s not found"); return false; @@ -59,7 +59,7 @@ public void cleanUp() {} @Override public String getLogLabel() { try { - final InspectedContainer container = docker.getContainer(containerId); + final ContainerDetails container = docker.getContainer(containerId); return String.format("on healthcheck '%s'",container != null ? container.getHealthcheck() : "[container not found]"); } catch (DockerAccessException e) { return String.format("on healthcheck [error fetching container: %s]", e.getMessage()); diff --git a/src/test/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandlerTest.java b/src/test/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandlerTest.java index 0a5a1d294..77d774b74 100644 --- a/src/test/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandlerTest.java +++ b/src/test/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandlerTest.java @@ -20,7 +20,6 @@ import io.fabric8.maven.docker.config.*; import io.fabric8.maven.docker.config.handler.AbstractConfigHandlerTest; -import io.fabric8.maven.docker.util.MojoParameters; import mockit.Expectations; import mockit.Mocked; import mockit.integration.junit4.JMockit; @@ -433,6 +432,7 @@ protected void validateRunConfiguration(RunImageConfiguration runConfig) { assertEquals("pattern", wait.getLog()); assertEquals("post_start_command", wait.getExec().getPostStart()); assertEquals("pre_stop_command", wait.getExec().getPreStop()); + assertTrue(wait.getExec().isBreakOnError()); assertEquals(5, wait.getTime()); assertEquals(0, wait.getExit().intValue()); @@ -514,8 +514,9 @@ private String[] getTestData() { k(ConfigKey.ULIMITS)+".4", "memlock=2048", k(ConfigKey.VOLUMES) + ".1", "/foo", k(ConfigKey.VOLUMES_FROM) + ".1", "from", - k(ConfigKey.PRE_STOP), "pre_stop_command", - k(ConfigKey.POST_START), "post_start_command", + k(ConfigKey.WAIT_EXEC_PRE_STOP), "pre_stop_command", + k(ConfigKey.WAIT_EXEC_POST_START), "post_start_command", + k(ConfigKey.WAIT_EXEC_BREAK_ON_ERROR), "true", k(ConfigKey.WAIT_LOG), "pattern", k(ConfigKey.WAIT_TIME), "5", k(ConfigKey.WAIT_EXIT), "0", diff --git a/src/test/java/io/fabric8/maven/docker/service/ContainerTrackerTest.java b/src/test/java/io/fabric8/maven/docker/service/ContainerTrackerTest.java index 69d87abee..3b5a65ecf 100644 --- a/src/test/java/io/fabric8/maven/docker/service/ContainerTrackerTest.java +++ b/src/test/java/io/fabric8/maven/docker/service/ContainerTrackerTest.java @@ -1,6 +1,6 @@ package io.fabric8.maven.docker.service; /* - * + * * Copyright 2016 Roland Huss * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,8 +52,8 @@ public void lookup() throws Exception { @Test public void removeContainer() throws Exception { String data[][] = new String[][] { - {"1", "name1", "alias1", "100", "200", "stop1", "label1"}, - {"2", "name2", "alias2", null, null, null, "label2"} + {"1", "name1", "alias1", "100", "200", "stop1", "label1", "false"}, + {"2", "name2", "alias2", null, null, null, "label2", "true"} }; List descs = registerAtTracker(data); @@ -72,9 +72,9 @@ public void removeContainer() throws Exception { public void removeDescriptors() throws Exception { String data[][] = new String[][] { - { "1", "name1", "alias1", "100", "200", "stop1", "label1" }, - { "2", "name2", "alias2", null, null, null, "label1" }, - { "3", "name3", null, null, null, null, "label2" } + { "1", "name1", "alias1", "100", "200", "stop1", "label1", "true" }, + { "2", "name2", "alias2", null, null, null, "label1", "false" }, + { "3", "name3", null, null, null, null, "label2", "true" } }; List descs = registerAtTracker(data); @@ -96,9 +96,9 @@ public void removeDescriptors() throws Exception { @Test public void removeAll() throws Exception { String data[][] = new String[][] { - { "1", "name1", "alias1", "100", "200", "stop1", "label1" }, - { "2", "name2", "alias2", null, null, null, "label1" }, - { "3", "name3", null, null, null, null, "label2" } + { "1", "name1", "alias1", "100", "200", "stop1", "label1", "true" }, + { "2", "name2", "alias2", null, null, null, "label1", "false" }, + { "3", "name3", null, null, null, null, "label2", "false" } }; List descs = registerAtTracker(data); @@ -124,6 +124,7 @@ private void verifyDescriptor(String[] d, ContainerTracker.ContainerShutdownDesc assertEquals(desc.getShutdownGracePeriod(),parseInt(d[3])); assertEquals(desc.getKillGracePeriod(),parseInt(d[4])); assertEquals(desc.getPreStop(),d[5]); + assertEquals(desc.isBreakOnError(), Boolean.parseBoolean(d[7])); assertNotNull(desc.getImageConfiguration()); } @@ -131,7 +132,7 @@ private List registerAtTracker(Str List descriptors = new ArrayList<>(); for (String[] d : data) { ImageConfiguration imageConfig = - getImageConfiguration(d[1], d[2], parseInt(d[3]), parseInt(d[4]), d[5]); + getImageConfiguration(d[1], d[2], parseInt(d[3]), parseInt(d[4]), d[5], Boolean.parseBoolean(d[7])); tracker.registerContainer(d[0], imageConfig, @@ -150,10 +151,10 @@ private PomLabel getPomLabel(String artifactId) { } private ImageConfiguration getImageConfiguration(String name, String alias) { - return getImageConfiguration(name, alias, 0,0,null); + return getImageConfiguration(name, alias, 0,0,null,false); } - private ImageConfiguration getImageConfiguration(String name, String alias, int shutdown,int kill, String preStop) { + private ImageConfiguration getImageConfiguration(String name, String alias, int shutdown,int kill, String preStop, boolean breakOnError) { WaitConfiguration waitConfig = null; if (shutdown != 0 && kill != 0) { WaitConfiguration.Builder builder = new WaitConfiguration.Builder() @@ -161,6 +162,7 @@ private ImageConfiguration getImageConfiguration(String name, String alias, int .kill(kill); if (preStop != null) { builder.preStop(preStop); + builder.breakOnError(breakOnError); } waitConfig = builder.build(); }