瀏覽代碼

Merge pull request #813 from docker/ecsVolumeService

CLI command to manage ECS volumes
Nicolas De loof 5 年之前
父節點
當前提交
ed69f38b44
共有 10 個文件被更改,包括 173 次插入61 次删除
  1. 41 13
      cli/cmd/volume/command.go
  2. 1 5
      cli/main.go
  3. 2 2
      ecs/aws.go
  4. 7 3
      ecs/awsResources.go
  5. 21 22
      ecs/aws_mock.go
  6. 1 1
      ecs/backend.go
  7. 8 4
      ecs/cloudformation_test.go
  8. 35 11
      ecs/sdk.go
  9. 57 0
      ecs/volumes.go
  10. 0 0
      resolve

+ 41 - 13
cli/cmd/volume/acivolume.go → cli/cmd/volume/command.go

@@ -20,25 +20,27 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 
 
-	"github.com/hashicorp/go-multierror"
-	"github.com/spf13/cobra"
-
 	"github.com/docker/compose-cli/aci"
 	"github.com/docker/compose-cli/aci"
 	"github.com/docker/compose-cli/api/client"
 	"github.com/docker/compose-cli/api/client"
 	"github.com/docker/compose-cli/cli/formatter"
 	"github.com/docker/compose-cli/cli/formatter"
+	"github.com/docker/compose-cli/context/store"
+	"github.com/docker/compose-cli/ecs"
 	formatter2 "github.com/docker/compose-cli/formatter"
 	formatter2 "github.com/docker/compose-cli/formatter"
 	"github.com/docker/compose-cli/progress"
 	"github.com/docker/compose-cli/progress"
+
+	"github.com/hashicorp/go-multierror"
+	"github.com/spf13/cobra"
 )
 )
 
 
