Introduce use of EFS Access Point to mount filesystems as volumes

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-10-19 09:48:23 +02:00
parent f5f87e342a
commit 6fa30284d9
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
13 changed files with 356 additions and 131 deletions

View File

@ -62,7 +62,7 @@ func (b *ecsAPIService) createAutoscalingPolicy(project *types.Project, resource
} }
// Why isn't this just the service ARN ????? // Why isn't this just the service ARN ?????
resourceID := cloudformation.Join("/", []string{"service", resources.cluster, cloudformation.GetAtt(serviceResourceName(service.Name), "Name")}) resourceID := cloudformation.Join("/", []string{"service", resources.cluster.ID(), cloudformation.GetAtt(serviceResourceName(service.Name), "Name")})
target := fmt.Sprintf("%sScalableTarget", normalizeResourceName(service.Name)) target := fmt.Sprintf("%sScalableTarget", normalizeResourceName(service.Name))
template.Resources[target] = &applicationautoscaling.ScalableTarget{ template.Resources[target] = &applicationautoscaling.ScalableTarget{

View File

@ -35,11 +35,11 @@ const (
// API hides aws-go-sdk into a simpler, focussed API subset // API hides aws-go-sdk into a simpler, focussed API subset
type API interface { type API interface {
CheckRequirements(ctx context.Context, region string) error CheckRequirements(ctx context.Context, region string) error
ClusterExists(ctx context.Context, name string) (bool, error) ResolveCluster(ctx context.Context, nameOrArn string) (awsResource, error)
CreateCluster(ctx context.Context, name string) (string, error) CreateCluster(ctx context.Context, name string) (string, error)
CheckVPC(ctx context.Context, vpcID string) error CheckVPC(ctx context.Context, vpcID string) error
GetDefaultVPC(ctx context.Context) (string, error) GetDefaultVPC(ctx context.Context) (string, error)
GetSubNets(ctx context.Context, vpcID string) ([]string, error) GetSubNets(ctx context.Context, vpcID string) ([]awsResource, error)
GetRoleArn(ctx context.Context, name string) (string, error) GetRoleArn(ctx context.Context, name string) (string, error)
StackExists(ctx context.Context, name string) (bool, error) StackExists(ctx context.Context, name string) (bool, error)
CreateStack(ctx context.Context, name string, template []byte) error CreateStack(ctx context.Context, name string, template []byte) error
@ -66,14 +66,14 @@ type API interface {
getURLWithPortMapping(ctx context.Context, targetGroupArns []string) ([]compose.PortPublisher, error) getURLWithPortMapping(ctx context.Context, targetGroupArns []string) ([]compose.PortPublisher, error)
ListTasks(ctx context.Context, cluster string, family string) ([]string, error) ListTasks(ctx context.Context, cluster string, family string) ([]string, error)
GetPublicIPs(ctx context.Context, interfaces ...string) (map[string]string, error) GetPublicIPs(ctx context.Context, interfaces ...string) (map[string]string, error)
LoadBalancerType(ctx context.Context, arn string) (string, error) ResolveLoadBalancer(ctx context.Context, nameOrArn string) (awsResource, string, error)
GetLoadBalancerURL(ctx context.Context, arn string) (string, error) GetLoadBalancerURL(ctx context.Context, arn string) (string, error)
GetParameter(ctx context.Context, name string) (string, error) GetParameter(ctx context.Context, name string) (string, error)
SecurityGroupExists(ctx context.Context, sg string) (bool, error) SecurityGroupExists(ctx context.Context, sg string) (bool, error)
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
FileSystemExists(ctx context.Context, id string) (bool, error) ResolveFileSystem(ctx context.Context, id string) (awsResource, error)
FindFileSystem(ctx context.Context, tags map[string]string) (string, error) FindFileSystem(ctx context.Context, tags map[string]string) (awsResource, error)
CreateFileSystem(ctx context.Context, tags map[string]string) (string, error) CreateFileSystem(ctx context.Context, tags map[string]string) (string, error)
DeleteFileSystem(ctx context.Context, id string) error DeleteFileSystem(ctx context.Context, id string) error
} }

View File

@ -38,13 +38,13 @@ import (
// awsResources hold the AWS component being used or created to support services definition // awsResources hold the AWS component being used or created to support services definition
type awsResources struct { type awsResources struct {
vpc string vpc string // shouldn't this also be an awsResource ?
subnets []string subnets []awsResource
cluster string cluster awsResource
loadBalancer string loadBalancer awsResource
loadBalancerType string loadBalancerType string
securityGroups map[string]string securityGroups map[string]string
filesystems map[string]string filesystems map[string]awsResource
} }
func (r *awsResources) serviceSecurityGroups(service types.ServiceConfig) []string { func (r *awsResources) serviceSecurityGroups(service types.ServiceConfig) []string {
@ -63,6 +63,63 @@ func (r *awsResources) allSecurityGroups() []string {
return securityGroups return securityGroups
} }
func (r *awsResources) subnetsIDs() []string {
var ids []string
for _, r := range r.subnets {
ids = append(ids, r.ID())
}
return ids
}
// awsResource is abstract representation for any (existing or future) AWS resource that we can refer both by ID or full ARN
type awsResource interface {
ARN() string
ID() string
}
// existingAWSResource hold references to an existing AWS component
type existingAWSResource struct {
arn string
id string
}
func (r existingAWSResource) ARN() string {
return r.arn
}
func (r existingAWSResource) ID() string {
return r.id
}
// cloudformationResource hold references to a future AWS resource managed by CloudFormation
// to be used by CloudFormation resources where Ref returns the Amazon Resource ID
type cloudformationResource struct {
logicalName string
}
func (r cloudformationResource) ARN() string {
return cloudformation.GetAtt(r.logicalName, "Arn")
}
func (r cloudformationResource) ID() string {
return cloudformation.Ref(r.logicalName)
}
// cloudformationARNResource hold references to a future AWS resource managed by CloudFormation
// to be used by CloudFormation resources where Ref returns the Amazon Resource Name (ARN)
type cloudformationARNResource struct {
logicalName string
nameProperty string
}
func (r cloudformationARNResource) ARN() string {
return cloudformation.Ref(r.logicalName)
}
func (r cloudformationARNResource) ID() string {
return cloudformation.GetAtt(r.logicalName, r.nameProperty)
}
// parse look into compose project for configured resource to use, and check they are valid // parse look into compose project for configured resource to use, and check they are valid
func (b *ecsAPIService) parse(ctx context.Context, project *types.Project, template *cloudformation.Template) (awsResources, error) { func (b *ecsAPIService) parse(ctx context.Context, project *types.Project, template *cloudformation.Template) (awsResources, error) {
r := awsResources{} r := awsResources{}
@ -90,28 +147,28 @@ func (b *ecsAPIService) parse(ctx context.Context, project *types.Project, templ
return r, nil return r, nil
} }
func (b *ecsAPIService) parseClusterExtension(ctx context.Context, project *types.Project, template *cloudformation.Template) (string, error) { func (b *ecsAPIService) parseClusterExtension(ctx context.Context, project *types.Project, template *cloudformation.Template) (awsResource, error) {
if x, ok := project.Extensions[extensionCluster]; ok { if x, ok := project.Extensions[extensionCluster]; ok {
cluster := x.(string) nameOrArn := x.(string) // can be name _or_ ARN.
ok, err := b.aws.ClusterExists(ctx, cluster) cluster, err := b.aws.ResolveCluster(ctx, nameOrArn)
if err != nil { if err != nil {
return "", err return nil, err
} }
if !ok { if !ok {
return "", errors.Wrapf(errdefs.ErrNotFound, "cluster %q does not exist", cluster) return nil, errors.Wrapf(errdefs.ErrNotFound, "cluster %q does not exist", cluster)
} }
template.Metadata["Cluster"] = cluster template.Metadata["Cluster"] = cluster.ARN()
return cluster, nil return cluster, nil
} }
return "", nil return nil, nil
} }
func (b *ecsAPIService) parseVPCExtension(ctx context.Context, project *types.Project) (string, []string, error) { func (b *ecsAPIService) parseVPCExtension(ctx context.Context, project *types.Project) (string, []awsResource, error) {
var vpc string var vpc string
if x, ok := project.Extensions[extensionVPC]; ok { if x, ok := project.Extensions[extensionVPC]; ok {
vpc = x.(string) vpcID := x.(string)
err := b.aws.CheckVPC(ctx, vpc) err := b.aws.CheckVPC(ctx, vpcID)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -134,22 +191,22 @@ func (b *ecsAPIService) parseVPCExtension(ctx context.Context, project *types.Pr
return vpc, subNets, nil return vpc, subNets, nil
} }
func (b *ecsAPIService) parseLoadBalancerExtension(ctx context.Context, project *types.Project) (string, string, error) { func (b *ecsAPIService) parseLoadBalancerExtension(ctx context.Context, project *types.Project) (awsResource, string, error) {
if x, ok := project.Extensions[extensionLoadBalancer]; ok { if x, ok := project.Extensions[extensionLoadBalancer]; ok {
loadBalancer := x.(string) nameOrArn := x.(string)
loadBalancerType, err := b.aws.LoadBalancerType(ctx, loadBalancer) loadBalancer, loadBalancerType, err := b.aws.ResolveLoadBalancer(ctx, nameOrArn)
if err != nil { if err != nil {
return "", "", err return nil, "", err
} }
required := getRequiredLoadBalancerType(project) required := getRequiredLoadBalancerType(project)
if loadBalancerType != required { if loadBalancerType != required {
return "", "", fmt.Errorf("load balancer %s is of type %s, project require a %s", loadBalancer, loadBalancerType, required) return nil, "", fmt.Errorf("load balancer %q is of type %s, project require a %s", nameOrArn, loadBalancerType, required)
} }
return loadBalancer, loadBalancerType, nil return loadBalancer, loadBalancerType, err
} }
return "", "", nil return nil, "", nil
} }
func (b *ecsAPIService) parseExternalNetworks(ctx context.Context, project *types.Project) (map[string]string, error) { func (b *ecsAPIService) parseExternalNetworks(ctx context.Context, project *types.Project) (map[string]string, error) {
@ -179,18 +236,15 @@ func (b *ecsAPIService) parseExternalNetworks(ctx context.Context, project *type
return securityGroups, nil return securityGroups, nil
} }
func (b *ecsAPIService) parseExternalVolumes(ctx context.Context, project *types.Project) (map[string]string, error) { func (b *ecsAPIService) parseExternalVolumes(ctx context.Context, project *types.Project) (map[string]awsResource, error) {
filesystems := make(map[string]string, len(project.Volumes)) filesystems := make(map[string]awsResource, len(project.Volumes))
for name, vol := range project.Volumes { for name, vol := range project.Volumes {
if vol.External.External { if vol.External.External {
exists, err := b.aws.FileSystemExists(ctx, vol.Name) arn, err := b.aws.ResolveFileSystem(ctx, vol.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !exists { filesystems[name] = arn
return nil, errors.Wrapf(errdefs.ErrNotFound, "EFS file system %q doesn't exist", vol.Name)
}
filesystems[name] = vol.Name
continue continue
} }
@ -199,12 +253,12 @@ func (b *ecsAPIService) parseExternalVolumes(ctx context.Context, project *types
compose.ProjectTag: project.Name, compose.ProjectTag: project.Name,
compose.VolumeTag: name, compose.VolumeTag: name,
} }
id, err := b.aws.FindFileSystem(ctx, tags) fileSystem, err := b.aws.FindFileSystem(ctx, tags)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if id != "" { if fileSystem != nil {
filesystems[name] = id filesystems[name] = fileSystem
} }
} }
return filesystems, nil return filesystems, nil
@ -223,14 +277,14 @@ func (b *ecsAPIService) ensureResources(resources *awsResources, project *types.
} }
func (b *ecsAPIService) ensureCluster(r *awsResources, project *types.Project, template *cloudformation.Template) { func (b *ecsAPIService) ensureCluster(r *awsResources, project *types.Project, template *cloudformation.Template) {
if r.cluster != "" { if r.cluster != nil {
return return
} }
template.Resources["Cluster"] = &ecs.Cluster{ template.Resources["Cluster"] = &ecs.Cluster{
ClusterName: project.Name, ClusterName: project.Name,
Tags: projectTags(project), Tags: projectTags(project),
} }
r.cluster = cloudformation.Ref("Cluster") r.cluster = cloudformationResource{logicalName: "Cluster"}
} }
func (b *ecsAPIService) ensureNetworks(r *awsResources, project *types.Project, template *cloudformation.Template) { func (b *ecsAPIService) ensureNetworks(r *awsResources, project *types.Project, template *cloudformation.Template) {
@ -319,13 +373,13 @@ func (b *ecsAPIService) ensureVolumes(r *awsResources, project *types.Project, t
ThroughputMode: throughputMode, ThroughputMode: throughputMode,
AWSCloudFormationDeletionPolicy: "Retain", AWSCloudFormationDeletionPolicy: "Retain",
} }
r.filesystems[name] = cloudformation.Ref(n) r.filesystems[name] = cloudformationResource{logicalName: n}
} }
return nil return nil
} }
func (b *ecsAPIService) ensureLoadBalancer(r *awsResources, project *types.Project, template *cloudformation.Template) { func (b *ecsAPIService) ensureLoadBalancer(r *awsResources, project *types.Project, template *cloudformation.Template) {
if r.loadBalancer != "" { if r.loadBalancer != nil {
return return
} }
if allServices(project.Services, func(it types.ServiceConfig) bool { if allServices(project.Services, func(it types.ServiceConfig) bool {
@ -345,11 +399,14 @@ func (b *ecsAPIService) ensureLoadBalancer(r *awsResources, project *types.Proje
template.Resources["LoadBalancer"] = &elasticloadbalancingv2.LoadBalancer{ template.Resources["LoadBalancer"] = &elasticloadbalancingv2.LoadBalancer{
Scheme: elbv2.LoadBalancerSchemeEnumInternetFacing, Scheme: elbv2.LoadBalancerSchemeEnumInternetFacing,
SecurityGroups: securityGroups, SecurityGroups: securityGroups,
Subnets: r.subnets, Subnets: r.subnetsIDs(),
Tags: projectTags(project), Tags: projectTags(project),
Type: balancerType, Type: balancerType,
} }
r.loadBalancer = cloudformation.Ref("LoadBalancer") r.loadBalancer = cloudformationARNResource{
logicalName: "LoadBalancer",
nameProperty: "LoadBalancerName",
}
r.loadBalancerType = balancerType r.loadBalancerType = balancerType
} }

View File

@ -6,12 +6,13 @@ 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
@ -65,21 +66,6 @@ func (mr *MockAPIMockRecorder) CheckVPC(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckVPC", reflect.TypeOf((*MockAPI)(nil).CheckVPC), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckVPC", reflect.TypeOf((*MockAPI)(nil).CheckVPC), arg0, arg1)
} }
// ClusterExists mocks base method
func (m *MockAPI) ClusterExists(arg0 context.Context, arg1 string) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ClusterExists", arg0, arg1)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ClusterExists indicates an expected call of ClusterExists
func (mr *MockAPIMockRecorder) ClusterExists(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterExists", reflect.TypeOf((*MockAPI)(nil).ClusterExists), arg0, arg1)
}
// CreateChangeSet mocks base method // CreateChangeSet mocks base method
func (m *MockAPI) CreateChangeSet(arg0 context.Context, arg1 string, arg2 []byte) (string, error) { func (m *MockAPI) CreateChangeSet(arg0 context.Context, arg1 string, arg2 []byte) (string, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -254,26 +240,11 @@ 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)
} }
// FileSystemExists mocks base method
func (m *MockAPI) FileSystemExists(arg0 context.Context, arg1 string) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FileSystemExists", arg0, arg1)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FileSystemExists indicates an expected call of FileSystemExists
func (mr *MockAPIMockRecorder) FileSystemExists(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FileSystemExists", reflect.TypeOf((*MockAPI)(nil).FileSystemExists), arg0, arg1)
}
// FindFileSystem mocks base method // FindFileSystem mocks base method
func (m *MockAPI) FindFileSystem(arg0 context.Context, arg1 map[string]string) (string, error) { func (m *MockAPI) FindFileSystem(arg0 context.Context, arg1 map[string]string) (awsResource, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindFileSystem", arg0, arg1) ret := m.ctrl.Call(m, "FindFileSystem", arg0, arg1)
ret0, _ := ret[0].(string) ret0, _ := ret[0].(awsResource)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
@ -439,10 +410,10 @@ func (mr *MockAPIMockRecorder) GetStackID(arg0, arg1 interface{}) *gomock.Call {
} }
// GetSubNets mocks base method // GetSubNets mocks base method
func (m *MockAPI) GetSubNets(arg0 context.Context, arg1 string) ([]string, error) { func (m *MockAPI) GetSubNets(arg0 context.Context, arg1 string) ([]awsResource, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSubNets", arg0, arg1) ret := m.ctrl.Call(m, "GetSubNets", arg0, arg1)
ret0, _ := ret[0].([]string) ret0, _ := ret[0].([]awsResource)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
@ -573,19 +544,50 @@ func (mr *MockAPIMockRecorder) ListTasks(arg0, arg1, arg2 interface{}) *gomock.C
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTasks", reflect.TypeOf((*MockAPI)(nil).ListTasks), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTasks", reflect.TypeOf((*MockAPI)(nil).ListTasks), arg0, arg1, arg2)
} }
// LoadBalancerType mocks base method // ResolveCluster mocks base method
func (m *MockAPI) LoadBalancerType(arg0 context.Context, arg1 string) (string, error) { func (m *MockAPI) ResolveCluster(arg0 context.Context, arg1 string) (awsResource, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LoadBalancerType", arg0, arg1) ret := m.ctrl.Call(m, "ResolveCluster", arg0, arg1)
ret0, _ := ret[0].(string) ret0, _ := ret[0].(awsResource)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// LoadBalancerType indicates an expected call of LoadBalancerType // ResolveCluster indicates an expected call of ResolveCluster
func (mr *MockAPIMockRecorder) LoadBalancerType(arg0, arg1 interface{}) *gomock.Call { func (mr *MockAPIMockRecorder) ResolveCluster(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBalancerType", reflect.TypeOf((*MockAPI)(nil).LoadBalancerType), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveCluster", reflect.TypeOf((*MockAPI)(nil).ResolveCluster), arg0, arg1)
}
// ResolveFileSystem mocks base method
func (m *MockAPI) ResolveFileSystem(arg0 context.Context, arg1 string) (awsResource, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ResolveFileSystem", arg0, arg1)
ret0, _ := ret[0].(awsResource)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ResolveFileSystem indicates an expected call of ResolveFileSystem
func (mr *MockAPIMockRecorder) ResolveFileSystem(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveFileSystem", reflect.TypeOf((*MockAPI)(nil).ResolveFileSystem), arg0, arg1)
}
// ResolveLoadBalancer mocks base method
func (m *MockAPI) ResolveLoadBalancer(arg0 context.Context, arg1 string) (awsResource, string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ResolveLoadBalancer", arg0, arg1)
ret0, _ := ret[0].(awsResource)
ret1, _ := ret[1].(string)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ResolveLoadBalancer indicates an expected call of ResolveLoadBalancer
func (mr *MockAPIMockRecorder) ResolveLoadBalancer(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveLoadBalancer", reflect.TypeOf((*MockAPI)(nil).ResolveLoadBalancer), arg0, arg1)
} }
// SecurityGroupExists mocks base method // SecurityGroupExists mocks base method

View File

@ -19,9 +19,6 @@ package ecs
import ( import (
"context" "context"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/containers" "github.com/docker/compose-cli/api/containers"
"github.com/docker/compose-cli/api/resources" "github.com/docker/compose-cli/api/resources"
@ -32,6 +29,9 @@ import (
"github.com/docker/compose-cli/context/cloud" "github.com/docker/compose-cli/context/cloud"
"github.com/docker/compose-cli/context/store" "github.com/docker/compose-cli/context/store"
"github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/errdefs"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
) )
const backendType = store.EcsContextType const backendType = store.EcsContextType

View File

@ -77,6 +77,8 @@ func (b *ecsAPIService) convert(ctx context.Context, project *types.Project) (*c
b.createNFSMountTarget(project, resources, template) b.createNFSMountTarget(project, resources, template)
b.createAccessPoints(project, resources, template)
for _, service := range project.Services { for _, service := range project.Services {
err := b.createService(project, service, template, resources) err := b.createService(project, service, template, resources)
if err != nil { if err != nil {
@ -96,7 +98,7 @@ func (b *ecsAPIService) convert(ctx context.Context, project *types.Project) (*c
func (b *ecsAPIService) createService(project *types.Project, service types.ServiceConfig, template *cloudformation.Template, resources awsResources) error { func (b *ecsAPIService) createService(project *types.Project, service types.ServiceConfig, template *cloudformation.Template, resources awsResources) error {
taskExecutionRole := b.createTaskExecutionRole(project, service, template) taskExecutionRole := b.createTaskExecutionRole(project, service, template)
taskRole := b.createTaskRole(project, service, template) taskRole := b.createTaskRole(project, service, template, resources)
definition, err := b.createTaskDefinition(project, service, resources) definition, err := b.createTaskDefinition(project, service, resources)
if err != nil { if err != nil {
@ -166,7 +168,7 @@ func (b *ecsAPIService) createService(project *types.Project, service types.Serv
template.Resources[serviceResourceName(service.Name)] = &ecs.Service{ template.Resources[serviceResourceName(service.Name)] = &ecs.Service{
AWSCloudFormationDependsOn: dependsOn, AWSCloudFormationDependsOn: dependsOn,
Cluster: resources.cluster, Cluster: resources.cluster.ARN(),
DesiredCount: desiredCount, DesiredCount: desiredCount,
DeploymentController: &ecs.Service_DeploymentController{ DeploymentController: &ecs.Service_DeploymentController{
Type: ecsapi.DeploymentControllerTypeEcs, Type: ecsapi.DeploymentControllerTypeEcs,
@ -182,7 +184,7 @@ func (b *ecsAPIService) createService(project *types.Project, service types.Serv
AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{ AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
AssignPublicIp: assignPublicIP, AssignPublicIp: assignPublicIP,
SecurityGroups: resources.serviceSecurityGroups(service), SecurityGroups: resources.serviceSecurityGroups(service),
Subnets: resources.subnets, Subnets: resources.subnetsIDs(),
}, },
}, },
PlatformVersion: platformVersion, PlatformVersion: platformVersion,
@ -287,7 +289,7 @@ func computeRollingUpdateLimits(service types.ServiceConfig) (int, int, error) {
func (b *ecsAPIService) createListener(service types.ServiceConfig, port types.ServicePortConfig, func (b *ecsAPIService) createListener(service types.ServiceConfig, port types.ServicePortConfig,
template *cloudformation.Template, template *cloudformation.Template,
targetGroupName string, loadBalancerARN string, protocol string) string { targetGroupName string, loadBalancer awsResource, protocol string) string {
listenerName := fmt.Sprintf( listenerName := fmt.Sprintf(
"%s%s%dListener", "%s%s%dListener",
normalizeResourceName(service.Name), normalizeResourceName(service.Name),
@ -309,7 +311,7 @@ func (b *ecsAPIService) createListener(service types.ServiceConfig, port types.S
Type: elbv2.ActionTypeEnumForward, Type: elbv2.ActionTypeEnumForward,
}, },
}, },
LoadBalancerArn: loadBalancerARN, LoadBalancerArn: loadBalancer.ARN(),
Protocol: protocol, Protocol: protocol,
Port: int(port.Target), Port: int(port.Target),
} }
@ -375,14 +377,21 @@ func (b *ecsAPIService) createTaskExecutionRole(project *types.Project, service
return taskExecutionRole return taskExecutionRole
} }
func (b *ecsAPIService) createTaskRole(project *types.Project, service types.ServiceConfig, template *cloudformation.Template) string { func (b *ecsAPIService) createTaskRole(project *types.Project, service types.ServiceConfig, template *cloudformation.Template, resources awsResources) string {
taskRole := fmt.Sprintf("%sTaskRole", normalizeResourceName(service.Name)) taskRole := fmt.Sprintf("%sTaskRole", normalizeResourceName(service.Name))
rolePolicies := []iam.Role_Policy{} rolePolicies := []iam.Role_Policy{}
if roles, ok := service.Extensions[extensionRole]; ok { if roles, ok := service.Extensions[extensionRole]; ok {
rolePolicies = append(rolePolicies, iam.Role_Policy{ rolePolicies = append(rolePolicies, iam.Role_Policy{
PolicyName: fmt.Sprintf("%s%sPolicy", normalizeResourceName(project.Name), normalizeResourceName(service.Name)),
PolicyDocument: roles, PolicyDocument: roles,
}) })
} }
for _, vol := range service.Volumes {
rolePolicies = append(rolePolicies, iam.Role_Policy{
PolicyName: fmt.Sprintf("%s%sVolumeMountPolicy", normalizeResourceName(project.Name), normalizeResourceName(service.Name)),
PolicyDocument: volumeMountPolicyDocument(vol.Source, resources.filesystems[vol.Source].ARN()),
})
}
managedPolicies := []string{} managedPolicies := []string{}
if v, ok := service.Extensions[extensionManagedPolicies]; ok { if v, ok := service.Extensions[extensionManagedPolicies]; ok {
for _, s := range v.([]interface{}) { for _, s := range v.([]interface{}) {

View File

@ -367,7 +367,7 @@ volumes:
external: true external: true
name: fs-123abc name: fs-123abc
`, useDefaultVPC, func(m *MockAPIMockRecorder) { `, useDefaultVPC, func(m *MockAPIMockRecorder) {
m.FileSystemExists(gomock.Any(), "fs-123abc").Return(true, nil) m.ResolveFileSystem(gomock.Any(), "fs-123abc").Return(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)
@ -395,7 +395,7 @@ volumes:
m.FindFileSystem(gomock.Any(), map[string]string{ m.FindFileSystem(gomock.Any(), map[string]string{
compose.ProjectTag: t.Name(), compose.ProjectTag: t.Name(),
compose.VolumeTag: "db-data", compose.VolumeTag: "db-data",
}).Return("", nil) }).Return(nil, nil)
}) })
n := volumeResourceName("db-data") n := volumeResourceName("db-data")
f := template.Resources[n].(*efs.FileSystem) f := template.Resources[n].(*efs.FileSystem)
@ -411,6 +411,25 @@ volumes:
assert.Equal(t, s.FileSystemId, cloudformation.Ref(n)) //nolint:staticcheck assert.Equal(t, s.FileSystemId, cloudformation.Ref(n)) //nolint:staticcheck
} }
func TestCreateAccessPoint(t *testing.T) {
template := convertYaml(t, `
services:
test:
image: nginx
volumes:
db-data:
driver_opts:
uid: 1002
gid: 1002
`, useDefaultVPC, func(m *MockAPIMockRecorder) {
m.FindFileSystem(gomock.Any(), gomock.Any()).Return(nil, nil)
})
a := template.Resources["DbdataAccessPoint"].(*efs.AccessPoint)
assert.Check(t, a != nil)
assert.Equal(t, a.PosixUser.Uid, "1002") //nolint:staticcheck
assert.Equal(t, a.PosixUser.Gid, "1002") //nolint:staticcheck
}
func TestReusePreviousVolume(t *testing.T) { func TestReusePreviousVolume(t *testing.T) {
template := convertYaml(t, ` template := convertYaml(t, `
services: services:
@ -422,7 +441,7 @@ volumes:
m.FindFileSystem(gomock.Any(), map[string]string{ m.FindFileSystem(gomock.Any(), map[string]string{
compose.ProjectTag: t.Name(), compose.ProjectTag: t.Name(),
compose.VolumeTag: "db-data", compose.VolumeTag: "db-data",
}).Return("fs-123abc", nil) }).Return(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)
@ -499,7 +518,10 @@ services:
test: test:
image: nginx image: nginx
`, useDefaultVPC, func(m *MockAPIMockRecorder) { `, useDefaultVPC, func(m *MockAPIMockRecorder) {
m.ClusterExists(gomock.Any(), "arn:aws:ecs:region:account:cluster/name").Return(true, nil) m.ResolveCluster(gomock.Any(), "arn:aws:ecs:region:account:cluster/name").Return(existingAWSResource{
arn: "arn:aws:ecs:region:account:cluster/name",
id: "name",
}, nil)
}) })
assert.Equal(t, template.Metadata["Cluster"], "arn:aws:ecs:region:account:cluster/name") assert.Equal(t, template.Metadata["Cluster"], "arn:aws:ecs:region:account:cluster/name")
} }
@ -548,7 +570,10 @@ func getMainContainer(def *ecs.TaskDefinition, t *testing.T) ecs.TaskDefinition_
func useDefaultVPC(m *MockAPIMockRecorder) { func useDefaultVPC(m *MockAPIMockRecorder) {
m.GetDefaultVPC(gomock.Any()).Return("vpc-123", nil) m.GetDefaultVPC(gomock.Any()).Return("vpc-123", nil)
m.GetSubNets(gomock.Any(), "vpc-123").Return([]string{"subnet1", "subnet2"}, nil) m.GetSubNets(gomock.Any(), "vpc-123").Return([]awsResource{
existingAWSResource{id: "subnet1"},
existingAWSResource{id: "subnet2"},
}, nil)
} }
func useGPU(m *MockAPIMockRecorder) { func useGPU(m *MockAPIMockRecorder) {

View File

@ -81,11 +81,15 @@ func (b *ecsAPIService) createTaskDefinition(project *types.Project, service typ
} }
for _, v := range service.Volumes { for _, v := range service.Volumes {
volume := project.Volumes[v.Source] n := fmt.Sprintf("%sAccessPoint", normalizeResourceName(v.Source))
volumes = append(volumes, ecs.TaskDefinition_Volume{ volumes = append(volumes, ecs.TaskDefinition_Volume{
EFSVolumeConfiguration: &ecs.TaskDefinition_EFSVolumeConfiguration{ EFSVolumeConfiguration: &ecs.TaskDefinition_EFSVolumeConfiguration{
FilesystemId: resources.filesystems[v.Source], AuthorizationConfig: &ecs.TaskDefinition_AuthorizationConfig{
RootDirectory: volume.DriverOpts["root_directory"], AccessPointId: cloudformation.Ref(n),
IAM: "ENABLED",
},
FilesystemId: resources.filesystems[v.Source].ID(),
TransitEncryption: "ENABLED",
}, },
Name: v.Source, Name: v.Source,
}) })

View File

@ -65,7 +65,7 @@ func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *typ
LaunchConfigurationName: cloudformation.Ref("LaunchConfiguration"), LaunchConfigurationName: cloudformation.Ref("LaunchConfiguration"),
MaxSize: "10", //TODO MaxSize: "10", //TODO
MinSize: "1", MinSize: "1",
VPCZoneIdentifier: resources.subnets, VPCZoneIdentifier: resources.subnetsIDs(),
} }
userData := base64.StdEncoding.EncodeToString([]byte( userData := base64.StdEncoding.EncodeToString([]byte(

View File

@ -16,6 +16,12 @@
package ecs package ecs
import (
"fmt"
"github.com/awslabs/goformation/v4/cloudformation"
)
const ( const (
ecsTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" ecsTaskExecutionPolicy = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
ecrReadOnlyPolicy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" ecrReadOnlyPolicy = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
@ -49,7 +55,31 @@ func policyDocument(service string) PolicyDocument {
}, },
}, },
} }
}
func volumeMountPolicyDocument(volume string, filesystem string) PolicyDocument {
ap := fmt.Sprintf("%sAccessPoint", normalizeResourceName(volume))
return PolicyDocument{
Version: "2012-10-17", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
Statement: []PolicyStatement{
{
Effect: "Allow",
Resource: []string{
filesystem,
},
Action: []string{
"elasticfilesystem:ClientMount",
"elasticfilesystem:ClientWrite",
"elasticfilesystem:ClientRootAccess",
},
Condition: Condition{
StringEquals: map[string]string{
"elasticfilesystem:AccessPointArn": cloudformation.Ref(ap),
},
},
},
},
}
} }
// PolicyDocument describes an IAM policy document // PolicyDocument describes an IAM policy document
@ -65,9 +95,16 @@ type PolicyStatement struct {
Action []string `json:",omitempty"` Action []string `json:",omitempty"`
Principal PolicyPrincipal `json:",omitempty"` Principal PolicyPrincipal `json:",omitempty"`
Resource []string `json:",omitempty"` Resource []string `json:",omitempty"`
Condition Condition `json:",omitempty"`
} }
// PolicyPrincipal describes an IAM policy principal // PolicyPrincipal describes an IAM policy principal
type PolicyPrincipal struct { type PolicyPrincipal struct {
Service string `json:",omitempty"` Service string `json:",omitempty"`
} }
// Condition is the map of all conditions in the statement entry.
type Condition struct {
StringEquals map[string]string `json:",omitempty"`
Bool map[string]string `json:",omitempty"`
}

View File

@ -29,6 +29,7 @@ import (
"github.com/docker/compose-cli/internal" "github.com/docker/compose-cli/internal"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/autoscaling"
@ -107,15 +108,22 @@ func (s sdk) CheckRequirements(ctx context.Context, region string) error {
return nil return nil
} }
func (s sdk) ClusterExists(ctx context.Context, name string) (bool, error) { func (s sdk) ResolveCluster(ctx context.Context, nameOrArn string) (awsResource, error) {
logrus.Debug("CheckRequirements if cluster was already created: ", name) logrus.Debug("CheckRequirements if cluster was already created: ", nameOrArn)
clusters, err := s.ECS.DescribeClustersWithContext(ctx, &ecs.DescribeClustersInput{ clusters, err := s.ECS.DescribeClustersWithContext(ctx, &ecs.DescribeClustersInput{
Clusters: []*string{aws.String(name)}, Clusters: []*string{aws.String(nameOrArn)},
}) })
if err != nil { if err != nil {
return false, err return nil, err
} }
return len(clusters.Clusters) > 0, nil if len(clusters.Clusters) == 0 {
return nil, errors.Wrapf(errdefs.ErrNotFound, "cluster %q does not exist", nameOrArn)
}
it := clusters.Clusters[0]
return existingAWSResource{
arn: aws.StringValue(it.ClusterArn),
id: aws.StringValue(it.ClusterName),
}, nil
} }
func (s sdk) CreateCluster(ctx context.Context, name string) (string, error) { func (s sdk) CreateCluster(ctx context.Context, name string) (string, error) {
@ -139,7 +147,7 @@ func (s sdk) CheckVPC(ctx context.Context, vpcID string) error {
if !*output.EnableDnsSupport.Value { if !*output.EnableDnsSupport.Value {
return fmt.Errorf("VPC %q doesn't have DNS resolution enabled", vpcID) return fmt.Errorf("VPC %q doesn't have DNS resolution enabled", vpcID)
} }
return err return nil
} }
func (s sdk) GetDefaultVPC(ctx context.Context) (string, error) { func (s sdk) GetDefaultVPC(ctx context.Context) (string, error) {
@ -161,7 +169,7 @@ func (s sdk) GetDefaultVPC(ctx context.Context) (string, error) {
return *vpcs.Vpcs[0].VpcId, nil return *vpcs.Vpcs[0].VpcId, nil
} }
func (s sdk) GetSubNets(ctx context.Context, vpcID string) ([]string, error) { func (s sdk) GetSubNets(ctx context.Context, vpcID string) ([]awsResource, error) {
logrus.Debug("Retrieve SubNets") logrus.Debug("Retrieve SubNets")
subnets, err := s.EC2.DescribeSubnetsWithContext(ctx, &ec2.DescribeSubnetsInput{ subnets, err := s.EC2.DescribeSubnetsWithContext(ctx, &ec2.DescribeSubnetsInput{
DryRun: nil, DryRun: nil,
@ -176,9 +184,12 @@ func (s sdk) GetSubNets(ctx context.Context, vpcID string) ([]string, error) {
return nil, err return nil, err
} }
ids := []string{} ids := []awsResource{}
for _, subnet := range subnets.Subnets { for _, subnet := range subnets.Subnets {
ids = append(ids, *subnet.SubnetId) ids = append(ids, existingAWSResource{
arn: aws.StringValue(subnet.SubnetArn),
id: aws.StringValue(subnet.SubnetId),
})
} }
return ids, nil return ids, nil
} }
@ -785,18 +796,31 @@ func (s sdk) GetPublicIPs(ctx context.Context, interfaces ...string) (map[string
return publicIPs, nil return publicIPs, nil
} }
func (s sdk) LoadBalancerType(ctx context.Context, arn string) (string, error) { func (s sdk) ResolveLoadBalancer(ctx context.Context, nameOrarn string) (awsResource, string, error) {
logrus.Debug("Check if LoadBalancer exists: ", arn) logrus.Debug("Check if LoadBalancer exists: ", nameOrarn)
var arns []*string
var names []*string
if arn.IsARN(nameOrarn) {
arns = append(arns, aws.String(nameOrarn))
} else {
names = append(names, aws.String(nameOrarn))
}
lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{ lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
LoadBalancerArns: []*string{aws.String(arn)}, LoadBalancerArns: arns,
Names: names,
}) })
if err != nil { if err != nil {
return "", err return nil, "", err
} }
if len(lbs.LoadBalancers) == 0 { if len(lbs.LoadBalancers) == 0 {
return "", fmt.Errorf("load balancer does not exist: %s", arn) return nil, "", errors.Wrapf(errdefs.ErrNotFound, "load balancer %q does not exist", nameOrarn)
} }
return aws.StringValue(lbs.LoadBalancers[0].Type), nil it := lbs.LoadBalancers[0]
return existingAWSResource{
arn: aws.StringValue(it.LoadBalancerArn),
id: aws.StringValue(it.LoadBalancerName),
}, aws.StringValue(it.Type), nil
} }
func (s sdk) GetLoadBalancerURL(ctx context.Context, arn string) (string, error) { func (s sdk) GetLoadBalancerURL(ctx context.Context, arn string) (string, error) {
@ -864,32 +888,42 @@ func (s sdk) DeleteAutoscalingGroup(ctx context.Context, arn string) error {
return err return err
} }
func (s sdk) FileSystemExists(ctx context.Context, id string) (bool, error) { func (s sdk) ResolveFileSystem(ctx context.Context, id string) (awsResource, error) {
desc, err := s.EFS.DescribeFileSystemsWithContext(ctx, &efs.DescribeFileSystemsInput{ desc, err := s.EFS.DescribeFileSystemsWithContext(ctx, &efs.DescribeFileSystemsInput{
FileSystemId: aws.String(id), FileSystemId: aws.String(id),
}) })
if err != nil { if err != nil {
return false, err return nil, err
} }
return len(desc.FileSystems) > 0, nil if len(desc.FileSystems) == 0 {
return nil, errors.Wrapf(errdefs.ErrNotFound, "EFS file system %q doesn't exist", id)
}
it := desc.FileSystems[0]
return existingAWSResource{
arn: aws.StringValue(it.FileSystemArn),
id: aws.StringValue(it.FileSystemId),
}, nil
} }
func (s sdk) FindFileSystem(ctx context.Context, tags map[string]string) (string, error) { func (s sdk) FindFileSystem(ctx context.Context, tags map[string]string) (awsResource, error) {
var token *string var token *string
for { for {
desc, err := s.EFS.DescribeFileSystemsWithContext(ctx, &efs.DescribeFileSystemsInput{ desc, err := s.EFS.DescribeFileSystemsWithContext(ctx, &efs.DescribeFileSystemsInput{
Marker: token, Marker: token,
}) })
if err != nil { if err != nil {
return "", err return nil, err
} }
for _, filesystem := range desc.FileSystems { for _, filesystem := range desc.FileSystems {
if containsAll(filesystem.Tags, tags) { if containsAll(filesystem.Tags, tags) {
return aws.StringValue(filesystem.FileSystemId), nil return existingAWSResource{
arn: aws.StringValue(filesystem.FileSystemArn),
id: aws.StringValue(filesystem.FileSystemId),
}, nil
} }
} }
if desc.NextMarker == token { if desc.NextMarker == token {
return "", nil return nil, nil
} }
token = desc.NextMarker token = desc.NextMarker
} }

View File

@ -98,7 +98,10 @@
], ],
"Properties": { "Properties": {
"Cluster": { "Cluster": {
"Ref": "Cluster" "Fn::GetAtt": [
"Cluster",
"Arn"
]
}, },
"DeploymentConfiguration": { "DeploymentConfiguration": {
"MaximumPercent": 200, "MaximumPercent": 200,
@ -299,6 +302,7 @@
"Action": [ "Action": [
"sts:AssumeRole" "sts:AssumeRole"
], ],
"Condition": {},
"Effect": "Allow", "Effect": "Allow",
"Principal": { "Principal": {
"Service": "ecs-tasks.amazonaws.com" "Service": "ecs-tasks.amazonaws.com"

View File

@ -19,6 +19,8 @@ package ecs
import ( import (
"fmt" "fmt"
"github.com/docker/compose-cli/api/compose"
"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"
@ -27,11 +29,11 @@ import (
func (b *ecsAPIService) createNFSMountTarget(project *types.Project, resources awsResources, template *cloudformation.Template) { func (b *ecsAPIService) createNFSMountTarget(project *types.Project, resources awsResources, template *cloudformation.Template) {
for volume := range project.Volumes { for volume := range project.Volumes {
for _, subnet := range resources.subnets { for _, subnet := range resources.subnets {
name := fmt.Sprintf("%sNFSMountTargetOn%s", normalizeResourceName(volume), normalizeResourceName(subnet)) name := fmt.Sprintf("%sNFSMountTargetOn%s", normalizeResourceName(volume), normalizeResourceName(subnet.ID()))
template.Resources[name] = &efs.MountTarget{ template.Resources[name] = &efs.MountTarget{
FileSystemId: resources.filesystems[volume], FileSystemId: resources.filesystems[volume].ID(),
SecurityGroups: resources.allSecurityGroups(), SecurityGroups: resources.allSecurityGroups(),
SubnetId: subnet, SubnetId: subnet.ID(),
} }
} }
} }
@ -40,7 +42,58 @@ func (b *ecsAPIService) createNFSMountTarget(project *types.Project, resources a
func (b *ecsAPIService) mountTargets(volume string, resources awsResources) []string { func (b *ecsAPIService) mountTargets(volume string, resources awsResources) []string {
var refs []string var refs []string
for _, subnet := range resources.subnets { for _, subnet := range resources.subnets {
refs = append(refs, fmt.Sprintf("%sNFSMountTargetOn%s", normalizeResourceName(volume), normalizeResourceName(subnet))) refs = append(refs, fmt.Sprintf("%sNFSMountTargetOn%s", normalizeResourceName(volume), normalizeResourceName(subnet.ID())))
} }
return refs return refs
} }
func (b *ecsAPIService) createAccessPoints(project *types.Project, r awsResources, template *cloudformation.Template) {
for name, volume := range project.Volumes {
n := fmt.Sprintf("%sAccessPoint", normalizeResourceName(name))
uid := volume.DriverOpts["uid"]
gid := volume.DriverOpts["gid"]
permissions := volume.DriverOpts["permissions"]
path := volume.DriverOpts["root_directory"]
ap := efs.AccessPoint{
AccessPointTags: []efs.AccessPoint_AccessPointTag{
{
Key: compose.ProjectTag,
Value: project.Name,
},
{
Key: compose.VolumeTag,
Value: name,
},
{
Key: "Name",
Value: fmt.Sprintf("%s_%s", project.Name, name),
},
},
FileSystemId: r.filesystems[name].ID(),
}
if uid != "" {
ap.PosixUser = &efs.AccessPoint_PosixUser{
Uid: uid,
Gid: gid,
}
}
if path != "" {
root := efs.AccessPoint_RootDirectory{
Path: path,
}
ap.RootDirectory = &root
if uid != "" {
root.CreationInfo = &efs.AccessPoint_CreationInfo{
OwnerUid: uid,
OwnerGid: gid,
Permissions: permissions,
}
}
}
template.Resources[n] = &ap
}
}