From 92c5992622e3e323f9f1ca6affd9f3c9020db958 Mon Sep 17 00:00:00 2001 From: Rudy Zhang Date: Wed, 7 Mar 2018 14:54:22 +0800 Subject: [PATCH] feature: add port mapping for container Add port mapping for container. You can use: -p or --port to port binging into container --expose to expose container's port to access. pouch run -d -p 8888:80 registry.docker.com/library/nginx:latest Signed-off-by: Rudy Zhang --- apis/swagger.yml | 3 +- apis/types/host_config.go | 23 +- cli/common_flags.go | 2 + cli/container.go | 68 ++++- daemon/mgr/container.go | 1 + daemon/mgr/network.go | 60 +++++ network/types/endpoint.go | 3 +- test/cli_network_test.go | 34 +++ .../github.com/docker/go-connections/LICENSE | 191 ++++++++++++++ .../docker/go-connections/nat/nat.go | 242 ++++++++++++++++++ .../docker/go-connections/nat/parse.go | 57 +++++ .../docker/go-connections/nat/sort.go | 96 +++++++ vendor/vendor.json | 6 + 13 files changed, 753 insertions(+), 33 deletions(-) create mode 100644 vendor/github.com/docker/go-connections/LICENSE create mode 100644 vendor/github.com/docker/go-connections/nat/nat.go create mode 100644 vendor/github.com/docker/go-connections/nat/parse.go create mode 100644 vendor/github.com/docker/go-connections/nat/sort.go diff --git a/apis/swagger.yml b/apis/swagger.yml index a29780fb4..2a2e3f9af 100644 --- a/apis/swagger.yml +++ b/apis/swagger.yml @@ -1184,8 +1184,7 @@ definitions: PortBindings: type: "object" description: "A map of exposed container ports and the host port they should map to." - additionalProperties: - $ref: "#/definitions/PortBinding" + $ref: "#/definitions/PortMap" AutoRemove: type: "boolean" description: "Automatically remove the container when the container's process exits. This has no effect if `RestartPolicy` is set." diff --git a/apis/types/host_config.go b/apis/types/host_config.go index 02bafd813..eb0510695 100644 --- a/apis/types/host_config.go +++ b/apis/types/host_config.go @@ -105,7 +105,7 @@ type HostConfig struct { PidMode string `json:"PidMode,omitempty"` // A map of exposed container ports and the host port they should map to. - PortBindings map[string]PortBinding `json:"PortBindings,omitempty"` + PortBindings PortMap `json:"PortBindings,omitempty"` // Gives the container full access to the host. Privileged bool `json:"Privileged,omitempty"` @@ -208,7 +208,7 @@ func (m *HostConfig) UnmarshalJSON(raw []byte) error { PidMode string `json:"PidMode,omitempty"` - PortBindings map[string]PortBinding `json:"PortBindings,omitempty"` + PortBindings PortMap `json:"PortBindings,omitempty"` Privileged bool `json:"Privileged,omitempty"` @@ -378,7 +378,7 @@ func (m HostConfig) MarshalJSON() ([]byte, error) { PidMode string `json:"PidMode,omitempty"` - PortBindings map[string]PortBinding `json:"PortBindings,omitempty"` + PortBindings PortMap `json:"PortBindings,omitempty"` Privileged bool `json:"Privileged,omitempty"` @@ -556,10 +556,6 @@ func (m *HostConfig) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := m.validatePortBindings(formats); err != nil { - res = append(res, err) - } - if err := m.validateRestartPolicy(formats); err != nil { res = append(res, err) } @@ -755,19 +751,6 @@ func (m *HostConfig) validateLogConfig(formats strfmt.Registry) error { return nil } -func (m *HostConfig) validatePortBindings(formats strfmt.Registry) error { - - if swag.IsZero(m.PortBindings) { // not required - return nil - } - - if err := validate.Required("PortBindings", "body", m.PortBindings); err != nil { - return err - } - - return nil -} - func (m *HostConfig) validateRestartPolicy(formats strfmt.Registry) error { if swag.IsZero(m.RestartPolicy) { // not required diff --git a/cli/common_flags.go b/cli/common_flags.go index adf3dc927..3e94f0a04 100644 --- a/cli/common_flags.go +++ b/cli/common_flags.go @@ -52,6 +52,8 @@ func addCommonFlags(flagSet *pflag.FlagSet) *container { flagSet.StringVar(&c.name, "name", "", "Specify name of container") flagSet.StringSliceVar(&c.networks, "net", nil, "Set networks to container") + flagSet.StringSliceVarP(&c.ports, "port", "p", nil, "Set container ports mapping") + flagSet.StringSliceVar(&c.expose, "expose", nil, "Set expose container's ports") flagSet.StringVar(&c.pidMode, "pid", "", "PID namespace to use") flagSet.BoolVar(&c.privileged, "privileged", false, "Give extended privileges to the container") diff --git a/cli/container.go b/cli/container.go index e3e5247d8..0e7eca39c 100644 --- a/cli/container.go +++ b/cli/container.go @@ -8,6 +8,7 @@ import ( "github.com/alibaba/pouch/apis/types" "github.com/alibaba/pouch/pkg/runconfig" + "github.com/docker/go-connections/nat" units "github.com/docker/go-units" strfmt "github.com/go-openapi/strfmt" @@ -45,6 +46,9 @@ type container struct { utsMode string sysctls []string networks []string + ports []string + expose []string + publicAll bool securityOpt []string capAdd []string capDrop []string @@ -131,18 +135,61 @@ func (c *container) config() (*types.ContainerCreateConfig, error) { } } + // parse port binding + tmpPorts, tmpPortBindings, err := nat.ParsePortSpecs(c.ports) + if err != nil { + return nil, err + } + // translate ports and portbingings + ports := map[string]interface{}{} + for n, p := range tmpPorts { + ports[string(n)] = p + } + portBindings := make(types.PortMap) + for n, pbs := range tmpPortBindings { + portBindings[string(n)] = []types.PortBinding{} + for _, tmpPb := range pbs { + pb := types.PortBinding{HostIP: tmpPb.HostIP, HostPort: tmpPb.HostPort} + portBindings[string(n)] = append(portBindings[string(n)], pb) + } + } + + for _, e := range c.expose { + if strings.Contains(e, ":") { + return nil, fmt.Errorf("invalid port format for --expose: %s", e) + } + + //support two formats for expose, original format /[] or /[] + proto, port := nat.SplitProtoPort(e) + //parse the start and end port and create a sequence of ports to expose + //if expose a port, the start and end port are the same + start, end, err := nat.ParsePortRange(port) + if err != nil { + return nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err) + } + for i := start; i <= end; i++ { + p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) + if err != nil { + return nil, err + } + if _, exists := ports[string(p)]; !exists { + ports[string(p)] = struct{}{} + } + } + } config := &types.ContainerCreateConfig{ ContainerConfig: types.ContainerConfig{ - Tty: c.tty, - Env: c.env, - Entrypoint: strings.Fields(c.entrypoint), - WorkingDir: c.workdir, - User: c.user, - Hostname: strfmt.Hostname(c.hostname), - Labels: labels, - Rich: c.rich, - RichMode: c.richMode, - InitScript: c.initScript, + Tty: c.tty, + Env: c.env, + Entrypoint: strings.Fields(c.entrypoint), + WorkingDir: c.workdir, + User: c.user, + Hostname: strfmt.Hostname(c.hostname), + Labels: labels, + Rich: c.rich, + RichMode: c.richMode, + InitScript: c.initScript, + ExposedPorts: ports, }, HostConfig: &types.HostConfig{ @@ -182,6 +229,7 @@ func (c *container) config() (*types.ContainerCreateConfig, error) { NetworkMode: networkMode, CapAdd: c.capAdd, CapDrop: c.capDrop, + PortBindings: portBindings, }, NetworkingConfig: networkingConfig, diff --git a/daemon/mgr/container.go b/daemon/mgr/container.go index f3e48d146..3c58ec6ee 100644 --- a/daemon/mgr/container.go +++ b/daemon/mgr/container.go @@ -1019,6 +1019,7 @@ func (mgr *ContainerManager) buildContainerEndpoint(c *ContainerMeta) *networkty DNSOptions: c.HostConfig.DNSOptions, DNSSearch: c.HostConfig.DNSSearch, MacAddress: c.Config.MacAddress, + PublishAllPorts: c.HostConfig.PublishAllPorts, ExposedPorts: c.Config.ExposedPorts, PortBindings: c.HostConfig.PortBindings, NetworkConfig: c.NetworkSettings, diff --git a/daemon/mgr/network.go b/daemon/mgr/network.go index 8a12722e0..af4248ae8 100644 --- a/daemon/mgr/network.go +++ b/daemon/mgr/network.go @@ -15,6 +15,7 @@ import ( "github.com/alibaba/pouch/pkg/randomid" netlog "github.com/Sirupsen/logrus" + "github.com/docker/go-connections/nat" "github.com/docker/libnetwork" nwconfig "github.com/docker/libnetwork/config" "github.com/docker/libnetwork/netlabel" @@ -508,6 +509,65 @@ func (nm *NetworkManager) sandboxOptions(endpoint *types.Endpoint) ([]libnetwork // TODO: secondary ip address // TODO: parse extra hosts // TODO: port mapping + var bindings = make(nat.PortMap) + if endpoint.PortBindings != nil { + for p, b := range endpoint.PortBindings { + bindings[nat.Port(p)] = []nat.PortBinding{} + for _, bb := range b { + bindings[nat.Port(p)] = append(bindings[nat.Port(p)], nat.PortBinding{ + HostIP: bb.HostIP, + HostPort: bb.HostPort, + }) + } + } + } + + portSpecs := endpoint.ExposedPorts + var ports = make([]nat.Port, len(portSpecs)) + var i int + for p := range endpoint.ExposedPorts { + ports[i] = nat.Port(p) + i++ + } + nat.SortPortMap(ports, bindings) + + var ( + exposeList []networktypes.TransportPort + pbList []networktypes.PortBinding + ) + for _, port := range ports { + expose := networktypes.TransportPort{} + expose.Proto = networktypes.ParseProtocol(port.Proto()) + expose.Port = uint16(port.Int()) + exposeList = append(exposeList, expose) + + pb := networktypes.PortBinding{Port: expose.Port, Proto: expose.Proto} + binding := bindings[port] + for i := 0; i < len(binding); i++ { + pbCopy := pb.GetCopy() + newP, err := nat.NewPort(nat.SplitProtoPort(binding[i].HostPort)) + var portStart, portEnd int + if err == nil { + portStart, portEnd, err = newP.Range() + } + if err != nil { + return nil, fmt.Errorf("failed to parsing HostPort value(%s):%v", binding[i].HostPort, err) + } + pbCopy.HostPort = uint16(portStart) + pbCopy.HostPortEnd = uint16(portEnd) + pbCopy.HostIP = net.ParseIP(binding[i].HostIP) + pbList = append(pbList, pbCopy) + } + + if endpoint.PublishAllPorts && len(binding) == 0 { + pbList = append(pbList, pb) + } + } + + sandboxOptions = append(sandboxOptions, + libnetwork.OptionPortMapping(pbList), + libnetwork.OptionExposedPorts(exposeList)) + return sandboxOptions, nil } diff --git a/network/types/endpoint.go b/network/types/endpoint.go index 6d597ef86..8cf813fbd 100644 --- a/network/types/endpoint.go +++ b/network/types/endpoint.go @@ -25,8 +25,9 @@ type Endpoint struct { NetworkDisabled bool NetworkMode string MacAddress string + PublishAllPorts bool ExposedPorts map[string]interface{} - PortBindings map[string]types.PortBinding + PortBindings types.PortMap NetworkConfig *types.NetworkSettings EndpointConfig *types.EndpointSettings diff --git a/test/cli_network_test.go b/test/cli_network_test.go index 465886787..66008d391 100644 --- a/test/cli_network_test.go +++ b/test/cli_network_test.go @@ -3,6 +3,7 @@ package main import ( "runtime" "strings" + "time" "github.com/alibaba/pouch/test/command" "github.com/alibaba/pouch/test/environment" @@ -283,3 +284,36 @@ func (suite *PouchNetworkSuite) TestNetworkCreateDup(c *check.C) { command.PouchRun("network", "remove", funcname) } + +func (suite *PouchNetworkSuite) TestNetworkPortMapping(c *check.C) { + pc, _, _, _ := runtime.Caller(0) + tmpname := strings.Split(runtime.FuncForPC(pc).Name(), ".") + var funcname string + for i := range tmpname { + funcname = tmpname[i] + } + + ret := icmd.RunCommand("which", "curl") + if ret.ExitCode != 0 { + c.Skip("Host does not have curl") + } + + expct := icmd.Expected{ + ExitCode: 0, + Out: "It works", + } + + image := "registry.hub.docker.com/library/httpd" + + command.PouchRun("pull", image).Assert(c, icmd.Success) + command.PouchRun("run", "-d", + "--name", funcname, + "-p", "9999:80", + image).Assert(c, icmd.Success) + + time.Sleep(1 * time.Second) + err := icmd.RunCommand("curl", "localhost:9999").Compare(expct) + c.Assert(err, check.IsNil) + + command.PouchRun("rm", "-f", funcname) +} diff --git a/vendor/github.com/docker/go-connections/LICENSE b/vendor/github.com/docker/go-connections/LICENSE new file mode 100644 index 000000000..b55b37bc3 --- /dev/null +++ b/vendor/github.com/docker/go-connections/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2015 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/docker/go-connections/nat/nat.go b/vendor/github.com/docker/go-connections/nat/nat.go new file mode 100644 index 000000000..bb7e4e336 --- /dev/null +++ b/vendor/github.com/docker/go-connections/nat/nat.go @@ -0,0 +1,242 @@ +// Package nat is a convenience package for manipulation of strings describing network ports. +package nat + +import ( + "fmt" + "net" + "strconv" + "strings" +) + +const ( + // portSpecTemplate is the expected format for port specifications + portSpecTemplate = "ip:hostPort:containerPort" +) + +// PortBinding represents a binding between a Host IP address and a Host Port +type PortBinding struct { + // HostIP is the host IP Address + HostIP string `json:"HostIp"` + // HostPort is the host port number + HostPort string +} + +// PortMap is a collection of PortBinding indexed by Port +type PortMap map[Port][]PortBinding + +// PortSet is a collection of structs indexed by Port +type PortSet map[Port]struct{} + +// Port is a string containing port number and protocol in the format "80/tcp" +type Port string + +// NewPort creates a new instance of a Port given a protocol and port number or port range +func NewPort(proto, port string) (Port, error) { + // Check for parsing issues on "port" now so we can avoid having + // to check it later on. + + portStartInt, portEndInt, err := ParsePortRangeToInt(port) + if err != nil { + return "", err + } + + if portStartInt == portEndInt { + return Port(fmt.Sprintf("%d/%s", portStartInt, proto)), nil + } + return Port(fmt.Sprintf("%d-%d/%s", portStartInt, portEndInt, proto)), nil +} + +// ParsePort parses the port number string and returns an int +func ParsePort(rawPort string) (int, error) { + if len(rawPort) == 0 { + return 0, nil + } + port, err := strconv.ParseUint(rawPort, 10, 16) + if err != nil { + return 0, err + } + return int(port), nil +} + +// ParsePortRangeToInt parses the port range string and returns start/end ints +func ParsePortRangeToInt(rawPort string) (int, int, error) { + if len(rawPort) == 0 { + return 0, 0, nil + } + start, end, err := ParsePortRange(rawPort) + if err != nil { + return 0, 0, err + } + return int(start), int(end), nil +} + +// Proto returns the protocol of a Port +func (p Port) Proto() string { + proto, _ := SplitProtoPort(string(p)) + return proto +} + +// Port returns the port number of a Port +func (p Port) Port() string { + _, port := SplitProtoPort(string(p)) + return port +} + +// Int returns the port number of a Port as an int +func (p Port) Int() int { + portStr := p.Port() + // We don't need to check for an error because we're going to + // assume that any error would have been found, and reported, in NewPort() + port, _ := ParsePort(portStr) + return port +} + +// Range returns the start/end port numbers of a Port range as ints +func (p Port) Range() (int, int, error) { + return ParsePortRangeToInt(p.Port()) +} + +// SplitProtoPort splits a port in the format of proto/port +func SplitProtoPort(rawPort string) (string, string) { + parts := strings.Split(rawPort, "/") + l := len(parts) + if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 { + return "", "" + } + 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 { + for _, availableProto := range []string{"tcp", "udp", "sctp"} { + if availableProto == proto { + return true + } + } + return false +} + +// ParsePortSpecs receives port specs in the format of ip:public:private/proto and parses +// these in to 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 { + portMappings, err := ParsePortSpec(rawPort) + if err != nil { + return nil, nil, err + } + + for _, portMapping := range portMappings { + port := portMapping.Port + if _, exists := exposedPorts[port]; !exists { + exposedPorts[port] = struct{}{} + } + bslice, exists := bindings[port] + if !exists { + bslice = []PortBinding{} + } + bindings[port] = append(bslice, portMapping.Binding) + } + } + return exposedPorts, bindings, nil +} + +// PortMapping is a data object mapping a Port to a PortBinding +type PortMapping struct { + Port Port + Binding PortBinding +} + +func splitParts(rawport string) (string, string, string) { + parts := strings.Split(rawport, ":") + n := len(parts) + containerport := parts[n-1] + + switch n { + case 1: + return "", "", containerport + case 2: + return "", parts[0], containerport + case 3: + return parts[0], parts[1], containerport + default: + return strings.Join(parts[:n-2], ":"), parts[n-2], containerport + } +} + +// ParsePortSpec parses a port specification string into a slice of PortMappings +func ParsePortSpec(rawPort string) ([]PortMapping, error) { + var proto string + rawIP, hostPort, containerPort := splitParts(rawPort) + proto, containerPort = SplitProtoPort(containerPort) + + // Strip [] from IPV6 addresses + ip, _, err := net.SplitHostPort(rawIP + ":") + if err != nil { + return nil, fmt.Errorf("Invalid ip address %v: %s", rawIP, err) + } + if ip != "" && net.ParseIP(ip) == nil { + return nil, fmt.Errorf("Invalid ip address: %s", ip) + } + if containerPort == "" { + return nil, fmt.Errorf("No port specified: %s", rawPort) + } + + startPort, endPort, err := ParsePortRange(containerPort) + if err != nil { + return nil, fmt.Errorf("Invalid containerPort: %s", containerPort) + } + + var startHostPort, endHostPort uint64 = 0, 0 + if len(hostPort) > 0 { + startHostPort, endHostPort, err = ParsePortRange(hostPort) + if err != nil { + return nil, fmt.Errorf("Invalid hostPort: %s", hostPort) + } + } + + if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { + // Allow host port range iff containerPort is not a range. + // In this case, use the host port range as the dynamic + // host port range to allocate into. + if endPort != startPort { + return nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) + } + } + + if !validateProto(strings.ToLower(proto)) { + return nil, fmt.Errorf("Invalid proto: %s", proto) + } + + ports := []PortMapping{} + for i := uint64(0); i <= (endPort - startPort); i++ { + containerPort = strconv.FormatUint(startPort+i, 10) + if len(hostPort) > 0 { + hostPort = strconv.FormatUint(startHostPort+i, 10) + } + // Set hostPort to a range only if there is a single container port + // and a dynamic host port. + if startPort == endPort && startHostPort != endHostPort { + hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) + } + port, err := NewPort(strings.ToLower(proto), containerPort) + if err != nil { + return nil, err + } + + binding := PortBinding{ + HostIP: ip, + HostPort: hostPort, + } + ports = append(ports, PortMapping{Port: port, Binding: binding}) + } + return ports, nil +} diff --git a/vendor/github.com/docker/go-connections/nat/parse.go b/vendor/github.com/docker/go-connections/nat/parse.go new file mode 100644 index 000000000..892adf8c6 --- /dev/null +++ b/vendor/github.com/docker/go-connections/nat/parse.go @@ -0,0 +1,57 @@ +package nat + +import ( + "fmt" + "strconv" + "strings" +) + +// PartParser parses and validates the specified string (data) using the specified template +// e.g. ip:public:private -> 192.168.0.1:80:8000 +// DEPRECATED: do not use, this function may be removed in a future version +func PartParser(template, data string) (map[string]string, error) { + // ip:public:private + var ( + templateParts = strings.Split(template, ":") + parts = strings.Split(data, ":") + out = make(map[string]string, len(templateParts)) + ) + if len(parts) != len(templateParts) { + return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template) + } + + for i, t := range templateParts { + value := "" + if len(parts) > i { + value = parts[i] + } + out[t] = value + } + return out, nil +} + +// ParsePortRange parses and validates the specified string as a port-range (8000-9000) +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 +} diff --git a/vendor/github.com/docker/go-connections/nat/sort.go b/vendor/github.com/docker/go-connections/nat/sort.go new file mode 100644 index 000000000..ce950171e --- /dev/null +++ b/vendor/github.com/docker/go-connections/nat/sort.go @@ -0,0 +1,96 @@ +package nat + +import ( + "sort" + "strings" +) + +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) +} + +// Sort sorts a list of ports using the provided predicate +// This function should compare `i` and `j`, returning true if `i` is +// considered to be less than `j` +func Sort(ports []Port, predicate func(i, j Port) bool) { + s := &portSorter{ports, predicate} + sort.Sort(s) +} + +type portMapEntry struct { + port Port + binding PortBinding +} + +type portMapSorter []portMapEntry + +func (s portMapSorter) Len() int { return len(s) } +func (s portMapSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// sort the port so that the order is: +// 1. port with larger specified bindings +// 2. larger port +// 3. port with tcp protocol +func (s portMapSorter) Less(i, j int) bool { + pi, pj := s[i].port, s[j].port + hpi, hpj := toInt(s[i].binding.HostPort), toInt(s[j].binding.HostPort) + return hpi > hpj || pi.Int() > pj.Int() || (pi.Int() == pj.Int() && strings.ToLower(pi.Proto()) == "tcp") +} + +// SortPortMap sorts the list of ports and their respected mapping. The ports +// will explicit HostPort will be placed first. +func SortPortMap(ports []Port, bindings PortMap) { + s := portMapSorter{} + for _, p := range ports { + if binding, ok := bindings[p]; ok { + for _, b := range binding { + s = append(s, portMapEntry{port: p, binding: b}) + } + bindings[p] = []PortBinding{} + } else { + s = append(s, portMapEntry{port: p}) + } + } + + sort.Sort(s) + var ( + i int + pm = make(map[Port]struct{}) + ) + // reorder ports + for _, entry := range s { + if _, ok := pm[entry.port]; !ok { + ports[i] = entry.port + pm[entry.port] = struct{}{} + i++ + } + // reorder bindings for this port + if _, ok := bindings[entry.port]; ok { + bindings[entry.port] = append(bindings[entry.port], entry.binding) + } + } +} + +func toInt(s string) uint64 { + i, _, err := ParsePortRange(s) + if err != nil { + i = 0 + } + return i +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 297dbcde6..ba02cf60e 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -403,6 +403,12 @@ "revision": "53a58da551e961b3710bbbdfabbc162c3f5f30f6", "revisionTime": "2018-01-31T23:13:00Z" }, + { + "checksumSHA1": "1IPGX6/BnX7QN4DjbBk0UafTB2U=", + "path": "github.com/docker/go-connections/nat", + "revision": "7395e3f8aa162843a74ed6d48e79627d9792ac55", + "revisionTime": "2018-02-28T14:10:15Z" + }, { "checksumSHA1": "Oq8DJr2mCWo5McNwdMFVwBsieXQ=", "path": "github.com/docker/go-events",