-// ACICommand manage volumes
-func ACICommand() *cobra.Command {
+// Command manage volumes
+func Command(ctype string) *cobra.Command {
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
 		Use:   "volume",
 		Use:   "volume",
 		Short: "Manages volumes",
 		Short: "Manages volumes",
 	}
 	}
 
 
 	cmd.AddCommand(
 	cmd.AddCommand(
-		createVolume(),
+		createVolume(ctype),
 		listVolume(),
 		listVolume(),
 		rmVolume(),
 		rmVolume(),
 		inspectVolume(),
 		inspectVolume(),
@@ -46,11 +48,25 @@ func ACICommand() *cobra.Command {
 	return cmd
 	return cmd
 }
 }
 
 
-func createVolume() *cobra.Command {
-	aciOpts := aci.VolumeCreateOptions{}
+func createVolume(ctype string) *cobra.Command {
+	var usage string
+	var short string
+	switch ctype {
+	case store.AciContextType:
+		usage = "create --storage-account ACCOUNT VOLUME"
+		short = "Creates an Azure file share to use as ACI volume."
+	case store.EcsContextType:
+		usage = "create [OPTIONS] VOLUME"
+		short = "Creates an EFS filesystem to use as AWS volume."
+	default:
+		usage = "create [OPTIONS] VOLUME"
+		short = "Creates a volume"
+	}
+
+	var opts interface{}
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:   "create --storage-account ACCOUNT VOLUME",
-		Short: "Creates an Azure file share to use as ACI volume.",
+		Use:   usage,
+		Short: short,
 		Args:  cobra.ExactArgs(1),
 		Args:  cobra.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			ctx := cmd.Context()
@@ -59,7 +75,7 @@ func createVolume() *cobra.Command {
 				return err
 				return err
 			}
 			}
 			result, err := progress.Run(ctx, func(ctx context.Context) (string, error) {
 			result, err := progress.Run(ctx, func(ctx context.Context) (string, error) {
-				volume, err := c.VolumeService().Create(ctx, args[0], aciOpts)
+				volume, err := c.VolumeService().Create(ctx, args[0], opts)
 				if err != nil {
 				if err != nil {
 					return "", err
 					return "", err
 				}
 				}
@@ -73,8 +89,20 @@ func createVolume() *cobra.Command {
 		},
 		},
 	}
 	}
 
 
-	cmd.Flags().StringVar(&aciOpts.Account, "storage-account", "", "Storage account name")
-	_ = cmd.MarkFlagRequired("storage-account")
+	switch ctype {
+	case store.AciContextType:
+		aciOpts := aci.VolumeCreateOptions{}
+		cmd.Flags().StringVar(&aciOpts.Account, "storage-account", "", "Storage account name")
+		_ = cmd.MarkFlagRequired("storage-account")
+		opts = aciOpts
+	case store.EcsContextType:
+		ecsOpts := ecs.VolumeCreateOptions{}
+		cmd.Flags().StringVar(&ecsOpts.KmsKeyID, "kms-key", "", "ID of the AWS KMS CMK to be used to protect the encrypted file system")
+		cmd.Flags().StringVar(&ecsOpts.PerformanceMode, "performance-mode", "", "performance mode of the file system. (generalPurpose|maxIO)")
+		cmd.Flags().Float64Var(&ecsOpts.ProvisionedThroughputInMibps, "provisioned-throughput", 0, "throughput in MiB/s (1-1024)")
+		cmd.Flags().StringVar(&ecsOpts.ThroughputMode, "throughput-mode", "", "throughput mode (bursting|provisioned)")
+		opts = ecsOpts
+	}
 	return cmd
 	return cmd
 }
 }
 
 

+ 1 - 5
cli/main.go

@@ -182,13 +182,9 @@ func main() {
 	root.AddCommand(
 	root.AddCommand(
 		run.Command(ctype),
 		run.Command(ctype),
 		compose.Command(ctype),
 		compose.Command(ctype),
+		volume.Command(ctype),
 	)
 	)
 
 
-	if ctype == store.AciContextType {
-		// we can also pass ctype as a parameter to the volume command and customize subcommands, flags, etc. when we have other backend implementations
-		root.AddCommand(volume.ACICommand())
-	}
-
 	ctx = apicontext.WithCurrentContext(ctx, currentContext)
 	ctx = apicontext.WithCurrentContext(ctx, currentContext)
 	ctx = store.WithContextStore(ctx, s)
 	ctx = store.WithContextStore(ctx, s)
 
 

+ 2 - 2
ecs/aws.go

@@ -73,7 +73,7 @@ type API interface {
 	DeleteCapacityProvider(ctx context.Context, arn string) error
 	DeleteCapacityProvider(ctx context.Context, arn string) error
 	DeleteAutoscalingGroup(ctx context.Context, arn string) error
 	DeleteAutoscalingGroup(ctx context.Context, arn string) error
 	ResolveFileSystem(ctx context.Context, id string) (awsResource, error)
 	ResolveFileSystem(ctx context.Context, id string) (awsResource, error)
-	FindFileSystem(ctx context.Context, tags map[string]string) (awsResource, error)
-	CreateFileSystem(ctx context.Context, tags map[string]string) (string, error)
+	ListFileSystems(ctx context.Context, tags map[string]string) ([]awsResource, error)
+	CreateFileSystem(ctx context.Context, tags map[string]string, options VolumeCreateOptions) (awsResource, error)
 	DeleteFileSystem(ctx context.Context, id string) error
 	DeleteFileSystem(ctx context.Context, id string) error
 }
 }

+ 7 - 3
ecs/awsResources.go

@@ -253,12 +253,16 @@ func (b *ecsAPIService) parseExternalVolumes(ctx context.Context, project *types
 			compose.ProjectTag: project.Name,
 			compose.ProjectTag: project.Name,
 			compose.VolumeTag:  name,
 			compose.VolumeTag:  name,
 		}
 		}
