diff --git a/commands.go b/commands.go index cc019f8c10..db8cc1d2f5 100644 --- a/commands.go +++ b/commands.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/nat" flag "github.com/dotcloud/docker/pkg/mflag" "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/pkg/term" @@ -799,7 +800,7 @@ func (cli *DockerCli) CmdPort(args ...string) error { return err } - if frontends, exists := out.NetworkSettings.Ports[Port(port+"/"+proto)]; exists && frontends != nil { + if frontends, exists := out.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil { for _, frontend := range frontends { fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) } @@ -1792,7 +1793,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container (name:alias)") cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables") - cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", PortSpecTemplateFormat)) + cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (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(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers") cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)") @@ -1885,7 +1886,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf domainname = parts[1] } - ports, portBindings, err := parsePortSpecs(flPublish.GetAll()) + ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll()) if err != nil { return nil, nil, cmd, err } @@ -1895,7 +1896,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf if strings.Contains(e, ":") { return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e) } - p := NewPort(splitProtoPort(e)) + p := nat.NewPort(nat.SplitProtoPort(e)) if _, exists := ports[p]; !exists { ports[p] = struct{}{} } diff --git a/config_test.go b/config_test.go index 31c961135a..1c808163e2 100644 --- a/config_test.go +++ b/config_test.go @@ -1,6 +1,7 @@ package docker import ( + "github.com/dotcloud/docker/nat" "testing" ) @@ -125,7 +126,7 @@ func TestMergeConfig(t *testing.T) { t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom) } - ports, _, err := parsePortSpecs([]string{"0000"}) + ports, _, err := nat.ParsePortSpecs([]string{"0000"}) if err != nil { t.Error(err) } diff --git a/container.go b/container.go index 81e8749d2a..991c2aeee7 100644 --- a/container.go +++ b/container.go @@ -8,6 +8,7 @@ import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/utils" @@ -86,7 +87,7 @@ type Config struct { AttachStdout bool AttachStderr bool PortSpecs []string // Deprecated - Can be in the format of 8080/tcp - ExposedPorts map[Port]struct{} + ExposedPorts map[nat.Port]struct{} Tty bool // Attach standard streams to a tty, including stdin if it is not closed. OpenStdin bool // Open stdin StdinOnce bool // If true, close stdin after the 1 attached client disconnects. @@ -147,7 +148,7 @@ type HostConfig struct { ContainerIDFile string LxcConf []KeyValuePair Privileged bool - PortBindings map[Port][]PortBinding + PortBindings nat.PortMap Links []string PublishAllPorts bool } @@ -189,38 +190,7 @@ type KeyValuePair struct { Value string } -type PortBinding struct { - HostIp string - HostPort string -} - -// 80/tcp -type Port string - -func (p Port) Proto() string { - parts := strings.Split(string(p), "/") - if len(parts) == 1 { - return "tcp" - } - return parts[1] -} - -func (p Port) Port() string { - return strings.Split(string(p), "/")[0] -} - -func (p Port) Int() int { - i, err := parsePort(p.Port()) - if err != nil { - panic(err) - } - return i -} - -func NewPort(proto, port string) Port { - return Port(fmt.Sprintf("%s/%s", port, proto)) -} - +// FIXME: move deprecated port stuff to nat to clean up the core. type PortMapping map[string]string // Deprecated type NetworkSettings struct { @@ -229,13 +199,13 @@ type NetworkSettings struct { Gateway string Bridge string PortMapping map[string]PortMapping // Deprecated - Ports map[Port][]PortBinding + Ports nat.PortMap } func (settings *NetworkSettings) PortMappingAPI() *engine.Table { var outs = engine.NewTable("", 0) for port, bindings := range settings.Ports { - p, _ := parsePort(port.Port()) + p, _ := nat.ParsePort(port.Port()) if len(bindings) == 0 { out := &engine.Env{} out.SetInt("PublicPort", p) @@ -245,7 +215,7 @@ func (settings *NetworkSettings) PortMappingAPI() *engine.Table { } for _, binding := range bindings { out := &engine.Env{} - h, _ := parsePort(binding.HostPort) + h, _ := nat.ParsePort(binding.HostPort) out.SetInt("PrivatePort", p) out.SetInt("PublicPort", h) out.Set("Type", port.Proto()) @@ -1152,8 +1122,8 @@ func (container *Container) allocateNetwork() error { } var ( - portSpecs = make(map[Port]struct{}) - bindings = make(map[Port][]PortBinding) + portSpecs = make(nat.PortSet) + bindings = make(nat.PortMap) ) if !container.State.IsGhost() { @@ -1177,7 +1147,7 @@ func (container *Container) allocateNetwork() error { for port := range portSpecs { binding := bindings[port] if container.hostConfig.PublishAllPorts && len(binding) == 0 { - binding = append(binding, PortBinding{}) + binding = append(binding, nat.PortBinding{}) } for i := 0; i < len(binding); i++ { @@ -1593,7 +1563,7 @@ func (container *Container) Copy(resource string) (archive.Archive, error) { } // Returns true if the container exposes a certain port -func (container *Container) Exposes(p Port) bool { +func (container *Container) Exposes(p nat.Port) bool { _, exists := container.Config.ExposedPorts[p] return exists } diff --git a/container_unit_test.go b/container_unit_test.go index 679ff57e73..dd915ad2e4 100644 --- a/container_unit_test.go +++ b/container_unit_test.go @@ -1,6 +1,7 @@ package docker import ( + "github.com/dotcloud/docker/nat" "testing" ) @@ -22,7 +23,7 @@ func TestParseLxcConfOpt(t *testing.T) { } func TestParseNetworkOptsPrivateOnly(t *testing.T) { - ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"}) + ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100::80"}) if err != nil { t.Fatal(err) } @@ -64,7 +65,7 @@ func TestParseNetworkOptsPrivateOnly(t *testing.T) { } func TestParseNetworkOptsPublic(t *testing.T) { - ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"}) + ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100:8080:80"}) if err != nil { t.Fatal(err) } @@ -106,7 +107,7 @@ func TestParseNetworkOptsPublic(t *testing.T) { } func TestParseNetworkOptsUdp(t *testing.T) { - ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"}) + ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100::6000/udp"}) if err != nil { t.Fatal(err) } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index da95967a30..70dde8f497 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/dotcloud/docker" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" "io" @@ -368,7 +369,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc eng = NewTestEngine(t) runtime = mkRuntimeFromEngine(eng, t) port = 5554 - p docker.Port + p nat.Port ) defer func() { if err != nil { @@ -387,8 +388,8 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc } else { t.Fatal(fmt.Errorf("Unknown protocol %v", proto)) } - ep := make(map[docker.Port]struct{}, 1) - p = docker.Port(fmt.Sprintf("%s/%s", strPort, proto)) + ep := make(map[nat.Port]struct{}, 1) + p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto)) ep[p] = struct{}{} jobCreate := eng.Job("create") @@ -411,8 +412,8 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc } jobStart := eng.Job("start", id) - portBindings := make(map[docker.Port][]docker.PortBinding) - portBindings[p] = []docker.PortBinding{ + portBindings := make(map[nat.Port][]nat.PortBinding) + portBindings[p] = []nat.PortBinding{ {}, } if err := jobStart.SetenvJson("PortsBindings", portBindings); err != nil { diff --git a/links.go b/links.go index aa1c08374b..ff39947a0d 100644 --- a/links.go +++ b/links.go @@ -3,6 +3,7 @@ package docker import ( "fmt" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/nat" "path" "strings" ) @@ -12,7 +13,7 @@ type Link struct { ChildIP string Name string ChildEnvironment []string - Ports []Port + Ports []nat.Port IsEnabled bool eng *engine.Engine } @@ -25,7 +26,7 @@ func NewLink(parent, child *Container, name string, eng *engine.Engine) (*Link, return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, name) } - ports := make([]Port, len(child.Config.ExposedPorts)) + ports := make([]nat.Port, len(child.Config.ExposedPorts)) var i int for p := range child.Config.ExposedPorts { ports[i] = p @@ -85,14 +86,14 @@ func (l *Link) ToEnv() []string { } // Default port rules -func (l *Link) getDefaultPort() *Port { - var p Port +func (l *Link) getDefaultPort() *nat.Port { + var p nat.Port i := len(l.Ports) if i == 0 { return nil } else if i > 1 { - sortPorts(l.Ports, func(ip, jp Port) bool { + 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") diff --git a/links_test.go b/links_test.go index 8a266a9a3d..0286d2395b 100644 --- a/links_test.go +++ b/links_test.go @@ -1,6 +1,7 @@ package docker import ( + "github.com/dotcloud/docker/nat" "strings" "testing" ) @@ -22,9 +23,9 @@ func TestLinkNew(t *testing.T) { from := newMockLinkContainer(fromID, "172.0.17.2") from.Config.Env = []string{} from.State = State{Running: true} - ports := make(map[Port]struct{}) + ports := make(nat.PortSet) - ports[Port("6379/tcp")] = struct{}{} + ports[nat.Port("6379/tcp")] = struct{}{} from.Config.ExposedPorts = ports @@ -51,7 +52,7 @@ func TestLinkNew(t *testing.T) { t.Fail() } for _, p := range link.Ports { - if p != Port("6379/tcp") { + if p != nat.Port("6379/tcp") { t.Fail() } } @@ -64,9 +65,9 @@ func TestLinkEnv(t *testing.T) { from := newMockLinkContainer(fromID, "172.0.17.2") from.Config.Env = []string{"PASSWORD=gordon"} from.State = State{Running: true} - ports := make(map[Port]struct{}) + ports := make(nat.PortSet) - ports[Port("6379/tcp")] = struct{}{} + ports[nat.Port("6379/tcp")] = struct{}{} from.Config.ExposedPorts = ports diff --git a/nat/nat.go b/nat/nat.go new file mode 100644 index 0000000000..f3af362f8b --- /dev/null +++ b/nat/nat.go @@ -0,0 +1,133 @@ +package nat + +// nat is a convenience package for docker's manipulation of strings describing +// network ports. + +import ( + "fmt" + "github.com/dotcloud/docker/utils" + "strconv" + "strings" +) + +const ( + PortSpecTemplate = "ip:hostPort:containerPort" + PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort" +) + +type PortBinding struct { + HostIp string + HostPort string +} + +type PortMap map[Port][]PortBinding + +type PortSet map[Port]struct{} + +// 80/tcp +type Port string + +func NewPort(proto, port string) Port { + return Port(fmt.Sprintf("%s/%s", port, proto)) +} + +func ParsePort(rawPort string) (int, error) { + port, err := strconv.ParseUint(rawPort, 10, 16) + if err != nil { + return 0, err + } + return int(port), nil +} + +func (p Port) Proto() string { + parts := strings.Split(string(p), "/") + if len(parts) == 1 { + return "tcp" + } + return parts[1] +} + +func (p Port) Port() string { + return strings.Split(string(p), "/")[0] +} + +func (p Port) Int() int { + i, err := ParsePort(p.Port()) + if err != nil { + panic(err) + } + return i +} + +// Splits a port in the format of port/proto +func SplitProtoPort(rawPort string) (string, string) { + parts := strings.Split(rawPort, "/") + l := len(parts) + if l == 0 { + return "", "" + } + if l == 1 { + return "tcp", rawPort + } + return parts[0], parts[1] +} + +// We will receive port specs in the format of ip:public:private/proto and these need to be +// parsed in the internal types +func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) { + var ( + exposedPorts = make(map[Port]struct{}, len(ports)) + bindings = make(map[Port][]PortBinding) + ) + + for _, rawPort := range ports { + proto := "tcp" + + if i := strings.LastIndex(rawPort, "/"); i != -1 { + proto = rawPort[i+1:] + rawPort = rawPort[:i] + } + if !strings.Contains(rawPort, ":") { + rawPort = fmt.Sprintf("::%s", rawPort) + } else if len(strings.Split(rawPort, ":")) == 2 { + rawPort = fmt.Sprintf(":%s", rawPort) + } + + parts, err := utils.PartParser(PortSpecTemplate, rawPort) + if err != nil { + return nil, nil, err + } + + var ( + containerPort = parts["containerPort"] + rawIp = parts["ip"] + hostPort = parts["hostPort"] + ) + + if containerPort == "" { + return nil, nil, fmt.Errorf("No port specified: %s", rawPort) + } + if _, err := strconv.ParseUint(containerPort, 10, 16); err != nil { + 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) + } + + 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) + } + return exposedPorts, bindings, nil +} diff --git a/nat/sort.go b/nat/sort.go new file mode 100644 index 0000000000..f36c12f7bb --- /dev/null +++ b/nat/sort.go @@ -0,0 +1,28 @@ +package nat + +import "sort" + +type portSorter struct { + ports []Port + by func(i, j Port) bool +} + +func (s *portSorter) Len() int { + return len(s.ports) +} + +func (s *portSorter) Swap(i, j int) { + s.ports[i], s.ports[j] = s.ports[j], s.ports[i] +} + +func (s *portSorter) Less(i, j int) bool { + ip := s.ports[i] + jp := s.ports[j] + + return s.by(ip, jp) +} + +func Sort(ports []Port, predicate func(i, j Port) bool) { + s := &portSorter{ports, predicate} + sort.Sort(s) +} diff --git a/sorter_unit_test.go b/nat/sort_test.go similarity index 86% rename from sorter_unit_test.go rename to nat/sort_test.go index 0669feedb3..5d490e321b 100644 --- a/sorter_unit_test.go +++ b/nat/sort_test.go @@ -1,4 +1,4 @@ -package docker +package nat import ( "fmt" @@ -11,7 +11,7 @@ func TestSortUniquePorts(t *testing.T) { Port("22/tcp"), } - sortPorts(ports, func(ip, jp Port) bool { + Sort(ports, func(ip, jp Port) bool { return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") }) @@ -30,7 +30,7 @@ func TestSortSamePortWithDifferentProto(t *testing.T) { Port("6379/udp"), } - sortPorts(ports, func(ip, jp Port) bool { + Sort(ports, func(ip, jp Port) bool { return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") }) diff --git a/sorter.go b/sorter.go index 9b3e1a9486..b49ac58c24 100644 --- a/sorter.go +++ b/sorter.go @@ -2,31 +2,6 @@ package docker import "sort" -type portSorter struct { - ports []Port - by func(i, j Port) bool -} - -func (s *portSorter) Len() int { - return len(s.ports) -} - -func (s *portSorter) Swap(i, j int) { - s.ports[i], s.ports[j] = s.ports[j], s.ports[i] -} - -func (s *portSorter) Less(i, j int) bool { - ip := s.ports[i] - jp := s.ports[j] - - return s.by(ip, jp) -} - -func sortPorts(ports []Port, predicate func(i, j Port) bool) { - s := &portSorter{ports, predicate} - sort.Sort(s) -} - type containerSorter struct { containers []*Container by func(i, j *Container) bool diff --git a/utils.go b/utils.go index e3ba08d51c..d4718954da 100644 --- a/utils.go +++ b/utils.go @@ -3,10 +3,10 @@ package docker import ( "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/utils" "io" - "strconv" "strings" "sync/atomic" ) @@ -98,7 +98,7 @@ func MergeConfig(userConf, imageConf *Config) error { userConf.ExposedPorts = imageConf.ExposedPorts } else if imageConf.ExposedPorts != nil { if userConf.ExposedPorts == nil { - userConf.ExposedPorts = make(map[Port]struct{}) + userConf.ExposedPorts = make(nat.PortSet) } for port := range imageConf.ExposedPorts { if _, exists := userConf.ExposedPorts[port]; !exists { @@ -109,9 +109,9 @@ func MergeConfig(userConf, imageConf *Config) error { if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 { if userConf.ExposedPorts == nil { - userConf.ExposedPorts = make(map[Port]struct{}) + userConf.ExposedPorts = make(nat.PortSet) } - ports, _, err := parsePortSpecs(userConf.PortSpecs) + ports, _, err := nat.ParsePortSpecs(userConf.PortSpecs) if err != nil { return err } @@ -125,10 +125,10 @@ func MergeConfig(userConf, imageConf *Config) error { if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 { utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", ")) if userConf.ExposedPorts == nil { - userConf.ExposedPorts = make(map[Port]struct{}) + userConf.ExposedPorts = make(nat.PortSet) } - ports, _, err := parsePortSpecs(imageConf.PortSpecs) + ports, _, err := nat.ParsePortSpecs(imageConf.PortSpecs) if err != nil { return err } @@ -212,96 +212,9 @@ func parseLxcOpt(opt string) (string, string, error) { return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } -// FIXME: network related stuff (including parsing) should be grouped in network file -const ( - PortSpecTemplate = "ip:hostPort:containerPort" - PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort" -) - -// We will receive port specs in the format of ip:public:private/proto and these need to be -// parsed in the internal types -func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) { - var ( - exposedPorts = make(map[Port]struct{}, len(ports)) - bindings = make(map[Port][]PortBinding) - ) - - for _, rawPort := range ports { - proto := "tcp" - - if i := strings.LastIndex(rawPort, "/"); i != -1 { - proto = rawPort[i+1:] - rawPort = rawPort[:i] - } - if !strings.Contains(rawPort, ":") { - rawPort = fmt.Sprintf("::%s", rawPort) - } else if len(strings.Split(rawPort, ":")) == 2 { - rawPort = fmt.Sprintf(":%s", rawPort) - } - - parts, err := utils.PartParser(PortSpecTemplate, rawPort) - if err != nil { - return nil, nil, err - } - - var ( - containerPort = parts["containerPort"] - rawIp = parts["ip"] - hostPort = parts["hostPort"] - ) - - if containerPort == "" { - return nil, nil, fmt.Errorf("No port specified: %s", rawPort) - } - if _, err := strconv.ParseUint(containerPort, 10, 16); err != nil { - 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) - } - - 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) - } - return exposedPorts, bindings, nil -} - -// Splits a port in the format of port/proto -func splitProtoPort(rawPort string) (string, string) { - parts := strings.Split(rawPort, "/") - l := len(parts) - if l == 0 { - return "", "" - } - if l == 1 { - return "tcp", rawPort - } - return parts[0], parts[1] -} - -func parsePort(rawPort string) (int, error) { - port, err := strconv.ParseUint(rawPort, 10, 16) - if err != nil { - return 0, err - } - return int(port), nil -} - func migratePortMappings(config *Config, hostConfig *HostConfig) error { if config.PortSpecs != nil { - ports, bindings, err := parsePortSpecs(config.PortSpecs) + ports, bindings, err := nat.ParsePortSpecs(config.PortSpecs) if err != nil { return err } @@ -314,7 +227,7 @@ func migratePortMappings(config *Config, hostConfig *HostConfig) error { } if config.ExposedPorts == nil { - config.ExposedPorts = make(map[Port]struct{}, len(ports)) + config.ExposedPorts = make(nat.PortSet, len(ports)) } for k, v := range ports { config.ExposedPorts[k] = v