Преглед изворни кода

add ability to publish range of ports
Closes #8899
Signed-off-by: Srini Brahmaroutu <srbrahma@us.ibm.com>

Srini Brahmaroutu пре 10 година
родитељ
комит
2338a9cf5a

+ 3 - 1
docs/man/docker-create.1.md

@@ -121,8 +121,10 @@ IMAGE [COMMAND] [ARG...]
    Publish all exposed ports to the host interfaces. The default is *false*.
    Publish all exposed ports to the host interfaces. The default is *false*.
 
 
 **-p**, **--publish**=[]
 **-p**, **--publish**=[]
-   Publish a container's port to the host
+   Publish a container's port, or a range of ports, to the host
                                format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
                                format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
+                               Both hostPort and containerPort can be specified as a range of ports. 
+                               When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. (e.g., `-p 1234-1236:1234-1236/tcp`)
                                (use 'docker port' to see the actual mapping)
                                (use 'docker port' to see the actual mapping)
 
 
 **--privileged**=*true*|*false*
 **--privileged**=*true*|*false*

+ 4 - 2
docs/man/docker-run.1.md

@@ -146,7 +146,7 @@ ENTRYPOINT.
    Read in a line delimited file of environment variables
    Read in a line delimited file of environment variables
 
 
 **--expose**=[]
 **--expose**=[]
-   Expose a port or a range of ports (e.g. --expose=3300-3310) from the container without publishing it to your host
+   Expose a port, or a range of ports (e.g. --expose=3300-3310), from the container without publishing it to your host
 
 
 **-h**, **--hostname**=""
 **-h**, **--hostname**=""
    Container host name
    Container host name
@@ -224,8 +224,10 @@ ports to a random port on the host between 49153 and 65535. To find the
 mapping between the host ports and the exposed ports, use **docker port**.
 mapping between the host ports and the exposed ports, use **docker port**.
 
 
 **-p**, **--publish**=[]
 **-p**, **--publish**=[]
-   Publish a container's port to the host
+   Publish a container's port, or range of ports, to the host.
                                format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
                                format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
+                               Both hostPort and containerPort can be specified as a range of ports. 
+                               When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. (e.g., `-p 1234-1236:1234-1236/tcp`)
                                (use 'docker port' to see the actual mapping)
                                (use 'docker port' to see the actual mapping)
 
 
 **--privileged**=*true*|*false*
 **--privileged**=*true*|*false*

+ 5 - 1
docs/sources/reference/commandline/cli.md

@@ -686,8 +686,10 @@ Creates a new container.
                                    'container:<name|id>': reuses another container network stack
                                    'container:<name|id>': reuses another container network stack
                                    'host': use the host network stack inside the container.  Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
                                    'host': use the host network stack inside the container.  Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
       -P, --publish-all=false    Publish all exposed ports to the host interfaces
       -P, --publish-all=false    Publish all exposed ports to the host interfaces
-      -p, --publish=[]           Publish a container's port to the host
+      -p, --publish=[]           Publish a container's port, or a range of ports (e.g., `-p 3300-3310`), to the host
                                    format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
                                    format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
+                                   Both hostPort and containerPort can be specified as a range of ports. 
+                                   When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. (e.g., `-p 1234-1236:1234-1236/tcp`)
                                    (use 'docker port' to see the actual mapping)
                                    (use 'docker port' to see the actual mapping)
       --privileged=false         Give extended privileges to this container
       --privileged=false         Give extended privileges to this container
       --restart=""               Restart policy to apply when a container exits (no, on-failure[:max-retry], always)
       --restart=""               Restart policy to apply when a container exits (no, on-failure[:max-retry], always)
@@ -1514,6 +1516,8 @@ removed before the image is removed.
       -P, --publish-all=false    Publish all exposed ports to the host interfaces
       -P, --publish-all=false    Publish all exposed ports to the host interfaces
       -p, --publish=[]           Publish a container's port to the host
       -p, --publish=[]           Publish a container's port to the host
                                    format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
                                    format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
