Skip to content

Commit 6bb5526

Browse files
authored
Merge branch 'main' into reusable_exposehostports
2 parents a818570 + 994b385 commit 6bb5526

File tree

47 files changed

+627
-269
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+627
-269
lines changed

.github/actions/setup-java/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: Sets up Java version
33
runs:
44
using: "composite"
55
steps:
6-
- uses: actions/setup-java@v3
6+
- uses: actions/setup-java@v4
77
with:
88
java-version: '8'
99
distribution: temurin

.github/actions/setup-junit-report/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ runs:
44
using: "composite"
55
steps:
66
- name: Publish Test Report
7-
uses: mikepenz/action-junit-report@v3
7+
uses: mikepenz/action-junit-report@v4
88
if: always() # always run even if the previous step fails
99
with:
1010
report_paths: '**/build/test-results/test/TEST-*.xml'

.github/settings.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ branches:
388388
# Required. Require branches to be up to date before merging.
389389
strict: true
390390
# Required. The list of status checks to require in order to merge into this branch
391-
contexts: ["core", "check_docs_examples", "in-docker_test", "ci/circleci: minimal_core", "test"]
391+
contexts: ["core", "check_docs_examples (:docs:examples:check)", "in-docker_test", "ci/circleci: minimal_core", "test"]
392392
# Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable.
393393
enforce_admins: false
394394
# Prevent merge commits from being pushed to matching branches

.github/workflows/update-gradle-wrapper.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ jobs:
2424
repo-token: ${{ secrets.GITHUB_TOKEN }}
2525
labels: dependencies
2626

27-
- uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # v1.0.3
27+
- uses: gradle/actions/wrapper-validation@v3

core/src/main/java/org/testcontainers/DockerClientFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ static Map<String, String> markerLabels() {
7777
return Collections.unmodifiableMap(labels);
7878
}
7979

80-
private static final DockerImageName TINY_IMAGE = DockerImageName.parse("alpine:3.16");
80+
private static final DockerImageName TINY_IMAGE = DockerImageName.parse("alpine:3.17");
8181

8282
private static DockerClientFactory instance;
8383

core/src/main/java/org/testcontainers/containers/ComposeContainer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ public void stop() {
165165
if (removeImages != null) {
166166
cmd += " --rmi " + removeImages.dockerRemoveImagesType();
167167
}
168-
this.composeDelegate.runWithCompose(this.localCompose, cmd);
168+
this.composeDelegate.runWithCompose(this.localCompose, cmd, this.env);
169169
} finally {
170170
this.project = this.composeDelegate.randomProjectId();
171171
}

core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public void stop() {
172172
if (removeImages != null) {
173173
cmd += " --rmi " + removeImages.dockerRemoveImagesType();
174174
}
175-
this.composeDelegate.runWithCompose(this.localCompose, cmd);
175+
this.composeDelegate.runWithCompose(this.localCompose, cmd, this.env);
176176
} finally {
177177
this.project = this.composeDelegate.randomProjectId();
178178
}

