diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index b8ff5e4a4a..97906b6722 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -370,9 +370,24 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err entrypoint = []string{""} } - ports, portBindings, err := nat.ParsePortSpecs(copts.publish.GetAll()) + publishOpts := copts.publish.GetAll() + var ports map[nat.Port]struct{} + var portBindings map[nat.Port][]nat.PortBinding + + ports, portBindings, err = nat.ParsePortSpecs(publishOpts) + + // If simple port parsing fails try to parse as long format if err != nil { - return nil, err + publishOpts, err = parsePortOpts(publishOpts) + if err != nil { + return nil, err + } + + ports, portBindings, err = nat.ParsePortSpecs(publishOpts) + + if err != nil { + return nil, err + } } // Merge in exposed ports to the map of published ports @@ -661,6 +676,23 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err }, nil } +func parsePortOpts(publishOpts []string) ([]string, error) { + optsList := []string{} + for _, publish := range publishOpts { + params := map[string]string{"protocol": "tcp"} + for _, param := range strings.Split(publish, ",") { + opt := strings.Split(param, "=") + if len(opt) < 2 { + return optsList, errors.Errorf("invalid publish opts format (should be name=value but got '%s')", param) + } + + params[opt[0]] = opt[1] + } + optsList = append(optsList, fmt.Sprintf("%s:%s/%s", params["target"], params["published"], params["protocol"])) + } + return optsList, nil +} + func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) { loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts) if loggingDriver == "none" && len(loggingOpts) > 0 { diff --git a/opts/port.go b/opts/port.go index 201aefafc8..a4a91b1d5d 100644 --- a/opts/port.go +++ b/opts/port.go @@ -151,17 +151,22 @@ func ConvertPortToPortConfig( if binding.HostIP != "" && binding.HostIP != "0.0.0.0" { logrus.Warnf("ignoring IP-address (%s:%s:%s) service will listen on '0.0.0.0'", binding.HostIP, binding.HostPort, port) } - hostPort, err := strconv.ParseUint(binding.HostPort, 10, 16) + + startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort) + if err != nil && binding.HostPort != "" { return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port()) } - ports = append(ports, swarm.PortConfig{ - //TODO Name: ? - Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())), - TargetPort: uint32(port.Int()), - PublishedPort: uint32(hostPort), - PublishMode: swarm.PortConfigPublishModeIngress, - }) + + for i := startHostPort; i <= endHostPort; i++ { + ports = append(ports, swarm.PortConfig{ + //TODO Name: ? + Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())), + TargetPort: uint32(port.Int()), + PublishedPort: uint32(i), + PublishMode: swarm.PortConfigPublishModeIngress, + }) + } } return ports, nil } diff --git a/opts/port_test.go b/opts/port_test.go index fb3498f7c5..1f6b994dfc 100644 --- a/opts/port_test.go +++ b/opts/port_test.go @@ -96,6 +96,40 @@ func TestPortOptValidSimpleSyntax(t *testing.T) { }, }, }, + { + value: "80-82:8080/udp", + expected: []swarm.PortConfig{ + { + Protocol: "udp", + TargetPort: 8080, + PublishedPort: 80, + PublishMode: swarm.PortConfigPublishModeIngress, + }, + { + Protocol: "udp", + TargetPort: 8080, + PublishedPort: 81, + PublishMode: swarm.PortConfigPublishModeIngress, + }, + { + Protocol: "udp", + TargetPort: 8080, + PublishedPort: 82, + PublishMode: swarm.PortConfigPublishModeIngress, + }, + }, + }, + { + value: "80-80:8080/tcp", + expected: []swarm.PortConfig{ + { + Protocol: "tcp", + TargetPort: 8080, + PublishedPort: 80, + PublishMode: swarm.PortConfigPublishModeIngress, + }, + }, + }, } for _, tc := range testCases { var port PortOpt