+                                   Both hostPort and containerPort can be specified as a range of ports. 
+                                   When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. (e.g., `-p 1234-1236:1234-1236/tcp`)
                                    (use 'docker port' to see the actual mapping)
                                    (use 'docker port' to see the actual mapping)
       --privileged=false         Give extended privileges to this container
       --privileged=false         Give extended privileges to this container
       --restart=""               Restart policy to apply when a container exits (no, on-failure[:max-retry], always)
       --restart=""               Restart policy to apply when a container exits (no, on-failure[:max-retry], always)

+ 5 - 4
docs/sources/reference/run.md

@@ -487,10 +487,11 @@ or override the Dockerfile's exposed defaults:
     --expose=[]: Expose a port or a range of ports from the container
     --expose=[]: Expose a port or a range of ports from the container
                 without publishing it to your host
                 without publishing it to your host
     -P=false   : Publish all exposed ports to the host interfaces
     -P=false   : Publish all exposed ports to the host interfaces
-    -p=[]      : Publish a container᾿s port to the host (format:
-                 ip:hostPort:containerPort | ip::containerPort |
-                 hostPort:containerPort | containerPort)
-                 (use 'docker port' to see the actual mapping)
+    -p=[]      : Publish a container᾿s port or a range of ports to the host 
+                   format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
+                   Both hostPort and containerPort can be specified as a range of ports. 
+                   When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. (e.g., `-p 1234-1236:1234-1236/tcp`)
+                   (use 'docker port' to see the actual mapping)
     --link=""  : Add link to another container (name:alias)
     --link=""  : Add link to another container (name:alias)
 
 
 As mentioned previously, `EXPOSE` (and `--expose`) makes ports available
 As mentioned previously, `EXPOSE` (and `--expose`) makes ports available

+ 99 - 0
integration-cli/docker_cli_create_test.go

@@ -2,6 +2,7 @@ package main
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"github.com/docker/docker/nat"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"testing"
 	"testing"
@@ -102,6 +103,104 @@ func TestCreateHostConfig(t *testing.T) {
 	logDone("create - hostconfig")
 	logDone("create - hostconfig")
 }
 }
 
 
