Skip to content

Commit bf9a57a

Browse files
committed
Merge pull request #575 from docker-java/issue-556
Support binding of port ranges
2 parents f24fc26 + 8af6f2f commit bf9a57a

17 files changed

+312
-228
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ public static ExposedPort parse(String serialized) throws IllegalArgumentExcepti
124124
String[] parts = serialized.split("/");
125125
switch (parts.length) {
126126
case 1:
127-
return new ExposedPort(Integer.valueOf(parts[0]));
127+
return new ExposedPort(Integer.parseInt(parts[0]));
128128
case 2:
129-
return new ExposedPort(Integer.valueOf(parts[0]), InternetProtocol.parse(parts[1]));
129+
return new ExposedPort(Integer.parseInt(parts[0]), InternetProtocol.parse(parts[1]));
130130
default:
131131
throw new IllegalArgumentException();
132132
}

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

Lines changed: 76 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,5 @@
11
package com.github.dockerjava.api.model;
22

3-
import static org.apache.commons.lang.StringUtils.isEmpty;
4-
5-
import java.io.IOException;
6-
import java.util.HashMap;
7-
import java.util.Iterator;
8-
import java.util.Map;
9-
import java.util.Map.Entry;
10-
11-
import org.apache.commons.lang.ArrayUtils;
12-
import org.apache.commons.lang.builder.EqualsBuilder;
13-
143
import com.fasterxml.jackson.core.JsonGenerator;
154
import com.fasterxml.jackson.core.JsonParser;
165
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -23,6 +12,16 @@
2312
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
2413
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
2514
import com.fasterxml.jackson.databind.node.NullNode;
15+
import org.apache.commons.lang.ArrayUtils;
16+
import org.apache.commons.lang.builder.EqualsBuilder;
17+
18+
import java.io.IOException;
19+
import java.util.HashMap;
20+
import java.util.Iterator;
21+
import java.util.Map;
22+
import java.util.Map.Entry;
23+
24+
import static org.apache.commons.lang.StringUtils.isEmpty;
2625