-		fileSystem, err := b.aws.FindFileSystem(ctx, tags)
+		previous, err := b.aws.ListFileSystems(ctx, tags)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
-		if fileSystem != nil {
-			filesystems[name] = fileSystem
+
+		if len(previous) > 1 {
+			return nil, fmt.Errorf("multiple filesystems are tags as project=%q, volume=%q", project.Name, name)
+		}
+		if len(previous) == 1 {
+			filesystems[name] = previous[0]
 		}
 		}
 	}
 	}
 	return filesystems, nil
 	return filesystems, nil

+ 21 - 22
ecs/aws_mock.go

@@ -6,13 +6,12 @@ package ecs
 
 
 import (
 import (
 	context "context"
 	context "context"
-	reflect "reflect"
-
 	cloudformation "github.com/aws/aws-sdk-go/service/cloudformation"
 	cloudformation "github.com/aws/aws-sdk-go/service/cloudformation"
 	ecs "github.com/aws/aws-sdk-go/service/ecs"
 	ecs "github.com/aws/aws-sdk-go/service/ecs"
 	compose "github.com/docker/compose-cli/api/compose"
 	compose "github.com/docker/compose-cli/api/compose"
 	secrets "github.com/docker/compose-cli/api/secrets"
 	secrets "github.com/docker/compose-cli/api/secrets"
 	gomock "github.com/golang/mock/gomock"
 	gomock "github.com/golang/mock/gomock"
+	reflect "reflect"
 )
 )
 
 
 // MockAPI is a mock of API interface
 // MockAPI is a mock of API interface
@@ -97,18 +96,18 @@ func (mr *MockAPIMockRecorder) CreateCluster(arg0, arg1 interface{}) *gomock.Cal
 }
 }
 
 
 // CreateFileSystem mocks base method
 // CreateFileSystem mocks base method
-func (m *MockAPI) CreateFileSystem(arg0 context.Context, arg1 map[string]string) (string, error) {
+func (m *MockAPI) CreateFileSystem(arg0 context.Context, arg1 map[string]string, arg2 VolumeCreateOptions) (awsResource, error) {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CreateFileSystem", arg0, arg1)
-	ret0, _ := ret[0].(string)
+	ret := m.ctrl.Call(m, "CreateFileSystem", arg0, arg1, arg2)
+	ret0, _ := ret[0].(awsResource)
 	ret1, _ := ret[1].(error)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 	return ret0, ret1
 }
 }
 
 
 // CreateFileSystem indicates an expected call of CreateFileSystem
 // CreateFileSystem indicates an expected call of CreateFileSystem
-func (mr *MockAPIMockRecorder) CreateFileSystem(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockAPIMockRecorder) CreateFileSystem(arg0, arg1, arg2 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFileSystem", reflect.TypeOf((*MockAPI)(nil).CreateFileSystem), arg0, arg1)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFileSystem", reflect.TypeOf((*MockAPI)(nil).CreateFileSystem), arg0, arg1, arg2)
 }
 }
 
 
 // CreateSecret mocks base method
 // CreateSecret mocks base method
@@ -240,21 +239,6 @@ func (mr *MockAPIMockRecorder) DescribeStackEvents(arg0, arg1 interface{}) *gomo
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeStackEvents", reflect.TypeOf((*MockAPI)(nil).DescribeStackEvents), arg0, arg1)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeStackEvents", reflect.TypeOf((*MockAPI)(nil).DescribeStackEvents), arg0, arg1)
 }
 }
 
 
-// FindFileSystem mocks base method
-func (m *MockAPI) FindFileSystem(arg0 context.Context, arg1 map[string]string) (awsResource, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "FindFileSystem", arg0, arg1)
-	ret0, _ := ret[0].(awsResource)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// FindFileSystem indicates an expected call of FindFileSystem
-func (mr *MockAPIMockRecorder) FindFileSystem(arg0, arg1 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindFileSystem", reflect.TypeOf((*MockAPI)(nil).FindFileSystem), arg0, arg1)
-}
-
 // GetDefaultVPC mocks base method
 // GetDefaultVPC mocks base method
 func (m *MockAPI) GetDefaultVPC(arg0 context.Context) (string, error) {
 func (m *MockAPI) GetDefaultVPC(arg0 context.Context) (string, error) {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
@@ -454,6 +438,21 @@ func (mr *MockAPIMockRecorder) InspectSecret(arg0, arg1 interface{}) *gomock.Cal
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InspectSecret", reflect.TypeOf((*MockAPI)(nil).InspectSecret), arg0, arg1)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InspectSecret", reflect.TypeOf((*MockAPI)(nil).InspectSecret), arg0, arg1)
 }
 }
 
 
