Skip to content

Commit 29643a4

Browse files
committed
Binding: Port and IP may be null
In Docker API, an undefined IP address is expressed as "". This is not intuitive and should be hidden from the user. Likewise, docker-java uses 0 for an unspecified port number. This also is not intuitive, especially if you consider that 0 is a legal port number. This change makes the undefinedness explicit by using null in both cases. When serializing, "" is used instead of null. This implies changing the type of hostPort from int to Integer.
1 parent 0ee67be commit 29643a4

4 files changed

Lines changed: 130 additions & 55 deletions

File tree

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

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,18 @@ public Map<ExposedPort, Binding[]> getBindings(){
9999
return ports;
100100
}
101101

102-
public static Binding Binding(String hostIp, int hostPort) {
102+
/**
103+
* Creates a {@link Binding} for the given IP address and port number.
104+
*/
105+
public static Binding Binding(String hostIp, Integer hostPort) {
103106
return new Binding(hostIp, hostPort);
104107
}
105-
public static Binding Binding(int hostPort) {
108+
109+
/**
110+
* Creates a {@link Binding} for the given port number, leaving the
111+
* IP address undefined.
112+
*/
113+
public static Binding Binding(Integer hostPort) {
106114
return new Binding(hostPort);
107115
}
108116

@@ -112,6 +120,8 @@ public static Binding Binding(int hostPort) {
112120
* used in a {@link PortBinding}.
113121
* It is characterized by an {@link #getHostIp() IP address} and a
114122
* {@link #getHostPort() port number}.
123+
* Both properties may be <code>null</code> in order to let Docker assign
124+
* them dynamically/using defaults.
115125
*
116126
* @see Ports#bind(ExposedPort, Binding)
117127
* @see ExposedPort
@@ -120,7 +130,7 @@ public static class Binding {
120130

121131
private final String hostIp;
122132

123-
private final int hostPort;
133+
private final Integer hostPort;
124134

125135
/**
126136
* Creates a {@link Binding} for the given {@link #getHostIp() IP address}
@@ -129,8 +139,8 @@ public static class Binding {
129139
* @see Ports#bind(ExposedPort, Binding)
130140
* @see ExposedPort
131141
*/
132-
public Binding(String hostIp, int hostPort) {
133-
this.hostIp = hostIp;
142+
public Binding(String hostIp, Integer hostPort) {
143+
this.hostIp = isEmpty(hostIp) ? null : hostIp;
134144
this.hostPort = hostPort;
135145
}
136146

@@ -141,23 +151,49 @@ public Binding(String hostIp, int hostPort) {
141151
* @see Ports#bind(ExposedPort, Binding)
142152
* @see ExposedPort
143153
*/
144-
public Binding(int hostPort) {
145-
this("", hostPort);
154+
public Binding(Integer hostPort) {
155+
this(null, hostPort);
146156
}
147157

158+
/**
159+
* Creates a {@link Binding} for the given {@link #getHostIp() IP address},
160+
* leaving the {@link #getHostPort() port number} undefined.
161+
*/
162+
public Binding(String hostIp) {
163+
this(hostIp, null);
164+
}
165+
166+
/**
167+
* Creates a {@link Binding} with both {@link #getHostIp() IP address} and
168+
* {@link #getHostPort() port number} undefined.
169+
*/
170+
public Binding() {
171+
this(null, null);
172+
}
173+
174+
/**
175+
* @return the IP address on the Docker host.
176+
* May be <code>null</code>, in which case Docker will bind the
177+
* port to all interfaces (<code>0.0.0.0</code>).
178+
*/
148179
public String getHostIp() {
149180
return hostIp;
150181
}
151182

152-
public int getHostPort() {
183+
/**
184+
* @return the port number on the Docker host.
185+
* May be <code>null</code>, in which case Docker will dynamically
186+
* assign a port.
187+
*/
188+
public Integer getHostPort() {
153189
return hostPort;
154190
}
155191

156192
/**
157193
* Parses a textual host and port specification (as used by the Docker CLI)
158194
* to a {@link Binding}.
159195
* <p>
160-
* Legal syntax: <code>[IP:]Port</code>
196+
* Legal syntax: <code>IP|IP:port|port</code>
161197
*
162198
* @param serialized serialized the specification, e.g.
163199
* <code>127.0.0.1:80</code>
@@ -166,13 +202,18 @@ public int getHostPort() {
166202
*/
167203
public static Binding parse(String serialized) throws IllegalArgumentException {
168204
try {
205+
if (serialized.isEmpty()) {
206+
return new Binding();
207+
}
208+
169209
String[] parts = serialized.split(":");
170210
switch (parts.length) {
171211
case 2: {
172212
return new Binding(parts[0], Integer.valueOf(parts[1]));
173213
}
174214
case 1: {
175-
return new Binding(Integer.valueOf(parts[0]));
215+
return parts[0].contains(".") ? new Binding(parts[0])
216+
: new Binding(Integer.valueOf(parts[0]));
176217
}
177218
default: {
178219
throw new IllegalArgumentException();
@@ -195,6 +236,8 @@ public static Binding parse(String serialized) throws IllegalArgumentException {
195236
public String toString() {
196237
if (isEmpty(hostIp)) {
197238
return Integer.toString(hostPort);
239+
} else if (hostPort == null) {
240+
return hostIp;
198241
} else {
199242
return hostIp + ":" + hostPort;
200243
}
@@ -249,8 +292,8 @@ public void serialize(Ports portBindings, JsonGenerator jsonGen,
249292
jsonGen.writeStartArray();
250293
for (Binding binding : entry.getValue()) {
251294
jsonGen.writeStartObject();
252-
jsonGen.writeStringField("HostIp", binding.getHostIp());
253-
jsonGen.writeStringField("HostPort", "" + binding.getHostPort());
295+
jsonGen.writeStringField("HostIp", binding.getHostIp() == null ? "" : binding.getHostIp());
296+
jsonGen.writeStringField("HostPort", binding.getHostPort() == null ? "" : binding.getHostPort().toString());
254297
jsonGen.writeEndObject();
255298
}
256299
jsonGen.writeEndArray();
Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,58 @@
1-
package com.github.dockerjava.api.model;
2-
3-
import static org.testng.Assert.assertEquals;
4-
5-
import org.testng.annotations.Test;
6-
7-
import com.github.dockerjava.api.model.Ports.Binding;
8-
9-
public class BindingTest {
10-
11-
@Test
12-
public void parseIpAndPort() {
13-
assertEquals(Binding.parse("127.0.0.1:80"), Ports.Binding("127.0.0.1", 80));
14-
}
15-
16-
@Test
17-
public void parsePortOnly() {
18-
assertEquals(Binding.parse("80"), Ports.Binding("", 80));
19-
}
20-
21-
@Test(expectedExceptions = IllegalArgumentException.class,
22-
expectedExceptionsMessageRegExp = "Error parsing Binding 'nonsense'")
23-
public void parseInvalidInput() {
24-
Binding.parse("nonsense");
25-
}
26-
27-
@Test(expectedExceptions = IllegalArgumentException.class,
28-
expectedExceptionsMessageRegExp = "Error parsing Binding 'null'")
29-
public void parseNull() {
30-
Binding.parse(null);
31-
}
32-
33-
@Test
34-
public void toStringIpAndHost() {
35-
assertEquals(Binding.parse("127.0.0.1:80").toString(), "127.0.0.1:80");
36-
}
37-
38-
@Test
39-
public void toStringPortOnly() {
40-
assertEquals(Binding.parse("80").toString(), "80");
41-
}
42-
43-
}
1+
package com.github.dockerjava.api.model;
2+
3+
import static org.testng.Assert.assertEquals;
4+
5+
import org.testng.annotations.Test;
6+
7+
import com.github.dockerjava.api.model.Ports.Binding;
8+
9+
public class BindingTest {
10+
11+
@Test
12+
public void parseIpAndPort() {
13+
assertEquals(Binding.parse("127.0.0.1:80"), Ports.Binding("127.0.0.1", 80));
14+
}
15+
16+
@Test
17+
public void parsePortOnly() {
18+
assertEquals(Binding.parse("80"), Ports.Binding(null, 80));
19+
}
20+
21+
@Test
22+
public void parseIPOnly() {
23+
assertEquals(Binding.parse("127.0.0.1"), Ports.Binding("127.0.0.1", null));
24+
}
25+
26+
@Test
27+
public void parseEmptyString() {
28+
assertEquals(Binding.parse(""), Ports.Binding(null, null));
29+
}
30+
31+
@Test(expectedExceptions = IllegalArgumentException.class,
32+
expectedExceptionsMessageRegExp = "Error parsing Binding 'nonsense'")
33+
public void parseInvalidInput() {
34+
Binding.parse("nonsense");
35+
}
36+
37+
@Test(expectedExceptions = IllegalArgumentException.class,
38+
expectedExceptionsMessageRegExp = "Error parsing Binding 'null'")
39+
public void parseNull() {
40+
Binding.parse(null);
41+
}
42+
43+
@Test
44+
public void toStringIpAndHost() {
45+
assertEquals(Binding.parse("127.0.0.1:80").toString(), "127.0.0.1:80");
46+
}
47+
48+
@Test
49+
public void toStringPortOnly() {
50+
assertEquals(Binding.parse("80").toString(), "80");
51+
}
52+
53+
@Test
54+
public void toStringIpOnly() {
55+
assertEquals(Binding.parse("127.0.0.1").toString(), "127.0.0.1");
56+
}
57+
58+
}

src/test/java/com/github/dockerjava/api/model/PortBindingTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ public void portsOnly() {
3434
new PortBinding(new Binding(80), TCP_8080));
3535
}
3636

37+
@Test
38+
public void exposedPortOnly() {
39+
assertEquals(PortBinding.parse("8080"),
40+
new PortBinding(new Binding(), TCP_8080));
41+
}
42+
43+
@Test
44+
public void dynamicHostPort() {
45+
assertEquals(PortBinding.parse("127.0.0.1::8080"),
46+
new PortBinding(new Binding("127.0.0.1"), TCP_8080));
47+
}
48+
3749
@Test(expectedExceptions = IllegalArgumentException.class,
3850
expectedExceptionsMessageRegExp = "Error parsing PortBinding 'nonsense'")
3951
public void parseInvalidInput() {

src/test/java/com/github/dockerjava/api/model/Ports_SerializingTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@ public void serializingPortWithMultipleBindings() throws Exception {
3434
assertEquals(objectMapper.writeValueAsString(ports), jsonWithDoubleBindingForOnePort);
3535
}
3636

37+
@Test
38+
public void serializingEmptyBinding() throws Exception {
39+
Ports ports = new Ports(ExposedPort.tcp(80), new Binding(null, null));
40+
assertEquals(objectMapper.writeValueAsString(ports), "{\"80/tcp\":[{\"HostIp\":\"\",\"HostPort\":\"\"}]}");
41+
}
3742
}

0 commit comments

Comments
 (0)