+func TestCreateWithPortRange(t *testing.T) {
+	runCmd := exec.Command(dockerBinary, "create", "-p", "3300-3303:3300-3303/tcp", "busybox", "echo")
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
+	if err != nil {
+		t.Fatal(out, err)
+	}
+
+	cleanedContainerID := stripTrailingCharacters(out)
+
+	inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
+	out, _, err = runCommandWithOutput(inspectCmd)
+	if err != nil {
+		t.Fatalf("out should've been a container id: %s, %v", out, err)
+	}
+
+	containers := []struct {
+		HostConfig *struct {
+			PortBindings map[nat.Port][]nat.PortBinding
+		}
+	}{}
+	if err := json.Unmarshal([]byte(out), &containers); err != nil {
+		t.Fatalf("Error inspecting the container: %s", err)
+	}
+	if len(containers) != 1 {
+		t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
+	}
+
+	c := containers[0]
+	if c.HostConfig == nil {
+		t.Fatalf("Expected HostConfig, got none")
+	}
+
+	if len(c.HostConfig.PortBindings) != 4 {
+		t.Fatalf("Expected 4 ports bindings, got %d", len(c.HostConfig.PortBindings))
+	}
+	for k, v := range c.HostConfig.PortBindings {
+		if len(v) != 1 {
+			t.Fatalf("Expected 1 ports binding, for the port  %s but found %s", k, v)
+		}
+		if k.Port() != v[0].HostPort {
+			t.Fatalf("Expected host port %d to match published port  %d", k.Port(), v[0].HostPort)
+		}
+	}
+
+	deleteAllContainers()
+
+	logDone("create - port range")
+}
+
+func TestCreateWithiLargePortRange(t *testing.T) {
+	runCmd := exec.Command(dockerBinary, "create", "-p", "1-65535:1-65535/tcp", "busybox", "echo")
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
+	if err != nil {
+		t.Fatal(out, err)
+	}
+
+	cleanedContainerID := stripTrailingCharacters(out)
+
+	inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
+	out, _, err = runCommandWithOutput(inspectCmd)
+	if err != nil {
+		t.Fatalf("out should've been a container id: %s, %v", out, err)
+	}
+
+	containers := []struct {
+		HostConfig *struct {
+			PortBindings map[nat.Port][]nat.PortBinding
+		}
+	}{}
+	if err := json.Unmarshal([]byte(out), &containers); err != nil {
+		t.Fatalf("Error inspecting the container: %s", err)
+	}
+	if len(containers) != 1 {
+		t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
+	}
+
+	c := containers[0]
+	if c.HostConfig == nil {
+		t.Fatalf("Expected HostConfig, got none")
+	}
+
+	if len(c.HostConfig.PortBindings) != 65535 {
+		t.Fatalf("Expected 65535 ports bindings, got %d", len(c.HostConfig.PortBindings))
+	}
+	for k, v := range c.HostConfig.PortBindings {
+		if len(v) != 1 {
+			t.Fatalf("Expected 1 ports binding, for the port  %s but found %s", k, v)
+		}
+		if k.Port() != v[0].HostPort {
+			t.Fatalf("Expected host port %d to match published port  %d", k.Port(), v[0].HostPort)
+		}
+	}
+
+	deleteAllContainers()
+
+	logDone("create - large port range")
+}
+
 // "test123" should be printed by docker create + start
 // "test123" should be printed by docker create + start
 func TestCreateEchoStdout(t *testing.T) {
 func TestCreateEchoStdout(t *testing.T) {
 	runCmd := exec.Command(dockerBinary, "create", "busybox", "echo", "test123")
 	runCmd := exec.Command(dockerBinary, "create", "busybox", "echo", "test123")

+ 24 - 0
integration-cli/docker_cli_run_test.go

@@ -2737,3 +2737,27 @@ func TestRunNetHost(t *testing.T) {
 
 
 	logDone("run - net host mode")
 	logDone("run - net host mode")
 }
 }
+
+func TestRunAllowPortRangeThroughPublish(t *testing.T) {
+	cmd := exec.Command(dockerBinary, "run", "-d", "--expose", "3000-3003", "-p", "3000-3003", "busybox", "top")
+	out, _, err := runCommandWithOutput(cmd)
+	defer deleteAllContainers()
+
+	id := strings.TrimSpace(out)
+	portstr, err := inspectFieldJSON(id, "NetworkSettings.Ports")
+	if err != nil {
+		t.Fatal(err)
+	}
+	var ports nat.PortMap
+	err = unmarshalJSON([]byte(portstr), &ports)
+	for port, binding := range ports {
+		portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0])
+		if portnum < 3000 || portnum > 3003 {
+			t.Fatalf("Port is out of range ", portnum, binding, out)
+		}
+		if binding == nil || len(binding) != 1 || len(binding[0].HostPort) == 0 {
+			t.Fatal("Port is not mapped for the port "+port, out)
+		}
+	}
+	logDone("run - allow port range through --expose flag")
+}

+ 33 - 16
nat/nat.go

@@ -122,31 +122,48 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
 		if containerPort == "" {
 		if containerPort == "" {
 			return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
 			return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
 		}
 		}
-		if _, err := strconv.ParseUint(containerPort, 10, 16); err != nil {
+
+		startPort, endPort, err := parsers.ParsePortRange(containerPort)
+		if err != nil {
 			return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
 			return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
 		}
 		}
-		if _, err := strconv.ParseUint(hostPort, 10, 16); hostPort != "" && err != nil {
-			return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
-		}
 
 
-		if !validateProto(proto) {
-			return nil, nil, fmt.Errorf("Invalid proto: %s", proto)
+		var startHostPort, endHostPort uint64 = 0, 0
+		if len(hostPort) > 0 {
+			startHostPort, endHostPort, err = parsers.ParsePortRange(hostPort)
+			if err != nil {
+				return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
+			}
 		}
 		}
 
 
