Bläddra i källkod

Support expanded ports in Compose loader

This commit adds support for expanded ports in Compose loader,
and add several unit tests for loading expanded port format.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
Yong Tang 8 år sedan
förälder
incheckning
f07a28a541

+ 9 - 13
cli/compose/convert/service.go

@@ -3,6 +3,7 @@ package convert
 import (
 import (
 	"fmt"
 	"fmt"
 	"os"
 	"os"
+	"sort"
 	"time"
 	"time"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
@@ -13,8 +14,6 @@ import (
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
-	"github.com/docker/go-connections/nat"
-	"sort"
 )
 )
 
 
 // Services from compose-file types to engine API types
 // Services from compose-file types to engine API types
@@ -367,19 +366,16 @@ func (a byPublishedPort) Len() int           { return len(a) }
 func (a byPublishedPort) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 func (a byPublishedPort) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 func (a byPublishedPort) Less(i, j int) bool { return a[i].PublishedPort < a[j].PublishedPort }
 func (a byPublishedPort) Less(i, j int) bool { return a[i].PublishedPort < a[j].PublishedPort }
 
 
-func convertEndpointSpec(source []string) (*swarm.EndpointSpec, error) {
+func convertEndpointSpec(source []composetypes.ServicePortConfig) (*swarm.EndpointSpec, error) {
 	portConfigs := []swarm.PortConfig{}
 	portConfigs := []swarm.PortConfig{}
-	ports, portBindings, err := nat.ParsePortSpecs(source)
-	if err != nil {
-		return nil, err
-	}
-
-	for port := range ports {
-		portConfig, err := opts.ConvertPortToPortConfig(port, portBindings)
-		if err != nil {
-			return nil, err
+	for _, port := range source {
+		portConfig := swarm.PortConfig{
+			Protocol:      swarm.PortConfigProtocol(port.Protocol),
+			TargetPort:    port.Target,
+			PublishedPort: port.Published,
+			PublishMode:   swarm.PortConfigPublishMode(port.Mode),
 		}
 		}
-		portConfigs = append(portConfigs, portConfig...)
+		portConfigs = append(portConfigs, portConfig)
 	}
 	}
 
 
 	sort.Sort(byPublishedPort(portConfigs))
 	sort.Sort(byPublishedPort(portConfigs))

+ 34 - 0
cli/compose/convert/service_test.go

@@ -143,6 +143,40 @@ func TestConvertHealthcheckDisableWithTest(t *testing.T) {
 	assert.Error(t, err, "test and disable can't be set")
 	assert.Error(t, err, "test and disable can't be set")
 }
 }
 
 
+func TestConvertEndpointSpec(t *testing.T) {
+	source := []composetypes.ServicePortConfig{
+		{
+			Protocol:  "udp",
+			Target:    53,
+			Published: 1053,
+			Mode:      "host",
+		},
+		{
+			Target:    8080,
+			Published: 80,
+		},
+	}
+	endpoint, err := convertEndpointSpec(source)
+
+	expected := swarm.EndpointSpec{
+		Ports: []swarm.PortConfig{
+			{
+				TargetPort:    8080,
+				PublishedPort: 80,
+			},
+			{
+				Protocol:      "udp",
+				TargetPort:    53,
+				PublishedPort: 1053,
+				PublishMode:   "host",
+			},
+		},
+	}
+
+	assert.NilError(t, err)
+	assert.DeepEqual(t, *endpoint, expected)
+}
+
 func TestConvertServiceNetworksOnlyDefault(t *testing.T) {
 func TestConvertServiceNetworksOnlyDefault(t *testing.T) {
 	networkConfigs := networkMap{}
 	networkConfigs := networkMap{}
 	networks := map[string]*composetypes.ServiceNetworkConfig{}
 	networks := map[string]*composetypes.ServiceNetworkConfig{}

+ 75 - 3
cli/compose/loader/loader.go

@@ -12,7 +12,9 @@ import (
 	"github.com/docker/docker/cli/compose/interpolation"
 	"github.com/docker/docker/cli/compose/interpolation"
 	"github.com/docker/docker/cli/compose/schema"
 	"github.com/docker/docker/cli/compose/schema"
 	"github.com/docker/docker/cli/compose/types"
 	"github.com/docker/docker/cli/compose/types"
-	"github.com/docker/docker/runconfig/opts"
+	"github.com/docker/docker/opts"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
+	"github.com/docker/go-connections/nat"
 	units "github.com/docker/go-units"
 	units "github.com/docker/go-units"
 	shellwords "github.com/mattn/go-shellwords"
 	shellwords "github.com/mattn/go-shellwords"
 	"github.com/mitchellh/mapstructure"
 	"github.com/mitchellh/mapstructure"
@@ -237,6 +239,8 @@ func transformHook(
 		return transformUlimits(data)
 		return transformUlimits(data)
 	case reflect.TypeOf(types.UnitBytes(0)):
 	case reflect.TypeOf(types.UnitBytes(0)):
 		return transformSize(data)
 		return transformSize(data)
+	case reflect.TypeOf([]types.ServicePortConfig{}):
+		return transformServicePort(data)
 	case reflect.TypeOf(types.ServiceSecretConfig{}):
 	case reflect.TypeOf(types.ServiceSecretConfig{}):
 		return transformServiceSecret(data)
 		return transformServiceSecret(data)
 	case reflect.TypeOf(types.StringOrNumberList{}):
 	case reflect.TypeOf(types.StringOrNumberList{}):
@@ -340,14 +344,14 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string) e
 
 
 		for _, file := range serviceConfig.EnvFile {
 		for _, file := range serviceConfig.EnvFile {
 			filePath := absPath(workingDir, file)
 			filePath := absPath(workingDir, file)
-			fileVars, err := opts.ParseEnvFile(filePath)
+			fileVars, err := runconfigopts.ParseEnvFile(filePath)
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
 			envVars = append(envVars, fileVars...)
 			envVars = append(envVars, fileVars...)
 		}
 		}
 
 
-		for k, v := range opts.ConvertKVStringsToMap(envVars) {
+		for k, v := range runconfigopts.ConvertKVStringsToMap(envVars) {
 			environment[k] = v
 			environment[k] = v
 		}
 		}
 	}
 	}
@@ -481,6 +485,41 @@ func transformExternal(data interface{}) (interface{}, error) {
 	}
 	}
 }
 }
 
 