2726
/**
2827
* A container for port bindings, made available as a {@link Map} via its {@link #getBindings()} method.
@@ -108,69 +107,85 @@ public Map<ExposedPort, Binding[]> getBindings() {
108107
// return bindings.toArray(new PortBinding[bindings.size()]);
109108
// }
110109

111-
/**
112-
* Creates a {@link Binding} for the given IP address and port number.
113-
*/
114-
public static Binding binding(String hostIp, Integer hostPort) {
115-
return new Binding(hostIp, hostPort);
116-
}
117-
118-
/**
119-
* Creates a {@link Binding} for the given port number, leaving the IP address undefined.
120-
*/
121-
public static Binding binding(Integer hostPort) {
122-
return new Binding(hostPort);
123-
}
124-
125110
/**
126111
* A {@link Binding} represents a socket on the Docker host that is used in a {@link PortBinding}. It is characterized by an
127-
* {@link #getHostIp() IP address} and a {@link #getHostPort() port number}. Both properties may be <code>null</code> in order to let
128-
* Docker assign them dynamically/using defaults.
112+
* {@link #getHostIp() IP address} and a {@link #getHostPortSpec() port spec}. Both properties may be <code>null</code> in order to
113+
* let Docker assign them dynamically/using defaults.
129114
*
130115
* @see Ports#bind(ExposedPort, Binding)
131116
* @see ExposedPort
132117
*/
133118
public static class Binding {
134119

135-
private final String hostIp;
136-
137-
private final Integer hostPort;
138-
139120
/**
140-
* Creates a {@link Binding} for the given {@link #getHostIp() IP address} and {@link #getHostPort() port number}.
121+
* Creates a {@link Binding} for the given {@link #getHostPortSpec() port spec}, leaving the {@link #getHostIp() IP address}
122+
* undefined.
141123
*
142124
* @see Ports#bind(ExposedPort, Binding)
143125
* @see ExposedPort
144126
*/
145-
public Binding(String hostIp, Integer hostPort) {
146-
this.hostIp = isEmpty(hostIp) ? null : hostIp;
147-
this.hostPort = hostPort;
127+
public static Binding bindPortSpec(String portSpec) {
128+
return new Binding(null, portSpec);
148129
}
149130

150131
/**
151-
* Creates a {@link Binding} for the given {@link #getHostPort() port number}, leaving the {@link #getHostIp() IP address}
132+
* Creates a {@link Binding} for the given {@link #getHostIp() IP address}, leaving the {@link #getHostPortSpec() port spec}
133+
* undefined.
134+
*/
135+
public static Binding bindIp(String hostIp) {
136+
return new Binding(hostIp, null);
137+
}
138+
139+
/**
140+
* Creates a {@link Binding} for the given {@link #getHostIp() IP address} and port number.
141+
*/
142+
public static Binding bindIpAndPort(String hostIp, int port) {
143+
return new Binding(hostIp, "" + port);
144+
}
145+
146+
/**
147+
* Creates a {@link Binding} for the given {@link #getHostIp() IP address} and port range.
148+
*/
149+
public static Binding bindIpAndPortRange(String hostIp, int lowPort, int highPort) {
150+
return new Binding(hostIp, lowPort + "-" + highPort);
151+
}
152+
153+
/**
154+
* Creates a {@link Binding} for the given port range, leaving the {@link #getHostIp() IP address}
152155
* undefined.
153-
*
154-
* @see Ports#bind(ExposedPort, Binding)
155-
* @see ExposedPort
156156
*/
157-
public Binding(Integer hostPort) {
158-
this(null, hostPort);
157+
public static Binding bindPortRange(int lowPort, int highPort) {
158+
return bindIpAndPortRange(null, lowPort, highPort);
159159
}
160160

161161
/**
162-
* Creates a {@link Binding} for the given {@link #getHostIp() IP address}, leaving the {@link #getHostPort() port number}
162+
* Creates a {@link Binding} for the given port leaving the {@link #getHostIp() IP address}
163163
* undefined.
164164
*/
165-
public Binding(String hostIp) {
166-
this(hostIp, null);
165+
public static Binding bindPort(int port) {
166+
return bindIpAndPort(null, port);
167167
}
168168

169169
/**
170-
* Creates a {@link Binding} with both {@link #getHostIp() IP address} and {@link #getHostPort() port number} undefined.
170+
* Creates an empty {@link Binding}.
171171
*/
172-
public Binding() {
173-
this(null, null);
172+
public static Binding empty() {
173+
return new Binding(null, null);
174+
}
175+
176+
private final String hostIp;
177+
178+
private final String hostPortSpec;
179+
180+
/**
181+
* Creates a {@link Binding} for the given {@link #getHostIp() host IP address} and {@link #getHostPortSpec() host port spec}.
182+
*
183+
* @see Ports#bind(ExposedPort, Binding)
184+
* @see ExposedPort
185+
*/
186+
public Binding(String hostIp, String hostPortSpec) {
187+
this.hostIp = isEmpty(hostIp) ? null : hostIp;
188+
this.hostPortSpec = hostPortSpec;
174189
}
175190

176191
/**
@@ -182,16 +197,17 @@ public String getHostIp() {
182197
}
183198

184199
/**
185-
* @return the port number on the Docker host. May be <code>null</code>, in which case Docker will dynamically assign a port.
200+
* @return the port spec for the binding on the Docker host. May reference a single port ("1234"), a port range ("1234-2345") or
201+
* <code>null</code>, in which case Docker will dynamically assign a port.
186202
*/
187-
public Integer getHostPort() {
188-
return hostPort;
203+
public String getHostPortSpec() {
204+
return hostPortSpec;
189205
}
190206

191207
/**
192208
* Parses a textual host and port specification (as used by the Docker CLI) to a {@link Binding}.
193209
* <p>
194-
* Legal syntax: <code>IP|IP:port|port</code>
210+
* Legal syntax: <code>IP|IP:portSpec|portSpec</code> where <code>portSpec</code> is either a single port or a port range
195211
*
196212
* @param serialized
197213
* serialized the specification, e.g. <code>127.0.0.1:80</code>
@@ -202,16 +218,16 @@ public Integer getHostPort() {
202218
public static Binding parse(String serialized) throws IllegalArgumentException {
203219
try {
204220
if (serialized.isEmpty()) {
205-
return new Binding();
221+
return Binding.empty();
206222
}
207223

208224
String[] parts = serialized.split(":");
209225
switch (parts.length) {
210226
case 2: {
211-
return new Binding(parts[0], Integer.valueOf(parts[1]));
227+
return new Binding(parts[0], parts[1]);
212228
}
213229
case 1: {
214-
return parts[0].contains(".") ? new Binding(parts[0]) : new Binding(Integer.valueOf(parts[0]));
230+
return parts[0].contains(".") ? Binding.bindIp(parts[0]) : Binding.bindPortSpec(parts[0]);
215231
}
216232
default: {
217233
throw new IllegalArgumentException();
@@ -231,19 +247,19 @@ public static Binding parse(String serialized) throws IllegalArgumentException {
231247
@Override
232248
public String toString() {
233249
if (isEmpty(hostIp)) {
234-
return Integer.toString(hostPort);
235-
} else if (hostPort == null) {
250+
return hostPortSpec;
251+
} else if (hostPortSpec == null) {
236252
return hostIp;
237253
} else {
238-
return hostIp + ":" + hostPort;
254+
return hostIp + ":" + hostPortSpec;
239255
}
240256
}
241257

242258
@Override
243259
public boolean equals(Object obj) {
244260
if (obj instanceof Binding) {
245261
Binding other = (Binding) obj;
246-
return new EqualsBuilder().append(hostIp, other.getHostIp()).append(hostPort, other.getHostPort())
262+
return new EqualsBuilder().append(hostIp, other.getHostIp()).append(hostPortSpec, other.getHostPortSpec())
247263
.isEquals();
248264
} else {
249265
return super.equals(obj);
@@ -270,7 +286,7 @@ public Ports deserialize(JsonParser jsonParser, DeserializationContext deseriali
270286
JsonNode bindingNode = bindingsArray.get(i);
271287
if (!bindingNode.equals(NullNode.getInstance())) {
272288
String hostIp = bindingNode.get("HostIp").textValue();
273-
int hostPort = bindingNode.get("HostPort").asInt();
289+
String hostPort = bindingNode.get("HostPort").textValue();
274290
out.bind(ExposedPort.parse(portNode.getKey()), new Binding(hostIp, hostPort));
275291
}
276292
}
@@ -294,8 +310,7 @@ public void serialize(Ports portBindings, JsonGenerator jsonGen, SerializerProvi
294310
for (Binding binding : entry.getValue()) {
295311
jsonGen.writeStartObject();
296312
jsonGen.writeStringField("HostIp", binding.getHostIp() == null ? "" : binding.getHostIp());
297-
jsonGen.writeStringField("HostPort", binding.getHostPort() == null ? "" : binding.getHostPort()
298-
.toString());
313+
jsonGen.writeStringField("HostPort", binding.getHostPortSpec() == null ? "" : binding.getHostPortSpec());
299314
jsonGen.writeEndObject();
300315
}
301316
jsonGen.writeEndArray();

src/test/java/com/github/dockerjava/api/command/InspectContainerResponseTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
import static org.hamcrest.Matchers.isEmptyString;
2626
import static org.hamcrest.Matchers.nullValue;
2727
import static org.hamcrest.core.IsNot.not;
28-
import static org.testng.Assert.*;
28+
import static org.testng.Assert.assertEquals;
29+
import static org.testng.Assert.assertFalse;
30+
import static org.testng.Assert.assertTrue;
2931

3032
/**
3133
* Tests for {@link InspectContainerResponse}.

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,26 @@ public class BindingTest {
1010

1111
@Test
1212
public void parseIpAndPort() {
13-
assertEquals(Binding.parse("127.0.0.1:80"), Ports.binding("127.0.0.1", 80));
13+
assertEquals(Binding.parse("127.0.0.1:80"), Binding.bindIpAndPort("127.0.0.1", 80));
1414
}
1515

1616
@Test
1717
public void parsePortOnly() {
18-
assertEquals(Binding.parse("80"), Ports.binding(null, 80));
18+
assertEquals(Binding.parse("80"), Binding.bindPort(80));
1919
}
2020

2121
@Test
2222
public void parseIPOnly() {
23-
assertEquals(Binding.parse("127.0.0.1"), Ports.binding("127.0.0.1", null));
23+
assertEquals(Binding.parse("127.0.0.1"), Binding.bindIp("127.0.0.1"));
2424
}
2525

2626
@Test
2727
public void parseEmptyString() {
28-
assertEquals(Binding.parse(""), Ports.binding(null, null));
28+
assertEquals(Binding.parse(""), Binding.empty());
2929
}
3030

31-
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Error parsing Binding 'nonsense'")
31+
// Strings can be used since it can be a range. Let the docker daemon do the validation.
32+
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Error parsing Binding 'nonsense'", enabled = false)
3233
public void parseInvalidInput() {
3334
Binding.parse("nonsense");
3435
}

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,35 @@ public class PortBindingTest {
1313
@Test
1414
public void fullDefinition() {
1515
assertEquals(PortBinding.parse("127.0.0.1:80:8080/tcp"),
16-
new PortBinding(new Binding("127.0.0.1", 80), TCP_8080));
16+
new PortBinding(Binding.bindIpAndPort("127.0.0.1", 80), TCP_8080));
1717
}
1818

1919
@Test
2020
public void noProtocol() {
21-
assertEquals(PortBinding.parse("127.0.0.1:80:8080"), new PortBinding(new Binding("127.0.0.1", 80), TCP_8080));
21+
assertEquals(PortBinding.parse("127.0.0.1:80:8080"), new PortBinding(Binding.bindIpAndPort("127.0.0.1", 80), TCP_8080));
2222
}
2323

2424
@Test
2525
public void noHostIp() {
26-
assertEquals(PortBinding.parse("80:8080/tcp"), new PortBinding(new Binding(80), TCP_8080));
26+
assertEquals(PortBinding.parse("80:8080/tcp"), new PortBinding(Binding.bindPort(80), TCP_8080));
2727
}
2828

2929
@Test
3030
public void portsOnly() {
31-
assertEquals(PortBinding.parse("80:8080"), new PortBinding(new Binding(80), TCP_8080));
31+
assertEquals(PortBinding.parse("80:8080"), new PortBinding(Binding.bindPort(80), TCP_8080));
3232
}
3333

3434
@Test
3535
public void exposedPortOnly() {
36-
assertEquals(PortBinding.parse("8080"), new PortBinding(new Binding(), TCP_8080));
36+
assertEquals(PortBinding.parse("8080"), new PortBinding(Binding.empty(), TCP_8080));
3737
}
3838

3939
@Test
4040
public void dynamicHostPort() {
41-
assertEquals(PortBinding.parse("127.0.0.1::8080"), new PortBinding(new Binding("127.0.0.1"), TCP_8080));
41+
assertEquals(PortBinding.parse("127.0.0.1::8080"), new PortBinding(Binding.bindIp("127.0.0.1"), TCP_8080));
4242
}
4343

44-
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Error parsing PortBinding 'nonsense'")
44+
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Error parsing PortBinding 'nonsense'", enabled = false)
4545
public void parseInvalidInput() {
4646
PortBinding.parse("nonsense");
4747
}

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package com.github.dockerjava.api.model;
22

3-
import static org.testng.Assert.assertEquals;
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.github.dockerjava.api.model.Ports.Binding;
5+
import org.testng.annotations.Test;
46

57
import java.util.Map;
68

7-
import org.testng.annotations.Test;
8-
9-
import com.fasterxml.jackson.databind.ObjectMapper;
10-
import com.github.dockerjava.api.model.Ports.Binding;
9+
import static org.testng.Assert.assertEquals;
1110

1211
public class Ports_SerializingTest {
1312
private final ObjectMapper objectMapper = new ObjectMapper();
@@ -24,15 +23,15 @@ public void deserializingPortWithMultipleBindings() throws Exception {
2423

2524
Binding[] bindings = map.get(ExposedPort.tcp(80));
2625
assertEquals(bindings.length, 2);
27-
assertEquals(bindings[0], new Binding("10.0.0.1", 80));
28-
assertEquals(bindings[1], new Binding("10.0.0.2", 80));
26+
assertEquals(bindings[0], new Binding("10.0.0.1", "80"));
27+
assertEquals(bindings[1], new Binding("10.0.0.2", "80"));
2928
}
3029

3130
@Test
3231
public void serializingPortWithMultipleBindings() throws Exception {
3332
Ports ports = new Ports();
34-
ports.bind(ExposedPort.tcp(80), new Binding("10.0.0.1", 80));
35-
ports.bind(ExposedPort.tcp(80), new Binding("10.0.0.2", 80));
33+
ports.bind(ExposedPort.tcp(80), new Binding("10.0.0.1", "80"));
34+
ports.bind(ExposedPort.tcp(80), new Binding("10.0.0.2", "80"));
3635
assertEquals(objectMapper.writeValueAsString(ports), jsonWithDoubleBindingForOnePort);
3736
}
3837

0 commit comments

Comments
 (0)