Skip to content

Commit 96b2065

Browse files
authored
Only publish exposed ports (#4122)
1 parent 3167adc commit 96b2065

File tree

7 files changed

+147
-59
lines changed

7 files changed

+147
-59
lines changed

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

Lines changed: 57 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package org.testcontainers.containers;
22

3-
import static com.google.common.collect.Lists.newArrayList;
4-
import static org.testcontainers.utility.CommandLine.runShellCommand;
5-
63
import com.fasterxml.jackson.core.JsonProcessingException;
74
import com.fasterxml.jackson.databind.MapperFeature;
85
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -16,46 +13,14 @@
1613
import com.github.dockerjava.api.model.HostConfig;
1714
import com.github.dockerjava.api.model.Link;
1815
import com.github.dockerjava.api.model.PortBinding;
16+
import com.github.dockerjava.api.model.Ports;
1917
import com.github.dockerjava.api.model.Volume;
2018
import com.github.dockerjava.api.model.VolumesFrom;
2119
import com.github.dockerjava.core.DefaultDockerClientConfig;
2220
import com.google.common.annotations.VisibleForTesting;
2321
import com.google.common.base.Strings;
2422
import com.google.common.collect.ImmutableMap;
2523
import com.google.common.hash.Hashing;
26-
import java.io.File;
27-
import java.io.IOException;
28-
import java.lang.reflect.InvocationTargetException;
29-
import java.lang.reflect.Method;
30-
import java.lang.reflect.UndeclaredThrowableException;
31-
import java.nio.charset.Charset;
32-
import java.nio.file.Files;
33-
import java.nio.file.Path;
34-
import java.nio.file.Paths;
35-
import java.time.Duration;
36-
import java.time.Instant;
37-
import java.util.ArrayList;
38-
import java.util.Arrays;
39-
import java.util.Collections;
40-
import java.util.HashMap;
41-
import java.util.HashSet;
42-
import java.util.Iterator;
43-
import java.util.LinkedHashMap;
44-
import java.util.LinkedHashSet;
45-
import java.util.List;
46-
import java.util.Map;
47-
import java.util.Map.Entry;
48-
import java.util.Optional;
49-
import java.util.Set;
50-
import java.util.concurrent.ExecutionException;
51-
import java.util.concurrent.Future;
52-
import java.util.concurrent.TimeUnit;
53-
import java.util.concurrent.atomic.AtomicInteger;
54-
import java.util.function.Consumer;
55-
import java.util.stream.Collectors;
56-
import java.util.stream.Stream;
57-
import java.util.zip.Adler32;
58-
import java.util.zip.Checksum;
5924
import lombok.AccessLevel;
6025
import lombok.Data;
6126
import lombok.NonNull;
@@ -97,6 +62,43 @@
9762
import org.testcontainers.utility.ResourceReaper;
9863
import org.testcontainers.utility.TestcontainersConfiguration;
9964

65+
import java.io.File;
66+
import java.io.IOException;
67+
import java.lang.reflect.InvocationTargetException;
68+
import java.lang.reflect.Method;
69+
import java.lang.reflect.UndeclaredThrowableException;
70+
import java.nio.charset.Charset;
71+
import java.nio.file.Files;
72+
import java.nio.file.Path;
73+
import java.nio.file.Paths;
74+
import java.time.Duration;
75+
import java.time.Instant;
76+
import java.util.ArrayList;
77+
import java.util.Arrays;
78+
import java.util.Collections;
79+
import java.util.HashMap;
80+
import java.util.HashSet;
81+
import java.util.Iterator;
82+
import java.util.LinkedHashMap;
83+
import java.util.LinkedHashSet;
84+
import java.util.List;
85+
import java.util.Map;
86+
import java.util.Map.Entry;
87+
import java.util.Optional;
88+
import java.util.Set;
89+
import java.util.concurrent.ExecutionException;
90+
import java.util.concurrent.Future;
91+
import java.util.concurrent.TimeUnit;
92+
import java.util.concurrent.atomic.AtomicInteger;
93+
import java.util.function.Consumer;
94+
import java.util.stream.Collectors;
95+
import java.util.stream.Stream;
96+
import java.util.zip.Adler32;
97+
import java.util.zip.Checksum;
98+
99+
import static com.google.common.collect.Lists.newArrayList;
100+
import static org.testcontainers.utility.CommandLine.runShellCommand;
101+
100102
/**
101103
* Base class for that allows a container to be launched and controlled.
102104
*/
@@ -718,21 +720,28 @@ public Set<Integer> getLivenessCheckPortNumbers() {
718720

719721
private void applyConfiguration(CreateContainerCmd createCommand) {
720722
HostConfig hostConfig = buildHostConfig();
721-
createCommand.withHostConfig(hostConfig);
722-
723-
// Set up exposed ports (where there are no host port bindings defined)
724-
ExposedPort[] portArray = exposedPorts.stream()
725-
.map(ExposedPort::new)
726-
.toArray(ExposedPort[]::new);
727723

728-
createCommand.withExposedPorts(portArray);
724+
// PortBindings must contain:
725+
// * all exposed ports with a randomized host port (equivalent to -p CONTAINER_PORT)
726+
// * all exposed ports with a fixed host port (equivalent to -p HOST_PORT:CONTAINER_PORT)
727+
Map<ExposedPort, PortBinding> allPortBindings = new HashMap<>();
728+
// First collect all the randomized host ports from our 'exposedPorts' field
729+
for (final Integer tcpPort : exposedPorts) {
730+
ExposedPort exposedPort = ExposedPort.tcp(tcpPort);
731+
allPortBindings.put(exposedPort, new PortBinding(Ports.Binding.empty(), exposedPort));
732+
}
733+
// Next collect all the fixed host ports from our 'portBindings' field, overwriting any randomized ports so that
734+
// we don't create two bindings for the same container port.
735+
for (final String portBinding : portBindings) {
736+
PortBinding parsedBinding = PortBinding.parse(portBinding);
737+
allPortBindings.put(parsedBinding.getExposedPort(), parsedBinding);
738+
}
739+
hostConfig.withPortBindings(new ArrayList<>(allPortBindings.values()));
729740

730-
// Set up exposed ports that need host port bindings
731-
PortBinding[] portBindingArray = portBindings.stream()
732-
.map(PortBinding::parse)
733-
.toArray(PortBinding[]::new);
741+
// Next, ExposedPorts must be set up to publish all of the above ports, both randomized and fixed.
742+
createCommand.withExposedPorts(new ArrayList<>(allPortBindings.keySet()));
734743

735-
createCommand.withPortBindings(portBindingArray);
744+
createCommand.withHostConfig(hostConfig);
736745

737746
if (commandParts != null) {
738747
createCommand.withCmd(commandParts);
@@ -798,8 +807,6 @@ private void applyConfiguration(CreateContainerCmd createCommand) {
798807
createCommand.withNetworkMode(networkForLinks.get());
799808
}
800809

801-
createCommand.withPublishAllPorts(true);
802-
803810
PortForwardingContainer.INSTANCE.getNetwork().ifPresent(it -> {
804811
withExtraHost(INTERNAL_HOST_HOSTNAME, it.getIpAddress());
805812
});

core/src/test/java/org/testcontainers/containers/GenericContainerTest.java

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package org.testcontainers.containers;
22

33
import com.github.dockerjava.api.DockerClient;
4+
import com.github.dockerjava.api.command.InspectContainerResponse;
45
import com.github.dockerjava.api.command.InspectContainerResponse.ContainerState;
6+
import com.github.dockerjava.api.model.ExposedPort;
57
import com.github.dockerjava.api.model.Info;
8+
import com.github.dockerjava.api.model.Ports;
9+
import com.google.common.primitives.Ints;
610
import lombok.RequiredArgsConstructor;
711
import lombok.SneakyThrows;
812
import lombok.experimental.FieldDefaults;
@@ -11,15 +15,23 @@
1115
import org.assertj.core.api.Assumptions;
1216
import org.junit.Test;
1317
import org.rnorth.ducttape.unreliables.Unreliables;
14-
import org.testcontainers.TestImages;
1518
import org.testcontainers.DockerClientFactory;
19+
import org.testcontainers.TestImages;
1620
import org.testcontainers.containers.startupcheck.StartupCheckStrategy;
1721
import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;
22+
import org.testcontainers.images.builder.ImageFromDockerfile;
1823

24+
import java.util.Arrays;
25+
import java.util.List;
26+
import java.util.Map;
1927
import java.util.concurrent.TimeUnit;
2028
import java.util.function.Predicate;
29+
import java.util.stream.Collectors;
2130

2231
import static org.assertj.core.api.Assertions.assertThatThrownBy;
32+
import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
33+
import static org.rnorth.visibleassertions.VisibleAssertions.assertThrows;
34+
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
2335

2436
public class GenericContainerTest {
2537

@@ -60,6 +72,53 @@ public void shouldReportErrorAfterWait() {
6072
}
6173
}
6274

75+
@Test
76+
public void shouldOnlyPublishExposedPorts() {
77+
ImageFromDockerfile image = new ImageFromDockerfile("publish-multiple")
78+
.withDockerfileFromBuilder(builder ->
79+
builder
80+
.from("testcontainers/helloworld:1.1.0")
81+
.expose(8080, 8081)
82+
.build()
83+
);
84+
try (GenericContainer<?> container = new GenericContainer<>(image).withExposedPorts(8080)) {
85+
container.start();
86+
87+
InspectContainerResponse inspectedContainer = container.getContainerInfo();
88+
89+
List<Integer> exposedPorts = Arrays.stream(inspectedContainer.getConfig().getExposedPorts())
90+
.map(ExposedPort::getPort)
91+
.collect(Collectors.toList());
92+
93+
assertEquals(
94+
"the exposed ports are all of those EXPOSEd by the image",
95+
Ints.asList(8080, 8081),
96+
exposedPorts
97+
);
98+
99+
Map<ExposedPort, Ports.Binding[]> hostBindings = inspectedContainer.getHostConfig().getPortBindings().getBindings();
100+
assertEquals(
101+
"only 1 port is bound on the host (published)",
102+
1,
103+
hostBindings.size()
104+
);
105+
106+
Integer mappedPort = container.getMappedPort(8080);
107+
assertTrue(
108+
"port 8080 is bound to a different port on the host",
109+
mappedPort != 8080
110+
);
111+
112+
assertThrows(
113+
"trying to get a non-bound port mapping fails",
114+
IllegalArgumentException.class,
115+
() -> {
116+
container.getMappedPort(8081);
117+
}
118+
);
119+
}
120+
}
121+
63122
static class NoopStartupCheckStrategy extends StartupCheckStrategy {
64123

65124
@Override

modules/clickhouse/src/main/java/org/testcontainers/containers/ClickHouseContainer.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package org.testcontainers.containers;
22

3+
import com.google.common.collect.Sets;
34
import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
45
import org.testcontainers.utility.DockerImageName;
56

67
import java.time.Duration;
8+
import java.util.Set;
79

810
public class ClickHouseContainer extends JdbcDatabaseContainer {
911
public static final String NAME = "clickhouse";
@@ -54,8 +56,8 @@ public ClickHouseContainer(final DockerImageName dockerImageName) {
5456
}
5557

5658
@Override
57-
protected Integer getLivenessCheckPort() {
58-
return getMappedPort(HTTP_PORT);
59+
public Set<Integer> getLivenessCheckPortNumbers() {
60+
return Sets.newHashSet(HTTP_PORT);
5961
}
6062

6163
@Override

modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseContainer.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,19 @@ public String getConnectionString() {
166166
protected void configure() {
167167
super.configure();
168168

169+
addExposedPorts(
170+
MGMT_PORT,
171+
MGMT_SSL_PORT,
172+
VIEW_PORT,
173+
VIEW_SSL_PORT,
174+
QUERY_PORT,
175+
QUERY_SSL_PORT,
176+
SEARCH_PORT,
177+
SEARCH_SSL_PORT,
178+
KV_PORT,
179+
KV_SSL_PORT
180+
);
181+
169182
WaitAllStrategy waitStrategy = new WaitAllStrategy();
170183

171184
// Makes sure that all nodes in the cluster are healthy.

modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package org.testcontainers.containers;
22

3+
import com.google.common.collect.Sets;
34
import org.testcontainers.utility.DockerImageName;
45

6+
import java.util.Set;
7+
58
/**
69
* Container implementation for the MariaDB project.
710
*
@@ -51,8 +54,8 @@ public MariaDBContainer(final DockerImageName dockerImageName) {
5154
}
5255

5356
@Override
54-
protected Integer getLivenessCheckPort() {
55-
return getMappedPort(MARIADB_PORT);
57+
public Set<Integer> getLivenessCheckPortNumbers() {
58+
return Sets.newHashSet(MARIADB_PORT);
5659
}
5760

5861
@Override

modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.testcontainers.containers;
22

3+
import com.google.common.collect.Sets;
34
import org.testcontainers.utility.DockerImageName;
45
import org.testcontainers.utility.LicenseAcceptance;
56

7+
import java.util.Set;
68
import java.util.regex.Pattern;
79
import java.util.stream.Stream;
810

@@ -60,8 +62,8 @@ public MSSQLServerContainer(final DockerImageName dockerImageName) {
6062
}
6163

6264
@Override
63-
protected Integer getLivenessCheckPort() {
64-
return getMappedPort(MS_SQL_SERVER_PORT);
65+
public Set<Integer> getLivenessCheckPortNumbers() {
66+
return Sets.newHashSet(MS_SQL_SERVER_PORT);
6567
}
6668

6769
@Override

modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.testcontainers.containers;
22

3+
import com.google.common.collect.Sets;
34
import org.testcontainers.utility.DockerImageName;
45
import org.testcontainers.utility.TestcontainersConfiguration;
56

7+
import java.util.Set;
68
import java.util.concurrent.Future;
79

810
/**
@@ -61,8 +63,8 @@ private void preconfigure() {
6163
}
6264

6365
@Override
64-
protected Integer getLivenessCheckPort() {
65-
return getMappedPort(ORACLE_PORT);
66+
public Set<Integer> getLivenessCheckPortNumbers() {
67+
return Sets.newHashSet(ORACLE_PORT);
6668
}
6769

6870
@Override

0 commit comments

Comments
 (0)