+func transformServicePort(data interface{}) (interface{}, error) {
+	switch entries := data.(type) {
+	case []interface{}:
+		// We process the list instead of individual items here.
+		// The reason is that one entry might be mapped to multiple ServicePortConfig.
+		// Therefore we take an input of a list and return an output of a list.
+		ports := []interface{}{}
+		for _, entry := range entries {
+			switch value := entry.(type) {
+			case int:
+				v, err := toServicePortConfigs(fmt.Sprint(value))
+				if err != nil {
+					return data, err
+				}
+				ports = append(ports, v...)
+			case string:
+				v, err := toServicePortConfigs(value)
+				if err != nil {
+					return data, err
+				}
+				ports = append(ports, v...)
+			case types.Dict:
+				ports = append(ports, value)
+			case map[string]interface{}:
+				ports = append(ports, value)
+			default:
+				return data, fmt.Errorf("invalid type %T for port", value)
+			}
+		}
+		return ports, nil
+	default:
+		return data, fmt.Errorf("invalid type %T for port", entries)
+	}
+}
+
 func transformServiceSecret(data interface{}) (interface{}, error) {
 func transformServiceSecret(data interface{}) (interface{}, error) {
 	switch value := data.(type) {
 	switch value := data.(type) {
 	case string:
 	case string:
@@ -572,6 +611,39 @@ func transformSize(value interface{}) (int64, error) {
 	panic(fmt.Errorf("invalid type for size %T", value))
 	panic(fmt.Errorf("invalid type for size %T", value))
 }
 }
 
 
+func toServicePortConfigs(value string) ([]interface{}, error) {
+	var portConfigs []interface{}
+
+	ports, portBindings, err := nat.ParsePortSpecs([]string{value})
+	if err != nil {
+		return nil, err
+	}
+	// We need to sort the key of the ports to make sure it is consistent
+	keys := []string{}
+	for port := range ports {
+		keys = append(keys, string(port))
+	}
+	sort.Strings(keys)
+
+	for _, key := range keys {
+		// Reuse ConvertPortToPortConfig so that it is consistent
+		portConfig, err := opts.ConvertPortToPortConfig(nat.Port(key), portBindings)
+		if err != nil {
+			return nil, err
+		}
+		for _, p := range portConfig {
+			portConfigs = append(portConfigs, types.ServicePortConfig{
+				Protocol:  string(p.Protocol),
+				Target:    p.TargetPort,
+				Published: p.PublishedPort,
+				Mode:      string(p.PublishMode),
+			})
+		}
+	}
+
+	return portConfigs, nil
+}
+
 func toMapStringString(value map[string]interface{}) map[string]string {
 func toMapStringString(value map[string]interface{}) map[string]string {
 	output := make(map[string]string)
 	output := make(map[string]string)
 	for key, value := range value {
 	for key, value := range value {

+ 224 - 8
cli/compose/loader/loader_test.go

@@ -675,14 +675,145 @@ func TestFullExample(t *testing.T) {
 			"other-other-network": nil,
 			"other-other-network": nil,
 		},
 		},
 		Pid: "host",
 		Pid: "host",
-		Ports: []string{
-			"3000",
-			"3000-3005",
-			"8000:8000",
-			"9090-9091:8080-8081",
-			"49100:22",
-			"127.0.0.1:8001:8001",
-			"127.0.0.1:5000-5010:5000-5010",
+		Ports: []types.ServicePortConfig{
+			//"3000",
+			{
+				Mode:     "ingress",
+				Target:   3000,
+				Protocol: "tcp",
+			},
+			//"3000-3005",
+			{
+				Mode:     "ingress",
+				Target:   3000,
+				Protocol: "tcp",
+			},
+			{
+				Mode:     "ingress",
+				Target:   3001,
+				Protocol: "tcp",
+			},
+			{
+				Mode:     "ingress",
+				Target:   3002,
+				Protocol: "tcp",
+			},
+			{
+				Mode:     "ingress",
+				Target:   3003,
+				Protocol: "tcp",
+			},
+			{
+				Mode:     "ingress",
+				Target:   3004,
+				Protocol: "tcp",
+			},
+			{
+				Mode:     "ingress",
+				Target:   3005,
+				Protocol: "tcp",
+			},
+			//"8000:8000",
+			{
+				Mode:      "ingress",
+				Target:    8000,
+				Published: 8000,
+				Protocol:  "tcp",
+			},
+			//"9090-9091:8080-8081",
+			{
+				Mode:      "ingress",
+				Target:    8080,
+				Published: 9090,
+				Protocol:  "tcp",
+			},
+			{
+				Mode:      "ingress",
+				Target:    8081,
+				Published: 9091,
+				Protocol:  "tcp",
+			},
+			//"49100:22",
+			{
+				Mode:      "ingress",
+				Target:    22,
+				Published: 49100,
+				Protocol:  "tcp",
+			},
+			//"127.0.0.1:8001:8001",
+			{
+				Mode:      "ingress",
+				Target:    8001,
+				Published: 8001,
+				Protocol:  "tcp",
+			},
+			//"127.0.0.1:5000-5010:5000-5010",
+			{
+				Mode:      "ingress",
+				Target:    5000,
+				Published: 5000,
+				Protocol:  "tcp",
+			},
+			{
+				Mode:      "ingress",
+				Target:    5001,
+				Published: 5001,
+				Protocol:  "tcp",
+			},
+			{
+				Mode:      "ingress",
+				Target:    5002,
+				Published: 5002,
+				Protocol:  "tcp",
+			},
+			{
+				Mode:      "ingress",
+				Target:    5003,
+				Published: 5003,
+				Protocol:  "tcp",
+			},
+			{
+				Mode:      "ingress",
+				Target:    5004,
+				Published: 5004,
+				Protocol:  "tcp",
+			},
+			{
+				Mode:      "ingress",
+				Target:    5005,
+				Published: 5005,
+				Protocol:  "tcp",
+			},
+			{
+				Mode:      "ingress",
+				Target:    5006,
+				Published: 5006,
+				Protocol:  "tcp",
+			},
+			{
+				Mode:      "ingress",
+				Target:    5007,
+				Published: 5007,
+				Protocol:  "tcp",
+			},
+			{
+				Mode:      "ingress",
+				Target:    5008,
+				Published: 5008,
+				Protocol:  "tcp",
+			},
+			{
+				Mode:      "ingress",
+				Target:    5009,
+				Published: 5009,
+				Protocol:  "tcp",
+			},
+			{
+				Mode:      "ingress",
+				Target:    5010,
+				Published: 5010,
+				Protocol:  "tcp",
+			},
 		},
 		},
 		Privileged: true,
 		Privileged: true,
 		ReadOnly:   true,
 		ReadOnly:   true,
@@ -825,3 +956,88 @@ networks:
 
 
 	assert.Equal(t, expected, config.Networks)
 	assert.Equal(t, expected, config.Networks)
 }
 }
+
+func TestLoadExpandedPortFormat(t *testing.T) {
+	config, err := loadYAML(`
+version: "3.1"
+services:
+  web:
+    image: busybox
+    ports:
+      - "80-82:8080-8082"
+      - "90-92:8090-8092/udp"
+      - "85:8500"
+      - 8600
+      - protocol: udp
+        target: 53
+        published: 10053
+      - mode: host
+        target: 22
+        published: 10022
+`)
+	assert.NoError(t, err)
+
+	expected := []types.ServicePortConfig{
+		{
+			Mode:      "ingress",
+			Target:    8080,
+			Published: 80,
+			Protocol:  "tcp",
+		},
+		{
+			Mode:      "ingress",
+			Target:    8081,
+			Published: 81,
+			Protocol:  "tcp",
+		},
+		{
+			Mode:      "ingress",
+			Target:    8082,
+			Published: 82,
+			Protocol:  "tcp",
+		},
+		{
+			Mode:      "ingress",
+			Target:    8090,
+			Published: 90,
+			Protocol:  "udp",
+		},
+		{
+			Mode:      "ingress",
+			Target:    8091,
+			Published: 91,
+			Protocol:  "udp",
+		},
+		{
+			Mode:      "ingress",
+			Target:    8092,
+			Published: 92,
+			Protocol:  "udp",
+		},
+		{
+			Mode:      "ingress",
+			Target:    8500,
+			Published: 85,
+			Protocol:  "tcp",
+		},
+		{
+			Mode:      "ingress",
+			Target:    8600,
+			Published: 0,
+			Protocol:  "tcp",
+		},
+		{
+			Target:    53,
+			Published: 10053,
+			Protocol:  "udp",
+		},
+		{
+			Mode:      "host",
+			Target:    22,
+			Published: 10022,
+		},
+	}
+
+	assert.Equal(t, 1, len(config.Services))
+	assert.Equal(t, expected, config.Services[0].Ports)
+}