Skip to content

Commit 97bf5ef

Browse files
committed
More intuitive way to add port bindings: new abstraction "PortBinding"
The use of Ports for adding port bindings requires knowledge of the internal data structure used for storing port bindings in Docker API. This commit adds a more intuitive alternative that works like the --publish option of the Docker CLI and some builder methods like withBinds(Bind...) and withLinks(Link...).
1 parent c656b26 commit 97bf5ef

File tree

6 files changed

+168
-8
lines changed

6 files changed

+168
-8
lines changed

src/main/java/com/github/dockerjava/api/command/StartContainerCmd.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.github.dockerjava.api.model.Device;
77
import com.github.dockerjava.api.model.Link;
88
import com.github.dockerjava.api.model.LxcConf;
9+
import com.github.dockerjava.api.model.PortBinding;
910
import com.github.dockerjava.api.model.Ports;
1011
import com.github.dockerjava.api.model.RestartPolicy;
1112

@@ -53,8 +54,21 @@ public interface StartContainerCmd extends DockerCmd<Void> {
5354

5455
public StartContainerCmd withLxcConf(LxcConf... lxcConf);
5556

57+
/**
58+
* Add the port bindings that are contained in the given {@link Ports}
59+
* object.
60+
*
61+
* @see #withPortBindings(PortBinding...)
62+
*/
5663
public StartContainerCmd withPortBindings(Ports portBindings);
5764

65+
/**
66+
* Add one or more {@link PortBinding}s.
67+
* This corresponds to the <code>--publish</code> (<code>-p</code>)
68+
* option of the <code>docker run</code> CLI command.
69+
*/
70+
public StartContainerCmd withPortBindings(PortBinding... portBindings);
71+
5872
public StartContainerCmd withPrivileged(boolean privileged);
5973

6074
public StartContainerCmd withPublishAllPorts(boolean publishAllPorts);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.github.dockerjava.api.model;
2+
3+
import com.github.dockerjava.api.command.InspectContainerResponse.HostConfig;
4+
import com.github.dockerjava.api.command.InspectContainerResponse.NetworkSettings;
5+
import com.github.dockerjava.api.model.Ports.Binding;
6+
7+
/**
8+
* In a {@link PortBinding}, a network socket on the Docker host, expressed
9+
* as a {@link Binding}, is bound to an {@link ExposedPort} of a container.
10+
* A {@link PortBinding} corresponds to the <code>--publish</code>
11+
* (<code>-p</code>) option of the <code>docker run</code> (and similar)
12+
* CLI command for adding port bindings to a container.
13+
* <p>
14+
* <i>Note: This is an abstraction used for creating new port bindings.
15+
* It is not to be confused with the abstraction used for querying existing
16+
* port bindings from a container configuration in
17+
* {@link NetworkSettings#getPorts()} and {@link HostConfig#getPortBindings()}.
18+
* In that context, a <code>Map&lt;ExposedPort, Binding[]&gt;</code> is used.</i>
19+
*/
20+
public class PortBinding {
21+
private final Binding binding;
22+
private final ExposedPort exposedPort;
23+
24+
public PortBinding(Binding binding, ExposedPort exposedPort) {
25+
this.binding = binding;
26+
this.exposedPort = exposedPort;
27+
}
28+
29+
public Binding getBinding() {
30+
return binding;
31+
}
32+
33+
public ExposedPort getExposedPort() {
34+
return exposedPort;
35+
}
36+
}

src/main/java/com/github/dockerjava/api/model/Ports.java

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
/**
2929
* A container for port bindings, made available as a {@link Map} via its
3030
* {@link #getBindings()} method.
31+
* <p>
32+
* <i>Note: This is an abstraction used for querying existing port bindings from
33+
* a container configuration.
34+
* It is not to be confused with the {@link PortBinding} abstraction used for
35+
* adding new port bindings to a container.</i>
3136
*
3237
* @see HostConfig#getPortBindings()
3338
* @see NetworkSettings#getPorts()
@@ -38,18 +43,42 @@ public class Ports {
3843

3944
private final Map<ExposedPort, Binding[]> ports = new HashMap<ExposedPort, Binding[]>();
4045

46+
/**
47+
* Creates a {@link Ports} object with no {@link PortBinding}s.
48+
* Use {@link #bind(ExposedPort, Binding)} or {@link #add(PortBinding...)}
49+
* to add {@link PortBinding}s.
50+
*/
4151
public Ports() { }
4252

53+
/**
54+
* Creates a {@link Ports} object with an initial {@link PortBinding} for
55+
* the specified {@link ExposedPort} and {@link Binding}.
56+
* Use {@link #bind(ExposedPort, Binding)} or {@link #add(PortBinding...)}
57+
* to add more {@link PortBinding}s.
58+
*/
4359
public Ports(ExposedPort exposedPort, Binding host) {
4460
bind(exposedPort, host);
4561
}
4662

47-
public void bind(ExposedPort exposedPort, Binding host) {
63+
/**
64+
* Adds a new {@link PortBinding} for the specified {@link ExposedPort} and
65+
* {@link Binding} to the current bindings.
66+
*/
67+
public void bind(ExposedPort exposedPort, Binding binding) {
4868
if (ports.containsKey(exposedPort)) {
4969
Binding[] bindings = ports.get(exposedPort);
50-
ports.put(exposedPort, (Binding[]) ArrayUtils.add(bindings, host));
70+
ports.put(exposedPort, (Binding[]) ArrayUtils.add(bindings, binding));
5171
} else {
52-
ports.put(exposedPort, new Binding[]{host});
72+
ports.put(exposedPort, new Binding[]{binding});
73+
}
74+
}
75+
76+
/**
77+
* Adds the specified {@link PortBinding}(s) to the list of {@link PortBinding}s.
78+
*/
79+
public void add(PortBinding... portBindings) {
80+
for (PortBinding binding : portBindings) {
81+
bind(binding.getExposedPort(), binding.getBinding());
5382
}
5483
}
5584

@@ -59,6 +88,9 @@ public String toString(){
5988
}
6089

6190
/**
91+
* Returns the port bindings in the format used by the Docker remote API,
92+
* i.e. the {@link Binding}s grouped by {@link ExposedPort}.
93+
*
6294
* @return the port bindings as a {@link Map} that contains one or more
6395
* {@link Binding}s per {@link ExposedPort}.
6496
*/
@@ -75,10 +107,12 @@ public static Binding Binding(int hostPort) {
75107

76108

77109
/**
78-
* The host part of a port binding.
79-
* In a port binding a container port, expressed as an {@link ExposedPort},
80-
* is published as a port of the Docker host.
110+
* A {@link Binding} represents a socket on the Docker host that is
111+
* used in a {@link PortBinding}.
112+
* It is characterized by an {@link #getHostIp() IP address} and a
113+
* {@link #getHostPort() port number}.
81114
*
115+
* @see Ports#bind(ExposedPort, Binding)
82116
* @see ExposedPort
83117
*/
84118
public static class Binding {
@@ -88,7 +122,8 @@ public static class Binding {
88122
private final int hostPort;
89123

90124
/**
91-
* Creates the host part of a port binding.
125+
* Creates a {@link Binding} for the given {@link #getHostIp() IP address}
126+
* and {@link #getHostPort() port number}.
92127
*
93128
* @see Ports#bind(ExposedPort, Binding)
94129
* @see ExposedPort
@@ -98,6 +133,13 @@ public Binding(String hostIp, int hostPort) {
98133
this.hostPort = hostPort;
99134
}
100135

136+
/**
137+
* Creates a {@link Binding} for the given {@link #getHostPort() port number},
138+
* leaving the {@link #getHostIp() IP address} undefined.
139+
*
140+
* @see Ports#bind(ExposedPort, Binding)
141+
* @see ExposedPort
142+
*/
101143
public Binding(int hostPort) {
102144
this("", hostPort);
103145
}

src/main/java/com/github/dockerjava/core/command/StartContainerCmdImpl.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.github.dockerjava.api.model.Link;
1414
import com.github.dockerjava.api.model.Links;
1515
import com.github.dockerjava.api.model.LxcConf;
16+
import com.github.dockerjava.api.model.PortBinding;
1617
import com.github.dockerjava.api.model.Ports;
1718
import com.github.dockerjava.api.model.RestartPolicy;
1819
import com.google.common.base.Preconditions;
@@ -179,6 +180,16 @@ public StartContainerCmd withPortBindings(Ports portBindings) {
179180
return this;
180181
}
181182

183+
@Override
184+
public StartContainerCmd withPortBindings(PortBinding... portBindings) {
185+
Preconditions.checkNotNull(portBindings, "portBindings was not specified");
186+
if (this.portBindings == null) {
187+
this.portBindings = new Ports();
188+
}
189+
this.portBindings.add(portBindings);
190+
return this;
191+
}
192+
182193
@Override
183194
public StartContainerCmd withPrivileged(boolean privileged) {
184195
this.privileged = privileged;

src/test/java/com/github/dockerjava/api/model/PortsTest.java renamed to src/test/java/com/github/dockerjava/api/model/Ports_SerializingTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import com.fasterxml.jackson.databind.ObjectMapper;
1010
import com.github.dockerjava.api.model.Ports.Binding;
1111

12-
public class PortsTest {
12+
public class Ports_SerializingTest {
1313
private final ObjectMapper objectMapper = new ObjectMapper();
1414
private final String jsonWithDoubleBindingForOnePort =
1515
"{\"80/tcp\":[{\"HostIp\":\"10.0.0.1\",\"HostPort\":\"80\"},{\"HostIp\":\"10.0.0.2\",\"HostPort\":\"80\"}]}";
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.github.dockerjava.api.model;
2+
3+
import static org.testng.Assert.assertEquals;
4+
5+
import java.util.Map;
6+
7+
import org.testng.annotations.BeforeMethod;
8+
import org.testng.annotations.Test;
9+
10+
import com.github.dockerjava.api.model.Ports.Binding;
11+
12+
/**
13+
* As there may be several {@link Binding}s per {@link ExposedPort},
14+
* it makes a difference if you add {@link PortBinding}s for the
15+
* same or different {@link ExposedPort}s to {@link Ports}.
16+
* This test verifies that the Map in {@link Ports} is populated
17+
* correctly in both cases.
18+
*/
19+
public class Ports_addBindingsTest {
20+
private static final ExposedPort TCP_80 = ExposedPort.tcp(80);
21+
private static final ExposedPort TCP_90 = ExposedPort.tcp(90);
22+
private static final Binding BINDING_8080 = Ports.Binding(8080);
23+
private static final Binding BINDING_9090 = Ports.Binding(9090);
24+
25+
private Ports ports;
26+
27+
@BeforeMethod
28+
public void setup() {
29+
ports = new Ports();
30+
}
31+
32+
@Test
33+
public void addTwoBindingsForDifferentExposedPorts() {
34+
ports.add(
35+
new PortBinding(BINDING_8080, TCP_80),
36+
new PortBinding(BINDING_9090, TCP_90));
37+
38+
Map<ExposedPort, Binding[]> bindings = ports.getBindings();
39+
// two keys with one value each
40+
assertEquals(bindings.size(), 2);
41+
assertEquals(bindings.get(TCP_80), new Binding[] { BINDING_8080 });
42+
assertEquals(bindings.get(TCP_90), new Binding[] { BINDING_9090 });
43+
}
44+
45+
@Test
46+
public void addTwoBindingsForSameExposedPort() {
47+
ports.add(
48+
new PortBinding(BINDING_8080, TCP_80),
49+
new PortBinding(BINDING_9090, TCP_80));
50+
51+
Map<ExposedPort, Binding[]> bindings = ports.getBindings();
52+
// one key with two values
53+
assertEquals(bindings.size(), 1);
54+
assertEquals(bindings.get(TCP_80), new Binding[] { BINDING_8080, BINDING_9090 });
55+
}
56+
57+
}

0 commit comments

Comments
 (0)