Skip to content

Commit

Permalink
Support expanded ports in Compose loader
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
yongtang committed Feb 7, 2017
1 parent c69e0f7 commit c534712
Show file tree
Hide file tree
Showing 4 changed files with 342 additions and 24 deletions.
22 changes: 9 additions & 13 deletions compose/convert/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package convert
import (
"fmt"
"os"
"sort"
"time"

"github.com/docker/docker/api/types"
Expand All @@ -13,8 +14,6 @@ import (
"github.com/docker/docker/client"
"github.com/docker/docker/opts"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/go-connections/nat"
"sort"
)

// Services from compose-file types to engine API types
Expand Down Expand Up @@ -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) 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{}
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))
Expand Down
34 changes: 34 additions & 0 deletions compose/convert/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,40 @@ func TestConvertHealthcheckDisableWithTest(t *testing.T) {
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) {
networkConfigs := networkMap{}
networks := map[string]*composetypes.ServiceNetworkConfig{}
Expand Down
78 changes: 75 additions & 3 deletions compose/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
"github.com/docker/docker/cli/compose/interpolation"
"github.com/docker/docker/cli/compose/schema"
"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"
shellwords "github.com/mattn/go-shellwords"
"github.com/mitchellh/mapstructure"
Expand Down Expand Up @@ -237,6 +239,8 @@ func transformHook(
return transformUlimits(data)
case reflect.TypeOf(types.UnitBytes(0)):
return transformSize(data)
case reflect.TypeOf([]types.ServicePortConfig{}):
return transformServicePort(data)
case reflect.TypeOf(types.ServiceSecretConfig{}):
return transformServiceSecret(data)
case reflect.TypeOf(types.StringOrNumberList{}):
Expand Down Expand Up @@ -340,14 +344,14 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string) e

for _, file := range serviceConfig.EnvFile {
filePath := absPath(workingDir, file)
fileVars, err := opts.ParseEnvFile(filePath)
fileVars, err := runconfigopts.ParseEnvFile(filePath)
if err != nil {
return err
}
envVars = append(envVars, fileVars...)
}

for k, v := range opts.ConvertKVStringsToMap(envVars) {
for k, v := range runconfigopts.ConvertKVStringsToMap(envVars) {
environment[k] = v
}
}
Expand Down Expand Up @@ -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) {
switch value := data.(type) {
case string:
Expand Down Expand Up @@ -572,6 +611,39 @@ func transformSize(value interface{}) (int64, error) {
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 {
output := make(map[string]string)
for key, value := range value {
Expand Down
Loading

0 comments on commit c534712

Please sign in to comment.