-		port := NewPort(proto, containerPort)
-		if _, exists := exposedPorts[port]; !exists {
-			exposedPorts[port] = struct{}{}
+		if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
+			return nil, nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
 		}
 		}
 
 
-		binding := PortBinding{
-			HostIp:   rawIp,
-			HostPort: hostPort,
+		if !validateProto(proto) {
+			return nil, nil, fmt.Errorf("Invalid proto: %s", proto)
 		}
 		}
-		bslice, exists := bindings[port]
-		if !exists {
-			bslice = []PortBinding{}
+
+		for i := uint64(0); i <= (endPort - startPort); i++ {
+			containerPort = strconv.FormatUint(startPort+i, 10)
+			if len(hostPort) > 0 {
+				hostPort = strconv.FormatUint(startHostPort+i, 10)
+			}
+			port := NewPort(proto, containerPort)
+			if _, exists := exposedPorts[port]; !exists {
+				exposedPorts[port] = struct{}{}
+			}
+
+			binding := PortBinding{
+				HostIp:   rawIp,
+				HostPort: hostPort,
+			}
+			bslice, exists := bindings[port]
+			if !exists {
+				bslice = []PortBinding{}
+			}
+			bindings[port] = append(bslice, binding)
 		}
 		}
-		bindings[port] = append(bslice, binding)
 	}
 	}
 	return exposedPorts, bindings, nil
 	return exposedPorts, bindings, nil
 }
 }

+ 95 - 3
nat/nat_test.go

