diff --git a/cli/command/stack/common.go b/cli/command/stack/common.go index 920a1af0cc..4ae8184933 100644 --- a/cli/command/stack/common.go +++ b/cli/command/stack/common.go @@ -9,18 +9,6 @@ import ( "github.com/docker/docker/client" ) -const ( - labelNamespace = "com.docker.stack.namespace" -) - -func getStackLabels(namespace string, labels map[string]string) map[string]string { - if labels == nil { - labels = make(map[string]string) - } - labels[labelNamespace] = namespace - return labels -} - func getStackFilter(namespace string) filters.Args { filter := filters.NewArgs() filter.Add("label", labelNamespace+"="+namespace) @@ -46,11 +34,3 @@ func getStackNetworks( ctx, types.NetworkListOptions{Filters: getStackFilter(namespace)}) } - -type namespace struct { - name string -} - -func (n namespace) scope(name string) string { - return n.name + "_" + name -} diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index 00a7634a0a..f1ab65ce95 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -179,51 +179,6 @@ func getConfigFile(filename string) (*composetypes.ConfigFile, error) { }, nil } -func convertNetworks( - namespace namespace, - networks map[string]composetypes.NetworkConfig, -) (map[string]types.NetworkCreate, []string) { - if networks == nil { - networks = make(map[string]composetypes.NetworkConfig) - } - - // TODO: only add default network if it's used - networks["default"] = composetypes.NetworkConfig{} - - externalNetworks := []string{} - result := make(map[string]types.NetworkCreate) - - for internalName, network := range networks { - if network.External.External { - externalNetworks = append(externalNetworks, network.External.Name) - continue - } - - createOpts := types.NetworkCreate{ - Labels: getStackLabels(namespace.name, network.Labels), - Driver: network.Driver, - Options: network.DriverOpts, - } - - if network.Ipam.Driver != "" || len(network.Ipam.Config) > 0 { - createOpts.IPAM = &networktypes.IPAM{} - } - - if network.Ipam.Driver != "" { - createOpts.IPAM.Driver = network.Ipam.Driver - } - for _, ipamConfig := range network.Ipam.Config { - config := networktypes.IPAMConfig{ - Subnet: ipamConfig.Subnet, - } - createOpts.IPAM.Config = append(createOpts.IPAM.Config, config) - } - result[internalName] = createOpts - } - - return result, externalNetworks -} - func validateExternalNetworks( ctx context.Context, dockerCli *command.DockerCli, diff --git a/pkg/composetransform/compose.go b/pkg/composetransform/compose.go new file mode 100644 index 0000000000..33f19e0c23 --- /dev/null +++ b/pkg/composetransform/compose.go @@ -0,0 +1,75 @@ +package composetransform + +import ( + composetypes "github.com/aanand/compose-file/types" + "github.com/docker/docker/api/types" + networktypes "github.com/docker/docker/api/types/network" +) + +const ( + labelNamespace = "com.docker.stack.namespace" +) + +// Namespace mangles names by prepending the name +type Namespace struct { + name string +} + +// Scope prepends the namespace to a name +func (n Namespace) Scope(name string) string { + return n.name + "_" + name +} + +// AddStackLabel returns labels with the namespace label added +func AddStackLabel(namespace Namespace, labels map[string]string) map[string]string { + if labels == nil { + labels = make(map[string]string) + } + labels[labelNamespace] = namespace.name + return labels +} + +type networks map[string]composetypes.NetworkConfig + +// ConvertNetworks from the compose-file type to the engine API type +func ConvertNetworks(namespace Namespace, networks networks) (map[string]types.NetworkCreate, []string) { + if networks == nil { + networks = make(map[string]composetypes.NetworkConfig) + } + + // TODO: only add default network if it's used + networks["default"] = composetypes.NetworkConfig{} + + externalNetworks := []string{} + result := make(map[string]types.NetworkCreate) + + for internalName, network := range networks { + if network.External.External { + externalNetworks = append(externalNetworks, network.External.Name) + continue + } + + createOpts := types.NetworkCreate{ + Labels: AddStackLabel(namespace, network.Labels), + Driver: network.Driver, + Options: network.DriverOpts, + } + + if network.Ipam.Driver != "" || len(network.Ipam.Config) > 0 { + createOpts.IPAM = &networktypes.IPAM{} + } + + if network.Ipam.Driver != "" { + createOpts.IPAM.Driver = network.Ipam.Driver + } + for _, ipamConfig := range network.Ipam.Config { + config := networktypes.IPAMConfig{ + Subnet: ipamConfig.Subnet, + } + createOpts.IPAM.Config = append(createOpts.IPAM.Config, config) + } + result[internalName] = createOpts + } + + return result, externalNetworks +} diff --git a/pkg/composetransform/compose_test.go b/pkg/composetransform/compose_test.go new file mode 100644 index 0000000000..e6f8f58c1b --- /dev/null +++ b/pkg/composetransform/compose_test.go @@ -0,0 +1,85 @@ +package composetransform + +import ( + "testing" + + composetypes "github.com/aanand/compose-file/types" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/pkg/testutil/assert" +) + +func TestNamespaceScope(t *testing.T) { + scoped := Namespace{name: "foo"}.Scope("bar") + assert.Equal(t, scoped, "foo_bar") +} + +func TestAddStackLabel(t *testing.T) { + labels := map[string]string{ + "something": "labeled", + } + actual := AddStackLabel(Namespace{name: "foo"}, labels) + expected := map[string]string{ + "something": "labeled", + labelNamespace: "foo", + } + assert.DeepEqual(t, actual, expected) +} + +func TestConvertNetworks(t *testing.T) { + namespace := Namespace{name: "foo"} + source := networks{ + "normal": composetypes.NetworkConfig{ + Driver: "overlay", + DriverOpts: map[string]string{ + "opt": "value", + }, + Ipam: composetypes.IPAMConfig{ + Driver: "driver", + Config: []*composetypes.IPAMPool{ + { + Subnet: "10.0.0.0", + }, + }, + }, + Labels: map[string]string{ + "something": "labeled", + }, + }, + "outside": composetypes.NetworkConfig{ + External: composetypes.External{ + External: true, + Name: "special", + }, + }, + } + expected := map[string]types.NetworkCreate{ + "default": { + Labels: map[string]string{ + labelNamespace: "foo", + }, + }, + "normal": { + Driver: "overlay", + IPAM: &network.IPAM{ + Driver: "driver", + Config: []network.IPAMConfig{ + { + Subnet: "10.0.0.0", + }, + }, + }, + Options: map[string]string{ + "opt": "value", + }, + Labels: map[string]string{ + labelNamespace: "foo", + "something": "labeled", + }, + }, + } + + networks, externals := ConvertNetworks(namespace, source) + assert.DeepEqual(t, networks, expected) + assert.DeepEqual(t, externals, []string{"special"}) +} diff --git a/pkg/testutil/assert/assert.go b/pkg/testutil/assert/assert.go index 6de0ef6779..6da8518a5e 100644 --- a/pkg/testutil/assert/assert.go +++ b/pkg/testutil/assert/assert.go @@ -7,6 +7,8 @@ import ( "reflect" "runtime" "strings" + + "github.com/davecgh/go-spew/spew" ) // TestingT is an interface which defines the methods of testing.T that are @@ -49,7 +51,8 @@ func NilError(t TestingT, err error) { // they are not "deeply equal". func DeepEqual(t TestingT, actual, expected interface{}) { if !reflect.DeepEqual(actual, expected) { - fatal(t, "Expected '%v' (%T) got '%v' (%T)", expected, expected, actual, actual) + fatal(t, "Expected (%T):\n%v\n\ngot (%T):\n%s\n", + expected, spew.Sdump(expected), actual, spew.Sdump(actual)) } }