|
28 | 28 | import java.util.HashSet; |
29 | 29 | import java.util.List; |
30 | 30 | import java.util.Objects; |
| 31 | +import java.util.Optional; |
31 | 32 | import java.util.Set; |
32 | 33 | import java.util.concurrent.TimeUnit; |
33 | 34 | import java.util.concurrent.atomic.AtomicBoolean; |
34 | 35 | import java.util.function.Predicate; |
| 36 | +import java.util.stream.Collectors; |
35 | 37 | import java.util.stream.Stream; |
36 | 38 |
|
37 | 39 | /** |
@@ -113,120 +115,139 @@ public static DockerClientProviderStrategy getFirstValidStrategy(List<DockerClie |
113 | 115 | } |
114 | 116 |
|
115 | 117 | 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"); |
116 | 154 |
|
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 | + }); |
229 | 172 | }); |
| 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(); |
230 | 251 | } |
231 | 252 |
|
232 | 253 | public static DockerClient getClientForConfig(TransportConfig transportConfig) { |
|
0 commit comments