Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,24 @@ default Integer getFirstMappedPort() {
* @see #getCurrentContainerInfo()
*/
default Integer getMappedPort(int originalPort) {
return getMappedPort(originalPort, InternetProtocol.TCP);
}

/**
* Get the actual mapped port for a given port exposed by the container, for a specific protocol.
* It should be used in conjunction with {@link #getHost()}.
* <p>
* Note: The returned port number might be outdated (for instance, after disconnecting from a network and reconnecting
* again). If you always need up-to-date value, override the {@link #getContainerInfo()} to return the
* {@link #getCurrentContainerInfo()}.
*
* @param originalPort the original port that is exposed
* @param protocol the protocol (TCP or UDP) that the port is exposed with
* @return the port that the exposed port is mapped to, or null if it is not exposed
* @see #getContainerInfo()
* @see #getCurrentContainerInfo()
*/
default Integer getMappedPort(int originalPort, InternetProtocol protocol) {
Preconditions.checkState(
this.getContainerId() != null,
"Mapped port can only be obtained after the container is started"
Expand All @@ -166,13 +184,17 @@ default Integer getMappedPort(int originalPort) {
Ports.Binding[] binding = new Ports.Binding[0];
final InspectContainerResponse containerInfo = this.getContainerInfo();
if (containerInfo != null) {
binding = containerInfo.getNetworkSettings().getPorts().getBindings().get(new ExposedPort(originalPort));
ExposedPort exposedPort = new ExposedPort(
originalPort,
com.github.dockerjava.api.model.InternetProtocol.parse(protocol.name())
);
binding = containerInfo.getNetworkSettings().getPorts().getBindings().get(exposedPort);
}

if (binding != null && binding.length > 0 && binding[0] != null) {
return Integer.valueOf(binding[0].getHostPortSpec());
} else {
throw new IllegalArgumentException("Requested port (" + originalPort + ") is not mapped");
throw new IllegalArgumentException("Requested port (" + originalPort + "/" + protocol.toDockerNotation() + ") is not mapped");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,30 @@ public void addExposedPorts(int... ports) {
this.containerDef.addExposedTcpPorts(ports);
}

/**
* Expose a container port using a specific protocol, so that a randomly chosen host port will be bound to it
* when the container starts. Use {@link ContainerState#getMappedPort(int, InternetProtocol)} to retrieve the
* bound host port once the container is running.
*
* @param port the container port to expose
* @param protocol the protocol (TCP or UDP) that the port should be exposed with
*/
public void addExposedPort(int port, InternetProtocol protocol) {
this.containerDef.addExposedPort(port, com.github.dockerjava.api.model.InternetProtocol.parse(protocol.name()));
}

/**
* Fluent variant of {@link #addExposedPort(int, InternetProtocol)}.
*
* @param port the container port to expose
* @param protocol the protocol (TCP or UDP) that the port should be exposed with
* @return this
*/
public SELF withExposedPort(int port, InternetProtocol protocol) {
addExposedPort(port, protocol);
return self();
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package org.testcontainers.containers;

import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.Ports;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Answers;
import org.mockito.Mockito;

import java.util.Collections;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -35,4 +43,37 @@ void test(String name, String testSet, List<Integer> expectedResult) {
List<Integer> result = containerState.getBoundPortNumbers();
assertThat(result).hasSameElementsAs(expectedResult);
}

@Test
void getMappedPortWithProtocolLooksUpTheBindingForThatProtocol() {
ContainerState containerState = mock(ContainerState.class);
doCallRealMethod().when(containerState).getMappedPort(anyInt(), any());
when(containerState.getContainerId()).thenReturn("container-id");

InspectContainerResponse containerInfo = Mockito.mock(InspectContainerResponse.class, Answers.RETURNS_DEEP_STUBS);
ExposedPort udpPort = new ExposedPort(12345, com.github.dockerjava.api.model.InternetProtocol.UDP);
when(containerInfo.getNetworkSettings().getPorts().getBindings())
.thenReturn(Collections.singletonMap(udpPort, new Ports.Binding[] { Ports.Binding.bindPort(54321) }));
when(containerState.getContainerInfo()).thenReturn(containerInfo);

Integer mappedPort = containerState.getMappedPort(12345, InternetProtocol.UDP);

assertThat(mappedPort).isEqualTo(54321);
}

@Test
void getMappedPortWithProtocolThrowsWhenNotMapped() {
ContainerState containerState = mock(ContainerState.class);
doCallRealMethod().when(containerState).getMappedPort(anyInt(), any());
when(containerState.getContainerId()).thenReturn("container-id");

InspectContainerResponse containerInfo = Mockito.mock(InspectContainerResponse.class, Answers.RETURNS_DEEP_STUBS);
when(containerInfo.getNetworkSettings().getPorts().getBindings()).thenReturn(Collections.emptyMap());
when(containerState.getContainerInfo()).thenReturn(containerInfo);

org.assertj.core.api.Assertions
.assertThatThrownBy(() -> containerState.getMappedPort(12345, InternetProtocol.UDP))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("12345/udp");
}
}
Loading