+// ListFileSystems mocks base method
+func (m *MockAPI) ListFileSystems(arg0 context.Context, arg1 map[string]string) ([]awsResource, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ListFileSystems", arg0, arg1)
+	ret0, _ := ret[0].([]awsResource)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ListFileSystems indicates an expected call of ListFileSystems
+func (mr *MockAPIMockRecorder) ListFileSystems(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListFileSystems", reflect.TypeOf((*MockAPI)(nil).ListFileSystems), arg0, arg1)
+}
+
 // ListSecrets mocks base method
 // ListSecrets mocks base method
 func (m *MockAPI) ListSecrets(arg0 context.Context) ([]secrets.Secret, error) {
 func (m *MockAPI) ListSecrets(arg0 context.Context) ([]secrets.Secret, error) {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()

+ 1 - 1
ecs/backend.go

@@ -98,7 +98,7 @@ func (b *ecsAPIService) SecretsService() secrets.Service {
 }
 }
 
 
 func (b *ecsAPIService) VolumeService() volumes.Service {
 func (b *ecsAPIService) VolumeService() volumes.Service {
-	return nil
+	return ecsVolumeService{backend: b}
 }
 }
 
 
 func (b *ecsAPIService) ResourceService() resources.Service {
 func (b *ecsAPIService) ResourceService() resources.Service {

+ 8 - 4
ecs/cloudformation_test.go

@@ -390,7 +390,7 @@ volumes:
         throughput_mode: provisioned
         throughput_mode: provisioned
         provisioned_throughput: 1024
         provisioned_throughput: 1024
 `, useDefaultVPC, func(m *MockAPIMockRecorder) {
 `, useDefaultVPC, func(m *MockAPIMockRecorder) {
-		m.FindFileSystem(gomock.Any(), map[string]string{
+		m.ListFileSystems(gomock.Any(), map[string]string{
 			compose.ProjectTag: t.Name(),
 			compose.ProjectTag: t.Name(),
 			compose.VolumeTag:  "db-data",
 			compose.VolumeTag:  "db-data",
 		}).Return(nil, nil)
 		}).Return(nil, nil)
@@ -420,7 +420,7 @@ volumes:
       uid: 1002
       uid: 1002
       gid: 1002
       gid: 1002
 `, useDefaultVPC, func(m *MockAPIMockRecorder) {
 `, useDefaultVPC, func(m *MockAPIMockRecorder) {
-		m.FindFileSystem(gomock.Any(), gomock.Any()).Return(nil, nil)
+		m.ListFileSystems(gomock.Any(), gomock.Any()).Return(nil, nil)
 	})
 	})
 	a := template.Resources["DbdataAccessPoint"].(*efs.AccessPoint)
 	a := template.Resources["DbdataAccessPoint"].(*efs.AccessPoint)
 	assert.Check(t, a != nil)
 	assert.Check(t, a != nil)
@@ -436,10 +436,14 @@ services:
 volumes:
 volumes:
   db-data: {}
   db-data: {}
 `, useDefaultVPC, func(m *MockAPIMockRecorder) {
 `, useDefaultVPC, func(m *MockAPIMockRecorder) {
-		m.FindFileSystem(gomock.Any(), map[string]string{
+		m.ListFileSystems(gomock.Any(), map[string]string{
 			compose.ProjectTag: t.Name(),
 			compose.ProjectTag: t.Name(),
 			compose.VolumeTag:  "db-data",
 			compose.VolumeTag:  "db-data",
-		}).Return(existingAWSResource{id: "fs-123abc"}, nil)
+		}).Return([]awsResource{
+			existingAWSResource{
+				id: "fs-123abc",
+			},
+		}, nil)
 	})
 	})
 	s := template.Resources["DbdataNFSMountTargetOnSubnet1"].(*efs.MountTarget)
 	s := template.Resources["DbdataNFSMountTargetOnSubnet1"].(*efs.MountTarget)
 	assert.Check(t, s != nil)
 	assert.Check(t, s != nil)