core/src/main/java/org/testcontainers/containers/PortForwardingContainer.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,9 @@ public enum PortForwardingContainer {
2424

2525
private static ContainerDef DEFINITION = new ContainerDef() {
2626
{
27-
setImage(DockerImageName.parse("testcontainers/sshd:1.1.0"));
27+
setImage(DockerImageName.parse("testcontainers/sshd:1.2.0"));
2828
addExposedTcpPort(22);
2929
addEnvVar("PASSWORD", PASSWORD);
30-
setCommand(
31-
"sh",
32-
"-c",
33-
// Disable ipv6 & Make it listen on all interfaces, not just localhost
34-
// Enable algorithms supported by our ssh client library
35-
"echo \"root:$PASSWORD\" | chpasswd && /usr/sbin/sshd -D -o PermitRootLogin=yes " +
36-
"-o AddressFamily=inet -o GatewayPorts=yes -o AllowAgentForwarding=yes -o AllowTcpForwarding=yes " +
37-
"-o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostkeyAlgorithms=+ssh-rsa "
38-
);
3930
}
4031
};
4132

core/src/main/java/org/testcontainers/images/RemoteDockerImage.java

Lines changed: 80 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import lombok.SneakyThrows;
1212
import lombok.ToString;
1313
import lombok.With;
14+
import org.awaitility.Awaitility;
15+
import org.awaitility.pollinterval.IterativePollInterval;
16+
import org.awaitility.pollinterval.PollInterval;
1417
import org.slf4j.Logger;
1518
import org.testcontainers.DockerClientFactory;
1619
import org.testcontainers.containers.ContainerFetchException;
@@ -21,9 +24,11 @@
2124

2225
import java.time.Duration;
2326
import java.time.Instant;
27+
import java.util.concurrent.Callable;
2428
import java.util.concurrent.CompletableFuture;
2529
import java.util.concurrent.ExecutionException;
2630
import java.util.concurrent.Future;
31+
import java.util.concurrent.atomic.AtomicReference;
2732

2833
@ToString
2934
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@@ -65,7 +70,7 @@ public RemoteDockerImage(@NonNull Future<String> imageFuture) {
6570
@SneakyThrows({ InterruptedException.class, ExecutionException.class })
6671
protected final String resolve() {
6772
final DockerImageName imageName = getImageName();
68-
Logger logger = DockerLoggerFactory.getLogger(imageName.toString());
73+
final Logger logger = DockerLoggerFactory.getLogger(imageName.toString());
6974
try {
7075
if (!imagePullPolicy.shouldPull(imageName)) {
7176
return imageName.asCanonicalNameString();
@@ -77,55 +82,88 @@ protected final String resolve() {
7782
imageName
7883
);
7984

80-
Exception lastFailure = null;
85+
final Instant startedAt = Instant.now();
8186
final Instant lastRetryAllowed = Instant.now().plus(PULL_RETRY_TIME_LIMIT);
82-
83-
Instant startedAt = Instant.now();
84-
while (Instant.now().isBefore(lastRetryAllowed)) {
85-
try {
86-
PullImageCmd pullImageCmd = dockerClient
87-
.pullImageCmd(imageName.getUnversionedPart())
88-
.withTag(imageName.getVersionPart());
89-
90-
try {
91-
pullImageCmd.exec(new TimeLimitedLoggedPullImageResultCallback(logger)).awaitCompletion();
92-
} catch (DockerClientException e) {
93-
// Try to fallback to x86
94-
pullImageCmd
95-
.withPlatform("linux/amd64")
96-
.exec(new TimeLimitedLoggedPullImageResultCallback(logger))
97-
.awaitCompletion();
98-
}
99-
String dockerImageName = imageName.asCanonicalNameString();
100-
logger.info("Image {} pull took {}", dockerImageName, Duration.between(startedAt, Instant.now()));
101-
102-
LocalImagesCache.INSTANCE.refreshCache(imageName);
103-
104-
return dockerImageName;
105-
} catch (InterruptedException | InternalServerErrorException e) {
106-
// these classes of exception often relate to timeout/connection errors so should be retried
107-
lastFailure = e;
108-
logger.warn(
109-
"Retrying pull for image: {} ({}s remaining)",
110-
imageName,
111-
Duration.between(Instant.now(), lastRetryAllowed).getSeconds()
112-
);
113-
}
87+
final AtomicReference<Exception> lastFailure = new AtomicReference<>();
88+
final PullImageCmd pullImageCmd = dockerClient
89+
.pullImageCmd(imageName.getUnversionedPart())
90+
.withTag(imageName.getVersionPart());
91+
final AtomicReference<String> dockerImageName = new AtomicReference<>();
92+
93+
// The following poll interval in ms: 50, 100, 200, 400, 800....
94+
// Results in ~70 requests in over 2 minutes
95+
final PollInterval interval = IterativePollInterval
96+
.iterative(duration -> duration.multipliedBy(2))
97+
.startDuration(Duration.ofMillis(50));
98+
99+
Awaitility
100+
.await()
101+
.pollInSameThread()
102+
.pollDelay(Duration.ZERO) // start checking immediately
103+
.atMost(PULL_RETRY_TIME_LIMIT)
104+
.pollInterval(interval)
105+
.until(
106+
tryImagePullCommand(pullImageCmd, logger, dockerImageName, imageName, lastFailure, lastRetryAllowed)
107+
);
108+
109+
if (dockerImageName.get() == null) {
110+
final Exception lastException = lastFailure.get();
111+
logger.error(
112+
"Failed to pull image: {}. Please check output of `docker pull {}`",
113+
imageName,
114+
imageName,
115+
lastException
116+
);
117+
throw new ContainerFetchException("Failed to pull image: " + imageName, lastException);
114118
}
115119

116-
logger.error(
117-
"Failed to pull image: {}. Please check output of `docker pull {}`",
118-
imageName,
119-
imageName,
120-
lastFailure
121-
);
122-
123-
throw new ContainerFetchException("Failed to pull image: " + imageName, lastFailure);
120+
logger.info("Image {} pull took {}", dockerImageName.get(), Duration.between(startedAt, Instant.now()));
121+
LocalImagesCache.INSTANCE.refreshCache(imageName);
122+
return dockerImageName.get();
124123
} catch (DockerClientException e) {
125124
throw new ContainerFetchException("Failed to get Docker client for " + imageName, e);
126125
}
127126
}
128127

128+
private Callable<Boolean> tryImagePullCommand(
129+
PullImageCmd pullImageCmd,
130+
Logger logger,
131+
AtomicReference<String> dockerImageName,
132+
DockerImageName imageName,
133+
AtomicReference<Exception> lastFailure,
134+
Instant lastRetryAllowed
135+
) {
136+
return () -> {
137+
try {
138+
pullImage(pullImageCmd, logger);
139+
dockerImageName.set(imageName.asCanonicalNameString());
140+
return true;
141+
} catch (InterruptedException | InternalServerErrorException e) {
142+
// these classes of exception often relate to timeout/connection errors so should be retried
143+
lastFailure.set(e);
144+
logger.warn(
145+
"Retrying pull for image: {} ({}s remaining)",
146+
imageName,
147+
Duration.between(Instant.now(), lastRetryAllowed).getSeconds()
148+
);
149+
return false;
150+
}
151+
};
152+
}
153+
154+
private TimeLimitedLoggedPullImageResultCallback pullImage(PullImageCmd pullImageCmd, Logger logger)
155+
throws InterruptedException {
156+
try {
157+
return pullImageCmd.exec(new TimeLimitedLoggedPullImageResultCallback(logger)).awaitCompletion();
158+
} catch (DockerClientException e) {
159+
// Try to fallback to x86
160+
return pullImageCmd
161+
.withPlatform("linux/amd64")
162+
.exec(new TimeLimitedLoggedPullImageResultCallback(logger))
163+
.awaitCompletion();
164+
}
165+
}
166+
129167
private DockerImageName getImageName() throws InterruptedException, ExecutionException {
130168
final DockerImageName specifiedImageName = imageNameFuture.get();
131169

core/src/test/java/org/testcontainers/TestImages.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public interface TestImages {
66
DockerImageName REDIS_IMAGE = DockerImageName.parse("redis:3.0.2");
77
DockerImageName RABBITMQ_IMAGE = DockerImageName.parse("rabbitmq:3.5.3");
88
DockerImageName MONGODB_IMAGE = DockerImageName.parse("mongo:3.1.5");
9-
DockerImageName ALPINE_IMAGE = DockerImageName.parse("alpine:3.16");
9+
DockerImageName ALPINE_IMAGE = DockerImageName.parse("alpine:3.17");
1010
DockerImageName DOCKER_REGISTRY_IMAGE = DockerImageName.parse("registry:2.7.0");
11-
DockerImageName TINY_IMAGE = DockerImageName.parse("alpine:3.16");
11+
DockerImageName TINY_IMAGE = DockerImageName.parse("alpine:3.17");
1212
}

0 commit comments

Comments
 (0)