mirror of https://github.com/docker/compose.git
				
				
				
			introduce awsResources to replace CloudFormation parameters
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
		
							parent
							
								
									b30cd649b9
								
							
						
					
					
						commit
						7034254911
					
				| 
						 | 
				
			
			@ -0,0 +1,278 @@
 | 
			
		|||
/*
 | 
			
		||||
   Copyright 2020 Docker Compose CLI authors
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package ecs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/aws/aws-sdk-go/service/elbv2"
 | 
			
		||||
	"github.com/awslabs/goformation/v4/cloudformation/ec2"
 | 
			
		||||
	"github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2"
 | 
			
		||||
 | 
			
		||||
	"github.com/awslabs/goformation/v4/cloudformation"
 | 
			
		||||
	"github.com/awslabs/goformation/v4/cloudformation/ecs"
 | 
			
		||||
	"github.com/compose-spec/compose-go/types"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// awsResources hold the AWS component being used or created to support services definition
 | 
			
		||||
type awsResources struct {
 | 
			
		||||
	sdk              sdk
 | 
			
		||||
	vpc              string
 | 
			
		||||
	subnets          []string
 | 
			
		||||
	cluster          string
 | 
			
		||||
	loadBalancer     string
 | 
			
		||||
	loadBalancerType string
 | 
			
		||||
	securityGroups   map[string]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *awsResources) serviceSecurityGroups(service types.ServiceConfig) []string {
 | 
			
		||||
	var groups []string
 | 
			
		||||
	for net := range service.Networks {
 | 
			
		||||
		groups = append(groups, r.securityGroups[net])
 | 
			
		||||
	}
 | 
			
		||||
	return groups
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *awsResources) allSecurityGroups() []string {
 | 
			
		||||
	var securityGroups []string
 | 
			
		||||
	for _, r := range r.securityGroups {
 | 
			
		||||
		securityGroups = append(securityGroups, r)
 | 
			
		||||
	}
 | 
			
		||||
	return securityGroups
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parse look into compose project for configured resource to use, and check they are valid
 | 
			
		||||
func (r *awsResources) parse(ctx context.Context, project *types.Project) error {
 | 
			
		||||
	return findProjectFnError(ctx, project,
 | 
			
		||||
		r.parseClusterExtension,
 | 
			
		||||
		r.parseVPCExtension,
 | 
			
		||||
		r.parseLoadBalancerExtension,
 | 
			
		||||
		r.parseSecurityGroupExtension,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *awsResources) parseClusterExtension(ctx context.Context, project *types.Project) error {
 | 
			
		||||
	if x, ok := project.Extensions[extensionCluster]; ok {
 | 
			
		||||
		cluster := x.(string)
 | 
			
		||||
		ok, err := r.sdk.ClusterExists(ctx, cluster)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return fmt.Errorf("cluster does not exist: %s", cluster)
 | 
			
		||||
		}
 | 
			
		||||
		r.cluster = cluster
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *awsResources) parseVPCExtension(ctx context.Context, project *types.Project) error {
 | 
			
		||||
	if x, ok := project.Extensions[extensionVPC]; ok {
 | 
			
		||||
		vpc := x.(string)
 | 
			
		||||
		err := r.sdk.CheckVPC(ctx, vpc)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		r.vpc = vpc
 | 
			
		||||
	} else {
 | 
			
		||||
		defaultVPC, err := r.sdk.GetDefaultVPC(ctx)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		r.vpc = defaultVPC
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	subNets, err := r.sdk.GetSubNets(ctx, r.vpc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if len(subNets) < 2 {
 | 
			
		||||
		return fmt.Errorf("VPC %s should have at least 2 associated subnets in different availability zones", r.vpc)
 | 
			
		||||
	}
 | 
			
		||||
	r.subnets = subNets
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *awsResources) parseLoadBalancerExtension(ctx context.Context, project *types.Project) error {
 | 
			
		||||
	if x, ok := project.Extensions[extensionLoadBalancer]; ok {
 | 
			
		||||
		loadBalancer := x.(string)
 | 
			
		||||
		loadBalancerType, err := r.sdk.LoadBalancerType(ctx, loadBalancer)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		required := getRequiredLoadBalancerType(project)
 | 
			
		||||
		if loadBalancerType != required {
 | 
			
		||||
			return fmt.Errorf("load balancer %s is of type %s, project require a %s", loadBalancer, loadBalancerType, required)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r.loadBalancer = loadBalancer
 | 
			
		||||
		r.loadBalancerType = loadBalancerType
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *awsResources) parseSecurityGroupExtension(ctx context.Context, project *types.Project) error {
 | 
			
		||||
	if r.securityGroups == nil {
 | 
			
		||||
		r.securityGroups = make(map[string]string, len(project.Networks))
 | 
			
		||||
	}
 | 
			
		||||
	for name, net := range project.Networks {
 | 
			
		||||
		if net.External.External {
 | 
			
		||||
			r.securityGroups[name] = net.Name
 | 
			
		||||
		}
 | 
			
		||||
		if x, ok := net.Extensions[extensionSecurityGroup]; ok {
 | 
			
		||||
			logrus.Warn("to use an existing security-group, use `network.external` and `network.name` in your compose file")
 | 
			
		||||
			logrus.Debugf("Security Group for network %q set by user to %q", net.Name, x)
 | 
			
		||||
			r.securityGroups[name] = x.(string)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ensure all required resources pre-exists or are defined as cloudformation resources
 | 
			
		||||
func (r *awsResources) ensure(project *types.Project, template *cloudformation.Template) {
 | 
			
		||||
	r.ensureCluster(project, template)
 | 
			
		||||
	r.ensureNetworks(project, template)
 | 
			
		||||
	r.ensureLoadBalancer(project, template)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *awsResources) ensureCluster(project *types.Project, template *cloudformation.Template) {
 | 
			
		||||
	if r.cluster != "" {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	template.Resources["Cluster"] = &ecs.Cluster{
 | 
			
		||||
		ClusterName: project.Name,
 | 
			
		||||
		Tags:        projectTags(project),
 | 
			
		||||
	}
 | 
			
		||||
	r.cluster = cloudformation.Ref("Cluster")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *awsResources) ensureNetworks(project *types.Project, template *cloudformation.Template) {
 | 
			
		||||
	if r.securityGroups == nil {
 | 
			
		||||
		r.securityGroups = make(map[string]string, len(project.Networks))
 | 
			
		||||
	}
 | 
			
		||||
	for name, net := range project.Networks {
 | 
			
		||||
		securityGroup := networkResourceName(name)
 | 
			
		||||
		template.Resources[securityGroup] = &ec2.SecurityGroup{
 | 
			
		||||
			GroupDescription: fmt.Sprintf("%s Security Group for %s network", project.Name, name),
 | 
			
		||||
			GroupName:        securityGroup,
 | 
			
		||||
			VpcId:            r.vpc,
 | 
			
		||||
			Tags:             networkTags(project, net),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ingress := securityGroup + "Ingress"
 | 
			
		||||
		template.Resources[ingress] = &ec2.SecurityGroupIngress{
 | 
			
		||||
			Description:           fmt.Sprintf("Allow communication within network %s", name),
 | 
			
		||||
			IpProtocol:            "-1", // all protocols
 | 
			
		||||
			GroupId:               cloudformation.Ref(securityGroup),
 | 
			
		||||
			SourceSecurityGroupId: cloudformation.Ref(securityGroup),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r.securityGroups[name] = cloudformation.Ref(securityGroup)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *awsResources) ensureLoadBalancer(project *types.Project, template *cloudformation.Template) {
 | 
			
		||||
	if r.loadBalancer != "" {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if allServices(project.Services, func(it types.ServiceConfig) bool {
 | 
			
		||||
		return len(it.Ports) == 0
 | 
			
		||||
	}) {
 | 
			
		||||
		logrus.Debug("Application does not expose any public port, so no need for a LoadBalancer")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	balancerType := getRequiredLoadBalancerType(project)
 | 
			
		||||
	template.Resources["LoadBalancer"] = &elasticloadbalancingv2.LoadBalancer{
 | 
			
		||||
		Scheme:         elbv2.LoadBalancerSchemeEnumInternetFacing,
 | 
			
		||||
		SecurityGroups: r.getLoadBalancerSecurityGroups(project),
 | 
			
		||||
		Subnets:        r.subnets,
 | 
			
		||||
		Tags:           projectTags(project),
 | 
			
		||||
		Type:           balancerType,
 | 
			
		||||
	}
 | 
			
		||||
	r.loadBalancer = cloudformation.Ref("LoadBalancer")
 | 
			
		||||
	r.loadBalancerType = balancerType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *awsResources) getLoadBalancerSecurityGroups(project *types.Project) []string {
 | 
			
		||||
	securityGroups := []string{}
 | 
			
		||||
	for name, network := range project.Networks {
 | 
			
		||||
		if !network.Internal {
 | 
			
		||||
			securityGroups = append(securityGroups, r.securityGroups[name])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return securityGroups
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getRequiredLoadBalancerType(project *types.Project) string {
 | 
			
		||||
	loadBalancerType := elbv2.LoadBalancerTypeEnumNetwork
 | 
			
		||||
	if allServices(project.Services, func(it types.ServiceConfig) bool {
 | 
			
		||||
		return allPorts(it.Ports, portIsHTTP)
 | 
			
		||||
	}) {
 | 
			
		||||
		loadBalancerType = elbv2.LoadBalancerTypeEnumApplication
 | 
			
		||||
	}
 | 
			
		||||
	return loadBalancerType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func portIsHTTP(it types.ServicePortConfig) bool {
 | 
			
		||||
	if v, ok := it.Extensions[extensionProtocol]; ok {
 | 
			
		||||
		protocol := v.(string)
 | 
			
		||||
		return protocol == "http" || protocol == "https"
 | 
			
		||||
	}
 | 
			
		||||
	return it.Target == 80 || it.Target == 443
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type projectFn func(ctx context.Context, project *types.Project) error
 | 
			
		||||
 | 
			
		||||
func findProjectFnError(ctx context.Context, project *types.Project, funcs ...projectFn) error {
 | 
			
		||||
	for _, fn := range funcs {
 | 
			
		||||
		err := fn(ctx, project)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// predicate[types.ServiceConfig]
 | 
			
		||||
type servicePredicate func(it types.ServiceConfig) bool
 | 
			
		||||
 | 
			
		||||
// all[types.ServiceConfig]
 | 
			
		||||
func allServices(services types.Services, p servicePredicate) bool {
 | 
			
		||||
	for _, s := range services {
 | 
			
		||||
		if !p(s) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// predicate[types.ServicePortConfig]
 | 
			
		||||
type portPredicate func(it types.ServicePortConfig) bool
 | 
			
		||||
 | 
			
		||||
// all[types.ServicePortConfig]
 | 
			
		||||
func allPorts(ports []types.ServicePortConfig, p portPredicate) bool {
 | 
			
		||||
	for _, s := range ports {
 | 
			
		||||
		if !p(s) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -73,17 +73,20 @@ func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, error) {
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sdk := newSDK(sess)
 | 
			
		||||
	return &ecsAPIService{
 | 
			
		||||
		ctx:    ecsCtx,
 | 
			
		||||
		Region: ecsCtx.Region,
 | 
			
		||||
		SDK:    newSDK(sess),
 | 
			
		||||
		ctx:       ecsCtx,
 | 
			
		||||
		Region:    ecsCtx.Region,
 | 
			
		||||
		SDK:       sdk,
 | 
			
		||||
		resources: awsResources{sdk: sdk},
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ecsAPIService struct {
 | 
			
		||||
	ctx    store.EcsContext
 | 
			
		||||
	Region string
 | 
			
		||||
	SDK    sdk
 | 
			
		||||
	ctx       store.EcsContext
 | 
			
		||||
	Region    string
 | 
			
		||||
	SDK       sdk
 | 
			
		||||
	resources awsResources
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *ecsAPIService) ContainerService() containers.Service {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,15 +35,6 @@ import (
 | 
			
		|||
	"github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
 | 
			
		||||
	cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
 | 
			
		||||
	"github.com/compose-spec/compose-go/types"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	parameterClusterName     = "ParameterClusterName"
 | 
			
		||||
	parameterVPCId           = "ParameterVPCId"
 | 
			
		||||
	parameterSubnet1Id       = "ParameterSubnet1Id"
 | 
			
		||||
	parameterSubnet2Id       = "ParameterSubnet2Id"
 | 
			
		||||
	parameterLoadBalancerARN = "ParameterLoadBalancerARN"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]byte, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -52,7 +43,12 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template, networks, err := b.convert(project)
 | 
			
		||||
	err = b.resources.parse(ctx, project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template, err := b.convert(project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +64,7 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = b.createCapacityProvider(ctx, project, networks, template)
 | 
			
		||||
	err = b.createCapacityProvider(ctx, project, template)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -77,86 +73,31 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Convert a compose project into a CloudFormation template
 | 
			
		||||
func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, map[string]string, error) { //nolint:gocyclo
 | 
			
		||||
func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, error) { //nolint:gocyclo
 | 
			
		||||
	template := cloudformation.NewTemplate()
 | 
			
		||||
	template.Description = "CloudFormation template created by Docker for deploying applications on Amazon ECS"
 | 
			
		||||
	template.Parameters[parameterClusterName] = cloudformation.Parameter{
 | 
			
		||||
		Type:        "String",
 | 
			
		||||
		Description: "Name of the ECS cluster to deploy to (optional)",
 | 
			
		||||
	}
 | 
			
		||||
	b.resources.ensure(project, template)
 | 
			
		||||
 | 
			
		||||
	template.Parameters[parameterVPCId] = cloudformation.Parameter{
 | 
			
		||||
		Type:        "AWS::EC2::VPC::Id",
 | 
			
		||||
		Description: "ID of the VPC",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		FIXME can't set subnets: Ref("SubnetIds") see https://github.com/awslabs/goformation/issues/282
 | 
			
		||||
		template.Parameters["SubnetIds"] = cloudformation.Parameter{
 | 
			
		||||
			Type:        "List<AWS::EC2::Subnet::Id>",
 | 
			
		||||
			Description: "The list of SubnetIds, for at least two Availability Zones in the region in your VPC",
 | 
			
		||||
		}
 | 
			
		||||
	*/
 | 
			
		||||
	template.Parameters[parameterSubnet1Id] = cloudformation.Parameter{
 | 
			
		||||
		Type:        "AWS::EC2::Subnet::Id",
 | 
			
		||||
		Description: "SubnetId, for Availability Zone 1 in the region in your VPC",
 | 
			
		||||
	}
 | 
			
		||||
	template.Parameters[parameterSubnet2Id] = cloudformation.Parameter{
 | 
			
		||||
		Type:        "AWS::EC2::Subnet::Id",
 | 
			
		||||
		Description: "SubnetId, for Availability Zone 2 in the region in your VPC",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template.Parameters[parameterLoadBalancerARN] = cloudformation.Parameter{
 | 
			
		||||
		Type:        "String",
 | 
			
		||||
		Description: "Name of the LoadBalancer to connect to (optional)",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template.Conditions["CreateCluster"] = cloudformation.Equals("", cloudformation.Ref(parameterClusterName))
 | 
			
		||||
 | 
			
		||||
	cluster := createCluster(project, template)
 | 
			
		||||
 | 
			
		||||
	networks := map[string]string{}
 | 
			
		||||
	for _, net := range project.Networks {
 | 
			
		||||
		networks[net.Name] = convertNetwork(project, net, cloudformation.Ref(parameterVPCId), template)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, s := range project.Secrets {
 | 
			
		||||
		if s.External.External {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		secret, err := ioutil.ReadFile(s.File)
 | 
			
		||||
	for name, secret := range project.Secrets {
 | 
			
		||||
		err := b.createSecret(project, name, secret, template)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		name := fmt.Sprintf("%sSecret", normalizeResourceName(s.Name))
 | 
			
		||||
		template.Resources[name] = &secretsmanager.Secret{
 | 
			
		||||
			Description:  "",
 | 
			
		||||
			SecretString: string(secret),
 | 
			
		||||
			Tags:         projectTags(project),
 | 
			
		||||
		}
 | 
			
		||||
		s.Name = cloudformation.Ref(name)
 | 
			
		||||
		project.Secrets[i] = s
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	createLogGroup(project, template)
 | 
			
		||||
	b.createLogGroup(project, template)
 | 
			
		||||
 | 
			
		||||
	// Private DNS namespace will allow DNS name for the services to be <service>.<project>.local
 | 
			
		||||
	createCloudMap(project, template)
 | 
			
		||||
 | 
			
		||||
	loadBalancerARN := createLoadBalancer(project, template)
 | 
			
		||||
	b.createCloudMap(project, template)
 | 
			
		||||
 | 
			
		||||
	for _, service := range project.Services {
 | 
			
		||||
		taskExecutionRole := b.createTaskExecutionRole(project, service, template)
 | 
			
		||||
		taskRole := b.createTaskRole(service, template)
 | 
			
		||||
 | 
			
		||||
		definition, err := convert(project, service)
 | 
			
		||||
		definition, err := b.createTaskExecution(project, service)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		taskExecutionRole := createTaskExecutionRole(project, service, template)
 | 
			
		||||
		definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
 | 
			
		||||
 | 
			
		||||
		taskRole := createTaskRole(service, template)
 | 
			
		||||
		if taskRole != "" {
 | 
			
		||||
			definition.TaskRoleArn = cloudformation.Ref(taskRole)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -165,34 +106,30 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
 | 
			
		|||
		template.Resources[taskDefinition] = definition
 | 
			
		||||
 | 
			
		||||
		var healthCheck *cloudmap.Service_HealthCheckConfig
 | 
			
		||||
		serviceRegistry := b.createServiceRegistry(service, template, healthCheck)
 | 
			
		||||
 | 
			
		||||
		serviceRegistry := createServiceRegistry(service, template, healthCheck)
 | 
			
		||||
 | 
			
		||||
		serviceSecurityGroups := []string{}
 | 
			
		||||
		for net := range service.Networks {
 | 
			
		||||
			serviceSecurityGroups = append(serviceSecurityGroups, networks[net])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		dependsOn := []string{}
 | 
			
		||||
		serviceLB := []ecs.Service_LoadBalancer{}
 | 
			
		||||
		if len(service.Ports) > 0 {
 | 
			
		||||
			for _, port := range service.Ports {
 | 
			
		||||
				protocol := strings.ToUpper(port.Protocol)
 | 
			
		||||
				if getLoadBalancerType(project) == elbv2.LoadBalancerTypeEnumApplication {
 | 
			
		||||
					// we don't set Https as a certificate must be specified for HTTPS listeners
 | 
			
		||||
					protocol = elbv2.ProtocolEnumHttp
 | 
			
		||||
				}
 | 
			
		||||
				if loadBalancerARN != "" {
 | 
			
		||||
					targetGroupName := createTargetGroup(project, service, port, template, protocol)
 | 
			
		||||
					listenerName := createListener(service, port, template, targetGroupName, loadBalancerARN, protocol)
 | 
			
		||||
					dependsOn = append(dependsOn, listenerName)
 | 
			
		||||
					serviceLB = append(serviceLB, ecs.Service_LoadBalancer{
 | 
			
		||||
						ContainerName:  service.Name,
 | 
			
		||||
						ContainerPort:  int(port.Target),
 | 
			
		||||
						TargetGroupArn: cloudformation.Ref(targetGroupName),
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
		var (
 | 
			
		||||
			dependsOn []string
 | 
			
		||||
			serviceLB []ecs.Service_LoadBalancer
 | 
			
		||||
		)
 | 
			
		||||
		for _, port := range service.Ports {
 | 
			
		||||
			for net := range service.Networks {
 | 
			
		||||
				b.createIngress(service, net, port, template)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			protocol := strings.ToUpper(port.Protocol)
 | 
			
		||||
			if b.resources.loadBalancerType == elbv2.LoadBalancerTypeEnumApplication {
 | 
			
		||||
				// we don't set Https as a certificate must be specified for HTTPS listeners
 | 
			
		||||
				protocol = elbv2.ProtocolEnumHttp
 | 
			
		||||
			}
 | 
			
		||||
			targetGroupName := b.createTargetGroup(project, service, port, template, protocol)
 | 
			
		||||
			listenerName := b.createListener(service, port, template, targetGroupName, b.resources.loadBalancer, protocol)
 | 
			
		||||
			dependsOn = append(dependsOn, listenerName)
 | 
			
		||||
			serviceLB = append(serviceLB, ecs.Service_LoadBalancer{
 | 
			
		||||
				ContainerName:  service.Name,
 | 
			
		||||
				ContainerPort:  int(port.Target),
 | 
			
		||||
				TargetGroupArn: cloudformation.Ref(targetGroupName),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		desiredCount := 1
 | 
			
		||||
| 
						 | 
				
			
			@ -206,7 +143,7 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
 | 
			
		|||
 | 
			
		||||
		minPercent, maxPercent, err := computeRollingUpdateLimits(service)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		assignPublicIP := ecsapi.AssignPublicIpEnabled
 | 
			
		||||
| 
						 | 
				
			
			@ -220,7 +157,7 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
 | 
			
		|||
 | 
			
		||||
		template.Resources[serviceResourceName(service.Name)] = &ecs.Service{
 | 
			
		||||
			AWSCloudFormationDependsOn: dependsOn,
 | 
			
		||||
			Cluster:                    cluster,
 | 
			
		||||
			Cluster:                    b.resources.cluster,
 | 
			
		||||
			DesiredCount:               desiredCount,
 | 
			
		||||
			DeploymentController: &ecs.Service_DeploymentController{
 | 
			
		||||
				Type: ecsapi.DeploymentControllerTypeEcs,
 | 
			
		||||
| 
						 | 
				
			
			@ -235,11 +172,8 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
 | 
			
		|||
			NetworkConfiguration: &ecs.Service_NetworkConfiguration{
 | 
			
		||||
				AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
 | 
			
		||||
					AssignPublicIp: assignPublicIP,
 | 
			
		||||
					SecurityGroups: serviceSecurityGroups,
 | 
			
		||||
					Subnets: []string{
 | 
			
		||||
						cloudformation.Ref(parameterSubnet1Id),
 | 
			
		||||
						cloudformation.Ref(parameterSubnet2Id),
 | 
			
		||||
					},
 | 
			
		||||
					SecurityGroups: b.resources.serviceSecurityGroups(service),
 | 
			
		||||
					Subnets:        b.resources.subnets,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			PlatformVersion:    platformVersion,
 | 
			
		||||
| 
						 | 
				
			
			@ -250,10 +184,46 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
 | 
			
		|||
			TaskDefinition:     cloudformation.Ref(normalizeResourceName(taskDefinition)),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return template, networks, nil
 | 
			
		||||
	return template, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createLogGroup(project *types.Project, template *cloudformation.Template) {
 | 
			
		||||
func (b *ecsAPIService) createIngress(service types.ServiceConfig, net string, port types.ServicePortConfig, template *cloudformation.Template) {
 | 
			
		||||
	protocol := strings.ToUpper(port.Protocol)
 | 
			
		||||
	if protocol == "" {
 | 
			
		||||
		protocol = "-1"
 | 
			
		||||
	}
 | 
			
		||||
	ingress := fmt.Sprintf("%s%dIngress", normalizeResourceName(net), port.Target)
 | 
			
		||||
	template.Resources[ingress] = &ec2.SecurityGroupIngress{
 | 
			
		||||
		CidrIp:      "0.0.0.0/0",
 | 
			
		||||
		Description: fmt.Sprintf("%s:%d/%s on %s nextwork", service.Name, port.Target, port.Protocol, net),
 | 
			
		||||
		GroupId:     b.resources.securityGroups[net],
 | 
			
		||||
		FromPort:    int(port.Target),
 | 
			
		||||
		IpProtocol:  protocol,
 | 
			
		||||
		ToPort:      int(port.Target),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *ecsAPIService) createSecret(project *types.Project, name string, s types.SecretConfig, template *cloudformation.Template) error {
 | 
			
		||||
	if s.External.External {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	sensitiveData, err := ioutil.ReadFile(s.File)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resource := fmt.Sprintf("%sSecret", normalizeResourceName(s.Name))
 | 
			
		||||
	template.Resources[resource] = &secretsmanager.Secret{
 | 
			
		||||
		Description:  fmt.Sprintf("Secret %s", s.Name),
 | 
			
		||||
		SecretString: string(sensitiveData),
 | 
			
		||||
		Tags:         projectTags(project),
 | 
			
		||||
	}
 | 
			
		||||
	s.Name = cloudformation.Ref(resource)
 | 
			
		||||
	project.Secrets[name] = s
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *ecsAPIService) createLogGroup(project *types.Project, template *cloudformation.Template) {
 | 
			
		||||
	retention := 0
 | 
			
		||||
	if v, ok := project.Extensions[extensionRetention]; ok {
 | 
			
		||||
		retention = v.(int)
 | 
			
		||||
| 
						 | 
				
			
			@ -305,74 +275,9 @@ func computeRollingUpdateLimits(service types.ServiceConfig) (int, int, error) {
 | 
			
		|||
	return minPercent, maxPercent, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getLoadBalancerType(project *types.Project) string {
 | 
			
		||||
	for _, service := range project.Services {
 | 
			
		||||
		for _, port := range service.Ports {
 | 
			
		||||
			protocol := port.Protocol
 | 
			
		||||
			v, ok := port.Extensions[extensionProtocol]
 | 
			
		||||
			if ok {
 | 
			
		||||
				protocol = v.(string)
 | 
			
		||||
			}
 | 
			
		||||
			if protocol == "http" || protocol == "https" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if port.Published != 80 && port.Published != 443 {
 | 
			
		||||
				return elbv2.LoadBalancerTypeEnumNetwork
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return elbv2.LoadBalancerTypeEnumApplication
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getLoadBalancerSecurityGroups(project *types.Project, template *cloudformation.Template) []string {
 | 
			
		||||
	securityGroups := []string{}
 | 
			
		||||
	for _, network := range project.Networks {
 | 
			
		||||
		if !network.Internal {
 | 
			
		||||
			net := convertNetwork(project, network, cloudformation.Ref(parameterVPCId), template)
 | 
			
		||||
			securityGroups = append(securityGroups, net)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return uniqueStrings(securityGroups)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createLoadBalancer(project *types.Project, template *cloudformation.Template) string {
 | 
			
		||||
	ports := 0
 | 
			
		||||
	for _, service := range project.Services {
 | 
			
		||||
		ports += len(service.Ports)
 | 
			
		||||
	}
 | 
			
		||||
	if ports == 0 {
 | 
			
		||||
		// Project do not expose any port (batch jobs?)
 | 
			
		||||
		// So no need to create a PortPublisher
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// load balancer names are limited to 32 characters total
 | 
			
		||||
	loadBalancerName := fmt.Sprintf("%.32s", fmt.Sprintf("%sLoadBalancer", strings.Title(project.Name)))
 | 
			
		||||
	// Create PortPublisher if `ParameterLoadBalancerName` is not set
 | 
			
		||||
	template.Conditions["CreateLoadBalancer"] = cloudformation.Equals("", cloudformation.Ref(parameterLoadBalancerARN))
 | 
			
		||||
 | 
			
		||||
	loadBalancerType := getLoadBalancerType(project)
 | 
			
		||||
	securityGroups := []string{}
 | 
			
		||||
	if loadBalancerType == elbv2.LoadBalancerTypeEnumApplication {
 | 
			
		||||
		securityGroups = getLoadBalancerSecurityGroups(project, template)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template.Resources[loadBalancerName] = &elasticloadbalancingv2.LoadBalancer{
 | 
			
		||||
		Name:           loadBalancerName,
 | 
			
		||||
		Scheme:         elbv2.LoadBalancerSchemeEnumInternetFacing,
 | 
			
		||||
		SecurityGroups: securityGroups,
 | 
			
		||||
		Subnets: []string{
 | 
			
		||||
			cloudformation.Ref(parameterSubnet1Id),
 | 
			
		||||
			cloudformation.Ref(parameterSubnet2Id),
 | 
			
		||||
		},
 | 
			
		||||
		Tags:                       projectTags(project),
 | 
			
		||||
		Type:                       loadBalancerType,
 | 
			
		||||
		AWSCloudFormationCondition: "CreateLoadBalancer",
 | 
			
		||||
	}
 | 
			
		||||
	return cloudformation.If("CreateLoadBalancer", cloudformation.Ref(loadBalancerName), cloudformation.Ref(parameterLoadBalancerARN))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createListener(service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, targetGroupName string, loadBalancerARN string, protocol string) string {
 | 
			
		||||
func (b *ecsAPIService) createListener(service types.ServiceConfig, port types.ServicePortConfig,
 | 
			
		||||
	template *cloudformation.Template,
 | 
			
		||||
	targetGroupName string, loadBalancerARN string, protocol string) string {
 | 
			
		||||
	listenerName := fmt.Sprintf(
 | 
			
		||||
		"%s%s%dListener",
 | 
			
		||||
		normalizeResourceName(service.Name),
 | 
			
		||||
| 
						 | 
				
			
			@ -401,7 +306,7 @@ func createListener(service types.ServiceConfig, port types.ServicePortConfig, t
 | 
			
		|||
	return listenerName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createTargetGroup(project *types.Project, service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, protocol string) string {
 | 
			
		||||
func (b *ecsAPIService) createTargetGroup(project *types.Project, service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, protocol string) string {
 | 
			
		||||
	targetGroupName := fmt.Sprintf(
 | 
			
		||||
		"%s%s%dTargetGroup",
 | 
			
		||||
		normalizeResourceName(service.Name),
 | 
			
		||||
| 
						 | 
				
			
			@ -414,12 +319,12 @@ func createTargetGroup(project *types.Project, service types.ServiceConfig, port
 | 
			
		|||
		Protocol:           protocol,
 | 
			
		||||
		Tags:               projectTags(project),
 | 
			
		||||
		TargetType:         elbv2.TargetTypeEnumIp,
 | 
			
		||||
		VpcId:              cloudformation.Ref(parameterVPCId),
 | 
			
		||||
		VpcId:              b.resources.vpc,
 | 
			
		||||
	}
 | 
			
		||||
	return targetGroupName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createServiceRegistry(service types.ServiceConfig, template *cloudformation.Template, healthCheck *cloudmap.Service_HealthCheckConfig) ecs.Service_ServiceRegistry {
 | 
			
		||||
func (b *ecsAPIService) createServiceRegistry(service types.ServiceConfig, template *cloudformation.Template, healthCheck *cloudmap.Service_HealthCheckConfig) ecs.Service_ServiceRegistry {
 | 
			
		||||
	serviceRegistration := fmt.Sprintf("%sServiceDiscoveryEntry", normalizeResourceName(service.Name))
 | 
			
		||||
	serviceRegistry := ecs.Service_ServiceRegistry{
 | 
			
		||||
		RegistryArn: cloudformation.GetAtt(serviceRegistration, "Arn"),
 | 
			
		||||
| 
						 | 
				
			
			@ -446,9 +351,9 @@ func createServiceRegistry(service types.ServiceConfig, template *cloudformation
 | 
			
		|||
	return serviceRegistry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createTaskExecutionRole(project *types.Project, service types.ServiceConfig, template *cloudformation.Template) string {
 | 
			
		||||
func (b *ecsAPIService) createTaskExecutionRole(project *types.Project, service types.ServiceConfig, template *cloudformation.Template) string {
 | 
			
		||||
	taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name))
 | 
			
		||||
	policies := createPolicies(project, service)
 | 
			
		||||
	policies := b.createPolicies(project, service)
 | 
			
		||||
	template.Resources[taskExecutionRole] = &iam.Role{
 | 
			
		||||
		AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
 | 
			
		||||
		Policies:                 policies,
 | 
			
		||||
| 
						 | 
				
			
			@ -460,7 +365,7 @@ func createTaskExecutionRole(project *types.Project, service types.ServiceConfig
 | 
			
		|||
	return taskExecutionRole
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createTaskRole(service types.ServiceConfig, template *cloudformation.Template) string {
 | 
			
		||||
func (b *ecsAPIService) createTaskRole(service types.ServiceConfig, template *cloudformation.Template) string {
 | 
			
		||||
	taskRole := fmt.Sprintf("%sTaskRole", normalizeResourceName(service.Name))
 | 
			
		||||
	rolePolicies := []iam.Role_Policy{}
 | 
			
		||||
	if roles, ok := service.Extensions[extensionRole]; ok {
 | 
			
		||||
| 
						 | 
				
			
			@ -485,88 +390,15 @@ func createTaskRole(service types.ServiceConfig, template *cloudformation.Templa
 | 
			
		|||
	return taskRole
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createCluster(project *types.Project, template *cloudformation.Template) string {
 | 
			
		||||
	template.Resources["Cluster"] = &ecs.Cluster{
 | 
			
		||||
		ClusterName:                project.Name,
 | 
			
		||||
		Tags:                       projectTags(project),
 | 
			
		||||
		AWSCloudFormationCondition: "CreateCluster",
 | 
			
		||||
	}
 | 
			
		||||
	cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(parameterClusterName))
 | 
			
		||||
	return cluster
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createCloudMap(project *types.Project, template *cloudformation.Template) {
 | 
			
		||||
func (b *ecsAPIService) createCloudMap(project *types.Project, template *cloudformation.Template) {
 | 
			
		||||
	template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{
 | 
			
		||||
		Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name),
 | 
			
		||||
		Name:        fmt.Sprintf("%s.local", project.Name),
 | 
			
		||||
		Vpc:         cloudformation.Ref(parameterVPCId),
 | 
			
		||||
		Vpc:         b.resources.vpc,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func convertNetwork(project *types.Project, net types.NetworkConfig, vpc string, template *cloudformation.Template) string {
 | 
			
		||||
	if net.External.External {
 | 
			
		||||
		return net.Name
 | 
			
		||||
	}
 | 
			
		||||
	if sg, ok := net.Extensions[extensionSecurityGroup]; ok {
 | 
			
		||||
		logrus.Warn("to use an existing security-group, set `network.external` and `network.name` in your compose file")
 | 
			
		||||
		logrus.Debugf("Security Group for network %q set by user to %q", net.Name, sg)
 | 
			
		||||
		return sg.(string)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var ingresses []ec2.SecurityGroup_Ingress
 | 
			
		||||
	if !net.Internal {
 | 
			
		||||
		for _, service := range project.Services {
 | 
			
		||||
			if _, ok := service.Networks[net.Name]; ok {
 | 
			
		||||
				for _, port := range service.Ports {
 | 
			
		||||
					protocol := strings.ToUpper(port.Protocol)
 | 
			
		||||
					if protocol == "" {
 | 
			
		||||
						protocol = "-1"
 | 
			
		||||
					}
 | 
			
		||||
					ingresses = append(ingresses, ec2.SecurityGroup_Ingress{
 | 
			
		||||
						CidrIp:      "0.0.0.0/0",
 | 
			
		||||
						Description: fmt.Sprintf("%s:%d/%s", service.Name, port.Target, port.Protocol),
 | 
			
		||||
						FromPort:    int(port.Target),
 | 
			
		||||
						IpProtocol:  protocol,
 | 
			
		||||
						ToPort:      int(port.Target),
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	securityGroup := networkResourceName(project, net.Name)
 | 
			
		||||
	template.Resources[securityGroup] = &ec2.SecurityGroup{
 | 
			
		||||
		GroupDescription:     fmt.Sprintf("%s %s Security Group", project.Name, net.Name),
 | 
			
		||||
		GroupName:            securityGroup,
 | 
			
		||||
		SecurityGroupIngress: ingresses,
 | 
			
		||||
		VpcId:                vpc,
 | 
			
		||||
		Tags:                 networkTags(project, net),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ingress := securityGroup + "Ingress"
 | 
			
		||||
	template.Resources[ingress] = &ec2.SecurityGroupIngress{
 | 
			
		||||
		Description:           fmt.Sprintf("Allow communication within network %s", net.Name),
 | 
			
		||||
		IpProtocol:            "-1", // all protocols
 | 
			
		||||
		GroupId:               cloudformation.Ref(securityGroup),
 | 
			
		||||
		SourceSecurityGroupId: cloudformation.Ref(securityGroup),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cloudformation.Ref(securityGroup)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func networkResourceName(project *types.Project, network string) string {
 | 
			
		||||
	return fmt.Sprintf("%s%sNetwork", normalizeResourceName(project.Name), normalizeResourceName(network))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func serviceResourceName(service string) string {
 | 
			
		||||
	return fmt.Sprintf("%sService", normalizeResourceName(service))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func normalizeResourceName(s string) string {
 | 
			
		||||
	return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, ""))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createPolicies(project *types.Project, service types.ServiceConfig) []iam.Role_Policy {
 | 
			
		||||
func (b *ecsAPIService) createPolicies(project *types.Project, service types.ServiceConfig) []iam.Role_Policy {
 | 
			
		||||
	var arns []string
 | 
			
		||||
	if value, ok := service.Extensions[extensionPullCredentials]; ok {
 | 
			
		||||
		arns = append(arns, value.(string))
 | 
			
		||||
| 
						 | 
				
			
			@ -593,14 +425,14 @@ func createPolicies(project *types.Project, service types.ServiceConfig) []iam.R
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func uniqueStrings(items []string) []string {
 | 
			
		||||
	keys := make(map[string]bool)
 | 
			
		||||
	unique := []string{}
 | 
			
		||||
	for _, item := range items {
 | 
			
		||||
		if _, val := keys[item]; !val {
 | 
			
		||||
			keys[item] = true
 | 
			
		||||
			unique = append(unique, item)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return unique
 | 
			
		||||
func networkResourceName(network string) string {
 | 
			
		||||
	return fmt.Sprintf("%sNetwork", normalizeResourceName(network))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func serviceResourceName(service string) string {
 | 
			
		||||
	return fmt.Sprintf("%sService", normalizeResourceName(service))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func normalizeResourceName(s string) string {
 | 
			
		||||
	return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, ""))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -170,13 +170,13 @@ networks:
 | 
			
		|||
  back-tier:
 | 
			
		||||
    internal: true
 | 
			
		||||
`)
 | 
			
		||||
	assert.Check(t, template.Resources["TestPublicNetwork"] != nil)
 | 
			
		||||
	assert.Check(t, template.Resources["TestBacktierNetwork"] != nil)
 | 
			
		||||
	assert.Check(t, template.Resources["TestBacktierNetworkIngress"] != nil)
 | 
			
		||||
	i := template.Resources["TestPublicNetworkIngress"]
 | 
			
		||||
	assert.Check(t, template.Resources["FronttierNetwork"] != nil)
 | 
			
		||||
	assert.Check(t, template.Resources["BacktierNetwork"] != nil)
 | 
			
		||||
	assert.Check(t, template.Resources["BacktierNetworkIngress"] != nil)
 | 
			
		||||
	i := template.Resources["FronttierNetworkIngress"]
 | 
			
		||||
	assert.Check(t, i != nil)
 | 
			
		||||
	ingress := *i.(*ec2.SecurityGroupIngress)
 | 
			
		||||
	assert.Check(t, ingress.SourceSecurityGroupId == cloudformation.Ref("TestPublicNetwork"))
 | 
			
		||||
	assert.Check(t, ingress.SourceSecurityGroupId == cloudformation.Ref("FronttierNetwork"))
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -187,13 +187,6 @@ func TestLoadBalancerTypeApplication(t *testing.T) {
 | 
			
		|||
    image: nginx
 | 
			
		||||
    ports:
 | 
			
		||||
      - 80:80
 | 
			
		||||
`,
 | 
			
		||||
		`services:
 | 
			
		||||
  test:
 | 
			
		||||
    image: nginx
 | 
			
		||||
    ports:
 | 
			
		||||
      - target: 8080
 | 
			
		||||
        protocol: http
 | 
			
		||||
`,
 | 
			
		||||
		`services:
 | 
			
		||||
  test:
 | 
			
		||||
| 
						 | 
				
			
			@ -205,7 +198,7 @@ func TestLoadBalancerTypeApplication(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
	for _, y := range cases {
 | 
			
		||||
		template := convertYaml(t, y)
 | 
			
		||||
		lb := template.Resources["TestLoadBalancer"]
 | 
			
		||||
		lb := template.Resources["LoadBalancer"]
 | 
			
		||||
		assert.Check(t, lb != nil)
 | 
			
		||||
		loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer)
 | 
			
		||||
		assert.Check(t, len(loadBalancer.Name) <= 32)
 | 
			
		||||
| 
						 | 
				
			
			@ -328,7 +321,7 @@ services:
 | 
			
		|||
          memory: 2043248M
 | 
			
		||||
`)
 | 
			
		||||
	backend := &ecsAPIService{}
 | 
			
		||||
	_, _, err := backend.convert(model)
 | 
			
		||||
	_, err := backend.convert(model)
 | 
			
		||||
	assert.ErrorContains(t, err, "the resources requested are not supported by ECS/Fargate")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -341,7 +334,7 @@ services:
 | 
			
		|||
      - 80:80
 | 
			
		||||
      - 88:88
 | 
			
		||||
`)
 | 
			
		||||
	lb := template.Resources["TestLoadBalancer"]
 | 
			
		||||
	lb := template.Resources["LoadBalancer"]
 | 
			
		||||
	assert.Check(t, lb != nil)
 | 
			
		||||
	loadBalancer := *lb.(*elasticloadbalancingv2.LoadBalancer)
 | 
			
		||||
	assert.Check(t, loadBalancer.Type == elbv2.LoadBalancerTypeEnumNetwork)
 | 
			
		||||
| 
						 | 
				
			
			@ -411,8 +404,13 @@ services:
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func convertResultAsString(t *testing.T, project *types.Project) string {
 | 
			
		||||
	backend := &ecsAPIService{}
 | 
			
		||||
	template, _, err := backend.convert(project)
 | 
			
		||||
	backend := &ecsAPIService{
 | 
			
		||||
		resources: awsResources{
 | 
			
		||||
			vpc:     "vpcID",
 | 
			
		||||
			subnets: []string{"subnet1", "subnet2"},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	template, err := backend.convert(project)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	resultAsJSON, err := marshall(template)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
| 
						 | 
				
			
			@ -432,7 +430,7 @@ func load(t *testing.T, paths ...string) *types.Project {
 | 
			
		|||
func convertYaml(t *testing.T, yaml string) *cloudformation.Template {
 | 
			
		||||
	project := loadConfig(t, yaml)
 | 
			
		||||
	backend := &ecsAPIService{}
 | 
			
		||||
	template, _, err := backend.convert(project)
 | 
			
		||||
	template, err := backend.convert(project)
 | 
			
		||||
	assert.NilError(t, err)
 | 
			
		||||
	return template
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,7 @@ import (
 | 
			
		|||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws"
 | 
			
		||||
 | 
			
		||||
	ecsapi "github.com/aws/aws-sdk-go/service/ecs"
 | 
			
		||||
	"github.com/awslabs/goformation/v4/cloudformation"
 | 
			
		||||
	"github.com/awslabs/goformation/v4/cloudformation/ecs"
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +40,7 @@ import (
 | 
			
		|||
 | 
			
		||||
const secretsInitContainerImage = "docker/ecs-secrets-sidecar"
 | 
			
		||||
 | 
			
		||||
func convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefinition, error) {
 | 
			
		||||
func (b *ecsAPIService) createTaskExecution(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefinition, error) {
 | 
			
		||||
	cpu, mem, err := toLimits(service)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										33
									
								
								ecs/ec2.go
								
								
								
								
							
							
						
						
									
										33
									
								
								ecs/ec2.go
								
								
								
								
							| 
						 | 
				
			
			@ -28,7 +28,7 @@ import (
 | 
			
		|||
	"github.com/compose-spec/compose-go/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *types.Project, networks map[string]string, template *cloudformation.Template) error {
 | 
			
		||||
func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *types.Project, template *cloudformation.Template) error {
 | 
			
		||||
	var ec2 bool
 | 
			
		||||
	for _, s := range project.Services {
 | 
			
		||||
		if requireEC2(s) {
 | 
			
		||||
| 
						 | 
				
			
			@ -51,11 +51,6 @@ func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *typ
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var securityGroups []string
 | 
			
		||||
	for _, r := range networks {
 | 
			
		||||
		securityGroups = append(securityGroups, r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template.Resources["CapacityProvider"] = &ecs.CapacityProvider{
 | 
			
		||||
		AutoScalingGroupProvider: &ecs.CapacityProvider_AutoScalingGroupProvider{
 | 
			
		||||
			AutoScalingGroupArn: cloudformation.Ref("AutoscalingGroup"),
 | 
			
		||||
| 
						 | 
				
			
			@ -63,36 +58,29 @@ func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *typ
 | 
			
		|||
				TargetCapacity: 100,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Tags:                       projectTags(project),
 | 
			
		||||
		AWSCloudFormationCondition: "CreateCluster",
 | 
			
		||||
		Tags: projectTags(project),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template.Resources["AutoscalingGroup"] = &autoscaling.AutoScalingGroup{
 | 
			
		||||
		LaunchConfigurationName: cloudformation.Ref("LaunchConfiguration"),
 | 
			
		||||
		MaxSize:                 "10", //TODO
 | 
			
		||||
		MinSize:                 "1",
 | 
			
		||||
		VPCZoneIdentifier: []string{
 | 
			
		||||
			cloudformation.Ref(parameterSubnet1Id),
 | 
			
		||||
			cloudformation.Ref(parameterSubnet2Id),
 | 
			
		||||
		},
 | 
			
		||||
		AWSCloudFormationCondition: "CreateCluster",
 | 
			
		||||
		VPCZoneIdentifier:       b.resources.subnets,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	userData := base64.StdEncoding.EncodeToString([]byte(
 | 
			
		||||
		fmt.Sprintf("#!/bin/bash\necho ECS_CLUSTER=%s >> /etc/ecs/ecs.config", project.Name)))
 | 
			
		||||
 | 
			
		||||
	template.Resources["LaunchConfiguration"] = &autoscaling.LaunchConfiguration{
 | 
			
		||||
		ImageId:                    ami,
 | 
			
		||||
		InstanceType:               machineType,
 | 
			
		||||
		SecurityGroups:             securityGroups,
 | 
			
		||||
		IamInstanceProfile:         cloudformation.Ref("EC2InstanceProfile"),
 | 
			
		||||
		UserData:                   userData,
 | 
			
		||||
		AWSCloudFormationCondition: "CreateCluster",
 | 
			
		||||
		ImageId:            ami,
 | 
			
		||||
		InstanceType:       machineType,
 | 
			
		||||
		SecurityGroups:     b.resources.allSecurityGroups(),
 | 
			
		||||
		IamInstanceProfile: cloudformation.Ref("EC2InstanceProfile"),
 | 
			
		||||
		UserData:           userData,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template.Resources["EC2InstanceProfile"] = &iam.InstanceProfile{
 | 
			
		||||
		Roles:                      []string{cloudformation.Ref("EC2InstanceRole")},
 | 
			
		||||
		AWSCloudFormationCondition: "CreateCluster",
 | 
			
		||||
		Roles: []string{cloudformation.Ref("EC2InstanceRole")},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template.Resources["EC2InstanceRole"] = &iam.Role{
 | 
			
		||||
| 
						 | 
				
			
			@ -100,8 +88,7 @@ func (b *ecsAPIService) createCapacityProvider(ctx context.Context, project *typ
 | 
			
		|||
		ManagedPolicyArns: []string{
 | 
			
		||||
			ecsEC2InstanceRole,
 | 
			
		||||
		},
 | 
			
		||||
		Tags:                       projectTags(project),
 | 
			
		||||
		AWSCloudFormationCondition: "CreateCluster",
 | 
			
		||||
		Tags: projectTags(project),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cluster := template.Resources["Cluster"].(*ecs.Cluster)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								ecs/ps.go
								
								
								
								
							
							
						
						
									
										12
									
								
								ecs/ps.go
								
								
								
								
							| 
						 | 
				
			
			@ -25,18 +25,15 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
func (b *ecsAPIService) Ps(ctx context.Context, project string) ([]compose.ServiceStatus, error) {
 | 
			
		||||
	parameters, err := b.SDK.ListStackParameters(ctx, project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	cluster := parameters[parameterClusterName]
 | 
			
		||||
 | 
			
		||||
	resources, err := b.SDK.ListStackResources(ctx, project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	servicesARN := []string{}
 | 
			
		||||
	var (
 | 
			
		||||
		cluster     = project
 | 
			
		||||
		servicesARN []string
 | 
			
		||||
	)
 | 
			
		||||
	for _, r := range resources {
 | 
			
		||||
		switch r.Type {
 | 
			
		||||
		case "AWS::ECS::Service":
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +42,7 @@ func (b *ecsAPIService) Ps(ctx context.Context, project string) ([]compose.Servi
 | 
			
		|||
			cluster = r.ARN
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(servicesARN) == 0 {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										33
									
								
								ecs/sdk.go
								
								
								
								
							
							
						
						
									
										33
									
								
								ecs/sdk.go
								
								
								
								
							| 
						 | 
				
			
			@ -196,22 +196,13 @@ func (s sdk) StackExists(ctx context.Context, name string) (bool, error) {
 | 
			
		|||
	return len(stacks.Stacks) > 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sdk) CreateStack(ctx context.Context, name string, template []byte, parameters map[string]string) error {
 | 
			
		||||
func (s sdk) CreateStack(ctx context.Context, name string, template []byte) error {
 | 
			
		||||
	logrus.Debug("Create CloudFormation stack")
 | 
			
		||||
 | 
			
		||||
	param := []*cloudformation.Parameter{}
 | 
			
		||||
	for name, value := range parameters {
 | 
			
		||||
		param = append(param, &cloudformation.Parameter{
 | 
			
		||||
			ParameterKey:   aws.String(name),
 | 
			
		||||
			ParameterValue: aws.String(value),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{
 | 
			
		||||
		OnFailure:        aws.String("DELETE"),
 | 
			
		||||
		StackName:        aws.String(name),
 | 
			
		||||
		TemplateBody:     aws.String(string(template)),
 | 
			
		||||
		Parameters:       param,
 | 
			
		||||
		TimeoutInMinutes: nil,
 | 
			
		||||
		Capabilities: []*string{
 | 
			
		||||
			aws.String(cloudformation.CapabilityCapabilityIam),
 | 
			
		||||
| 
						 | 
				
			
			@ -226,24 +217,15 @@ func (s sdk) CreateStack(ctx context.Context, name string, template []byte, para
 | 
			
		|||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sdk) CreateChangeSet(ctx context.Context, name string, template []byte, parameters map[string]string) (string, error) {
 | 
			
		||||
func (s sdk) CreateChangeSet(ctx context.Context, name string, template []byte) (string, error) {
 | 
			
		||||
	logrus.Debug("Create CloudFormation Changeset")
 | 
			
		||||
 | 
			
		||||
	param := []*cloudformation.Parameter{}
 | 
			
		||||
	for name := range parameters {
 | 
			
		||||
		param = append(param, &cloudformation.Parameter{
 | 
			
		||||
			ParameterKey:     aws.String(name),
 | 
			
		||||
			UsePreviousValue: aws.Bool(true),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05"))
 | 
			
		||||
	changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{
 | 
			
		||||
		ChangeSetName: aws.String(update),
 | 
			
		||||
		ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate),
 | 
			
		||||
		StackName:     aws.String(name),
 | 
			
		||||
		TemplateBody:  aws.String(string(template)),
 | 
			
		||||
		Parameters:    param,
 | 
			
		||||
		Capabilities: []*string{
 | 
			
		||||
			aws.String(cloudformation.CapabilityCapabilityIam),
 | 
			
		||||
		},
 | 
			
		||||
| 
						 | 
				
			
			@ -647,15 +629,18 @@ func (s sdk) GetPublicIPs(ctx context.Context, interfaces ...string) (map[string
 | 
			
		|||
	return publicIPs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sdk) LoadBalancerExists(ctx context.Context, arn string) (bool, error) {
 | 
			
		||||
	logrus.Debug("CheckRequirements if PortPublisher exists: ", arn)
 | 
			
		||||
func (s sdk) LoadBalancerType(ctx context.Context, arn string) (string, error) {
 | 
			
		||||
	logrus.Debug("Check if LoadBalancer exists: ", arn)
 | 
			
		||||
	lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
 | 
			
		||||
		LoadBalancerArns: []*string{aws.String(arn)},
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return len(lbs.LoadBalancers) > 0, nil
 | 
			
		||||
	if len(lbs.LoadBalancers) == 0 {
 | 
			
		||||
		return "", fmt.Errorf("load balancer does not exist: %s", arn)
 | 
			
		||||
	}
 | 
			
		||||
	return aws.StringValue(lbs.LoadBalancers[0].Type), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sdk) GetLoadBalancerURL(ctx context.Context, arn string) (string, error) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,59 +1,15 @@
 | 
			
		|||
{
 | 
			
		||||
  "AWSTemplateFormatVersion": "2010-09-09",
 | 
			
		||||
  "Conditions": {
 | 
			
		||||
    "CreateCluster": {
 | 
			
		||||
      "Fn::Equals": [
 | 
			
		||||
        "",
 | 
			
		||||
        {
 | 
			
		||||
          "Ref": "ParameterClusterName"
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "CreateLoadBalancer": {
 | 
			
		||||
      "Fn::Equals": [
 | 
			
		||||
        "",
 | 
			
		||||
        {
 | 
			
		||||
          "Ref": "ParameterLoadBalancerARN"
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "Description": "CloudFormation template created by Docker for deploying applications on Amazon ECS",
 | 
			
		||||
  "Parameters": {
 | 
			
		||||
    "ParameterClusterName": {
 | 
			
		||||
      "Description": "Name of the ECS cluster to deploy to (optional)",
 | 
			
		||||
      "Type": "String"
 | 
			
		||||
    },
 | 
			
		||||
    "ParameterLoadBalancerARN": {
 | 
			
		||||
      "Description": "Name of the LoadBalancer to connect to (optional)",
 | 
			
		||||
      "Type": "String"
 | 
			
		||||
    },
 | 
			
		||||
    "ParameterSubnet1Id": {
 | 
			
		||||
      "Description": "SubnetId, for Availability Zone 1 in the region in your VPC",
 | 
			
		||||
      "Type": "AWS::EC2::Subnet::Id"
 | 
			
		||||
    },
 | 
			
		||||
    "ParameterSubnet2Id": {
 | 
			
		||||
      "Description": "SubnetId, for Availability Zone 2 in the region in your VPC",
 | 
			
		||||
      "Type": "AWS::EC2::Subnet::Id"
 | 
			
		||||
    },
 | 
			
		||||
    "ParameterVPCId": {
 | 
			
		||||
      "Description": "ID of the VPC",
 | 
			
		||||
      "Type": "AWS::EC2::VPC::Id"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "Resources": {
 | 
			
		||||
    "CloudMap": {
 | 
			
		||||
      "Properties": {
 | 
			
		||||
        "Description": "Service Map for Docker Compose project TestSimpleConvert",
 | 
			
		||||
        "Name": "TestSimpleConvert.local",
 | 
			
		||||
        "Vpc": {
 | 
			
		||||
          "Ref": "ParameterVPCId"
 | 
			
		||||
        }
 | 
			
		||||
        "Vpc": "vpcID"
 | 
			
		||||
      },
 | 
			
		||||
      "Type": "AWS::ServiceDiscovery::PrivateDnsNamespace"
 | 
			
		||||
    },
 | 
			
		||||
    "Cluster": {
 | 
			
		||||
      "Condition": "CreateCluster",
 | 
			
		||||
      "Properties": {
 | 
			
		||||
        "ClusterName": "TestSimpleConvert",
 | 
			
		||||
        "Tags": [
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +21,72 @@
 | 
			
		|||
      },
 | 
			
		||||
      "Type": "AWS::ECS::Cluster"
 | 
			
		||||
    },
 | 
			
		||||
    "Default80Ingress": {
 | 
			
		||||
      "Properties": {
 | 
			
		||||
        "CidrIp": "0.0.0.0/0",
 | 
			
		||||
        "Description": "simple:80/tcp on default nextwork",
 | 
			
		||||
        "FromPort": 80,
 | 
			
		||||
        "GroupId": {
 | 
			
		||||
          "Ref": "DefaultNetwork"
 | 
			
		||||
        },
 | 
			
		||||
        "IpProtocol": "TCP",
 | 
			
		||||
        "ToPort": 80
 | 
			
		||||
      },
 | 
			
		||||
      "Type": "AWS::EC2::SecurityGroupIngress"
 | 
			
		||||
    },
 | 
			
		||||
    "DefaultNetwork": {
 | 
			
		||||
      "Properties": {
 | 
			
		||||
        "GroupDescription": "TestSimpleConvert Security Group for default network",
 | 
			
		||||
        "GroupName": "DefaultNetwork",
 | 
			
		||||
        "Tags": [
 | 
			
		||||
          {
 | 
			
		||||
            "Key": "com.docker.compose.project",
 | 
			
		||||
            "Value": "TestSimpleConvert"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "Key": "com.docker.compose.network",
 | 
			
		||||
            "Value": "default"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "VpcId": "vpcID"
 | 
			
		||||
      },
 | 
			
		||||
      "Type": "AWS::EC2::SecurityGroup"
 | 
			
		||||
    },
 | 
			
		||||
    "DefaultNetworkIngress": {
 | 
			
		||||
      "Properties": {
 | 
			
		||||
        "Description": "Allow communication within network default",
 | 
			
		||||
        "GroupId": {
 | 
			
		||||
          "Ref": "DefaultNetwork"
 | 
			
		||||
        },
 | 
			
		||||
        "IpProtocol": "-1",
 | 
			
		||||
        "SourceSecurityGroupId": {
 | 
			
		||||
          "Ref": "DefaultNetwork"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Type": "AWS::EC2::SecurityGroupIngress"
 | 
			
		||||
    },
 | 
			
		||||
    "LoadBalancer": {
 | 
			
		||||
      "Properties": {
 | 
			
		||||
        "Scheme": "internet-facing",
 | 
			
		||||
        "SecurityGroups": [
 | 
			
		||||
          {
 | 
			
		||||
            "Ref": "DefaultNetwork"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "Subnets": [
 | 
			
		||||
          "subnet1",
 | 
			
		||||
          "subnet2"
 | 
			
		||||
        ],
 | 
			
		||||
        "Tags": [
 | 
			
		||||
          {
 | 
			
		||||
            "Key": "com.docker.compose.project",
 | 
			
		||||
            "Value": "TestSimpleConvert"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "Type": "application"
 | 
			
		||||
      },
 | 
			
		||||
      "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer"
 | 
			
		||||
    },
 | 
			
		||||
    "LogGroup": {
 | 
			
		||||
      "Properties": {
 | 
			
		||||
        "LogGroupName": "/docker-compose/TestSimpleConvert"
 | 
			
		||||
| 
						 | 
				
			
			@ -77,15 +99,7 @@
 | 
			
		|||
      ],
 | 
			
		||||
      "Properties": {
 | 
			
		||||
        "Cluster": {
 | 
			
		||||
          "Fn::If": [
 | 
			
		||||
            "CreateCluster",
 | 
			
		||||
            {
 | 
			
		||||
              "Ref": "Cluster"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              "Ref": "ParameterClusterName"
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
          "Ref": "Cluster"
 | 
			
		||||
        },
 | 
			
		||||
        "DeploymentConfiguration": {
 | 
			
		||||
          "MaximumPercent": 200,
 | 
			
		||||
| 
						 | 
				
			
			@ -110,16 +124,12 @@
 | 
			
		|||
            "AssignPublicIp": "ENABLED",
 | 
			
		||||
            "SecurityGroups": [
 | 
			
		||||
              {
 | 
			
		||||
                "Ref": "TestSimpleConvertDefaultNetwork"
 | 
			
		||||
                "Ref": "DefaultNetwork"
 | 
			
		||||
              }
 | 
			
		||||
            ],
 | 
			
		||||
            "Subnets": [
 | 
			
		||||
              {
 | 
			
		||||
                "Ref": "ParameterSubnet1Id"
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                "Ref": "ParameterSubnet2Id"
 | 
			
		||||
              }
 | 
			
		||||
              "subnet1",
 | 
			
		||||
              "subnet2"
 | 
			
		||||
            ]
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			@ -191,15 +201,7 @@
 | 
			
		|||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "LoadBalancerArn": {
 | 
			
		||||
          "Fn::If": [
 | 
			
		||||
            "CreateLoadBalancer",
 | 
			
		||||
            {
 | 
			
		||||
              "Ref": "TestSimpleConvertLoadBalancer"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              "Ref": "ParameterLoadBalancerARN"
 | 
			
		||||
            }
 | 
			
		||||
          ]
 | 
			
		||||
          "Ref": "LoadBalancer"
 | 
			
		||||
        },
 | 
			
		||||
        "Port": 80,
 | 
			
		||||
        "Protocol": "HTTP"
 | 
			
		||||
| 
						 | 
				
			
			@ -217,9 +219,7 @@
 | 
			
		|||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "TargetType": "ip",
 | 
			
		||||
        "VpcId": {
 | 
			
		||||
          "Ref": "ParameterVPCId"
 | 
			
		||||
        }
 | 
			
		||||
        "VpcId": "vpcID"
 | 
			
		||||
      },
 | 
			
		||||
      "Type": "AWS::ElasticLoadBalancingV2::TargetGroup"
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -304,76 +304,6 @@
 | 
			
		|||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      "Type": "AWS::IAM::Role"
 | 
			
		||||
    },
 | 
			
		||||
    "TestSimpleConvertDefaultNetwork": {
 | 
			
		||||
      "Properties": {
 | 
			
		||||
        "GroupDescription": "TestSimpleConvert default Security Group",
 | 
			
		||||
        "GroupName": "TestSimpleConvertDefaultNetwork",
 | 
			
		||||
        "SecurityGroupIngress": [
 | 
			
		||||
          {
 | 
			
		||||
            "CidrIp": "0.0.0.0/0",
 | 
			
		||||
            "Description": "simple:80/tcp",
 | 
			
		||||
            "FromPort": 80,
 | 
			
		||||
            "IpProtocol": "TCP",
 | 
			
		||||
            "ToPort": 80
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "Tags": [
 | 
			
		||||
          {
 | 
			
		||||
            "Key": "com.docker.compose.project",
 | 
			
		||||
            "Value": "TestSimpleConvert"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "Key": "com.docker.compose.network",
 | 
			
		||||
            "Value": "default"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "VpcId": {
 | 
			
		||||
          "Ref": "ParameterVPCId"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Type": "AWS::EC2::SecurityGroup"
 | 
			
		||||
    },
 | 
			
		||||
    "TestSimpleConvertDefaultNetworkIngress": {
 | 
			
		||||
      "Properties": {
 | 
			
		||||
        "Description": "Allow communication within network default",
 | 
			
		||||
        "GroupId": {
 | 
			
		||||
          "Ref": "TestSimpleConvertDefaultNetwork"
 | 
			
		||||
        },
 | 
			
		||||
        "IpProtocol": "-1",
 | 
			
		||||
        "SourceSecurityGroupId": {
 | 
			
		||||
          "Ref": "TestSimpleConvertDefaultNetwork"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Type": "AWS::EC2::SecurityGroupIngress"
 | 
			
		||||
    },
 | 
			
		||||
    "TestSimpleConvertLoadBalancer": {
 | 
			
		||||
      "Condition": "CreateLoadBalancer",
 | 
			
		||||
      "Properties": {
 | 
			
		||||
        "Name": "TestSimpleConvertLoadBalancer",
 | 
			
		||||
        "Scheme": "internet-facing",
 | 
			
		||||
        "SecurityGroups": [
 | 
			
		||||
          {
 | 
			
		||||
            "Ref": "TestSimpleConvertDefaultNetwork"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "Subnets": [
 | 
			
		||||
          {
 | 
			
		||||
            "Ref": "ParameterSubnet1Id"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "Ref": "ParameterSubnet2Id"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "Tags": [
 | 
			
		||||
          {
 | 
			
		||||
            "Key": "com.docker.compose.project",
 | 
			
		||||
            "Value": "TestSimpleConvert"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "Type": "application"
 | 
			
		||||
      },
 | 
			
		||||
      "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										87
									
								
								ecs/up.go
								
								
								
								
							
							
						
						
									
										87
									
								
								ecs/up.go
								
								
								
								
							| 
						 | 
				
			
			@ -32,42 +32,11 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project) error {
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cluster, err := b.GetCluster(ctx, project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template, err := b.Convert(ctx, project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vpc, err := b.GetVPC(ctx, project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	subNets, err := b.SDK.GetSubNets(ctx, vpc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if len(subNets) < 2 {
 | 
			
		||||
		return fmt.Errorf("VPC %s should have at least 2 associated subnets in different availability zones", vpc)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lb, err := b.GetLoadBalancer(ctx, project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	parameters := map[string]string{
 | 
			
		||||
		parameterClusterName:     cluster,
 | 
			
		||||
		parameterVPCId:           vpc,
 | 
			
		||||
		parameterSubnet1Id:       subNets[0],
 | 
			
		||||
		parameterSubnet2Id:       subNets[1],
 | 
			
		||||
		parameterLoadBalancerARN: lb,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	update, err := b.SDK.StackExists(ctx, project.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
| 
						 | 
				
			
			@ -75,7 +44,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project) error {
 | 
			
		|||
	operation := stackCreate
 | 
			
		||||
	if update {
 | 
			
		||||
		operation = stackUpdate
 | 
			
		||||
		changeset, err := b.SDK.CreateChangeSet(ctx, project.Name, template, parameters)
 | 
			
		||||
		changeset, err := b.SDK.CreateChangeSet(ctx, project.Name, template)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +53,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project) error {
 | 
			
		|||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		err = b.SDK.CreateStack(ctx, project.Name, template, parameters)
 | 
			
		||||
		err = b.SDK.CreateStack(ctx, project.Name, template)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -101,55 +70,3 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project) error {
 | 
			
		|||
	err = b.WaitStackCompletion(ctx, project.Name, operation)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b ecsAPIService) GetVPC(ctx context.Context, project *types.Project) (string, error) {
 | 
			
		||||
	var vpcID string
 | 
			
		||||
	//check compose file for custom VPC selected
 | 
			
		||||
	if vpc, ok := project.Extensions[extensionVPC]; ok {
 | 
			
		||||
		vpcID = vpc.(string)
 | 
			
		||||
	} else {
 | 
			
		||||
		defaultVPC, err := b.SDK.GetDefaultVPC(ctx)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		vpcID = defaultVPC
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := b.SDK.CheckVPC(ctx, vpcID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return vpcID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b ecsAPIService) GetLoadBalancer(ctx context.Context, project *types.Project) (string, error) {
 | 
			
		||||
	//check compose file for custom VPC selected
 | 
			
		||||
	if ext, ok := project.Extensions[extensionLB]; ok {
 | 
			
		||||
		lb := ext.(string)
 | 
			
		||||
		ok, err := b.SDK.LoadBalancerExists(ctx, lb)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return "", fmt.Errorf("load balancer does not exist: %s", lb)
 | 
			
		||||
		}
 | 
			
		||||
		return lb, nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b ecsAPIService) GetCluster(ctx context.Context, project *types.Project) (string, error) {
 | 
			
		||||
	//check compose file for custom VPC selected
 | 
			
		||||
	if ext, ok := project.Extensions[extensionCluster]; ok {
 | 
			
		||||
		cluster := ext.(string)
 | 
			
		||||
		ok, err := b.SDK.ClusterExists(ctx, cluster)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return "", fmt.Errorf("cluster does not exist: %s", cluster)
 | 
			
		||||
		}
 | 
			
		||||
		return cluster, nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,7 +38,7 @@ func (b *ecsAPIService) createNFSmountIngress(securityGroups []string, project *
 | 
			
		|||
				if ext, ok := network.Extensions[extensionSecurityGroup]; ok {
 | 
			
		||||
					source = ext.(string)
 | 
			
		||||
				} else {
 | 
			
		||||
					source = networkResourceName(project, net)
 | 
			
		||||
					source = networkResourceName(net)
 | 
			
		||||
				}
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								ecs/x.go
								
								
								
								
							
							
						
						
									
										2
									
								
								ecs/x.go
								
								
								
								
							| 
						 | 
				
			
			@ -20,7 +20,7 @@ const (
 | 
			
		|||
	extensionSecurityGroup   = "x-aws-securitygroup"
 | 
			
		||||
	extensionVPC             = "x-aws-vpc"
 | 
			
		||||
	extensionPullCredentials = "x-aws-pull_credentials"
 | 
			
		||||
	extensionLB              = "x-aws-loadbalancer"
 | 
			
		||||
	extensionLoadBalancer    = "x-aws-loadbalancer"
 | 
			
		||||
	extensionProtocol        = "x-aws-protocol"
 | 
			
		||||
	extensionCluster         = "x-aws-cluster"
 | 
			
		||||
	extensionKeys            = "x-aws-keys"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue