55import com .github .dockerjava .core .DockerClientConfig ;
66import com .github .dockerjava .netty .NettyDockerCmdExecFactory ;
77import com .google .common .base .Throwables ;
8+ import org .apache .commons .io .IOUtils ;
89import org .jetbrains .annotations .Nullable ;
10+ import org .rnorth .ducttape .TimeoutException ;
911import org .rnorth .ducttape .ratelimits .RateLimiter ;
1012import org .rnorth .ducttape .ratelimits .RateLimiterBuilder ;
1113import org .rnorth .ducttape .unreliables .Unreliables ;
1214import org .slf4j .Logger ;
1315import org .slf4j .LoggerFactory ;
16+ import org .testcontainers .utility .TestcontainersConfiguration ;
1417
1518import java .util .ArrayList ;
19+ import java .util .Comparator ;
1620import java .util .List ;
21+ import java .util .Objects ;
1722import java .util .concurrent .TimeUnit ;
23+ import java .util .stream .Stream ;
1824
1925/**
2026 * Mechanism to find a viable Docker client configuration according to the host system environment.
@@ -39,6 +45,17 @@ public abstract class DockerClientProviderStrategy {
3945 */
4046 public abstract String getDescription ();
4147
48+ protected boolean isApplicable () {
49+ return true ;
50+ }
51+
52+ /**
53+ * @return highest to lowest priority value
54+ */
55+ protected int getPriority () {
56+ return 0 ;
57+ }
58+
4259 protected static final Logger LOGGER = LoggerFactory .getLogger (DockerClientProviderStrategy .class );
4360
4461 /**
@@ -49,45 +66,70 @@ public abstract class DockerClientProviderStrategy {
4966 public static DockerClientProviderStrategy getFirstValidStrategy (List <DockerClientProviderStrategy > strategies ) {
5067 List <String > configurationFailures = new ArrayList <>();
5168
52- for (DockerClientProviderStrategy strategy : strategies ) {
53- try {
54- strategy .test ();
55- LOGGER .info ("Looking for Docker environment. Tried {}" , strategy .getDescription ());
56- return strategy ;
57- } catch (Exception | ExceptionInInitializerError | NoClassDefFoundError e ) {
58- @ Nullable String throwableMessage = e .getMessage ();
59- @ SuppressWarnings ("ThrowableResultOfMethodCallIgnored" )
60- Throwable rootCause = Throwables .getRootCause (e );
61- @ Nullable String rootCauseMessage = rootCause .getMessage ();
62-
63- String failureDescription ;
64- if (throwableMessage != null && throwableMessage .equals (rootCauseMessage )) {
65- failureDescription = String .format ("%s: failed with exception %s (%s)" ,
66- strategy .getClass ().getSimpleName (),
67- e .getClass ().getSimpleName (),
68- throwableMessage );
69- } else {
70- failureDescription = String .format ("%s: failed with exception %s (%s). Root cause %s (%s)" ,
71- strategy .getClass ().getSimpleName (),
72- e .getClass ().getSimpleName (),
73- throwableMessage ,
74- rootCause .getClass ().getSimpleName (),
75- rootCauseMessage
76- );
77- }
78- configurationFailures .add (failureDescription );
79-
80- LOGGER .debug (failureDescription );
81- }
82- }
83-
84- LOGGER .error ("Could not find a valid Docker environment. Please check configuration. Attempted configurations were:" );
85- for (String failureMessage : configurationFailures ) {
86- LOGGER .error (" " + failureMessage );
87- }
88- LOGGER .error ("As no valid configuration was found, execution cannot continue" );
89-
90- throw new IllegalStateException ("Could not find a valid Docker environment. Please see logs and check configuration" );
69+ return Stream
70+ .concat (
71+ Stream
72+ .of (TestcontainersConfiguration .getInstance ().getDockerClientStrategyClassName ())
73+ .filter (Objects ::nonNull )
74+ .flatMap (it -> {
75+ try {
76+ Class <? extends DockerClientProviderStrategy > strategyClass = (Class ) Thread .currentThread ().getContextClassLoader ().loadClass (it );
77+ return Stream .of (strategyClass .newInstance ());
78+ } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e ) {
79+ LOGGER .warn ("Can't instantiate a strategy from " + it , e );
80+ return Stream .empty ();
81+ }
82+ }),
83+ strategies
84+ .stream ()
85+ .filter (DockerClientProviderStrategy ::isApplicable )
86+ .sorted (Comparator .comparing (DockerClientProviderStrategy ::getPriority ).reversed ())
87+ )
88+ .flatMap (strategy -> {
89+ try {
90+ strategy .test ();
91+ LOGGER .info ("Found Docker environment with {}" , strategy .getDescription ());
92+
93+ TestcontainersConfiguration .getInstance ().updateGlobalConfig ("docker.client.strategy" , strategy .getClass ().getName ());
94+
95+ return Stream .of (strategy );
96+ } catch (Exception | ExceptionInInitializerError | NoClassDefFoundError e ) {
97+ @ Nullable String throwableMessage = e .getMessage ();
98+ @ SuppressWarnings ("ThrowableResultOfMethodCallIgnored" )
99+ Throwable rootCause = Throwables .getRootCause (e );
100+ @ Nullable String rootCauseMessage = rootCause .getMessage ();
101+
102+ String failureDescription ;
103+ if (throwableMessage != null && throwableMessage .equals (rootCauseMessage )) {
104+ failureDescription = String .format ("%s: failed with exception %s (%s)" ,
105+ strategy .getClass ().getSimpleName (),
106+ e .getClass ().getSimpleName (),
107+ throwableMessage );
108+ } else {
109+ failureDescription = String .format ("%s: failed with exception %s (%s). Root cause %s (%s)" ,
110+ strategy .getClass ().getSimpleName (),
111+ e .getClass ().getSimpleName (),
112+ throwableMessage ,
113+ rootCause .getClass ().getSimpleName (),
114+ rootCauseMessage
115+ );
116+ }
117+ configurationFailures .add (failureDescription );
118+
119+ LOGGER .debug (failureDescription );
120+ return Stream .empty ();
121+ }
122+ })
123+ .findAny ()
124+ .orElseThrow (() -> {
125+ LOGGER .error ("Could not find a valid Docker environment. Please check configuration. Attempted configurations were:" );
126+ for (String failureMessage : configurationFailures ) {
127+ LOGGER .error (" " + failureMessage );
128+ }
129+ LOGGER .error ("As no valid configuration was found, execution cannot continue" );
130+
131+ return new IllegalStateException ("Could not find a valid Docker environment. Please see logs and check configuration" );
132+ });
91133 }
92134
93135 /**
@@ -105,13 +147,18 @@ protected DockerClient getClientForConfig(DockerClientConfig config) {
105147 }
106148
107149 protected void ping (DockerClient client , int timeoutInSeconds ) {
108- Unreliables .retryUntilSuccess (timeoutInSeconds , TimeUnit .SECONDS , () -> {
109- return PING_RATE_LIMITER .getWhenReady (() -> {
110- LOGGER .debug ("Pinging docker daemon..." );
111- client .pingCmd ().exec ();
112- return true ;
150+ try {
151+ Unreliables .retryUntilSuccess (timeoutInSeconds , TimeUnit .SECONDS , () -> {
152+ return PING_RATE_LIMITER .getWhenReady (() -> {
153+ LOGGER .debug ("Pinging docker daemon..." );
154+ client .pingCmd ().exec ();
155+ return true ;
156+ });
113157 });
114- });
158+ } catch (TimeoutException e ) {
159+ IOUtils .closeQuietly (client );
160+ throw e ;
161+ }
115162 }
116163
117164 public String getDockerHostIpAddress () {
0 commit comments