@@ -108,7 +108,7 @@ func TestParsePortSpecs(t *testing.T) {
 	portMap, bindingMap, err = ParsePortSpecs([]string{"1234/tcp", "2345/udp"})
 	portMap, bindingMap, err = ParsePortSpecs([]string{"1234/tcp", "2345/udp"})
 
 
 	if err != nil {
 	if err != nil {
-		t.Fatalf("Error while processing ParsePortSpecs: %s", err.Error())
+		t.Fatalf("Error while processing ParsePortSpecs: %s", err)
 	}
 	}
 
 
 	if _, ok := portMap[Port("1234/tcp")]; !ok {
 	if _, ok := portMap[Port("1234/tcp")]; !ok {
@@ -136,7 +136,7 @@ func TestParsePortSpecs(t *testing.T) {
 	portMap, bindingMap, err = ParsePortSpecs([]string{"1234:1234/tcp", "2345:2345/udp"})
 	portMap, bindingMap, err = ParsePortSpecs([]string{"1234:1234/tcp", "2345:2345/udp"})
 
 
 	if err != nil {
 	if err != nil {
-		t.Fatalf("Error while processing ParsePortSpecs: %s", err.Error())
+		t.Fatalf("Error while processing ParsePortSpecs: %s", err)
 	}
 	}
 
 
 	if _, ok := portMap[Port("1234/tcp")]; !ok {
 	if _, ok := portMap[Port("1234/tcp")]; !ok {
@@ -166,7 +166,7 @@ func TestParsePortSpecs(t *testing.T) {
 	portMap, bindingMap, err = ParsePortSpecs([]string{"0.0.0.0:1234:1234/tcp", "0.0.0.0:2345:2345/udp"})
 	portMap, bindingMap, err = ParsePortSpecs([]string{"0.0.0.0:1234:1234/tcp", "0.0.0.0:2345:2345/udp"})
 
 
 	if err != nil {
 	if err != nil {
-		t.Fatalf("Error while processing ParsePortSpecs: %s", err.Error())
+		t.Fatalf("Error while processing ParsePortSpecs: %s", err)
 	}
 	}
 
 
 	if _, ok := portMap[Port("1234/tcp")]; !ok {
 	if _, ok := portMap[Port("1234/tcp")]; !ok {
@@ -199,3 +199,95 @@ func TestParsePortSpecs(t *testing.T) {
 		t.Fatal("Received no error while trying to parse a hostname instead of ip")
 		t.Fatal("Received no error while trying to parse a hostname instead of ip")
 	}
 	}
 }
 }
+
+func TestParsePortSpecsWithRange(t *testing.T) {
+	var (
+		portMap    map[Port]struct{}
+		bindingMap map[Port][]PortBinding
+		err        error
+	)
+
+	portMap, bindingMap, err = ParsePortSpecs([]string{"1234-1236/tcp", "2345-2347/udp"})
+
+	if err != nil {
+		t.Fatalf("Error while processing ParsePortSpecs: %s", err)
+	}
+
+	if _, ok := portMap[Port("1235/tcp")]; !ok {
+		t.Fatal("1234/tcp was not parsed properly")
+	}
+
+	if _, ok := portMap[Port("2346/udp")]; !ok {
+		t.Fatal("2345/udp was not parsed properly")
+	}
+
+	for portspec, bindings := range bindingMap {
+		if len(bindings) != 1 {
+			t.Fatalf("%s should have exactly one binding", portspec)
+		}
+
+		if bindings[0].HostIp != "" {
+			t.Fatalf("HostIp should not be set for %s", portspec)
+		}
+
+		if bindings[0].HostPort != "" {
+			t.Fatalf("HostPort should not be set for %s", portspec)
+		}
+	}
+
+	portMap, bindingMap, err = ParsePortSpecs([]string{"1234-1236:1234-1236/tcp", "2345-2347:2345-2347/udp"})
+
+	if err != nil {
+		t.Fatalf("Error while processing ParsePortSpecs: %s", err)
+	}
+
+	if _, ok := portMap[Port("1235/tcp")]; !ok {
+		t.Fatal("1234/tcp was not parsed properly")
+	}
+
+	if _, ok := portMap[Port("2346/udp")]; !ok {
+		t.Fatal("2345/udp was not parsed properly")
+	}
+
+	for portspec, bindings := range bindingMap {
+		_, port := SplitProtoPort(string(portspec))
+		if len(bindings) != 1 {
+			t.Fatalf("%s should have exactly one binding", portspec)
+		}
+
+		if bindings[0].HostIp != "" {
+			t.Fatalf("HostIp should not be set for %s", portspec)
+		}
+
+		if bindings[0].HostPort != port {
+			t.Fatalf("HostPort should be %s for %s", port, portspec)
+		}
+	}
+
+	portMap, bindingMap, err = ParsePortSpecs([]string{"0.0.0.0:1234-1236:1234-1236/tcp", "0.0.0.0:2345-2347:2345-2347/udp"})
+
+	if err != nil {
+		t.Fatalf("Error while processing ParsePortSpecs: %s", err)
+	}
+
+	if _, ok := portMap[Port("1235/tcp")]; !ok {
+		t.Fatal("1234/tcp was not parsed properly")
+	}
+
+	if _, ok := portMap[Port("2346/udp")]; !ok {
+		t.Fatal("2345/udp was not parsed properly")
+	}
+
+	for portspec, bindings := range bindingMap {
+		_, port := SplitProtoPort(string(portspec))
+		if len(bindings) != 1 || bindings[0].HostIp != "0.0.0.0" || bindings[0].HostPort != port {
+			t.Fatalf("Expect single binding to port %d but found %s", port, bindings)
+		}
+	}
+
+	_, _, err = ParsePortSpecs([]string{"localhost:1234-1236:1234-1236/tcp"})
+
+	if err == nil {
+		t.Fatal("Received no error while trying to parse a hostname instead of ip")
+	}
+}

+ 25 - 0
pkg/parsers/parsers.go

@@ -104,3 +104,28 @@ func ParseKeyValueOpt(opt string) (string, string, error) {
 	}
 	}
 	return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
 	return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
 }
 }
+
+func ParsePortRange(ports string) (uint64, uint64, error) {
+	if ports == "" {
+		return 0, 0, fmt.Errorf("Empty string specified for ports.")
+	}
+	if !strings.Contains(ports, "-") {
+		start, err := strconv.ParseUint(ports, 10, 16)
+		end := start
+		return start, end, err
+	}
+
+	parts := strings.Split(ports, "-")
+	start, err := strconv.ParseUint(parts[0], 10, 16)
+	if err != nil {
+		return 0, 0, err
+	}
+	end, err := strconv.ParseUint(parts[1], 10, 16)
+	if err != nil {
+		return 0, 0, err
+	}
+	if end < start {
+		return 0, 0, fmt.Errorf("Invalid range specified for the Port: %s", ports)
+	}
+	return start, end, nil
+}

+ 33 - 0
pkg/parsers/parsers_test.go

@@ -1,6 +1,7 @@
 package parsers
 package parsers
 
 
 import (
 import (
+	"strings"
 	"testing"
 	"testing"
 )
 )
 
 
@@ -81,3 +82,35 @@ func TestParsePortMapping(t *testing.T) {
 		t.Fail()
 		t.Fail()
 	}
 	}
 }
 }
