From 6b778c96336fe9c0bcfea477092aab8cdfc0c896 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 30 Nov 2016 15:33:54 -0500 Subject: [PATCH 1/5] Move ConvertNetworks to composetransform package. Signed-off-by: Daniel Nephin --- cli/command/stack/common.go | 20 ------- cli/command/stack/deploy.go | 45 --------------- pkg/composetransform/compose.go | 75 ++++++++++++++++++++++++ pkg/composetransform/compose_test.go | 85 ++++++++++++++++++++++++++++ pkg/testutil/assert/assert.go | 5 +- 5 files changed, 164 insertions(+), 66 deletions(-) create mode 100644 pkg/composetransform/compose.go create mode 100644 pkg/composetransform/compose_test.go 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)) } } From 7685e80fc9cb79206d81757eee071f0244323600 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 30 Nov 2016 16:34:29 -0500 Subject: [PATCH 2/5] Move ConvertVolumes to composetransform package. Signed-off-by: Daniel Nephin --- api/types/mount/mount.go | 10 +++ cli/command/stack/common.go | 3 +- cli/command/stack/deploy.go | 127 ++------------------------- pkg/composetransform/compose.go | 15 +++- pkg/composetransform/compose_test.go | 6 +- pkg/composetransform/volume.go | 116 ++++++++++++++++++++++++ pkg/composetransform/volume_test.go | 112 +++++++++++++++++++++++ 7 files changed, 264 insertions(+), 125 deletions(-) create mode 100644 pkg/composetransform/volume.go create mode 100644 pkg/composetransform/volume_test.go diff --git a/api/types/mount/mount.go b/api/types/mount/mount.go index 8ac89402f9..31f2365b8e 100644 --- a/api/types/mount/mount.go +++ b/api/types/mount/mount.go @@ -50,6 +50,16 @@ const ( PropagationSlave Propagation = "slave" ) +// Propagations is the list of all valid mount propagations +var Propagations = []Propagation{ + PropagationRPrivate, + PropagationPrivate, + PropagationRShared, + PropagationShared, + PropagationRSlave, + PropagationSlave, +} + // BindOptions defines options specific to mounts of type "bind". type BindOptions struct { Propagation Propagation `json:",omitempty"` diff --git a/cli/command/stack/common.go b/cli/command/stack/common.go index 4ae8184933..050528de4e 100644 --- a/cli/command/stack/common.go +++ b/cli/command/stack/common.go @@ -7,11 +7,12 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" + "github.com/docker/docker/pkg/composetransform" ) func getStackFilter(namespace string) filters.Args { filter := filters.NewArgs() - filter.Add("label", labelNamespace+"="+namespace) + filter.Add("label", composetransform.LabelNamespace+"="+namespace) return filter } diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index f1ab65ce95..e8238cde66 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -16,13 +16,12 @@ import ( composetypes "github.com/aanand/compose-file/types" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/mount" - networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" dockerclient "github.com/docker/docker/client" "github.com/docker/docker/opts" + "github.com/docker/docker/pkg/composetransform" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/go-connections/nat" ) @@ -121,9 +120,9 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo return err } - namespace := namespace{name: opts.namespace} + namespace := composetransform.NewNamespace(opts.namespace) - networks, externalNetworks := convertNetworks(namespace, config.Networks) + networks, externalNetworks := composetransform.ConvertNetworks(namespace, config.Networks) if err := validateExternalNetworks(ctx, dockerCli, externalNetworks); err != nil { return err } @@ -204,12 +203,12 @@ func validateExternalNetworks( func createNetworks( ctx context.Context, dockerCli *command.DockerCli, - namespace namespace, + namespace composetransform.Namespace, networks map[string]types.NetworkCreate, ) error { client := dockerCli.Client() - existingNetworks, err := getStackNetworks(ctx, client, namespace.name) + existingNetworks, err := getStackNetworks(ctx, client, namespace.Name()) if err != nil { return err } @@ -220,7 +219,7 @@ func createNetworks( } for internalName, createOpts := range networks { - name := namespace.scope(internalName) + name := namespace.Scope(internalName) if _, exists := existingNetworkMap[name]; exists { continue } @@ -241,7 +240,7 @@ func createNetworks( func convertServiceNetworks( networks map[string]*composetypes.ServiceNetworkConfig, networkConfigs map[string]composetypes.NetworkConfig, - namespace namespace, + namespace composetransform.Namespace, name string, ) ([]swarm.NetworkAttachmentConfig, error) { if len(networks) == 0 { @@ -275,116 +274,6 @@ func convertServiceNetworks( return nets, nil } -func convertVolumes( - serviceVolumes []string, - stackVolumes map[string]composetypes.VolumeConfig, - namespace namespace, -) ([]mount.Mount, error) { - var mounts []mount.Mount - - for _, volumeSpec := range serviceVolumes { - mount, err := convertVolumeToMount(volumeSpec, stackVolumes, namespace) - if err != nil { - return nil, err - } - mounts = append(mounts, mount) - } - return mounts, nil -} - -func convertVolumeToMount( - volumeSpec string, - stackVolumes map[string]composetypes.VolumeConfig, - namespace namespace, -) (mount.Mount, error) { - var source, target string - var mode []string - - // TODO: split Windows path mappings properly - parts := strings.SplitN(volumeSpec, ":", 3) - - switch len(parts) { - case 3: - source = parts[0] - target = parts[1] - mode = strings.Split(parts[2], ",") - case 2: - source = parts[0] - target = parts[1] - case 1: - target = parts[0] - default: - return mount.Mount{}, fmt.Errorf("invald volume: %s", volumeSpec) - } - - // TODO: catch Windows paths here - if strings.HasPrefix(source, "/") { - return mount.Mount{ - Type: mount.TypeBind, - Source: source, - Target: target, - ReadOnly: isReadOnly(mode), - BindOptions: getBindOptions(mode), - }, nil - } - - stackVolume, exists := stackVolumes[source] - if !exists { - return mount.Mount{}, fmt.Errorf("undefined volume: %s", source) - } - - var volumeOptions *mount.VolumeOptions - if stackVolume.External.Name != "" { - source = stackVolume.External.Name - } else { - volumeOptions = &mount.VolumeOptions{ - Labels: getStackLabels(namespace.name, stackVolume.Labels), - NoCopy: isNoCopy(mode), - } - - if stackVolume.Driver != "" { - volumeOptions.DriverConfig = &mount.Driver{ - Name: stackVolume.Driver, - Options: stackVolume.DriverOpts, - } - } - source = namespace.scope(source) - } - return mount.Mount{ - Type: mount.TypeVolume, - Source: source, - Target: target, - ReadOnly: isReadOnly(mode), - VolumeOptions: volumeOptions, - }, nil -} - -func modeHas(mode []string, field string) bool { - for _, item := range mode { - if item == field { - return true - } - } - return false -} - -func isReadOnly(mode []string) bool { - return modeHas(mode, "ro") -} - -func isNoCopy(mode []string) bool { - return modeHas(mode, "nocopy") -} - -func getBindOptions(mode []string) *mount.BindOptions { - for _, item := range mode { - if strings.Contains(item, "private") || strings.Contains(item, "shared") || strings.Contains(item, "slave") { - return &mount.BindOptions{Propagation: mount.Propagation(item)} - } - } - return nil -} - func deployServices( ctx context.Context, dockerCli *command.DockerCli, @@ -494,7 +383,7 @@ func convertService( return swarm.ServiceSpec{}, err } - mounts, err := convertVolumes(service.Volumes, volumes, namespace) + mounts, err := composetransform.ConvertVolumes(service.Volumes, volumes, namespace) if err != nil { // TODO: better error message (include service name) return swarm.ServiceSpec{}, err diff --git a/pkg/composetransform/compose.go b/pkg/composetransform/compose.go index 33f19e0c23..791ff27c07 100644 --- a/pkg/composetransform/compose.go +++ b/pkg/composetransform/compose.go @@ -7,7 +7,8 @@ import ( ) const ( - labelNamespace = "com.docker.stack.namespace" + // LabelNamespace is the label used to track stack resources + LabelNamespace = "com.docker.stack.namespace" ) // Namespace mangles names by prepending the name @@ -20,12 +21,22 @@ func (n Namespace) Scope(name string) string { return n.name + "_" + name } +// Name returns the name of the namespace +func (n Namespace) Name() string { + return n.name +} + +// NewNamespace returns a new Namespace for scoping of names +func NewNamespace(name string) Namespace { + return Namespace{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 + labels[LabelNamespace] = namespace.name return labels } diff --git a/pkg/composetransform/compose_test.go b/pkg/composetransform/compose_test.go index e6f8f58c1b..a934f4e808 100644 --- a/pkg/composetransform/compose_test.go +++ b/pkg/composetransform/compose_test.go @@ -21,7 +21,7 @@ func TestAddStackLabel(t *testing.T) { actual := AddStackLabel(Namespace{name: "foo"}, labels) expected := map[string]string{ "something": "labeled", - labelNamespace: "foo", + LabelNamespace: "foo", } assert.DeepEqual(t, actual, expected) } @@ -56,7 +56,7 @@ func TestConvertNetworks(t *testing.T) { expected := map[string]types.NetworkCreate{ "default": { Labels: map[string]string{ - labelNamespace: "foo", + LabelNamespace: "foo", }, }, "normal": { @@ -73,7 +73,7 @@ func TestConvertNetworks(t *testing.T) { "opt": "value", }, Labels: map[string]string{ - labelNamespace: "foo", + LabelNamespace: "foo", "something": "labeled", }, }, diff --git a/pkg/composetransform/volume.go b/pkg/composetransform/volume.go new file mode 100644 index 0000000000..d6ee808a50 --- /dev/null +++ b/pkg/composetransform/volume.go @@ -0,0 +1,116 @@ +package composetransform + +import ( + "fmt" + "strings" + + composetypes "github.com/aanand/compose-file/types" + "github.com/docker/docker/api/types/mount" +) + +type volumes map[string]composetypes.VolumeConfig + +// ConvertVolumes from compose-file types to engine api types +func ConvertVolumes(serviceVolumes []string, stackVolumes volumes, namespace Namespace) ([]mount.Mount, error) { + var mounts []mount.Mount + + for _, volumeSpec := range serviceVolumes { + mount, err := convertVolumeToMount(volumeSpec, stackVolumes, namespace) + if err != nil { + return nil, err + } + mounts = append(mounts, mount) + } + return mounts, nil +} + +func convertVolumeToMount(volumeSpec string, stackVolumes volumes, namespace Namespace) (mount.Mount, error) { + var source, target string + var mode []string + + // TODO: split Windows path mappings properly + parts := strings.SplitN(volumeSpec, ":", 3) + + switch len(parts) { + case 3: + source = parts[0] + target = parts[1] + mode = strings.Split(parts[2], ",") + case 2: + source = parts[0] + target = parts[1] + case 1: + target = parts[0] + default: + return mount.Mount{}, fmt.Errorf("invald volume: %s", volumeSpec) + } + + // TODO: catch Windows paths here + if strings.HasPrefix(source, "/") { + return mount.Mount{ + Type: mount.TypeBind, + Source: source, + Target: target, + ReadOnly: isReadOnly(mode), + BindOptions: getBindOptions(mode), + }, nil + } + + stackVolume, exists := stackVolumes[source] + if !exists { + return mount.Mount{}, fmt.Errorf("undefined volume: %s", source) + } + + var volumeOptions *mount.VolumeOptions + if stackVolume.External.Name != "" { + source = stackVolume.External.Name + } else { + volumeOptions = &mount.VolumeOptions{ + Labels: AddStackLabel(namespace, stackVolume.Labels), + NoCopy: isNoCopy(mode), + } + + if stackVolume.Driver != "" { + volumeOptions.DriverConfig = &mount.Driver{ + Name: stackVolume.Driver, + Options: stackVolume.DriverOpts, + } + } + source = namespace.Scope(source) + } + return mount.Mount{ + Type: mount.TypeVolume, + Source: source, + Target: target, + ReadOnly: isReadOnly(mode), + VolumeOptions: volumeOptions, + }, nil +} + +func modeHas(mode []string, field string) bool { + for _, item := range mode { + if item == field { + return true + } + } + return false +} + +func isReadOnly(mode []string) bool { + return modeHas(mode, "ro") +} + +func isNoCopy(mode []string) bool { + return modeHas(mode, "nocopy") +} + +func getBindOptions(mode []string) *mount.BindOptions { + for _, item := range mode { + for _, propagation := range mount.Propagations { + if mount.Propagation(item) == propagation { + return &mount.BindOptions{Propagation: mount.Propagation(item)} + } + } + } + return nil +} diff --git a/pkg/composetransform/volume_test.go b/pkg/composetransform/volume_test.go new file mode 100644 index 0000000000..fd5a2a84b3 --- /dev/null +++ b/pkg/composetransform/volume_test.go @@ -0,0 +1,112 @@ +package composetransform + +import ( + "testing" + + composetypes "github.com/aanand/compose-file/types" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/pkg/testutil/assert" +) + +func TestIsReadOnly(t *testing.T) { + assert.Equal(t, isReadOnly([]string{"foo", "bar", "ro"}), true) + assert.Equal(t, isReadOnly([]string{"ro"}), true) + assert.Equal(t, isReadOnly([]string{}), false) + assert.Equal(t, isReadOnly([]string{"foo", "rw"}), false) + assert.Equal(t, isReadOnly([]string{"foo"}), false) +} + +func TestIsNoCopy(t *testing.T) { + assert.Equal(t, isNoCopy([]string{"foo", "bar", "nocopy"}), true) + assert.Equal(t, isNoCopy([]string{"nocopy"}), true) + assert.Equal(t, isNoCopy([]string{}), false) + assert.Equal(t, isNoCopy([]string{"foo", "rw"}), false) +} + +func TesTGetBindOptions(t *testing.T) { + opts := getBindOptions([]string{"slave"}) + expected := &mount.BindOptions{Propagation: mount.PropagationSlave} + assert.Equal(t, opts, expected) +} + +func TesTGetBindOptionsNone(t *testing.T) { + opts := getBindOptions([]string{"ro"}) + assert.Equal(t, opts, nil) +} + +func TestConvertVolumeToMountNamedVolume(t *testing.T) { + stackVolumes := volumes{ + "normal": composetypes.VolumeConfig{ + Driver: "glusterfs", + DriverOpts: map[string]string{ + "opt": "value", + }, + Labels: map[string]string{ + "something": "labeled", + }, + }, + } + namespace := NewNamespace("foo") + expected := mount.Mount{ + Type: mount.TypeVolume, + Source: "foo_normal", + Target: "/foo", + ReadOnly: true, + VolumeOptions: &mount.VolumeOptions{ + Labels: map[string]string{ + LabelNamespace: "foo", + "something": "labeled", + }, + DriverConfig: &mount.Driver{ + Name: "glusterfs", + Options: map[string]string{ + "opt": "value", + }, + }, + }, + } + mount, err := convertVolumeToMount("normal:/foo:ro", stackVolumes, namespace) + assert.NilError(t, err) + assert.DeepEqual(t, mount, expected) +} + +func TestConvertVolumeToMountNamedVolumeExternal(t *testing.T) { + stackVolumes := volumes{ + "outside": composetypes.VolumeConfig{ + External: composetypes.External{ + External: true, + Name: "special", + }, + }, + } + namespace := NewNamespace("foo") + expected := mount.Mount{ + Type: mount.TypeVolume, + Source: "special", + Target: "/foo", + } + mount, err := convertVolumeToMount("outside:/foo", stackVolumes, namespace) + assert.NilError(t, err) + assert.DeepEqual(t, mount, expected) +} + +func TestConvertVolumeToMountBind(t *testing.T) { + stackVolumes := volumes{} + namespace := NewNamespace("foo") + expected := mount.Mount{ + Type: mount.TypeBind, + Source: "/bar", + Target: "/foo", + ReadOnly: true, + BindOptions: &mount.BindOptions{Propagation: mount.PropagationShared}, + } + mount, err := convertVolumeToMount("/bar:/foo:ro,shared", stackVolumes, namespace) + assert.NilError(t, err) + assert.DeepEqual(t, mount, expected) +} + +func TestConvertVolumeToMountVolumeDoesNotExist(t *testing.T) { + namespace := NewNamespace("foo") + _, err := convertVolumeToMount("unknown:/foo:ro", volumes{}, namespace) + assert.Error(t, err, "undefined volume: unknown") +} From 655e48e65309f0191bbbb8b2e9730b6976e148cd Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 30 Nov 2016 17:38:40 -0500 Subject: [PATCH 3/5] Move ConvertService to composetransform package. Signed-off-by: Daniel Nephin --- cli/command/stack/common.go | 13 + cli/command/stack/deploy.go | 327 +----------------------- cli/command/stack/deploy_bundlefile.go | 13 +- cli/command/stack/list.go | 12 +- cli/command/stack/ps.go | 3 +- cli/command/stack/services.go | 4 +- pkg/composetransform/service.go | 329 +++++++++++++++++++++++++ pkg/composetransform/service_test.go | 40 +++ 8 files changed, 399 insertions(+), 342 deletions(-) create mode 100644 pkg/composetransform/service.go create mode 100644 pkg/composetransform/service_test.go diff --git a/cli/command/stack/common.go b/cli/command/stack/common.go index 050528de4e..c3a43f2cd8 100644 --- a/cli/command/stack/common.go +++ b/cli/command/stack/common.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" + "github.com/docker/docker/opts" "github.com/docker/docker/pkg/composetransform" ) @@ -16,6 +17,18 @@ func getStackFilter(namespace string) filters.Args { return filter } +func getStackFilterFromOpt(namespace string, opt opts.FilterOpt) filters.Args { + filter := opt.Value() + filter.Add("label", composetransform.LabelNamespace+"="+namespace) + return filter +} + +func getAllStacksFilter() filters.Args { + filter := filters.NewArgs() + filter.Add("label", composetransform.LabelNamespace) + return filter +} + func getServices( ctx context.Context, apiclient client.APIClient, diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index e8238cde66..957f92f29e 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -7,7 +7,6 @@ import ( "os" "sort" "strings" - "time" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -15,15 +14,11 @@ import ( "github.com/aanand/compose-file/loader" composetypes "github.com/aanand/compose-file/types" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" dockerclient "github.com/docker/docker/client" - "github.com/docker/docker/opts" "github.com/docker/docker/pkg/composetransform" - runconfigopts "github.com/docker/docker/runconfig/opts" - "github.com/docker/go-connections/nat" ) const ( @@ -129,7 +124,7 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil { return err } - services, err := convertServices(namespace, config) + services, err := composetransform.ConvertServices(namespace, config) if err != nil { return err } @@ -237,54 +232,17 @@ func createNetworks( return nil } -func convertServiceNetworks( - networks map[string]*composetypes.ServiceNetworkConfig, - networkConfigs map[string]composetypes.NetworkConfig, - namespace composetransform.Namespace, - name string, -) ([]swarm.NetworkAttachmentConfig, error) { - if len(networks) == 0 { - return []swarm.NetworkAttachmentConfig{ - { - Target: namespace.scope("default"), - Aliases: []string{name}, - }, - }, nil - } - - nets := []swarm.NetworkAttachmentConfig{} - for networkName, network := range networks { - networkConfig, ok := networkConfigs[networkName] - if !ok { - return []swarm.NetworkAttachmentConfig{}, fmt.Errorf("invalid network: %s", networkName) - } - var aliases []string - if network != nil { - aliases = network.Aliases - } - target := namespace.scope(networkName) - if networkConfig.External.External { - target = networkName - } - nets = append(nets, swarm.NetworkAttachmentConfig{ - Target: target, - Aliases: append(aliases, name), - }) - } - return nets, nil -} - func deployServices( ctx context.Context, dockerCli *command.DockerCli, services map[string]swarm.ServiceSpec, - namespace namespace, + namespace composetransform.Namespace, sendAuth bool, ) error { apiClient := dockerCli.Client() out := dockerCli.Out() - existingServices, err := getServices(ctx, apiClient, namespace.name) + existingServices, err := getServices(ctx, apiClient, namespace.Name()) if err != nil { return err } @@ -295,7 +253,7 @@ func deployServices( } for internalName, serviceSpec := range services { - name := namespace.scope(internalName) + name := namespace.Scope(internalName) encodedAuth := "" if sendAuth { @@ -343,280 +301,3 @@ func deployServices( return nil } - -func convertServices( - namespace namespace, - config *composetypes.Config, -) (map[string]swarm.ServiceSpec, error) { - result := make(map[string]swarm.ServiceSpec) - - services := config.Services - volumes := config.Volumes - networks := config.Networks - - for _, service := range services { - serviceSpec, err := convertService(namespace, service, networks, volumes) - if err != nil { - return nil, err - } - result[service.Name] = serviceSpec - } - - return result, nil -} - -func convertService( - namespace namespace, - service composetypes.ServiceConfig, - networkConfigs map[string]composetypes.NetworkConfig, - volumes map[string]composetypes.VolumeConfig, -) (swarm.ServiceSpec, error) { - name := namespace.scope(service.Name) - - endpoint, err := convertEndpointSpec(service.Ports) - if err != nil { - return swarm.ServiceSpec{}, err - } - - mode, err := convertDeployMode(service.Deploy.Mode, service.Deploy.Replicas) - if err != nil { - return swarm.ServiceSpec{}, err - } - - mounts, err := composetransform.ConvertVolumes(service.Volumes, volumes, namespace) - if err != nil { - // TODO: better error message (include service name) - return swarm.ServiceSpec{}, err - } - - resources, err := convertResources(service.Deploy.Resources) - if err != nil { - return swarm.ServiceSpec{}, err - } - - restartPolicy, err := convertRestartPolicy( - service.Restart, service.Deploy.RestartPolicy) - if err != nil { - return swarm.ServiceSpec{}, err - } - - healthcheck, err := convertHealthcheck(service.HealthCheck) - if err != nil { - return swarm.ServiceSpec{}, err - } - - networks, err := convertServiceNetworks(service.Networks, networkConfigs, namespace, service.Name) - if err != nil { - return swarm.ServiceSpec{}, err - } - - var logDriver *swarm.Driver - if service.Logging != nil { - logDriver = &swarm.Driver{ - Name: service.Logging.Driver, - Options: service.Logging.Options, - } - } - - serviceSpec := swarm.ServiceSpec{ - Annotations: swarm.Annotations{ - Name: name, - Labels: getStackLabels(namespace.name, service.Deploy.Labels), - }, - TaskTemplate: swarm.TaskSpec{ - ContainerSpec: swarm.ContainerSpec{ - Image: service.Image, - Command: service.Entrypoint, - Args: service.Command, - Hostname: service.Hostname, - Hosts: convertExtraHosts(service.ExtraHosts), - Healthcheck: healthcheck, - Env: convertEnvironment(service.Environment), - Labels: getStackLabels(namespace.name, service.Labels), - Dir: service.WorkingDir, - User: service.User, - Mounts: mounts, - StopGracePeriod: service.StopGracePeriod, - TTY: service.Tty, - OpenStdin: service.StdinOpen, - }, - LogDriver: logDriver, - Resources: resources, - RestartPolicy: restartPolicy, - Placement: &swarm.Placement{ - Constraints: service.Deploy.Placement.Constraints, - }, - }, - EndpointSpec: endpoint, - Mode: mode, - Networks: networks, - UpdateConfig: convertUpdateConfig(service.Deploy.UpdateConfig), - } - - return serviceSpec, nil -} - -func convertExtraHosts(extraHosts map[string]string) []string { - hosts := []string{} - for host, ip := range extraHosts { - hosts = append(hosts, fmt.Sprintf("%s %s", ip, host)) - } - return hosts -} - -func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container.HealthConfig, error) { - if healthcheck == nil { - return nil, nil - } - var ( - err error - timeout, interval time.Duration - retries int - ) - if healthcheck.Disable { - if len(healthcheck.Test) != 0 { - return nil, fmt.Errorf("command and disable key can't be set at the same time") - } - return &container.HealthConfig{ - Test: []string{"NONE"}, - }, nil - - } - if healthcheck.Timeout != "" { - timeout, err = time.ParseDuration(healthcheck.Timeout) - if err != nil { - return nil, err - } - } - if healthcheck.Interval != "" { - interval, err = time.ParseDuration(healthcheck.Interval) - if err != nil { - return nil, err - } - } - if healthcheck.Retries != nil { - retries = int(*healthcheck.Retries) - } - return &container.HealthConfig{ - Test: healthcheck.Test, - Timeout: timeout, - Interval: interval, - Retries: retries, - }, nil -} - -func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) { - // TODO: log if restart is being ignored - if source == nil { - policy, err := runconfigopts.ParseRestartPolicy(restart) - if err != nil { - return nil, err - } - // TODO: is this an accurate convertion? - switch { - case policy.IsNone(): - return nil, nil - case policy.IsAlways(), policy.IsUnlessStopped(): - return &swarm.RestartPolicy{ - Condition: swarm.RestartPolicyConditionAny, - }, nil - case policy.IsOnFailure(): - attempts := uint64(policy.MaximumRetryCount) - return &swarm.RestartPolicy{ - Condition: swarm.RestartPolicyConditionOnFailure, - MaxAttempts: &attempts, - }, nil - } - } - return &swarm.RestartPolicy{ - Condition: swarm.RestartPolicyCondition(source.Condition), - Delay: source.Delay, - MaxAttempts: source.MaxAttempts, - Window: source.Window, - }, nil -} - -func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig { - if source == nil { - return nil - } - parallel := uint64(1) - if source.Parallelism != nil { - parallel = *source.Parallelism - } - return &swarm.UpdateConfig{ - Parallelism: parallel, - Delay: source.Delay, - FailureAction: source.FailureAction, - Monitor: source.Monitor, - MaxFailureRatio: source.MaxFailureRatio, - } -} - -func convertResources(source composetypes.Resources) (*swarm.ResourceRequirements, error) { - resources := &swarm.ResourceRequirements{} - if source.Limits != nil { - cpus, err := opts.ParseCPUs(source.Limits.NanoCPUs) - if err != nil { - return nil, err - } - resources.Limits = &swarm.Resources{ - NanoCPUs: cpus, - MemoryBytes: int64(source.Limits.MemoryBytes), - } - } - if source.Reservations != nil { - cpus, err := opts.ParseCPUs(source.Reservations.NanoCPUs) - if err != nil { - return nil, err - } - resources.Reservations = &swarm.Resources{ - NanoCPUs: cpus, - MemoryBytes: int64(source.Reservations.MemoryBytes), - } - } - return resources, nil -} - -func convertEndpointSpec(source []string) (*swarm.EndpointSpec, error) { - portConfigs := []swarm.PortConfig{} - ports, portBindings, err := nat.ParsePortSpecs(source) - if err != nil { - return nil, err - } - - for port := range ports { - portConfigs = append( - portConfigs, - opts.ConvertPortToPortConfig(port, portBindings)...) - } - - return &swarm.EndpointSpec{Ports: portConfigs}, nil -} - -func convertEnvironment(source map[string]string) []string { - var output []string - - for name, value := range source { - output = append(output, fmt.Sprintf("%s=%s", name, value)) - } - - return output -} - -func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error) { - serviceMode := swarm.ServiceMode{} - - switch mode { - case "global": - if replicas != nil { - return serviceMode, fmt.Errorf("replicas can only be used with replicated mode") - } - serviceMode.Global = &swarm.GlobalService{} - case "replicated", "": - serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas} - default: - return serviceMode, fmt.Errorf("Unknown mode: %s", mode) - } - return serviceMode, nil -} diff --git a/cli/command/stack/deploy_bundlefile.go b/cli/command/stack/deploy_bundlefile.go index c82c46e424..f9a4162389 100644 --- a/cli/command/stack/deploy_bundlefile.go +++ b/cli/command/stack/deploy_bundlefile.go @@ -6,6 +6,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli/command" + "github.com/docker/docker/pkg/composetransform" ) func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deployOptions) error { @@ -18,20 +19,20 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy return err } - namespace := namespace{name: opts.namespace} + namespace := composetransform.NewNamespace(opts.namespace) networks := make(map[string]types.NetworkCreate) for _, service := range bundle.Services { for _, networkName := range service.Networks { networks[networkName] = types.NetworkCreate{ - Labels: getStackLabels(namespace.name, nil), + Labels: composetransform.AddStackLabel(namespace, nil), } } } services := make(map[string]swarm.ServiceSpec) for internalName, service := range bundle.Services { - name := namespace.scope(internalName) + name := namespace.Scope(internalName) var ports []swarm.PortConfig for _, portSpec := range service.Ports { @@ -44,7 +45,7 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy nets := []swarm.NetworkAttachmentConfig{} for _, networkName := range service.Networks { nets = append(nets, swarm.NetworkAttachmentConfig{ - Target: namespace.scope(networkName), + Target: namespace.Scope(networkName), Aliases: []string{networkName}, }) } @@ -52,7 +53,7 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy serviceSpec := swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: name, - Labels: getStackLabels(namespace.name, service.Labels), + Labels: composetransform.AddStackLabel(namespace, service.Labels), }, TaskTemplate: swarm.TaskSpec{ ContainerSpec: swarm.ContainerSpec{ @@ -63,7 +64,7 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy // Service Labels will not be copied to Containers // automatically during the deployment so we apply // it here. - Labels: getStackLabels(namespace.name, nil), + Labels: composetransform.AddStackLabel(namespace, nil), }, }, EndpointSpec: &swarm.EndpointSpec{ diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index f655b929ad..52e593316e 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -9,10 +9,10 @@ import ( "golang.org/x/net/context" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/docker/docker/client" + "github.com/docker/docker/pkg/composetransform" "github.com/spf13/cobra" ) @@ -81,23 +81,19 @@ func getStacks( ctx context.Context, apiclient client.APIClient, ) ([]*stack, error) { - - filter := filters.NewArgs() - filter.Add("label", labelNamespace) - services, err := apiclient.ServiceList( ctx, - types.ServiceListOptions{Filters: filter}) + types.ServiceListOptions{Filters: getAllStacksFilter()}) if err != nil { return nil, err } m := make(map[string]*stack, 0) for _, service := range services { labels := service.Spec.Labels - name, ok := labels[labelNamespace] + name, ok := labels[composetransform.LabelNamespace] if !ok { return nil, fmt.Errorf("cannot get label %s for service %s", - labelNamespace, service.ID) + composetransform.LabelNamespace, service.ID) } ztack, ok := m[name] if !ok { diff --git a/cli/command/stack/ps.go b/cli/command/stack/ps.go index 7a5e069cbe..497fb97b5e 100644 --- a/cli/command/stack/ps.go +++ b/cli/command/stack/ps.go @@ -49,8 +49,7 @@ func runPS(dockerCli *command.DockerCli, opts psOptions) error { client := dockerCli.Client() ctx := context.Background() - filter := opts.filter.Value() - filter.Add("label", labelNamespace+"="+opts.namespace) + filter := getStackFilterFromOpt(opts.namespace, opts.filter) if !opts.all && !filter.Include("desired-state") { filter.Add("desired-state", string(swarm.TaskStateRunning)) filter.Add("desired-state", string(swarm.TaskStateAccepted)) diff --git a/cli/command/stack/services.go b/cli/command/stack/services.go index 1ca1c8c129..a46652df7c 100644 --- a/cli/command/stack/services.go +++ b/cli/command/stack/services.go @@ -43,9 +43,7 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error { ctx := context.Background() client := dockerCli.Client() - filter := opts.filter.Value() - filter.Add("label", labelNamespace+"="+opts.namespace) - + filter := getStackFilterFromOpt(opts.namespace, opts.filter) services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: filter}) if err != nil { return err diff --git a/pkg/composetransform/service.go b/pkg/composetransform/service.go new file mode 100644 index 0000000000..431c06db3f --- /dev/null +++ b/pkg/composetransform/service.go @@ -0,0 +1,329 @@ +package composetransform + +import ( + "fmt" + "time" + + composetypes "github.com/aanand/compose-file/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/opts" + runconfigopts "github.com/docker/docker/runconfig/opts" + "github.com/docker/go-connections/nat" +) + +// ConvertServices from compose-file types to engine API types +func ConvertServices( + namespace Namespace, + config *composetypes.Config, +) (map[string]swarm.ServiceSpec, error) { + result := make(map[string]swarm.ServiceSpec) + + services := config.Services + volumes := config.Volumes + networks := config.Networks + + for _, service := range services { + serviceSpec, err := convertService(namespace, service, networks, volumes) + if err != nil { + return nil, err + } + result[service.Name] = serviceSpec + } + + return result, nil +} + +func convertService( + namespace Namespace, + service composetypes.ServiceConfig, + networkConfigs map[string]composetypes.NetworkConfig, + volumes map[string]composetypes.VolumeConfig, +) (swarm.ServiceSpec, error) { + name := namespace.Scope(service.Name) + + endpoint, err := convertEndpointSpec(service.Ports) + if err != nil { + return swarm.ServiceSpec{}, err + } + + mode, err := convertDeployMode(service.Deploy.Mode, service.Deploy.Replicas) + if err != nil { + return swarm.ServiceSpec{}, err + } + + mounts, err := ConvertVolumes(service.Volumes, volumes, namespace) + if err != nil { + // TODO: better error message (include service name) + return swarm.ServiceSpec{}, err + } + + resources, err := convertResources(service.Deploy.Resources) + if err != nil { + return swarm.ServiceSpec{}, err + } + + restartPolicy, err := convertRestartPolicy( + service.Restart, service.Deploy.RestartPolicy) + if err != nil { + return swarm.ServiceSpec{}, err + } + + healthcheck, err := convertHealthcheck(service.HealthCheck) + if err != nil { + return swarm.ServiceSpec{}, err + } + + networks, err := convertServiceNetworks(service.Networks, networkConfigs, namespace, service.Name) + if err != nil { + return swarm.ServiceSpec{}, err + } + + var logDriver *swarm.Driver + if service.Logging != nil { + logDriver = &swarm.Driver{ + Name: service.Logging.Driver, + Options: service.Logging.Options, + } + } + + serviceSpec := swarm.ServiceSpec{ + Annotations: swarm.Annotations{ + Name: name, + Labels: AddStackLabel(namespace, service.Deploy.Labels), + }, + TaskTemplate: swarm.TaskSpec{ + ContainerSpec: swarm.ContainerSpec{ + Image: service.Image, + Command: service.Entrypoint, + Args: service.Command, + Hostname: service.Hostname, + Hosts: convertExtraHosts(service.ExtraHosts), + Healthcheck: healthcheck, + Env: convertEnvironment(service.Environment), + Labels: AddStackLabel(namespace, service.Labels), + Dir: service.WorkingDir, + User: service.User, + Mounts: mounts, + StopGracePeriod: service.StopGracePeriod, + TTY: service.Tty, + OpenStdin: service.StdinOpen, + }, + LogDriver: logDriver, + Resources: resources, + RestartPolicy: restartPolicy, + Placement: &swarm.Placement{ + Constraints: service.Deploy.Placement.Constraints, + }, + }, + EndpointSpec: endpoint, + Mode: mode, + Networks: networks, + UpdateConfig: convertUpdateConfig(service.Deploy.UpdateConfig), + } + + return serviceSpec, nil +} + +func convertServiceNetworks( + networks map[string]*composetypes.ServiceNetworkConfig, + networkConfigs networks, + namespace Namespace, + name string, +) ([]swarm.NetworkAttachmentConfig, error) { + if len(networks) == 0 { + return []swarm.NetworkAttachmentConfig{ + { + Target: namespace.Scope("default"), + Aliases: []string{name}, + }, + }, nil + } + + nets := []swarm.NetworkAttachmentConfig{} + for networkName, network := range networks { + networkConfig, ok := networkConfigs[networkName] + if !ok { + return []swarm.NetworkAttachmentConfig{}, fmt.Errorf("invalid network: %s", networkName) + } + var aliases []string + if network != nil { + aliases = network.Aliases + } + target := namespace.Scope(networkName) + if networkConfig.External.External { + target = networkName + } + nets = append(nets, swarm.NetworkAttachmentConfig{ + Target: target, + Aliases: append(aliases, name), + }) + } + return nets, nil +} + +func convertExtraHosts(extraHosts map[string]string) []string { + hosts := []string{} + for host, ip := range extraHosts { + hosts = append(hosts, fmt.Sprintf("%s %s", ip, host)) + } + return hosts +} + +func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container.HealthConfig, error) { + if healthcheck == nil { + return nil, nil + } + var ( + err error + timeout, interval time.Duration + retries int + ) + if healthcheck.Disable { + if len(healthcheck.Test) != 0 { + return nil, fmt.Errorf("command and disable key can't be set at the same time") + } + return &container.HealthConfig{ + Test: []string{"NONE"}, + }, nil + + } + if healthcheck.Timeout != "" { + timeout, err = time.ParseDuration(healthcheck.Timeout) + if err != nil { + return nil, err + } + } + if healthcheck.Interval != "" { + interval, err = time.ParseDuration(healthcheck.Interval) + if err != nil { + return nil, err + } + } + if healthcheck.Retries != nil { + retries = int(*healthcheck.Retries) + } + return &container.HealthConfig{ + Test: healthcheck.Test, + Timeout: timeout, + Interval: interval, + Retries: retries, + }, nil +} + +func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) { + // TODO: log if restart is being ignored + if source == nil { + policy, err := runconfigopts.ParseRestartPolicy(restart) + if err != nil { + return nil, err + } + switch { + case policy.IsNone(): + return nil, nil + case policy.IsAlways(), policy.IsUnlessStopped(): + return &swarm.RestartPolicy{ + Condition: swarm.RestartPolicyConditionAny, + }, nil + case policy.IsOnFailure(): + attempts := uint64(policy.MaximumRetryCount) + return &swarm.RestartPolicy{ + Condition: swarm.RestartPolicyConditionOnFailure, + MaxAttempts: &attempts, + }, nil + default: + return nil, fmt.Errorf("unknown restart policy: %s", restart) + } + } + return &swarm.RestartPolicy{ + Condition: swarm.RestartPolicyCondition(source.Condition), + Delay: source.Delay, + MaxAttempts: source.MaxAttempts, + Window: source.Window, + }, nil +} + +func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig { + if source == nil { + return nil + } + parallel := uint64(1) + if source.Parallelism != nil { + parallel = *source.Parallelism + } + return &swarm.UpdateConfig{ + Parallelism: parallel, + Delay: source.Delay, + FailureAction: source.FailureAction, + Monitor: source.Monitor, + MaxFailureRatio: source.MaxFailureRatio, + } +} + +func convertResources(source composetypes.Resources) (*swarm.ResourceRequirements, error) { + resources := &swarm.ResourceRequirements{} + if source.Limits != nil { + cpus, err := opts.ParseCPUs(source.Limits.NanoCPUs) + if err != nil { + return nil, err + } + resources.Limits = &swarm.Resources{ + NanoCPUs: cpus, + MemoryBytes: int64(source.Limits.MemoryBytes), + } + } + if source.Reservations != nil { + cpus, err := opts.ParseCPUs(source.Reservations.NanoCPUs) + if err != nil { + return nil, err + } + resources.Reservations = &swarm.Resources{ + NanoCPUs: cpus, + MemoryBytes: int64(source.Reservations.MemoryBytes), + } + } + return resources, nil +} + +func convertEndpointSpec(source []string) (*swarm.EndpointSpec, error) { + portConfigs := []swarm.PortConfig{} + ports, portBindings, err := nat.ParsePortSpecs(source) + if err != nil { + return nil, err + } + + for port := range ports { + portConfigs = append( + portConfigs, + opts.ConvertPortToPortConfig(port, portBindings)...) + } + + return &swarm.EndpointSpec{Ports: portConfigs}, nil +} + +func convertEnvironment(source map[string]string) []string { + var output []string + + for name, value := range source { + output = append(output, fmt.Sprintf("%s=%s", name, value)) + } + + return output +} + +func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error) { + serviceMode := swarm.ServiceMode{} + + switch mode { + case "global": + if replicas != nil { + return serviceMode, fmt.Errorf("replicas can only be used with replicated mode") + } + serviceMode.Global = &swarm.GlobalService{} + case "replicated", "": + serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas} + default: + return serviceMode, fmt.Errorf("Unknown mode: %s", mode) + } + return serviceMode, nil +} diff --git a/pkg/composetransform/service_test.go b/pkg/composetransform/service_test.go new file mode 100644 index 0000000000..8d713c6ba8 --- /dev/null +++ b/pkg/composetransform/service_test.go @@ -0,0 +1,40 @@ +package composetransform + +import ( + "testing" + + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/pkg/testutil/assert" +) + +func TestConvertRestartPolicyFromNone(t *testing.T) { + policy, err := convertRestartPolicy("no", nil) + var expected *swarm.RestartPolicy + assert.NilError(t, err) + assert.Equal(t, policy, expected) +} + +func TestConvertRestartPolicyFromUnknown(t *testing.T) { + _, err := convertRestartPolicy("unknown", nil) + assert.Error(t, err, "unknown restart policy: unknown") +} + +func TestConvertRestartPolicyFromAlways(t *testing.T) { + policy, err := convertRestartPolicy("always", nil) + expected := &swarm.RestartPolicy{ + Condition: swarm.RestartPolicyConditionAny, + } + assert.NilError(t, err) + assert.DeepEqual(t, policy, expected) +} + +func TestConvertRestartPolicyFromFailure(t *testing.T) { + policy, err := convertRestartPolicy("on-failure:4", nil) + attempts := uint64(4) + expected := &swarm.RestartPolicy{ + Condition: swarm.RestartPolicyConditionOnFailure, + MaxAttempts: &attempts, + } + assert.NilError(t, err) + assert.DeepEqual(t, policy, expected) +} From 4664dd06f1184d11c62de8a4e5e6653ed26c3790 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 1 Dec 2016 13:15:18 -0500 Subject: [PATCH 4/5] More unit tests for converting services in stack deploy Signed-off-by: Daniel Nephin --- pkg/composetransform/compose.go | 8 +- pkg/composetransform/compose_test.go | 2 +- pkg/composetransform/service.go | 9 +- pkg/composetransform/service_test.go | 157 ++++++++++++++++++++++++++- pkg/composetransform/volume_test.go | 10 +- 5 files changed, 171 insertions(+), 15 deletions(-) diff --git a/pkg/composetransform/compose.go b/pkg/composetransform/compose.go index 791ff27c07..686a654809 100644 --- a/pkg/composetransform/compose.go +++ b/pkg/composetransform/compose.go @@ -40,16 +40,18 @@ func AddStackLabel(namespace Namespace, labels map[string]string) map[string]str return labels } -type networks map[string]composetypes.NetworkConfig +type networkMap 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) { +func ConvertNetworks(namespace Namespace, networks networkMap) (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{} + if _, ok := networks["default"]; !ok { + networks["default"] = composetypes.NetworkConfig{} + } externalNetworks := []string{} result := make(map[string]types.NetworkCreate) diff --git a/pkg/composetransform/compose_test.go b/pkg/composetransform/compose_test.go index a934f4e808..2e5a3fd11b 100644 --- a/pkg/composetransform/compose_test.go +++ b/pkg/composetransform/compose_test.go @@ -28,7 +28,7 @@ func TestAddStackLabel(t *testing.T) { func TestConvertNetworks(t *testing.T) { namespace := Namespace{name: "foo"} - source := networks{ + source := networkMap{ "normal": composetypes.NetworkConfig{ Driver: "overlay", DriverOpts: map[string]string{ diff --git a/pkg/composetransform/service.go b/pkg/composetransform/service.go index 431c06db3f..935eac5d84 100644 --- a/pkg/composetransform/service.go +++ b/pkg/composetransform/service.go @@ -127,7 +127,7 @@ func convertService( func convertServiceNetworks( networks map[string]*composetypes.ServiceNetworkConfig, - networkConfigs networks, + networkConfigs networkMap, namespace Namespace, name string, ) ([]swarm.NetworkAttachmentConfig, error) { @@ -144,7 +144,8 @@ func convertServiceNetworks( for networkName, network := range networks { networkConfig, ok := networkConfigs[networkName] if !ok { - return []swarm.NetworkAttachmentConfig{}, fmt.Errorf("invalid network: %s", networkName) + return []swarm.NetworkAttachmentConfig{}, fmt.Errorf( + "service %q references network %q, which is not declared", name, networkName) } var aliases []string if network != nil { @@ -152,7 +153,7 @@ func convertServiceNetworks( } target := namespace.Scope(networkName) if networkConfig.External.External { - target = networkName + target = networkConfig.External.Name } nets = append(nets, swarm.NetworkAttachmentConfig{ Target: target, @@ -181,7 +182,7 @@ func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container ) if healthcheck.Disable { if len(healthcheck.Test) != 0 { - return nil, fmt.Errorf("command and disable key can't be set at the same time") + return nil, fmt.Errorf("test and disable can't be set at the same time") } return &container.HealthConfig{ Test: []string{"NONE"}, diff --git a/pkg/composetransform/service_test.go b/pkg/composetransform/service_test.go index 8d713c6ba8..b2e55f6c8d 100644 --- a/pkg/composetransform/service_test.go +++ b/pkg/composetransform/service_test.go @@ -1,17 +1,21 @@ package composetransform import ( + "sort" + "strings" "testing" + "time" + composetypes "github.com/aanand/compose-file/types" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/testutil/assert" ) func TestConvertRestartPolicyFromNone(t *testing.T) { policy, err := convertRestartPolicy("no", nil) - var expected *swarm.RestartPolicy assert.NilError(t, err) - assert.Equal(t, policy, expected) + assert.Equal(t, policy, (*swarm.RestartPolicy)(nil)) } func TestConvertRestartPolicyFromUnknown(t *testing.T) { @@ -38,3 +42,152 @@ func TestConvertRestartPolicyFromFailure(t *testing.T) { assert.NilError(t, err) assert.DeepEqual(t, policy, expected) } + +func TestConvertEnvironment(t *testing.T) { + source := map[string]string{ + "foo": "bar", + "key": "value", + } + env := convertEnvironment(source) + sort.Strings(env) + assert.DeepEqual(t, env, []string{"foo=bar", "key=value"}) +} + +func TestConvertResourcesFull(t *testing.T) { + source := composetypes.Resources{ + Limits: &composetypes.Resource{ + NanoCPUs: "0.003", + MemoryBytes: composetypes.UnitBytes(300000000), + }, + Reservations: &composetypes.Resource{ + NanoCPUs: "0.002", + MemoryBytes: composetypes.UnitBytes(200000000), + }, + } + resources, err := convertResources(source) + assert.NilError(t, err) + + expected := &swarm.ResourceRequirements{ + Limits: &swarm.Resources{ + NanoCPUs: 3000000, + MemoryBytes: 300000000, + }, + Reservations: &swarm.Resources{ + NanoCPUs: 2000000, + MemoryBytes: 200000000, + }, + } + assert.DeepEqual(t, resources, expected) +} + +func TestConvertHealthcheck(t *testing.T) { + retries := uint64(10) + source := &composetypes.HealthCheckConfig{ + Test: []string{"EXEC", "touch", "/foo"}, + Timeout: "30s", + Interval: "2ms", + Retries: &retries, + } + expected := &container.HealthConfig{ + Test: source.Test, + Timeout: 30 * time.Second, + Interval: 2 * time.Millisecond, + Retries: 10, + } + + healthcheck, err := convertHealthcheck(source) + assert.NilError(t, err) + assert.DeepEqual(t, healthcheck, expected) +} + +func TestConvertHealthcheckDisable(t *testing.T) { + source := &composetypes.HealthCheckConfig{Disable: true} + expected := &container.HealthConfig{ + Test: []string{"NONE"}, + } + + healthcheck, err := convertHealthcheck(source) + assert.NilError(t, err) + assert.DeepEqual(t, healthcheck, expected) +} + +func TestConvertHealthcheckDisableWithTest(t *testing.T) { + source := &composetypes.HealthCheckConfig{ + Disable: true, + Test: []string{"EXEC", "touch"}, + } + _, err := convertHealthcheck(source) + assert.Error(t, err, "test and disable can't be set") +} + +func TestConvertServiceNetworksOnlyDefault(t *testing.T) { + networkConfigs := networkMap{} + networks := map[string]*composetypes.ServiceNetworkConfig{} + + configs, err := convertServiceNetworks( + networks, networkConfigs, NewNamespace("foo"), "service") + + expected := []swarm.NetworkAttachmentConfig{ + { + Target: "foo_default", + Aliases: []string{"service"}, + }, + } + + assert.NilError(t, err) + assert.DeepEqual(t, configs, expected) +} + +func TestConvertServiceNetworks(t *testing.T) { + networkConfigs := networkMap{ + "front": composetypes.NetworkConfig{ + External: composetypes.External{ + External: true, + Name: "fronttier", + }, + }, + "back": composetypes.NetworkConfig{}, + } + networks := map[string]*composetypes.ServiceNetworkConfig{ + "front": { + Aliases: []string{"something"}, + }, + "back": { + Aliases: []string{"other"}, + }, + } + + configs, err := convertServiceNetworks( + networks, networkConfigs, NewNamespace("foo"), "service") + + expected := []swarm.NetworkAttachmentConfig{ + { + Target: "foo_back", + Aliases: []string{"other", "service"}, + }, + { + Target: "fronttier", + Aliases: []string{"something", "service"}, + }, + } + + sortedConfigs := byTargetSort(configs) + sort.Sort(&sortedConfigs) + + assert.NilError(t, err) + assert.DeepEqual(t, []swarm.NetworkAttachmentConfig(sortedConfigs), expected) +} + +type byTargetSort []swarm.NetworkAttachmentConfig + +func (s byTargetSort) Len() int { + return len(s) +} + +func (s byTargetSort) Less(i, j int) bool { + return strings.Compare(s[i].Target, s[j].Target) < 0 +} + +func (s byTargetSort) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} diff --git a/pkg/composetransform/volume_test.go b/pkg/composetransform/volume_test.go index fd5a2a84b3..1b3f056058 100644 --- a/pkg/composetransform/volume_test.go +++ b/pkg/composetransform/volume_test.go @@ -23,15 +23,15 @@ func TestIsNoCopy(t *testing.T) { assert.Equal(t, isNoCopy([]string{"foo", "rw"}), false) } -func TesTGetBindOptions(t *testing.T) { +func TestGetBindOptions(t *testing.T) { opts := getBindOptions([]string{"slave"}) - expected := &mount.BindOptions{Propagation: mount.PropagationSlave} - assert.Equal(t, opts, expected) + expected := mount.BindOptions{Propagation: mount.PropagationSlave} + assert.Equal(t, *opts, expected) } -func TesTGetBindOptionsNone(t *testing.T) { +func TestGetBindOptionsNone(t *testing.T) { opts := getBindOptions([]string{"ro"}) - assert.Equal(t, opts, nil) + assert.Equal(t, opts, (*mount.BindOptions)(nil)) } func TestConvertVolumeToMountNamedVolume(t *testing.T) { From 643fd775edb0a05d649f4d484e966428eb3a6b09 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 5 Dec 2016 16:14:08 -0500 Subject: [PATCH 5/5] Move pkg to cli/compose/convert Signed-off-by: Daniel Nephin --- cli/command/stack/common.go | 8 ++++---- cli/command/stack/deploy.go | 12 ++++++------ cli/command/stack/deploy_bundlefile.go | 10 +++++----- cli/command/stack/list.go | 6 +++--- .../compose/convert}/compose.go | 6 +++--- .../compose/convert}/compose_test.go | 6 +++--- .../compose/convert}/service.go | 8 ++++---- .../compose/convert}/service_test.go | 2 +- .../compose/convert}/volume.go | 6 +++--- .../compose/convert}/volume_test.go | 2 +- 10 files changed, 33 insertions(+), 33 deletions(-) rename {pkg/composetransform => cli/compose/convert}/compose.go (91%) rename {pkg/composetransform => cli/compose/convert}/compose_test.go (93%) rename {pkg/composetransform => cli/compose/convert}/service.go (97%) rename {pkg/composetransform => cli/compose/convert}/service_test.go (99%) rename {pkg/composetransform => cli/compose/convert}/volume.go (92%) rename {pkg/composetransform => cli/compose/convert}/volume_test.go (99%) diff --git a/cli/command/stack/common.go b/cli/command/stack/common.go index c3a43f2cd8..5c4996d666 100644 --- a/cli/command/stack/common.go +++ b/cli/command/stack/common.go @@ -6,26 +6,26 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/cli/compose/convert" "github.com/docker/docker/client" "github.com/docker/docker/opts" - "github.com/docker/docker/pkg/composetransform" ) func getStackFilter(namespace string) filters.Args { filter := filters.NewArgs() - filter.Add("label", composetransform.LabelNamespace+"="+namespace) + filter.Add("label", convert.LabelNamespace+"="+namespace) return filter } func getStackFilterFromOpt(namespace string, opt opts.FilterOpt) filters.Args { filter := opt.Value() - filter.Add("label", composetransform.LabelNamespace+"="+namespace) + filter.Add("label", convert.LabelNamespace+"="+namespace) return filter } func getAllStacksFilter() filters.Args { filter := filters.NewArgs() - filter.Add("label", composetransform.LabelNamespace) + filter.Add("label", convert.LabelNamespace) return filter } diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index 957f92f29e..32ebd62d3f 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -17,8 +17,8 @@ import ( "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" + "github.com/docker/docker/cli/compose/convert" dockerclient "github.com/docker/docker/client" - "github.com/docker/docker/pkg/composetransform" ) const ( @@ -115,16 +115,16 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo return err } - namespace := composetransform.NewNamespace(opts.namespace) + namespace := convert.NewNamespace(opts.namespace) - networks, externalNetworks := composetransform.ConvertNetworks(namespace, config.Networks) + networks, externalNetworks := convert.Networks(namespace, config.Networks) if err := validateExternalNetworks(ctx, dockerCli, externalNetworks); err != nil { return err } if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil { return err } - services, err := composetransform.ConvertServices(namespace, config) + services, err := convert.Services(namespace, config) if err != nil { return err } @@ -198,7 +198,7 @@ func validateExternalNetworks( func createNetworks( ctx context.Context, dockerCli *command.DockerCli, - namespace composetransform.Namespace, + namespace convert.Namespace, networks map[string]types.NetworkCreate, ) error { client := dockerCli.Client() @@ -236,7 +236,7 @@ func deployServices( ctx context.Context, dockerCli *command.DockerCli, services map[string]swarm.ServiceSpec, - namespace composetransform.Namespace, + namespace convert.Namespace, sendAuth bool, ) error { apiClient := dockerCli.Client() diff --git a/cli/command/stack/deploy_bundlefile.go b/cli/command/stack/deploy_bundlefile.go index f9a4162389..5a178c4ab6 100644 --- a/cli/command/stack/deploy_bundlefile.go +++ b/cli/command/stack/deploy_bundlefile.go @@ -6,7 +6,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli/command" - "github.com/docker/docker/pkg/composetransform" + "github.com/docker/docker/cli/compose/convert" ) func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deployOptions) error { @@ -19,13 +19,13 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy return err } - namespace := composetransform.NewNamespace(opts.namespace) + namespace := convert.NewNamespace(opts.namespace) networks := make(map[string]types.NetworkCreate) for _, service := range bundle.Services { for _, networkName := range service.Networks { networks[networkName] = types.NetworkCreate{ - Labels: composetransform.AddStackLabel(namespace, nil), + Labels: convert.AddStackLabel(namespace, nil), } } } @@ -53,7 +53,7 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy serviceSpec := swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: name, - Labels: composetransform.AddStackLabel(namespace, service.Labels), + Labels: convert.AddStackLabel(namespace, service.Labels), }, TaskTemplate: swarm.TaskSpec{ ContainerSpec: swarm.ContainerSpec{ @@ -64,7 +64,7 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy // Service Labels will not be copied to Containers // automatically during the deployment so we apply // it here. - Labels: composetransform.AddStackLabel(namespace, nil), + Labels: convert.AddStackLabel(namespace, nil), }, }, EndpointSpec: &swarm.EndpointSpec{ diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index 52e593316e..9b6c645e29 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -11,8 +11,8 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" + "github.com/docker/docker/cli/compose/convert" "github.com/docker/docker/client" - "github.com/docker/docker/pkg/composetransform" "github.com/spf13/cobra" ) @@ -90,10 +90,10 @@ func getStacks( m := make(map[string]*stack, 0) for _, service := range services { labels := service.Spec.Labels - name, ok := labels[composetransform.LabelNamespace] + name, ok := labels[convert.LabelNamespace] if !ok { return nil, fmt.Errorf("cannot get label %s for service %s", - composetransform.LabelNamespace, service.ID) + convert.LabelNamespace, service.ID) } ztack, ok := m[name] if !ok { diff --git a/pkg/composetransform/compose.go b/cli/compose/convert/compose.go similarity index 91% rename from pkg/composetransform/compose.go rename to cli/compose/convert/compose.go index 686a654809..e0684482b8 100644 --- a/pkg/composetransform/compose.go +++ b/cli/compose/convert/compose.go @@ -1,4 +1,4 @@ -package composetransform +package convert import ( composetypes "github.com/aanand/compose-file/types" @@ -42,8 +42,8 @@ func AddStackLabel(namespace Namespace, labels map[string]string) map[string]str type networkMap map[string]composetypes.NetworkConfig -// ConvertNetworks from the compose-file type to the engine API type -func ConvertNetworks(namespace Namespace, networks networkMap) (map[string]types.NetworkCreate, []string) { +// Networks from the compose-file type to the engine API type +func Networks(namespace Namespace, networks networkMap) (map[string]types.NetworkCreate, []string) { if networks == nil { networks = make(map[string]composetypes.NetworkConfig) } diff --git a/pkg/composetransform/compose_test.go b/cli/compose/convert/compose_test.go similarity index 93% rename from pkg/composetransform/compose_test.go rename to cli/compose/convert/compose_test.go index 2e5a3fd11b..8f8e8ea6d8 100644 --- a/pkg/composetransform/compose_test.go +++ b/cli/compose/convert/compose_test.go @@ -1,4 +1,4 @@ -package composetransform +package convert import ( "testing" @@ -26,7 +26,7 @@ func TestAddStackLabel(t *testing.T) { assert.DeepEqual(t, actual, expected) } -func TestConvertNetworks(t *testing.T) { +func TestNetworks(t *testing.T) { namespace := Namespace{name: "foo"} source := networkMap{ "normal": composetypes.NetworkConfig{ @@ -79,7 +79,7 @@ func TestConvertNetworks(t *testing.T) { }, } - networks, externals := ConvertNetworks(namespace, source) + networks, externals := Networks(namespace, source) assert.DeepEqual(t, networks, expected) assert.DeepEqual(t, externals, []string{"special"}) } diff --git a/pkg/composetransform/service.go b/cli/compose/convert/service.go similarity index 97% rename from pkg/composetransform/service.go rename to cli/compose/convert/service.go index 935eac5d84..458b518a46 100644 --- a/pkg/composetransform/service.go +++ b/cli/compose/convert/service.go @@ -1,4 +1,4 @@ -package composetransform +package convert import ( "fmt" @@ -12,8 +12,8 @@ import ( "github.com/docker/go-connections/nat" ) -// ConvertServices from compose-file types to engine API types -func ConvertServices( +// Services from compose-file types to engine API types +func Services( namespace Namespace, config *composetypes.Config, ) (map[string]swarm.ServiceSpec, error) { @@ -52,7 +52,7 @@ func convertService( return swarm.ServiceSpec{}, err } - mounts, err := ConvertVolumes(service.Volumes, volumes, namespace) + mounts, err := Volumes(service.Volumes, volumes, namespace) if err != nil { // TODO: better error message (include service name) return swarm.ServiceSpec{}, err diff --git a/pkg/composetransform/service_test.go b/cli/compose/convert/service_test.go similarity index 99% rename from pkg/composetransform/service_test.go rename to cli/compose/convert/service_test.go index b2e55f6c8d..a6884917de 100644 --- a/pkg/composetransform/service_test.go +++ b/cli/compose/convert/service_test.go @@ -1,4 +1,4 @@ -package composetransform +package convert import ( "sort" diff --git a/pkg/composetransform/volume.go b/cli/compose/convert/volume.go similarity index 92% rename from pkg/composetransform/volume.go rename to cli/compose/convert/volume.go index d6ee808a50..4eb5788204 100644 --- a/pkg/composetransform/volume.go +++ b/cli/compose/convert/volume.go @@ -1,4 +1,4 @@ -package composetransform +package convert import ( "fmt" @@ -10,8 +10,8 @@ import ( type volumes map[string]composetypes.VolumeConfig -// ConvertVolumes from compose-file types to engine api types -func ConvertVolumes(serviceVolumes []string, stackVolumes volumes, namespace Namespace) ([]mount.Mount, error) { +// Volumes from compose-file types to engine api types +func Volumes(serviceVolumes []string, stackVolumes volumes, namespace Namespace) ([]mount.Mount, error) { var mounts []mount.Mount for _, volumeSpec := range serviceVolumes { diff --git a/pkg/composetransform/volume_test.go b/cli/compose/convert/volume_test.go similarity index 99% rename from pkg/composetransform/volume_test.go rename to cli/compose/convert/volume_test.go index 1b3f056058..5e9c042b5f 100644 --- a/pkg/composetransform/volume_test.go +++ b/cli/compose/convert/volume_test.go @@ -1,4 +1,4 @@ -package composetransform +package convert import ( "testing"