diff --git a/cli/compose/convert/service.go b/cli/compose/convert/service.go index a8613c0878..ef6a04ebcf 100644 --- a/cli/compose/convert/service.go +++ b/cli/compose/convert/service.go @@ -3,6 +3,7 @@ package convert import ( "fmt" "os" + "sort" "time" "github.com/docker/docker/api/types" @@ -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 @@ -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)) diff --git a/cli/compose/convert/service_test.go b/cli/compose/convert/service_test.go index 2e614d730c..64ccfd038e 100644 --- a/cli/compose/convert/service_test.go +++ b/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") } +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{} diff --git a/cli/compose/loader/loader.go b/cli/compose/loader/loader.go index 2c92666c51..2ccef7198d 100644 --- a/cli/compose/loader/loader.go +++ b/cli/compose/loader/loader.go @@ -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" @@ -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{}): @@ -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 } } @@ -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: @@ -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 { diff --git a/cli/compose/loader/loader_test.go b/cli/compose/loader/loader_test.go index 3a2f27204d..53f4280b64 100644 --- a/cli/compose/loader/loader_test.go +++ b/cli/compose/loader/loader_test.go @@ -675,14 +675,145 @@ func TestFullExample(t *testing.T) { "other-other-network": nil, }, 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, ReadOnly: true, @@ -825,3 +956,88 @@ 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) +} diff --git a/cli/compose/schema/bindata.go b/cli/compose/schema/bindata.go index bb91fbfa5f..0b5aa18b75 100644 --- a/cli/compose/schema/bindata.go +++ b/cli/compose/schema/bindata.go @@ -89,7 +89,7 @@ func dataConfig_schema_v30Json() (*asset, error) { return a, nil } -var _dataConfig_schema_v31Json = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x1a\xcb\x8e\xdb\x36\xf0\xee\xaf\x10\x94\xdc\xe2\xdd\x4d\xd1\xa0\x40\x73\xeb\xb1\xa7\xf6\xdc\x85\x23\xd0\xd2\x58\x66\x96\x22\x19\x92\x72\xd6\x09\xfc\xef\x05\xf5\x32\x45\x91\x22\x6d\x2b\xd9\x45\xd1\xd3\xae\xc5\x99\xe1\xbc\x67\x38\xe4\xf7\x55\x92\xa4\x6f\x65\xbe\x87\x0a\xa5\x1f\x93\x74\xaf\x14\xff\xf8\xf0\xf0\x59\x32\x7a\xd7\x7e\xbd\x67\xa2\x7c\x28\x04\xda\xa9\xbb\xf7\x1f\x1e\xda\x6f\x6f\xd2\xb5\xc6\xc3\x85\x46\xc9\x19\xdd\xe1\x32\x6b\x57\xb2\xc3\xaf\xf7\xbf\xdc\x6b\xf4\x16\x44\x1d\x39\x68\x20\xb6\xfd\x0c\xb9\x6a\xbf\x09\xf8\x52\x63\x01\x1a\xf9\x31\x3d\x80\x90\x98\xd1\x74\xb3\x5e\xe9\x35\x2e\x18\x07\xa1\x30\xc8\xf4\x63\xa2\x99\x4b\x92\x01\xa4\xff\x60\x90\x95\x4a\x60\x5a\xa6\xcd\xe7\x53\x43\x21\x49\x52\x09\xe2\x80\x73\x83\xc2\xc0\xea\x9b\x87\x33\xfd\x87\x01\x6c\x6d\x53\x35\x98\x6d\xbe\x73\xa4\x14\x08\xfa\xf7\x94\xb7\x66\xf9\xd3\x23\xba\xfb\xf6\xc7\xdd\x3f\xef\xef\x7e\xbf\xcf\xee\x36\xef\xde\x8e\x96\xb5\x7e\x05\xec\xda\xed\x0b\xd8\x61\x8a\x15\x66\x74\xd8\x3f\x1d\x20\x4f\xdd\x7f\xa7\x61\x63\x54\x14\x0d\x30\x22\xa3\xbd\x77\x88\x48\x18\xcb\x4c\x41\x7d\x65\xe2\x29\x24\xf3\x00\xf6\x42\x32\x77\xfb\x3b\x64\x1e\x8b\x73\x60\xa4\xae\x82\x16\xec\xa1\x5e\x48\x98\x76\xfb\x65\xec\x27\x21\x17\xa0\xc2\x2e\xdb\x42\xbd\x98\xc7\xea\xed\x6f\x13\x78\xd5\x0b\x3d\x0b\xdb\x42\x18\x7b\x37\x0c\x8e\xc2\xdb\xa5\x2a\x57\x78\xf9\x75\x35\x28\xcb\xa3\xa5\x02\x38\x61\x47\xfd\xcd\xa3\x8f\x16\xa0\x02\xaa\xd2\x41\x05\x49\x92\x6e\x6b\x4c\x0a\x5b\xa3\x8c\xc2\x5f\x9a\xc4\xa3\xf1\x31\x49\xbe\xdb\x99\xcc\xa0\xd3\xac\x8f\x7e\xf9\x0d\x3e\xac\x7b\x64\x19\xd6\x73\x46\x15\x3c\xab\x46\xa8\xf9\xad\x5b\x15\xb0\xfc\x09\xc4\x0e\x13\x88\xc5\x40\xa2\x94\x33\x2a\x23\x58\xaa\x8c\x89\xac\xc0\xb9\x4a\x4f\x16\xfa\x84\x5e\xd8\x9f\x06\x54\xe3\xd7\x66\xe5\x20\x98\xe6\x88\x67\xa8\x28\x46\x72\x20\x21\xd0\x31\x5d\x27\x29\x56\x50\x49\xb7\x88\x49\x5a\x53\xfc\xa5\x86\x3f\x3b\x10\x25\x6a\xb0\xe9\x16\x82\xf1\xe5\x09\x97\x82\xd5\x3c\xe3\x48\x68\x07\x9b\x57\x7f\x9a\xb3\xaa\x42\x74\x29\xaf\xbb\x44\x8e\x08\xcd\x33\xaa\x10\xa6\x20\x32\x8a\xaa\x90\x23\xe9\xa8\x03\x5a\xc8\xac\x2d\xf8\xb3\x6e\xb4\xcb\x5a\x7c\x69\x11\x18\xaa\xff\xa2\xf6\x28\xe8\x9c\x63\xb7\x64\xb4\x6b\x6b\xde\x52\x0b\x31\x93\x80\x44\xbe\xbf\x12\x9f\x55\x08\xd3\x18\xdd\x01\x55\xe2\xc8\x19\x6e\xfd\xe5\xd5\x39\x02\xd0\x43\x36\xe4\x92\x8b\xd5\x00\xf4\x80\x05\xa3\x55\x1f\x0d\x31\x09\x66\x48\xf2\x1a\xff\x99\x33\x09\xb6\x62\x2c\x01\xcd\xa5\x41\xd4\x91\x4e\x7a\x8c\xc7\x5e\xf0\x75\x92\xd2\xba\xda\x82\xd0\x3d\xec\x08\x72\xc7\x44\x85\x34\xb3\xfd\xde\xc6\xf2\x48\xd3\x0e\xcf\x33\x15\x68\xca\xa0\xcb\x3a\x22\x19\xc1\xf4\x69\x79\x17\x87\x67\x25\x50\xb6\x67\x52\xc5\xe7\x70\x03\x7d\x0f\x88\xa8\x7d\xbe\x87\xfc\x69\x06\xdd\x84\x1a\x61\x33\xa9\x62\x9c\x1c\x57\xa8\x0c\x03\xf1\x3c\x04\x42\xd0\x16\xc8\x55\x72\x2e\xaa\x7c\x83\x2c\x2b\x4b\x0d\xea\xf3\xb8\x49\xe7\xd2\x2d\x87\x6a\x7e\x21\xf0\x01\x44\x6c\x01\x67\xfc\xdc\x70\xd9\x8b\xe1\x06\x24\x09\x77\x9f\x23\xd0\x4f\xf7\x6d\xf3\x39\x13\x55\xcd\x7f\x84\xa4\x1b\xbb\x5d\x48\xac\xba\xef\xfa\x62\x49\x18\xd7\x50\x8c\xac\x52\xa1\x5c\xf7\x0d\x02\xa4\xc7\xae\x67\xd0\xee\x74\x93\x55\xac\xf0\x39\xe8\x04\xd8\xd6\x8d\x37\x53\x5f\x5c\x08\x93\xab\xfa\xc7\x28\xd3\x05\x0f\x10\x01\x69\x7c\xec\xc5\xb2\x79\x66\x37\xec\x62\x0d\x1c\x22\x18\x49\x08\x07\xbb\x57\x91\x23\x6a\x98\x1f\x3e\x44\xfa\x84\x0b\xf7\xb7\x59\x5c\x0f\xaa\x97\x66\x7c\x8f\x1c\x20\x75\x66\xa5\x09\x37\x17\x23\x9b\x40\xb4\xfd\xe0\x16\x9e\xe3\xc2\x9f\x2b\x9a\x0c\x61\x06\x18\x67\x42\x4d\xa2\xeb\xe7\x94\xfb\x76\xeb\x9b\xab\x3d\x17\xf8\x80\x09\x94\x30\x3e\xb5\x6c\x19\x23\x80\xe8\x28\xf5\x08\x40\x45\xc6\x28\x39\x46\x40\x4a\x85\x44\xf0\x40\x21\x21\xaf\x05\x56\xc7\x8c\x71\xb5\x78\x9f\x21\xf7\x55\x26\xf1\x37\x18\x5b\xf3\x9c\xef\x3b\x42\x1b\x8b\x21\x6b\x42\x72\xa5\x41\x7d\x29\x29\x1c\xc6\x8e\x44\x18\x4c\x54\xe1\x14\x95\x4a\x56\x8b\x3c\xf6\x80\xad\xf7\x44\xa2\x84\xd8\x23\xbc\x76\xb7\x71\xd8\xcc\x03\x97\x97\x00\x4f\x0a\x5d\x67\xc2\x50\x55\xb6\x7f\x9b\x79\xe5\xe4\x0c\x7d\x79\x94\xb9\xba\xae\x5b\x93\xaa\xc0\x34\x63\x1c\x68\x30\x36\xa4\x62\x3c\x2b\x05\xca\x21\xe3\x20\x30\x73\xaa\x62\x6d\x46\x7a\x51\x0b\xa4\xf7\x9f\x92\x91\xb8\xa4\x88\x84\xc2\x4c\x55\x7c\x77\xe5\xb1\x52\xa9\x70\xb0\xd7\x04\x57\xd8\x1f\x34\x0e\xaf\x8d\xe8\x00\xda\xea\xef\x2e\xfa\x33\x05\xff\xcc\x29\xa6\x0a\x4a\xed\x26\x53\xa7\x9a\xe9\x39\xe7\x5b\xce\x88\x5e\x73\x8f\xc4\xd8\xa0\x33\x7c\x24\x6d\x60\xee\x94\x1b\xc1\xd5\x89\x3a\xf9\x1a\xdd\x75\x34\xf4\xd6\x1d\x23\x1b\x27\xfc\x45\xc5\xdc\x66\x63\xe3\xad\xa7\xee\xa0\xaa\x65\xf0\x58\xd0\xc0\x50\x39\xd7\xd2\x0e\xa0\xc6\xd0\x7e\xd1\x6a\xa1\xdb\x64\x1d\x04\x05\x76\x73\xbb\xb2\x24\xbb\x60\xec\x6e\x9d\x58\x7b\x02\xae\x79\xb2\x09\x1a\x9c\xbf\xcf\xcf\xb6\x3b\x20\xef\xdc\x19\x4b\xb4\xb5\x26\xae\xae\xe0\xd6\xde\x28\x0e\xe1\x1c\x23\x40\x09\x6c\xd9\xa5\x4f\xd4\x66\x3e\x01\xf9\x3a\xc7\x46\x0a\x57\xc0\x6a\x77\xc1\x5b\x99\xfe\xdd\x21\xa5\xc6\x5c\x3e\x60\x54\x03\xd2\xb6\xe9\xe3\x60\xd4\xbe\xbb\x0c\x1a\x2e\x26\x48\x04\x70\x82\x73\x24\x43\x89\xe8\x86\xf1\x44\xcd\x0b\xa4\x20\x6b\xef\x65\x2f\x4a\xfd\x33\x39\x9f\x23\x81\x08\x01\x82\x65\x15\x93\x43\xd3\x02\x08\x3a\x5e\x55\x3e\x1b\xf4\x1d\xc2\xa4\x16\x90\xa1\x5c\x75\x57\xbf\x01\x9f\x4b\x2b\x46\xb1\x62\xce\x0c\x11\xb7\x65\x85\x9e\xb3\x7e\xdb\x06\x24\xd4\xd9\x8c\x9b\xfa\xd8\xc9\x82\xe1\x09\x6d\xe3\x77\x59\x75\x9e\x31\xd1\xb9\xd6\x7b\x3c\xa6\xdf\x71\x22\xba\x00\xa9\x33\xc9\x30\xf8\x09\xe2\x07\x4b\x4b\x77\xca\xc8\x38\x23\x38\x3f\x2e\x25\x61\xce\x68\xab\xe4\x18\x87\xb8\xd1\x03\xb5\x3b\xe8\x56\xa8\xe2\x2a\x18\xac\x0d\xc2\x57\x4c\x0b\xf6\xf5\x82\x0d\x97\x73\x25\x4e\x50\x0e\x56\xbe\xbb\x55\xd1\x52\x09\x84\xa9\xba\xb8\x9c\xdf\x2a\xd6\x0d\xd5\x7c\xf0\xcf\x40\xd6\x1f\xe0\xc2\xf7\xe8\x9e\x4c\x9f\xf3\x3a\x38\x0d\xac\xa0\x62\xc2\xe9\x80\x0b\x3c\xf4\x08\x89\xd8\x83\x2d\x50\xd5\xa2\xc6\xc7\x1d\x54\xc6\xf8\xf2\xa7\x8d\xf0\x88\x78\x13\x4e\x48\x98\xa3\x6a\xa9\xe8\x88\x1e\xa8\xa7\xce\x1a\x9c\xcc\xcf\x2d\x12\xff\xec\x22\xc4\x75\x98\xf7\x0e\x42\xd6\x5b\xea\x19\x21\x4c\x4f\x19\xae\x5b\xfe\xf8\x63\xca\xc9\x7f\x28\xb9\x2d\xe9\xf5\x77\x61\x1e\xab\x3e\x0e\x3d\xf3\x7a\xd0\xd5\x26\xda\xc4\xde\x8b\xa8\xe5\xf8\x6f\xda\x77\x7b\x44\xe0\xea\xf3\x91\x52\x28\xdf\x47\x1d\x09\x2e\x6c\x1a\x6f\xc8\x43\xdd\xe3\xa7\x40\x1a\xea\xa0\xfe\xcf\x42\xff\x11\x9f\xfd\x79\xfe\xd5\xbd\x35\x0b\x3e\xf2\x6a\xa0\xae\xae\xe3\x11\x2f\x9b\x5e\x81\xcd\x5e\xda\x14\xe3\x19\xa4\x61\x92\xe9\x78\x60\x4e\x93\xd1\x57\x6f\x1d\xc6\x66\xcc\x86\x0d\xe6\x78\x0e\x3c\x2e\xa6\x73\x33\xa7\x1e\xc4\x73\x15\x63\x6d\xda\x29\x71\x5e\xf2\x05\x93\xcd\xfd\xbb\x99\x96\x61\xee\x8a\xfc\x07\xd5\xda\x05\xe6\x79\x6e\x9b\x5a\xe7\x8c\x5e\xbb\xd3\x27\x9e\x9e\xf8\x37\xf0\x27\x0f\x3e\xb5\x9c\xf4\x38\x19\x5f\x7d\x1f\xcf\x64\xdb\xc7\x9a\x9b\x91\x7e\x2c\x90\xf6\xc1\x89\x91\xdd\x37\xe6\xd1\xcb\x67\x46\xe7\x33\x50\x7b\x22\xdc\x3f\xc7\xf4\x5c\x80\xac\xcc\xbf\xcd\xd3\xd9\xd5\x69\xf5\x6f\x00\x00\x00\xff\xff\x3e\x1e\x04\x4e\xb3\x2f\x00\x00") +var _dataConfig_schema_v31Json = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x5a\xcd\x8f\xdc\x28\x16\xbf\xd7\x5f\x61\x39\xb9\xa5\x3f\xb2\xda\x68\xa5\xcd\x6d\x8f\x7b\x9a\x39\x4f\xcb\xb1\x28\xfb\x95\x8b\x34\x06\x02\xb8\xd2\x95\xa8\xff\xf7\x11\xfe\x2a\x8c\xc1\xe0\x2e\xf7\x74\x34\x9a\x53\x77\x99\xdf\x03\xde\xf7\xe3\xc1\xcf\x5d\x92\xa4\xef\x65\x71\x84\x1a\xa5\x9f\x93\xf4\xa8\x14\xff\x7c\x7f\xff\x55\x32\x7a\xdb\x7d\xbd\x63\xa2\xba\x2f\x05\x3a\xa8\xdb\x8f\x9f\xee\xbb\x6f\xef\xd2\x1b\x4d\x87\x4b\x4d\x52\x30\x7a\xc0\x55\xde\x8d\xe4\xa7\x7f\xdf\xfd\xeb\x4e\x93\x77\x10\x75\xe6\xa0\x41\x6c\xff\x15\x0a\xd5\x7d\x13\xf0\xad\xc1\x02\x34\xf1\x43\x7a\x02\x21\x31\xa3\x69\x76\xb3\xd3\x63\x5c\x30\x0e\x42\x61\x90\xe9\xe7\x44\x6f\x2e\x49\x46\xc8\xf0\xc1\x98\x56\x2a\x81\x69\x95\xb6\x9f\x9f\xdb\x19\x92\x24\x95\x20\x4e\xb8\x30\x66\x18\xb7\xfa\xee\xfe\x32\xff\xfd\x08\xbb\xb1\x67\x35\x36\xdb\x7e\xe7\x48\x29\x10\xf4\xf7\xf9\xde\xda\xe1\x2f\x0f\xe8\xf6\xc7\xff\x6e\xff\xf8\x78\xfb\xdf\xbb\xfc\x36\xfb\xf0\x7e\x32\xac\xe5\x2b\xe0\xd0\x2d\x5f\xc2\x01\x53\xac\x30\xa3\xe3\xfa\xe9\x88\x7c\xee\xff\x7b\x1e\x17\x46\x65\xd9\x82\x11\x99\xac\x7d\x40\x44\xc2\x94\x67\x0a\xea\x3b\x13\x8f\x21\x9e\x47\xd8\x1b\xf1\xdc\xaf\xef\xe0\x79\xca\xce\x89\x91\xa6\x0e\x6a\x70\x40\xbd\x11\x33\xdd\xf2\xdb\xe8\x4f\x42\x21\x40\x85\x4d\xb6\x43\xbd\x99\xc5\xea\xe5\xaf\x63\x78\x37\x30\xbd\x88\xed\x10\xc6\xda\xed\x06\x27\xee\xed\x12\x95\xcb\xbd\xfc\xb2\x1a\x85\xe5\x91\x52\x09\x9c\xb0\xb3\xfe\xe6\x91\x47\x07\xa8\x81\xaa\x74\x14\x41\x92\xa4\xfb\x06\x93\xd2\x96\x28\xa3\xf0\x9b\x9e\xe2\xc1\xf8\x98\x24\x3f\xed\x48\x66\xcc\xd3\x8e\x4f\x7e\xf9\x15\x3e\x8e\x7b\x78\x19\xc7\x0b\x46\x15\x3c\xa9\x96\xa9\xe5\xa5\x3b\x11\xb0\xe2\x11\xc4\x01\x13\x88\xa5\x40\xa2\x92\x0b\x22\x23\x58\xaa\x9c\x89\xbc\xc4\x85\x4a\x9f\x2d\xf2\xd9\x7c\x61\x7b\x1a\x49\x8d\x5f\xd9\xce\x31\x61\x5a\x20\x9e\xa3\xb2\x9c\xf0\x81\x84\x40\xe7\xf4\x26\x49\xb1\x82\x5a\xba\x59\x4c\xd2\x86\xe2\x6f\x0d\xfc\xbf\x87\x28\xd1\x80\x3d\x6f\x29\x18\xdf\x7e\xe2\x4a\xb0\x86\xe7\x1c\x09\x6d\x60\xcb\xe2\x4f\x0b\x56\xd7\x88\x6e\x65\x75\x6b\xf8\x88\x90\x3c\xa3\x0a\x61\x0a\x22\xa7\xa8\x0e\x19\x92\xf6\x3a\xa0\xa5\xcc\xbb\x84\xbf\x68\x46\x87\xbc\xa3\x97\xd6\x04\x63\xf6\xdf\x54\x1f\x25\x5d\x32\xec\x6e\x1a\x6d\xda\x7a\x6f\xa9\x45\x98\x4b\x40\xa2\x38\xbe\x90\x9e\xd5\x08\xd3\x18\xd9\x01\x55\xe2\xcc\x19\xee\xec\xe5\x97\x33\x04\xa0\xa7\x7c\x8c\x25\xab\xc5\x00\xf4\x84\x05\xa3\xf5\xe0\x0d\x31\x01\x66\x0c\xf2\x9a\xfe\x89\x33\x09\xb6\x60\x2c\x06\xcd\xa1\x91\xd5\x89\x4c\x06\x8a\x87\x81\xf1\x9b\x24\xa5\x4d\xbd\x07\xa1\x6b\xd8\x09\xf2\xc0\x44\x8d\xf4\x66\x87\xb5\x8d\xe1\x89\xa4\x1d\x96\x67\x0a\xd0\xe4\x41\xa7\x75\x44\x72\x82\xe9\xe3\xf6\x26\x0e\x4f\x4a\xa0\xfc\xc8\xa4\x8a\x8f\xe1\x06\xf9\x11\x10\x51\xc7\xe2\x08\xc5\xe3\x02\xb9\x89\x9a\x50\x33\xa9\x62\x8c\x1c\xd7\xa8\x0a\x83\x78\x11\x82\x10\xb4\x07\xf2\x22\x3e\x37\x15\xbe\x31\x2d\xab\x2a\x0d\xf5\x59\xdc\xac\x72\xe9\x87\x43\x39\xbf\x14\xf8\x04\x22\x36\x81\x33\x7e\x29\xb8\xec\xc1\x70\x01\x92\x84\xab\xcf\x09\xf4\xcb\x5d\x57\x7c\x2e\x78\x55\xfb\x1f\x21\x69\x66\x97\x0b\x89\x95\xf7\x5d\x5f\x2c\x0e\xe3\x0a\x8a\x89\x56\x6a\x54\xe8\xba\x41\x80\xf4\xe8\xf5\x02\xed\x4f\x37\x79\xcd\x4a\x9f\x81\xce\xc0\xb6\x6c\xbc\x91\x7a\x75\x22\x4c\x5e\x54\x3f\x46\xa9\x2e\x78\x80\x08\x70\xe3\xdb\x5e\xec\x36\x2f\xdb\x0d\x9b\x58\x8b\x43\x04\x23\x09\x61\x67\xf7\x0a\x72\x32\x1b\xe6\xa7\x4f\x91\x36\xe1\xa2\xfd\xcf\x22\xad\x87\xd4\x3b\x67\x7c\x8d\x1c\x98\xea\xb2\x95\xd6\xdd\x5c\x1b\xc9\x02\xde\xf6\xca\x25\x3c\xc7\xa5\x3f\x56\xb4\x11\xc2\x74\x30\xce\x84\x9a\x79\xd7\xfa\x74\xef\xb3\x60\x53\x5c\x43\x9c\xba\x24\xfc\x6e\xf1\x99\x34\x66\xea\x8e\x22\x9a\xfb\x5f\xd0\x3f\xc2\x9e\x91\x2e\x44\x29\x07\x5a\x21\x51\xc1\xf4\x18\x82\xa9\x82\x0a\x84\x87\x80\x37\x7b\x82\xe5\x11\xca\x35\x34\x82\x29\x56\x30\x12\xe7\x18\xce\xe3\x67\xbc\x33\x4c\x27\xcc\xae\xae\xcd\xb8\xc0\x27\x4c\xa0\xb2\x38\xde\x33\x46\x00\xd1\x49\xa2\x10\x80\xca\x9c\x51\x72\x8e\x40\x4a\x85\x44\xf0\xf8\x27\xa1\x68\x04\x56\xe7\x9c\x71\xb5\x79\x55\x28\x8f\x75\x2e\xf1\x0f\x98\xfa\xde\xc5\xea\xfb\x89\x32\x6b\x43\x56\x3f\x2b\x79\x2d\xf7\xf3\x99\xed\x2b\xb9\x8d\x64\x8d\x28\xae\x73\x9c\x45\x7c\x33\x0d\x72\xcb\xe0\x6a\x0d\x78\xe6\xf0\xbd\x0a\x43\x35\xd4\xa2\xab\x38\x03\xb5\x3c\xcb\x42\xbd\xac\xb6\x96\xaa\xc4\x34\x67\x1c\x68\xd0\x37\xa4\x62\x3c\xaf\x04\x2a\x20\xe7\x20\x30\x73\x8a\x62\x12\x60\xcb\x46\x20\xbd\xfe\x7c\x1a\x89\x2b\x8a\xdc\x71\xc7\x80\xaa\x9a\x1f\x5e\xd8\x04\x50\x2a\xec\xec\x0d\xc1\x35\xf6\x3b\x8d\xc3\x6a\x23\xea\xb5\xae\x56\x73\x97\x68\x0b\xe5\x59\x54\xc8\x5e\x38\x21\x2c\x1f\x10\x22\x4e\x06\x47\x24\x56\xa4\x8e\xd6\x31\x0f\x9e\xfc\xe4\x3a\x37\x38\xf7\x35\xb9\x99\x6a\xe7\xbb\xe9\x37\x92\x39\xf1\xab\x4a\x2f\x7b\x1b\x99\xb7\xfa\x71\x3b\x55\x23\x83\x87\xb8\x16\x43\xe5\xd2\x01\x64\x84\x1a\x57\x2c\x9b\x66\x0b\x7d\xa8\xd1\x4e\x50\x62\xf7\x6e\x77\x16\x67\x2b\x2e\x49\xac\xfe\xc2\x30\x81\xab\xfb\x6f\x42\x83\xb7\x25\xcb\x37\x11\x3d\xc8\x7b\x4b\x80\x25\xda\x5b\xfd\x71\x97\x73\x6b\x6b\x14\xa7\x70\x8c\x11\xa0\x04\xb6\xf4\x32\x04\x6a\x33\x9e\x80\xfc\x35\x9b\x7c\x0a\xd7\xc0\x1a\x77\xc2\xdb\x99\xf6\xdd\x13\xa5\xc6\x2d\x4a\x40\xa9\x06\xd2\xd6\xe9\xc3\xa8\xd4\xe1\x2c\x10\x54\x5c\x8c\x93\x08\xe0\x04\x17\x48\x86\x02\xd1\x15\xcd\xa4\x86\x97\x48\x41\xde\xdd\xa2\xaf\x0a\xfd\x0b\x31\x9f\x23\x81\x08\x01\x82\x65\x1d\x13\x43\xd3\x12\x08\x3a\xbf\x28\x7d\xb6\xe4\x07\x84\x49\x23\x20\x47\x85\xea\x2f\xea\x03\x36\x97\xd6\x8c\x62\xc5\x9c\x11\x22\x6e\xc9\x1a\x3d\xe5\xc3\xb2\x2d\x24\x54\xd9\x4c\x8b\xfa\xd8\x3e\x90\x61\x09\x5d\xe1\xb7\x2e\x3b\x2f\xa8\xe8\x92\xeb\x3d\x16\x33\xac\x38\x63\x5d\x80\xd4\x91\x64\x6c\xd3\x05\xe9\x83\xa9\xa5\x3f\x65\xe4\x9c\x11\x5c\x9c\xb7\xe2\xb0\x60\xb4\x13\x72\x8c\x41\x5c\x69\x81\xda\x1c\x74\x29\x54\x73\x15\x74\xd6\x96\xe0\x3b\xa6\x25\xfb\xbe\x62\xc1\xed\x4c\x89\x13\x54\x80\x15\xef\xae\x15\xb4\x54\x02\x61\xaa\x56\xa7\xf3\x6b\xd9\xba\x22\x9b\x8f\xf6\x19\x88\xfa\x23\x2e\xfc\xea\xc1\x13\xe9\x0b\xde\x04\x7b\xb7\x35\xd4\x4c\x38\x0d\x70\x83\x67\x39\x21\x16\x07\xd8\x06\x59\x2d\xaa\xd9\xdf\xa3\x72\xc6\xb7\x3f\x6d\x84\x1b\xfa\x59\x38\x20\x61\x8e\xea\xad\xbc\x23\xfa\xfa\x23\x75\xe6\xe0\x64\xb9\x6f\x91\xf8\x7b\x17\xa1\x5d\x87\xf7\xde\x23\x64\xb3\xa7\x9e\x16\xc2\xfc\x94\xb1\x65\x53\x6c\xc3\xa0\x37\xdc\x5c\x7a\xb4\xfa\x30\xd6\xcc\x37\xa3\xac\xb2\x68\x15\x7b\xaf\x0d\xb7\xdb\x7f\x5b\xbe\xdb\x2d\x02\x57\x9d\x8f\x94\x42\xc5\x31\xea\x48\xb0\xb2\x68\xbc\x22\x0e\xf5\x4f\xd5\x02\x61\xa8\x47\xfd\x13\x85\xfe\x26\x36\xfb\xd7\xd9\x57\xff\x32\x30\xf8\x24\xaf\x45\xbd\x38\x8f\x47\xbc\x43\xfb\x05\x74\xf6\xd6\xaa\x98\xf6\x20\x0d\x95\xcc\xdb\x03\x4b\x92\x8c\xbe\x28\xed\x29\xb2\xe9\x36\x6c\x98\xe3\xf1\xf6\x34\x99\x2e\xf5\x9c\x06\x88\xe7\x2a\xc6\x5a\xb4\x17\xe2\x32\xe7\x1b\x06\x9b\xbb\x0f\x0b\x25\xc3\xd2\x83\x86\x57\xca\xb5\x1b\xf4\xf3\xdc\x3a\xb5\xce\x19\x83\x74\xe7\x0f\x72\x3d\xfe\x6f\xd0\xcf\x9e\xe7\x6a\x3e\xe9\x79\xd6\xbe\xfa\x39\xed\xc9\x76\x4f\x6b\xb3\x89\x7c\x2c\x48\xf7\x3c\xc8\x88\xee\x99\x79\xf4\xf2\xa9\xd1\xf9\x68\xd7\xee\x08\x0f\x8f\x67\x3d\x17\x20\x3b\xf3\x6f\xfb\xd0\x79\xf7\xbc\xfb\x33\x00\x00\xff\xff\xfa\xcc\x57\x15\x61\x31\x00\x00") func dataConfig_schema_v31JsonBytes() ([]byte, error) { return bindataRead( diff --git a/cli/compose/schema/data/config_schema_v3.1.json b/cli/compose/schema/data/config_schema_v3.1.json index f76154ea12..b9d4221995 100644 --- a/cli/compose/schema/data/config_schema_v3.1.json +++ b/cli/compose/schema/data/config_schema_v3.1.json @@ -167,8 +167,20 @@ "ports": { "type": "array", "items": { - "type": ["string", "number"], - "format": "ports" + "oneOf": [ + {"type": "number", "format": "ports"}, + {"type": "string", "format": "ports"}, + { + "type": "object", + "properties": { + "mode": {"type": "string"}, + "target": {"type": "integer"}, + "published": {"type": "integer"}, + "protocol": {"type": "string"} + }, + "additionalProperties": false + } + ] }, "uniqueItems": true }, diff --git a/cli/compose/types/types.go b/cli/compose/types/types.go index 4bb5cb6d2d..c74014fb14 100644 --- a/cli/compose/types/types.go +++ b/cli/compose/types/types.go @@ -106,7 +106,7 @@ type ServiceConfig struct { NetworkMode string `mapstructure:"network_mode"` Networks map[string]*ServiceNetworkConfig Pid string - Ports StringOrNumberList + Ports []ServicePortConfig Privileged bool ReadOnly bool `mapstructure:"read_only"` Restart string @@ -215,6 +215,14 @@ type ServiceNetworkConfig struct { Ipv6Address string `mapstructure:"ipv6_address"` } +// ServicePortConfig is the port configuration for a service +type ServicePortConfig struct { + Mode string + Target uint32 + Published uint32 + Protocol string +} + // ServiceSecretConfig is the secret configuration for a service type ServiceSecretConfig struct { Source string