+
+func TestParsePortRange(t *testing.T) {
+	if start, end, err := ParsePortRange("8000-8080"); err != nil || start != 8000 || end != 8080 {
+		t.Fatalf("Error: %s or Expecting {start,end} values {8000,8080} but found {%d,%d}.", err, start, end)
+	}
+}
+
+func TestParsePortRangeIncorrectRange(t *testing.T) {
+	if _, _, err := ParsePortRange("9000-8080"); err == nil || !strings.Contains(err.Error(), "Invalid range specified for the Port") {
+		t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
+	}
+}
+
+func TestParsePortRangeIncorrectEndRange(t *testing.T) {
+	if _, _, err := ParsePortRange("8000-a"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
+		t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
+	}
+
+	if _, _, err := ParsePortRange("8000-30a"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
+		t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
+	}
+}
+
+func TestParsePortRangeIncorrectStartRange(t *testing.T) {
+	if _, _, err := ParsePortRange("a-8000"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
+		t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
+	}
+
+	if _, _, err := ParsePortRange("30a-8000"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
+		t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
+	}
+}

+ 2 - 2
runconfig/config_test.go

@@ -256,8 +256,8 @@ func TestMerge(t *testing.T) {
 		t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
 		t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
 	}
 	}
 	for portSpecs := range configUser.ExposedPorts {
 	for portSpecs := range configUser.ExposedPorts {
-		if portSpecs.Port() != "0000" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
-			t.Fatalf("Expected 0000 or 1111 or 2222 or 3333, found %s", portSpecs)
+		if portSpecs.Port() != "0" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
+			t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs)
 		}
 		}
 	}
 	}
 
 

+ 5 - 4
runconfig/parse.go

@@ -197,11 +197,12 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		if strings.Contains(e, "-") {
 		if strings.Contains(e, "-") {
 			proto, port := nat.SplitProtoPort(e)
 			proto, port := nat.SplitProtoPort(e)
 			//parse the start and end port and create a sequence of ports to expose
 			//parse the start and end port and create a sequence of ports to expose
-			parts := strings.Split(port, "-")
-			start, _ := strconv.Atoi(parts[0])
-			end, _ := strconv.Atoi(parts[1])
+			start, end, err := parsers.ParsePortRange(port)
+			if err != nil {
+				return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
+			}
 			for i := start; i <= end; i++ {
 			for i := start; i <= end; i++ {
-				p := nat.NewPort(proto, strconv.Itoa(i))
+				p := nat.NewPort(proto, strconv.FormatUint(i, 10))
 				if _, exists := ports[p]; !exists {
 				if _, exists := ports[p]; !exists {
 					ports[p] = struct{}{}
 					ports[p] = struct{}{}
 				}
 				}