+ 35 - 11
ecs/sdk.go

@@ -904,7 +904,8 @@ func (s sdk) ResolveFileSystem(ctx context.Context, id string) (awsResource, err
 	}, nil
 	}, nil
 }
 }
 
 
-func (s sdk) FindFileSystem(ctx context.Context, tags map[string]string) (awsResource, error) {
+func (s sdk) ListFileSystems(ctx context.Context, tags map[string]string) ([]awsResource, error) {
+	var results []awsResource
 	var token *string
 	var token *string
 	for {
 	for {
 		desc, err := s.EFS.DescribeFileSystemsWithContext(ctx, &efs.DescribeFileSystemsInput{
 		desc, err := s.EFS.DescribeFileSystemsWithContext(ctx, &efs.DescribeFileSystemsInput{
@@ -915,14 +916,14 @@ func (s sdk) FindFileSystem(ctx context.Context, tags map[string]string) (awsRes
 		}
 		}
 		for _, filesystem := range desc.FileSystems {
 		for _, filesystem := range desc.FileSystems {
 			if containsAll(filesystem.Tags, tags) {
 			if containsAll(filesystem.Tags, tags) {
-				return existingAWSResource{
+				results = append(results, existingAWSResource{
 					arn: aws.StringValue(filesystem.FileSystemArn),
 					arn: aws.StringValue(filesystem.FileSystemArn),
 					id:  aws.StringValue(filesystem.FileSystemId),
 					id:  aws.StringValue(filesystem.FileSystemId),
-				}, nil
+				})
 			}
 			}
 		}
 		}
 		if desc.NextMarker == token {
 		if desc.NextMarker == token {
-			return nil, nil
+			return results, nil
 		}
 		}
 		token = desc.NextMarker
 		token = desc.NextMarker
 	}
 	}
@@ -941,7 +942,7 @@ TAGS:
 	return true
 	return true
 }
 }
 
 
-func (s sdk) CreateFileSystem(ctx context.Context, tags map[string]string) (string, error) {
+func (s sdk) CreateFileSystem(ctx context.Context, tags map[string]string, options VolumeCreateOptions) (awsResource, error) {
 	var efsTags []*efs.Tag
 	var efsTags []*efs.Tag
 	for k, v := range tags {
 	for k, v := range tags {
 		efsTags = append(efsTags, &efs.Tag{
 		efsTags = append(efsTags, &efs.Tag{
@@ -949,16 +950,39 @@ func (s sdk) CreateFileSystem(ctx context.Context, tags map[string]string) (stri
 			Value: aws.String(v),
 			Value: aws.String(v),
 		})
 		})
 	}
 	}
+	var (
+		k *string
+		p *string
+		f *float64
+		t *string
+	)
+	if options.ProvisionedThroughputInMibps > 1 {
+		f = aws.Float64(options.ProvisionedThroughputInMibps)
+	}
+	if options.KmsKeyID != "" {
+		k = aws.String(options.KmsKeyID)
+	}
+	if options.PerformanceMode != "" {
+		p = aws.String(options.PerformanceMode)
+	}
+	if options.ThroughputMode != "" {
+		t = aws.String(options.ThroughputMode)
+	}
 	res, err := s.EFS.CreateFileSystemWithContext(ctx, &efs.CreateFileSystemInput{
 	res, err := s.EFS.CreateFileSystemWithContext(ctx, &efs.CreateFileSystemInput{
-		Encrypted: aws.Bool(true),
-		Tags:      efsTags,
+		Encrypted:                    aws.Bool(true),
+		KmsKeyId:                     k,
+		PerformanceMode:              p,
+		ProvisionedThroughputInMibps: f,
+		ThroughputMode:               t,
+		Tags:                         efsTags,
 	})
 	})
 	if err != nil {
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	}
-	id := aws.StringValue(res.FileSystemId)
-	logrus.Debugf("Created file system %q", id)
-	return id, nil
+	return existingAWSResource{
+		id:  aws.StringValue(res.FileSystemId),
+		arn: aws.StringValue(res.FileSystemArn),
+	}, nil
 }
 }
 
 
 func (s sdk) DeleteFileSystem(ctx context.Context, id string) error {
 func (s sdk) DeleteFileSystem(ctx context.Context, id string) error {

+ 57 - 0
ecs/volumes.go

@@ -17,13 +17,17 @@
 package ecs
 package ecs
 
 
 import (
 import (
+	"context"
 	"fmt"
 	"fmt"
 
 
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/compose"
+	"github.com/docker/compose-cli/api/volumes"
+	"github.com/docker/compose-cli/errdefs"
 
 
 	"github.com/awslabs/goformation/v4/cloudformation"
 	"github.com/awslabs/goformation/v4/cloudformation"
 	"github.com/awslabs/goformation/v4/cloudformation/efs"
 	"github.com/awslabs/goformation/v4/cloudformation/efs"
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
+	"github.com/pkg/errors"
 )
 )
 
 
 func (b *ecsAPIService) createNFSMountTarget(project *types.Project, resources awsResources, template *cloudformation.Template) {
 func (b *ecsAPIService) createNFSMountTarget(project *types.Project, resources awsResources, template *cloudformation.Template) {
@@ -97,3 +101,56 @@ func (b *ecsAPIService) createAccessPoints(project *types.Project, r awsResource
 		template.Resources[n] = &ap
 		template.Resources[n] = &ap
 	}
 	}
 }
 }
+
+// VolumeCreateOptions hold EFS filesystem creation options
+type VolumeCreateOptions struct {
+	KmsKeyID                     string
+	PerformanceMode              string
+	ProvisionedThroughputInMibps float64
+	ThroughputMode               string
+}
+
+type ecsVolumeService struct {
+	backend *ecsAPIService
+}
+
+func (e ecsVolumeService) List(ctx context.Context) ([]volumes.Volume, error) {
+	filesystems, err := e.backend.aws.ListFileSystems(ctx, nil)
+	if err != nil {
+		return nil, err
+	}
+	var vol []volumes.Volume
+	for _, fs := range filesystems {
+		vol = append(vol, volumes.Volume{
+			ID:          fs.ID(),
+			Description: fs.ARN(),
+		})
+	}
+	return vol, nil
+}
+
+func (e ecsVolumeService) Create(ctx context.Context, name string, options interface{}) (volumes.Volume, error) {
+	fs, err := e.backend.aws.CreateFileSystem(ctx, map[string]string{
+		"Name": name,
+	}, options.(VolumeCreateOptions))
+	return volumes.Volume{
+		ID:          fs.ID(),
+		Description: fs.ARN(),
+	}, err
+
+}
+
+func (e ecsVolumeService) Delete(ctx context.Context, volumeID string, options interface{}) error {
+	return e.backend.aws.DeleteFileSystem(ctx, volumeID)
+}
+
+func (e ecsVolumeService) Inspect(ctx context.Context, volumeID string) (volumes.Volume, error) {
+	ok, err := e.backend.aws.ResolveFileSystem(ctx, volumeID)
+	if ok == nil {
+		err = errors.Wrapf(errdefs.ErrNotFound, "filesystem %q does not exists", volumeID)
+	}
+	return volumes.Volume{
+		ID:          volumeID,
+		Description: ok.ARN(),
+	}, err
+}

+ 0 - 0
resolve