package org.testcontainers.dockerclient; import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.core.DockerClientBuilder; import com.github.dockerjava.core.DockerClientConfig; import com.github.dockerjava.netty.NettyDockerCmdExecFactory; import com.google.common.base.Throwables; import org.jetbrains.annotations.Nullable; import org.rnorth.ducttape.ratelimits.RateLimiter; import org.rnorth.ducttape.ratelimits.RateLimiterBuilder; import org.rnorth.ducttape.unreliables.Unreliables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * Mechanism to find a viable Docker client configuration according to the host system environment. */ public abstract class DockerClientProviderStrategy { protected DockerClient client; protected DockerClientConfig config; private static final RateLimiter PING_RATE_LIMITER = RateLimiterBuilder.newBuilder() .withRate(2, TimeUnit.SECONDS) .withConstantThroughput() .build(); /** * @throws InvalidConfigurationException if this strategy fails */ public abstract void test() throws InvalidConfigurationException; /** * @return a short textual description of the strategy */ public abstract String getDescription(); protected static final Logger LOGGER = LoggerFactory.getLogger(DockerClientProviderStrategy.class); /** * Determine the right DockerClientConfig to use for building clients by trial-and-error. * * @return a working DockerClientConfig, as determined by successful execution of a ping command */ public static DockerClientProviderStrategy getFirstValidStrategy(List<DockerClientProviderStrategy> strategies) { List<String> configurationFailures = new ArrayList<>(); for (DockerClientProviderStrategy strategy : strategies) { try { strategy.test(); LOGGER.info("Looking for Docker environment. Tried {}", strategy.getDescription()); return strategy; } catch (Exception | ExceptionInInitializerError | NoClassDefFoundError e) { @Nullable String throwableMessage = e.getMessage(); @SuppressWarnings("ThrowableResultOfMethodCallIgnored") Throwable rootCause = Throwables.getRootCause(e); @Nullable String rootCauseMessage = rootCause.getMessage(); String failureDescription; if (throwableMessage != null && throwableMessage.equals(rootCauseMessage)) { failureDescription = String.format("%s: failed with exception %s (%s)", strategy.getClass().getSimpleName(), e.getClass().getSimpleName(), throwableMessage); } else { failureDescription = String.format("%s: failed with exception %s (%s). Root cause %s (%s)", strategy.getClass().getSimpleName(), e.getClass().getSimpleName(), throwableMessage, rootCause.getClass().getSimpleName(), rootCauseMessage ); } configurationFailures.add(failureDescription); LOGGER.debug(failureDescription); } } LOGGER.error("Could not find a valid Docker environment. Please check configuration. Attempted configurations were:"); for (String failureMessage : configurationFailures) { LOGGER.error(" " + failureMessage); } LOGGER.error("As no valid configuration was found, execution cannot continue"); throw new IllegalStateException("Could not find a valid Docker environment. Please see logs and check configuration"); } /** * @return a usable, tested, Docker client configuration for the host system environment */ public DockerClient getClient() { return new AuditLoggingDockerClient(client); } protected DockerClient getClientForConfig(DockerClientConfig config) { return DockerClientBuilder .getInstance(config) .withDockerCmdExecFactory(new NettyDockerCmdExecFactory()) .build(); } protected void ping(DockerClient client, int timeoutInSeconds) { Unreliables.retryUntilSuccess(timeoutInSeconds, TimeUnit.SECONDS, () -> { return PING_RATE_LIMITER.getWhenReady(() -> { LOGGER.debug("Pinging docker daemon..."); client.pingCmd().exec(); return true; }); }); } public String getDockerHostIpAddress() { return DockerClientConfigUtils.getDockerHostIpAddress(this.config); } class InvalidConfigurationException extends RuntimeException { public InvalidConfigurationException(String s) { super(s); } public InvalidConfigurationException(String message, Throwable cause) { super(message, cause); } } }