瀏覽代碼

Move ConvertVolumes to composetransform package.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
Daniel Nephin 8 年之前
父節點
當前提交
732e19892d

+ 10 - 0
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"`

+ 2 - 1
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
 }
 

+ 8 - 131
cli/command/stack/deploy.go

@@ -13,13 +13,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"
 	"github.com/spf13/cobra"
@@ -120,10 +119,10 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo
 		return err
 	}
 
-	namespace := namespace{name: opts.namespace}
+	namespace := composetransform.NewNamespace(opts.namespace)
 
 	serviceNetworks := getServicesDeclaredNetworks(config.Services)
-	networks, externalNetworks := convertNetworks(namespace, config.Networks, serviceNetworks)
+	networks, externalNetworks := composetransform.ConvertNetworks(namespace, config.Networks, serviceNetworks)
 	if err := validateExternalNetworks(ctx, dockerCli, externalNetworks); err != nil {
 		return err
 	}
@@ -217,12 +216,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
 	}
@@ -233,7 +232,7 @@ func createNetworks(
 	}
 
 	for internalName, createOpts := range networks {
-		name := namespace.scope(internalName)
+		name := namespace.Scope(internalName)
 		if _, exists := existingNetworkMap[name]; exists {
 			continue
 		}
@@ -254,7 +253,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 {
@@ -288,128 +287,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)
-
-	for _, part := range parts {
-		if strings.TrimSpace(part) == "" {
-			return mount.Mount{}, fmt.Errorf("invalid volume: %s", volumeSpec)
-		}
-	}
-
-	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]
-	}
-
-	if source == "" {
-		// Anonymous volume
-		return mount.Mount{
-			Type:   mount.TypeVolume,
-			Target: target,
-		}, nil
-	}
-
-	// 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,
@@ -519,7 +396,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

+ 13 - 2
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
 }
 

+ 3 - 3
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",
 			},
 		},

+ 120 - 0
pkg/composetransform/volume.go

@@ -0,0 +1,120 @@
+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)
+
+	for _, part := range parts {
+		if strings.TrimSpace(part) == "" {
+			return mount.Mount{}, fmt.Errorf("invalid volume: %s", volumeSpec)
+		}
+	}
+
+	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]
+	}
+
+	// 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
+}

+ 112 - 0
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")
+}