adding support for port ranges on --expose
Closes #1834 Signed-off-by: Srini Brahmaroutu <srbrahma@us.ibm.com>
This commit is contained in:
parent
0f9f5f3fdf
commit
fd774a818c
11 changed files with 171 additions and 55 deletions
|
@ -5,7 +5,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
||||
"github.com/docker/docker/daemon/networkdriver/portmapper"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/nat"
|
||||
"github.com/docker/docker/pkg/iptables"
|
||||
"github.com/docker/docker/pkg/networkfs/resolvconf"
|
||||
"github.com/docker/docker/pkg/parsers/kernel"
|
||||
|
@ -515,18 +516,13 @@ func LinkContainers(job *engine.Job) engine.Status {
|
|||
ignoreErrors = job.GetenvBool("IgnoreErrors")
|
||||
ports = job.GetenvList("Ports")
|
||||
)
|
||||
split := func(p string) (string, string) {
|
||||
parts := strings.Split(p, "/")
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
|
||||
for _, p := range ports {
|
||||
port, proto := split(p)
|
||||
for _, value := range ports {
|
||||
port := nat.Port(value)
|
||||
if output, err := iptables.Raw(action, "FORWARD",
|
||||
"-i", bridgeIface, "-o", bridgeIface,
|
||||
"-p", proto,
|
||||
"-p", port.Proto(),
|
||||
"-s", parentIP,
|
||||
"--dport", port,
|
||||
"--dport", strconv.Itoa(port.Int()),
|
||||
"-d", childIP,
|
||||
"-j", "ACCEPT"); !ignoreErrors && err != nil {
|
||||
return job.Error(err)
|
||||
|
@ -536,9 +532,9 @@ func LinkContainers(job *engine.Job) engine.Status {
|
|||
|
||||
if output, err := iptables.Raw(action, "FORWARD",
|
||||
"-i", bridgeIface, "-o", bridgeIface,
|
||||
"-p", proto,
|
||||
"-p", port.Proto(),
|
||||
"-s", childIP,
|
||||
"--sport", port,
|
||||
"--sport", strconv.Itoa(port.Int()),
|
||||
"-d", parentIP,
|
||||
"-j", "ACCEPT"); !ignoreErrors && err != nil {
|
||||
return job.Error(err)
|
||||
|
|
|
@ -79,7 +79,7 @@ docker-create - Create a new container
|
|||
Read in a line delimited file of environment variables
|
||||
|
||||
**--expose**=[]
|
||||
Expose a port 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**=""
|
||||
Container host name
|
||||
|
|
|
@ -132,12 +132,8 @@ ENTRYPOINT.
|
|||
**--env-file**=[]
|
||||
Read in a line delimited file of environment variables
|
||||
|
||||
**--expose**=*port*
|
||||
Expose a port from the container without publishing it to your host. A
|
||||
containers port can be exposed to other containers in three ways: 1) The
|
||||
developer can expose the port using the EXPOSE parameter of the Dockerfile, 2)
|
||||
the operator can use the **--expose** option with **docker run**, or 3) the
|
||||
container can be started with the **--link**.
|
||||
**--expose**=[]
|
||||
Expose a port or a range of ports (e.g. --expose=3300-3310) from the container without publishing it to your host
|
||||
|
||||
**-h**, **--hostname**=*hostname*
|
||||
Sets the container host name that is available inside the container.
|
||||
|
|
|
@ -509,7 +509,7 @@ Creates a new container.
|
|||
-e, --env=[] Set environment variables
|
||||
--entrypoint="" Overwrite the default ENTRYPOINT of the image
|
||||
--env-file=[] Read in a line delimited file of environment variables
|
||||
--expose=[] Expose a port from the container without publishing it to your host
|
||||
--expose=[] Expose a port or a range of ports (e.g. --expose=3300-3310) from the container without publishing it to your host
|
||||
-h, --hostname="" Container host name
|
||||
-i, --interactive=false Keep STDIN open even if not attached
|
||||
--link=[] Add link to another container in the form of name:alias
|
||||
|
@ -1211,7 +1211,7 @@ removed before the image is removed.
|
|||
-e, --env=[] Set environment variables
|
||||
--entrypoint="" Overwrite the default ENTRYPOINT of the image
|
||||
--env-file=[] Read in a line delimited file of environment variables
|
||||
--expose=[] Expose a port from the container without publishing it to your host
|
||||
--expose=[] Expose a port or a range of ports (e.g. --expose=3300-3310) from the container without publishing it to your host
|
||||
-h, --hostname="" Container host name
|
||||
-i, --interactive=false Keep STDIN open even if not attached
|
||||
--link=[] Add link to another container in the form of name:alias
|
||||
|
|
|
@ -413,7 +413,7 @@ the `EXPOSE` instruction to give a hint to the operator about what
|
|||
incoming ports might provide services. The following options work with
|
||||
or override the Dockerfile's exposed defaults:
|
||||
|
||||
--expose=[]: Expose a port from the container
|
||||
--expose=[]: Expose a port or a range of ports from the container
|
||||
without publishing it to your host
|
||||
-P=false : Publish all exposed ports to the host interfaces
|
||||
-p=[] : Publish a container᾿s port to the host (format:
|
||||
|
@ -422,7 +422,7 @@ or override the Dockerfile's exposed defaults:
|
|||
(use 'docker port' to see the actual mapping)
|
||||
--link="" : Add link to another container (name:alias)
|
||||
|
||||
As mentioned previously, `EXPOSE` (and `--expose`) make a port available
|
||||
As mentioned previously, `EXPOSE` (and `--expose`) makes ports available
|
||||
**in** a container for incoming connections. The port number on the
|
||||
inside of the container (where the service listens) does not need to be
|
||||
the same number as the port exposed on the outside of the container
|
||||
|
|
|
@ -13,11 +13,13 @@ import (
|
|||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/nat"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"github.com/docker/docker/pkg/networkfs/resolvconf"
|
||||
"github.com/kr/pty"
|
||||
|
@ -2473,3 +2475,31 @@ func TestRunSlowStdoutConsumer(t *testing.T) {
|
|||
|
||||
logDone("run - slow consumer")
|
||||
}
|
||||
|
||||
func TestRunAllowPortRangeThroughExpose(t *testing.T) {
|
||||
cmd := exec.Command(dockerBinary, "run", "-d", "--expose", "3000-3003", "-P", "busybox", "top")
|
||||
out, _, err := runCommandWithOutput(cmd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
if err := deleteContainer(id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
logDone("run - allow port range through --expose flag")
|
||||
}
|
||||
|
|
|
@ -47,6 +47,20 @@ func (l *Link) Alias() string {
|
|||
return alias
|
||||
}
|
||||
|
||||
func nextContiguous(ports []nat.Port, value int, index int) int {
|
||||
if index+1 == len(ports) {
|
||||
return index
|
||||
}
|
||||
for i := index + 1; i < len(ports); i++ {
|
||||
if ports[i].Int() > value+1 {
|
||||
return i - 1
|
||||
}
|
||||
|
||||
value++
|
||||
}
|
||||
return len(ports) - 1
|
||||
}
|
||||
|
||||
func (l *Link) ToEnv() []string {
|
||||
env := []string{}
|
||||
alias := strings.Replace(strings.ToUpper(l.Alias()), "-", "_", -1)
|
||||
|
@ -55,12 +69,35 @@ func (l *Link) ToEnv() []string {
|
|||
env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%s", alias, p.Proto(), l.ChildIP, p.Port()))
|
||||
}
|
||||
|
||||
// Load exposed ports into the environment
|
||||
for _, p := range l.Ports {
|
||||
//sort the ports so that we can bulk the continuous ports together
|
||||
nat.Sort(l.Ports, func(ip, jp nat.Port) bool {
|
||||
// If the two ports have the same number, tcp takes priority
|
||||
// Sort in desc order
|
||||
return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && strings.ToLower(ip.Proto()) == "tcp")
|
||||
})
|
||||
|
||||
for i := 0; i < len(l.Ports); {
|
||||
p := l.Ports[i]
|
||||
j := nextContiguous(l.Ports, p.Int(), i)
|
||||
if j > i+1 {
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_START=%s://%s:%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto(), l.ChildIP, p.Port()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_ADDR=%s", alias, p.Port(), strings.ToUpper(p.Proto()), l.ChildIP))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PROTO=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PORT_START=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Port()))
|
||||
|
||||
q := l.Ports[j]
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_END=%s://%s:%s", alias, p.Port(), strings.ToUpper(q.Proto()), q.Proto(), l.ChildIP, q.Port()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PORT_END=%s", alias, p.Port(), strings.ToUpper(q.Proto()), q.Port()))
|
||||
|
||||
i = j + 1
|
||||
continue
|
||||
}
|
||||
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s=%s://%s:%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto(), l.ChildIP, p.Port()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_ADDR=%s", alias, p.Port(), strings.ToUpper(p.Proto()), l.ChildIP))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PORT=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Port()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PROTO=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto()))
|
||||
i++
|
||||
}
|
||||
|
||||
// Load the linked container's name into the environment
|
||||
|
@ -125,7 +162,7 @@ func (l *Link) toggle(action string, ignoreErrors bool) error {
|
|||
|
||||
out := make([]string, len(l.Ports))
|
||||
for i, p := range l.Ports {
|
||||
out[i] = fmt.Sprintf("%s/%s", p.Port(), p.Proto())
|
||||
out[i] = string(p)
|
||||
}
|
||||
job.SetenvList("Ports", out)
|
||||
|
||||
|
|
|
@ -107,3 +107,52 @@ func TestLinkEnv(t *testing.T) {
|
|||
t.Fatalf("Expected gordon, got %s", env["DOCKER_ENV_PASSWORD"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkMultipleEnv(t *testing.T) {
|
||||
ports := make(nat.PortSet)
|
||||
ports[nat.Port("6379/tcp")] = struct{}{}
|
||||
ports[nat.Port("6380/tcp")] = struct{}{}
|
||||
ports[nat.Port("6381/tcp")] = struct{}{}
|
||||
|
||||
link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rawEnv := link.ToEnv()
|
||||
env := make(map[string]string, len(rawEnv))
|
||||
for _, e := range rawEnv {
|
||||
parts := strings.Split(e, "=")
|
||||
if len(parts) != 2 {
|
||||
t.FailNow()
|
||||
}
|
||||
env[parts[0]] = parts[1]
|
||||
}
|
||||
if env["DOCKER_PORT"] != "tcp://172.0.17.2:6379" {
|
||||
t.Fatalf("Expected 172.0.17.2:6379, got %s", env["DOCKER_PORT"])
|
||||
}
|
||||
if env["DOCKER_PORT_6379_TCP_START"] != "tcp://172.0.17.2:6379" {
|
||||
t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT_6379_TCP_START"])
|
||||
}
|
||||
if env["DOCKER_PORT_6379_TCP_END"] != "tcp://172.0.17.2:6381" {
|
||||
t.Fatalf("Expected tcp://172.0.17.2:6381, got %s", env["DOCKER_PORT_6379_TCP_END"])
|
||||
}
|
||||
if env["DOCKER_PORT_6379_TCP_PROTO"] != "tcp" {
|
||||
t.Fatalf("Expected tcp, got %s", env["DOCKER_PORT_6379_TCP_PROTO"])
|
||||
}
|
||||
if env["DOCKER_PORT_6379_TCP_ADDR"] != "172.0.17.2" {
|
||||
t.Fatalf("Expected 172.0.17.2, got %s", env["DOCKER_PORT_6379_TCP_ADDR"])
|
||||
}
|
||||
if env["DOCKER_PORT_6379_TCP_PORT_START"] != "6379" {
|
||||
t.Fatalf("Expected 6379, got %s", env["DOCKER_PORT_6379_TCP_PORT_START"])
|
||||
}
|
||||
if env["DOCKER_PORT_6379_TCP_PORT_END"] != "6381" {
|
||||
t.Fatalf("Expected 6381, got %s", env["DOCKER_PORT_6379_TCP_PORT_END"])
|
||||
}
|
||||
if env["DOCKER_NAME"] != "/db/docker" {
|
||||
t.Fatalf("Expected /db/docker, got %s", env["DOCKER_NAME"])
|
||||
}
|
||||
if env["DOCKER_ENV_PASSWORD"] != "gordon" {
|
||||
t.Fatalf("Expected gordon, got %s", env["DOCKER_ENV_PASSWORD"])
|
||||
}
|
||||
}
|
||||
|
|
39
nat/nat.go
39
nat/nat.go
|
@ -42,44 +42,37 @@ func ParsePort(rawPort string) (int, error) {
|
|||
}
|
||||
|
||||
func (p Port) Proto() string {
|
||||
parts := strings.Split(string(p), "/")
|
||||
if len(parts) == 1 {
|
||||
return "tcp"
|
||||
}
|
||||
return parts[1]
|
||||
proto, _ := SplitProtoPort(string(p))
|
||||
return proto
|
||||
}
|
||||
|
||||
func (p Port) Port() string {
|
||||
return strings.Split(string(p), "/")[0]
|
||||
_, port := SplitProtoPort(string(p))
|
||||
return port
|
||||
}
|
||||
|
||||
func (p Port) Int() int {
|
||||
i, err := ParsePort(p.Port())
|
||||
port, err := ParsePort(p.Port())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
return port
|
||||
}
|
||||
|
||||
// Splits a port in the format of proto/port
|
||||
func SplitProtoPort(rawPort string) (string, string) {
|
||||
var port string
|
||||
var proto string
|
||||
|
||||
parts := strings.Split(rawPort, "/")
|
||||
|
||||
if len(parts) == 0 || parts[0] == "" { // we have "" or ""/
|
||||
port = ""
|
||||
proto = ""
|
||||
} else { // we have # or #/ or #/...
|
||||
port = parts[0]
|
||||
if len(parts) > 1 && parts[1] != "" {
|
||||
proto = parts[1] // we have #/...
|
||||
} else {
|
||||
proto = "tcp" // we have # or #/
|
||||
}
|
||||
l := len(parts)
|
||||
if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
return proto, port
|
||||
if l == 1 {
|
||||
return "tcp", rawPort
|
||||
}
|
||||
if len(parts[1]) == 0 {
|
||||
return "tcp", parts[0]
|
||||
}
|
||||
return parts[1], parts[0]
|
||||
}
|
||||
|
||||
func validateProto(proto string) bool {
|
||||
|
|
|
@ -76,13 +76,13 @@ func TestSplitProtoPort(t *testing.T) {
|
|||
proto, port = SplitProtoPort("")
|
||||
|
||||
if proto != "" || port != "" {
|
||||
t.Fatal("parsing an empty string yielded surprising results")
|
||||
t.Fatal("parsing an empty string yielded surprising results", proto, port)
|
||||
}
|
||||
|
||||
proto, port = SplitProtoPort("1234")
|
||||
|
||||
if proto != "tcp" || port != "1234" {
|
||||
t.Fatal("tcp is not the default protocol for portspec '1234'")
|
||||
t.Fatal("tcp is not the default protocol for portspec '1234'", proto, port)
|
||||
}
|
||||
|
||||
proto, port = SplitProtoPort("1234/")
|
||||
|
|
|
@ -71,7 +71,7 @@ func Parse(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config,
|
|||
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a line delimited file of environment variables")
|
||||
|
||||
cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host\nformat: %s\n(use 'docker port' to see the actual mapping)", nat.PortSpecTemplateFormat))
|
||||
cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host")
|
||||
cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port or a range of ports (e.g. --expose=3300-3310) from the container without publishing it to your host")
|
||||
cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom DNS servers")
|
||||
cmd.Var(&flDnsSearch, []string{"-dns-search"}, "Set custom DNS search domains (Use --dns-search=. if you don't wish to set the search domain)")
|
||||
cmd.Var(&flExtraHosts, []string{"-add-host"}, "Add a custom host-to-IP mapping (host:ip)")
|
||||
|
@ -197,9 +197,24 @@ func Parse(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config,
|
|||
if strings.Contains(e, ":") {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
|
||||
}
|
||||
p := nat.NewPort(nat.SplitProtoPort(e))
|
||||
if _, exists := ports[p]; !exists {
|
||||
ports[p] = struct{}{}
|
||||
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
|
||||
if strings.Contains(e, "-") {
|
||||
proto, port := nat.SplitProtoPort(e)
|
||||
//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])
|
||||
for i := start; i <= end; i++ {
|
||||
p := nat.NewPort(proto, strconv.Itoa(i))
|
||||
if _, exists := ports[p]; !exists {
|
||||
ports[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p := nat.NewPort(nat.SplitProtoPort(e))
|
||||
if _, exists := ports[p]; !exists {
|
||||
ports[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue