mirror of
https://github.com/docker/compose.git
synced 2025-07-25 14:44:29 +02:00
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:
parent
f5f87e342a
commit
6fa30284d9
@ -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{
|
||||||
|
10
ecs/aws.go
10
ecs/aws.go
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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{}) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
@ -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(
|
||||||
|
37
ecs/iam.go
37
ecs/iam.go
@ -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"`
|
||||||
|
}
|
||||||
|
78
ecs/sdk.go
78
ecs/sdk.go
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user