Skip to content

Commit abf1846

Browse files
authored
Give EnvironmentAndSystemPropertyClientProviderStrategy the highest priority (#4472)
1 parent d44e616 commit abf1846

File tree

1 file changed

+133
-112
lines changed

1 file changed

+133
-112
lines changed

core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java

Lines changed: 133 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@
2828
import java.util.HashSet;
2929
import java.util.List;
3030
import java.util.Objects;
31+
import java.util.Optional;
3132
import java.util.Set;
3233
import java.util.concurrent.TimeUnit;
3334
import java.util.concurrent.atomic.AtomicBoolean;
3435
import java.util.function.Predicate;
36+
import java.util.stream.Collectors;
3537
import java.util.stream.Stream;
3638

3739
/**
@@ -113,120 +115,139 @@ public static DockerClientProviderStrategy getFirstValidStrategy(List<DockerClie
113115
}
114116

115117
List<String> configurationFailures = new ArrayList<>();
118+
List<DockerClientProviderStrategy> allStrategies = new ArrayList<>();
119+
120+
// The environment has the highest priority
121+
allStrategies.add(new EnvironmentAndSystemPropertyClientProviderStrategy());
122+
123+
// Next strategy to try out is the one configured using the Testcontainers configuration mechanism
124+
loadConfiguredStrategy().ifPresent(allStrategies::add);
125+
126+
// Finally, add all other strategies ordered by their internal priority
127+
strategies
128+
.stream()
129+
.sorted(Comparator.comparing(DockerClientProviderStrategy::getPriority).reversed())
130+
.collect(Collectors.toCollection(() -> allStrategies));
131+
132+
133+
Predicate<DockerClientProviderStrategy> distinctStrategyClassPredicate = new Predicate<DockerClientProviderStrategy>() {
134+
final Set<Class<? extends DockerClientProviderStrategy>> classes = new HashSet<>();
135+
136+
@Override
137+
public boolean test(DockerClientProviderStrategy dockerClientProviderStrategy) {
138+
return classes.add(dockerClientProviderStrategy.getClass());
139+
}
140+
};
141+
142+
return allStrategies
143+
.stream()
144+
.filter(distinctStrategyClassPredicate)
145+
.filter(DockerClientProviderStrategy::isApplicable)
146+
.filter(strategy -> tryOutStrategy(configurationFailures, strategy))
147+
.findFirst()
148+
.orElseThrow(() -> {
149+
log.error("Could not find a valid Docker environment. Please check configuration. Attempted configurations were:");
150+
for (String failureMessage : configurationFailures) {
151+
log.error(" " + failureMessage);
152+
}
153+
log.error("As no valid configuration was found, execution cannot continue");
116154

117-
String dockerClientStrategyClassName = TestcontainersConfiguration.getInstance().getDockerClientStrategyClassName();
118-
return Stream
119-
.concat(
120-
Stream
121-
.of(dockerClientStrategyClassName)
122-
.filter(Objects::nonNull)
123-
.flatMap(it -> {
124-
try {
125-
Class<? extends DockerClientProviderStrategy> strategyClass = (Class) Thread.currentThread().getContextClassLoader().loadClass(it);
126-
return Stream.of(strategyClass.newInstance());
127-
} catch (ClassNotFoundException e) {
128-
log.warn("Can't instantiate a strategy from {} (ClassNotFoundException). " +
129-
"This probably means that cached configuration refers to a client provider " +
130-
"class that is not available in this version of Testcontainers. Other " +
131-
"strategies will be tried instead.", it);
132-
return Stream.empty();
133-
} catch (InstantiationException | IllegalAccessException e) {
134-
log.warn("Can't instantiate a strategy from {}", it, e);
135-
return Stream.empty();
136-
}
137-
})
138-
// Ignore persisted strategy if it's not persistable anymore
139-
.filter(DockerClientProviderStrategy::isPersistable)
140-
.peek(strategy -> log.info("Loaded {} from ~/.testcontainers.properties, will try it first", strategy.getClass().getName())),
141-
strategies
142-
.stream()
143-
.sorted(Comparator.comparing(DockerClientProviderStrategy::getPriority).reversed())
144-
)
145-
.filter(new Predicate<DockerClientProviderStrategy>() {
146-
147-
final Set<Class<? extends DockerClientProviderStrategy>> classes = new HashSet<>();
148-
149-
@Override
150-
public boolean test(DockerClientProviderStrategy dockerClientProviderStrategy) {
151-
return classes.add(dockerClientProviderStrategy.getClass());
152-
}
153-
})
154-
.filter(DockerClientProviderStrategy::isApplicable)
155-
.flatMap(strategy -> {
156-
try {
157-
DockerClient dockerClient = strategy.getDockerClient();
158-
159-
Info info;
160-
try {
161-
info = Unreliables.retryUntilSuccess(TestcontainersConfiguration.getInstance().getClientPingTimeout(), TimeUnit.SECONDS, () -> {
162-
return strategy.PING_RATE_LIMITER.getWhenReady(() -> {
163-
log.debug("Pinging docker daemon...");
164-
return dockerClient.infoCmd().exec();
165-
});
166-
});
167-
} catch (TimeoutException e) {
168-
IOUtils.closeQuietly(dockerClient);
169-
throw e;
170-
}
171-
log.info("Found Docker environment with {}", strategy.getDescription());
172-
log.debug(
173-
"Transport type: '{}', Docker host: '{}'",
174-
TestcontainersConfiguration.getInstance().getTransportType(),
175-
strategy.getTransportConfig().getDockerHost()
176-
);
177-
178-
log.debug("Checking Docker OS type for {}", strategy.getDescription());
179-
String osType = info.getOsType();
180-
if (StringUtils.isBlank(osType)) {
181-
log.warn("Could not determine Docker OS type");
182-
} else if (!osType.equals("linux")) {
183-
log.warn("{} is currently not supported", osType);
184-
throw new InvalidConfigurationException(osType + " containers are currently not supported");
185-
}
186-
187-
if (strategy.isPersistable()) {
188-
TestcontainersConfiguration.getInstance().updateUserConfig("docker.client.strategy", strategy.getClass().getName());
189-
}
190-
191-
return Stream.of(strategy);
192-
} catch (Exception | ExceptionInInitializerError | NoClassDefFoundError e) {
193-
@Nullable String throwableMessage = e.getMessage();
194-
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
195-
Throwable rootCause = Throwables.getRootCause(e);
196-
@Nullable String rootCauseMessage = rootCause.getMessage();
197-
198-
String failureDescription;
199-
if (throwableMessage != null && throwableMessage.equals(rootCauseMessage)) {
200-
failureDescription = String.format("%s: failed with exception %s (%s)",
201-
strategy.getClass().getSimpleName(),
202-
e.getClass().getSimpleName(),
203-
throwableMessage);
204-
} else {
205-
failureDescription = String.format("%s: failed with exception %s (%s). Root cause %s (%s)",
206-
strategy.getClass().getSimpleName(),
207-
e.getClass().getSimpleName(),
208-
throwableMessage,
209-
rootCause.getClass().getSimpleName(),
210-
rootCauseMessage
211-
);
212-
}
213-
configurationFailures.add(failureDescription);
214-
215-
log.debug(failureDescription);
216-
return Stream.empty();
217-
}
218-
})
219-
.findAny()
220-
.orElseThrow(() -> {
221-
log.error("Could not find a valid Docker environment. Please check configuration. Attempted configurations were:");
222-
for (String failureMessage : configurationFailures) {
223-
log.error(" " + failureMessage);
224-
}
225-
log.error("As no valid configuration was found, execution cannot continue");
226-
227-
FAIL_FAST_ALWAYS.set(true);
228-
return new IllegalStateException("Could not find a valid Docker environment. Please see logs and check configuration");
155+
FAIL_FAST_ALWAYS.set(true);
156+
return new IllegalStateException("Could not find a valid Docker environment. Please see logs and check configuration");
157+
});
158+
}
159+
160+
private static boolean tryOutStrategy(List<String> configurationFailures, DockerClientProviderStrategy strategy) {
161+
try {
162+
log.debug("Trying out strategy: {}", strategy.getClass().getSimpleName());
163+
DockerClient dockerClient = strategy.getDockerClient();
164+
165+
Info info;
166+
try {
167+
info = Unreliables.retryUntilSuccess(TestcontainersConfiguration.getInstance().getClientPingTimeout(), TimeUnit.SECONDS, () -> {
168+
return strategy.PING_RATE_LIMITER.getWhenReady(() -> {
169+
log.debug("Pinging docker daemon...");
170+
return dockerClient.infoCmd().exec();
171+
});
229172
});
173+
} catch (TimeoutException e) {
174+
IOUtils.closeQuietly(dockerClient);
175+
throw e;
176+
}
177+
log.info("Found Docker environment with {}", strategy.getDescription());
178+
log.debug(
179+
"Transport type: '{}', Docker host: '{}'",
180+
TestcontainersConfiguration.getInstance().getTransportType(),
181+
strategy.getTransportConfig().getDockerHost()
182+
);
183+
184+
log.debug("Checking Docker OS type for {}", strategy.getDescription());
185+
String osType = info.getOsType();
186+
if (StringUtils.isBlank(osType)) {
187+
log.warn("Could not determine Docker OS type");
188+
} else if (!osType.equals("linux")) {
189+
log.warn("{} is currently not supported", osType);
190+
throw new InvalidConfigurationException(osType + " containers are currently not supported");
191+
}
192+
193+
if (strategy.isPersistable()) {
194+
TestcontainersConfiguration.getInstance().updateUserConfig("docker.client.strategy", strategy.getClass().getName());
195+
}
196+
197+
return true;
198+
} catch (Exception | ExceptionInInitializerError | NoClassDefFoundError e) {
199+
@Nullable String throwableMessage = e.getMessage();
200+
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
201+
Throwable rootCause = Throwables.getRootCause(e);
202+
@Nullable String rootCauseMessage = rootCause.getMessage();
203+
204+
String failureDescription;
205+
if (throwableMessage != null && throwableMessage.equals(rootCauseMessage)) {
206+
failureDescription = String.format("%s: failed with exception %s (%s)",
207+
strategy.getClass().getSimpleName(),
208+
e.getClass().getSimpleName(),
209+
throwableMessage);
210+
} else {
211+
failureDescription = String.format("%s: failed with exception %s (%s). Root cause %s (%s)",
212+
strategy.getClass().getSimpleName(),
213+
e.getClass().getSimpleName(),
214+
throwableMessage,
215+
rootCause.getClass().getSimpleName(),
216+
rootCauseMessage
217+
);
218+
}
219+
configurationFailures.add(failureDescription);
220+
221+
log.debug(failureDescription);
222+
return false;
223+
}
224+
}
225+
226+
private static Optional<? extends DockerClientProviderStrategy> loadConfiguredStrategy() {
227+
String configuredDockerClientStrategyClassName = TestcontainersConfiguration.getInstance().getDockerClientStrategyClassName();
228+
229+
return Stream
230+
.of(configuredDockerClientStrategyClassName)
231+
.filter(Objects::nonNull)
232+
.flatMap(it -> {
233+
try {
234+
Class<? extends DockerClientProviderStrategy> strategyClass = (Class) Thread.currentThread().getContextClassLoader().loadClass(it);
235+
return Stream.of(strategyClass.newInstance());
236+
} catch (ClassNotFoundException e) {
237+
log.warn("Can't instantiate a strategy from {} (ClassNotFoundException). " +
238+
"This probably means that cached configuration refers to a client provider " +
239+
"class that is not available in this version of Testcontainers. Other " +
240+
"strategies will be tried instead.", it);
241+
return Stream.empty();
242+
} catch (InstantiationException | IllegalAccessException e) {
243+
log.warn("Can't instantiate a strategy from {}", it, e);
244+
return Stream.empty();
245+
}
246+
})
247+
// Ignore persisted strategy if it's not persistable anymore
248+
.filter(DockerClientProviderStrategy::isPersistable)
249+
.peek(strategy -> log.info("Loaded {} from ~/.testcontainers.properties, will try it first", strategy.getClass().getName()))
250+
.findFirst();
230251
}
231252

232253
public static DockerClient getClientForConfig(TransportConfig transportConfig) {

0 commit comments

Comments
 (0)