|  | @@ -34,22 +34,21 @@ import (
 | 
	
		
			
				|  |  |  	"github.com/awslabs/goformation/v4/cloudformation/logs"
 | 
	
		
			
				|  |  |  	"github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
 | 
	
		
			
				|  |  |  	cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
 | 
	
		
			
				|  |  | -	"github.com/compose-spec/compose-go/compatibility"
 | 
	
		
			
				|  |  | -	"github.com/compose-spec/compose-go/errdefs"
 | 
	
		
			
				|  |  |  	"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) {
 | 
	
		
			
				|  |  | -	template, networks, err := b.convert(project)
 | 
	
		
			
				|  |  | +	err := b.checkCompatibility(project)
 | 
	
		
			
				|  |  | +	if err != nil {
 | 
	
		
			
				|  |  | +		return nil, err
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	resources, err := b.parse(ctx, project)
 | 
	
		
			
				|  |  | +	if err != nil {
 | 
	
		
			
				|  |  | +		return nil, err
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	template, err := b.convert(project, resources)
 | 
	
		
			
				|  |  |  	if err != nil {
 | 
	
		
			
				|  |  |  		return nil, err
 | 
	
		
			
				|  |  |  	}
 | 
	
	
		
			
				|  | @@ -58,43 +57,14 @@ func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]
 | 
	
		
			
				|  |  |  	// as "source security group" use an arbitrary network attached to service(s) who mounts target volume
 | 
	
		
			
				|  |  |  	for n, vol := range project.Volumes {
 | 
	
		
			
				|  |  |  		err := b.SDK.WithVolumeSecurityGroups(ctx, vol.Name, func(securityGroups []string) error {
 | 
	
		
			
				|  |  | -			target := securityGroups[0]
 | 
	
		
			
				|  |  | -			for _, s := range project.Services {
 | 
	
		
			
				|  |  | -				for _, v := range s.Volumes {
 | 
	
		
			
				|  |  | -					if v.Source != n {
 | 
	
		
			
				|  |  | -						continue
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -					var source string
 | 
	
		
			
				|  |  | -					for net := range s.Networks {
 | 
	
		
			
				|  |  | -						network := project.Networks[net]
 | 
	
		
			
				|  |  | -						if ext, ok := network.Extensions[extensionSecurityGroup]; ok {
 | 
	
		
			
				|  |  | -							source = ext.(string)
 | 
	
		
			
				|  |  | -						} else {
 | 
	
		
			
				|  |  | -							source = networkResourceName(project, net)
 | 
	
		
			
				|  |  | -						}
 | 
	
		
			
				|  |  | -						break
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -					name := fmt.Sprintf("%sNFSMount%s", s.Name, n)
 | 
	
		
			
				|  |  | -					template.Resources[name] = &ec2.SecurityGroupIngress{
 | 
	
		
			
				|  |  | -						Description:           fmt.Sprintf("Allow NFS mount for %s on %s", s.Name, n),
 | 
	
		
			
				|  |  | -						GroupId:               target,
 | 
	
		
			
				|  |  | -						SourceSecurityGroupId: cloudformation.Ref(source),
 | 
	
		
			
				|  |  | -						IpProtocol:            "tcp",
 | 
	
		
			
				|  |  | -						FromPort:              2049,
 | 
	
		
			
				|  |  | -						ToPort:                2049,
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -					service := template.Resources[serviceResourceName(s.Name)].(*ecs.Service)
 | 
	
		
			
				|  |  | -					service.AWSCloudFormationDependsOn = append(service.AWSCloudFormationDependsOn, name)
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -			return nil
 | 
	
		
			
				|  |  | +			return b.createNFSmountIngress(securityGroups, project, n, template)
 | 
	
		
			
				|  |  |  		})
 | 
	
		
			
				|  |  |  		if err != nil {
 | 
	
		
			
				|  |  |  			return nil, err
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	err = b.createCapacityProvider(ctx, project, networks, template)
 | 
	
		
			
				|  |  | +	err = b.createCapacityProvider(ctx, project, template, resources)
 | 
	
		
			
				|  |  |  	if err != nil {
 | 
	
		
			
				|  |  |  		return nil, err
 | 
	
		
			
				|  |  |  	}
 | 
	
	
		
			
				|  | @@ -103,103 +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
 | 
	
		
			
				|  |  | -	var checker compatibility.Checker = &fargateCompatibilityChecker{
 | 
	
		
			
				|  |  | -		compatibility.AllowList{
 | 
	
		
			
				|  |  | -			Supported: compatibleComposeAttributes,
 | 
	
		
			
				|  |  | -		},
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	compatibility.Check(project, checker)
 | 
	
		
			
				|  |  | -	for _, err := range checker.Errors() {
 | 
	
		
			
				|  |  | -		if errdefs.IsIncompatibleError(err) {
 | 
	
		
			
				|  |  | -			logrus.Error(err.Error())
 | 
	
		
			
				|  |  | -		} else {
 | 
	
		
			
				|  |  | -			logrus.Warn(err.Error())
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	if !compatibility.IsCompatible(checker) {
 | 
	
		
			
				|  |  | -		return nil, nil, fmt.Errorf("compose file is incompatible with Amazon ECS")
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +func (b *ecsAPIService) convert(project *types.Project, resources awsResources) (*cloudformation.Template, error) {
 | 
	
		
			
				|  |  |  	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)",
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	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)
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | +	b.ensureResources(&resources, project, 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
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		name := fmt.Sprintf("%sSecret", normalizeResourceName(s.Name))
 | 
	
		
			
				|  |  | -		template.Resources[name] = &secretsmanager.Secret{
 | 
	
		
			
				|  |  | -			Description:  "",
 | 
	
		
			
				|  |  | -			SecretString: string(secret),
 | 
	
		
			
				|  |  | -			Tags:         projectTags(project),
 | 
	
		
			
				|  |  | +			return nil, err
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | -		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, resources.vpc)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	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(service, definition, template)
 | 
	
		
			
				|  |  |  		definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		taskRole := createTaskRole(service, template)
 | 
	
		
			
				|  |  |  		if taskRole != "" {
 | 
	
		
			
				|  |  |  			definition.TaskRoleArn = cloudformation.Ref(taskRole)
 | 
	
		
			
				|  |  |  		}
 | 
	
	
		
			
				|  | @@ -208,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])
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +		var (
 | 
	
		
			
				|  |  | +			dependsOn []string
 | 
	
		
			
				|  |  | +			serviceLB []ecs.Service_LoadBalancer
 | 
	
		
			
				|  |  | +		)
 | 
	
		
			
				|  |  | +		for _, port := range service.Ports {
 | 
	
		
			
				|  |  | +			for net := range service.Networks {
 | 
	
		
			
				|  |  | +				b.createIngress(service, net, port, template, resources)
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		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),
 | 
	
		
			
				|  |  | -					})
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | +			protocol := strings.ToUpper(port.Protocol)
 | 
	
		
			
				|  |  | +			if 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, resources.vpc)
 | 
	
		
			
				|  |  | +			listenerName := b.createListener(service, port, template, targetGroupName, 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
 | 
	
	
		
			
				|  | @@ -249,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
 | 
	
	
		
			
				|  | @@ -263,7 +157,7 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		template.Resources[serviceResourceName(service.Name)] = &ecs.Service{
 | 
	
		
			
				|  |  |  			AWSCloudFormationDependsOn: dependsOn,
 | 
	
		
			
				|  |  | -			Cluster:                    cluster,
 | 
	
		
			
				|  |  | +			Cluster:                    resources.cluster,
 | 
	
		
			
				|  |  |  			DesiredCount:               desiredCount,
 | 
	
		
			
				|  |  |  			DeploymentController: &ecs.Service_DeploymentController{
 | 
	
		
			
				|  |  |  				Type: ecsapi.DeploymentControllerTypeEcs,
 | 
	
	
		
			
				|  | @@ -278,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: resources.serviceSecurityGroups(service),
 | 
	
		
			
				|  |  | +					Subnets:        resources.subnets,
 | 
	
		
			
				|  |  |  				},
 | 
	
		
			
				|  |  |  			},
 | 
	
		
			
				|  |  |  			PlatformVersion:    platformVersion,
 | 
	
	
		
			
				|  | @@ -293,10 +184,48 @@ func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Templat
 | 
	
		
			
				|  |  |  			TaskDefinition:     cloudformation.Ref(normalizeResourceName(taskDefinition)),
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | -	return template, networks, nil
 | 
	
		
			
				|  |  | +	return template, nil
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const allProtocols = "-1"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +func (b *ecsAPIService) createIngress(service types.ServiceConfig, net string, port types.ServicePortConfig, template *cloudformation.Template, resources awsResources) {
 | 
	
		
			
				|  |  | +	protocol := strings.ToUpper(port.Protocol)
 | 
	
		
			
				|  |  | +	if protocol == "" {
 | 
	
		
			
				|  |  | +		protocol = allProtocols
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +	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:     resources.securityGroups[net],
 | 
	
		
			
				|  |  | +		FromPort:    int(port.Target),
 | 
	
		
			
				|  |  | +		IpProtocol:  protocol,
 | 
	
		
			
				|  |  | +		ToPort:      int(port.Target),
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -func createLogGroup(project *types.Project, template *cloudformation.Template) {
 | 
	
		
			
				|  |  | +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)
 | 
	
	
		
			
				|  | @@ -348,74 +277,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),
 | 
	
	
		
			
				|  | @@ -444,7 +308,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, vpc string) string {
 | 
	
		
			
				|  |  |  	targetGroupName := fmt.Sprintf(
 | 
	
		
			
				|  |  |  		"%s%s%dTargetGroup",
 | 
	
		
			
				|  |  |  		normalizeResourceName(service.Name),
 | 
	
	
		
			
				|  | @@ -457,12 +321,12 @@ func createTargetGroup(project *types.Project, service types.ServiceConfig, port
 | 
	
		
			
				|  |  |  		Protocol:           protocol,
 | 
	
		
			
				|  |  |  		Tags:               projectTags(project),
 | 
	
		
			
				|  |  |  		TargetType:         elbv2.TargetTypeEnumIp,
 | 
	
		
			
				|  |  | -		VpcId:              cloudformation.Ref(parameterVPCId),
 | 
	
		
			
				|  |  | +		VpcId:              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"),
 | 
	
	
		
			
				|  | @@ -489,9 +353,9 @@ func createServiceRegistry(service types.ServiceConfig, template *cloudformation
 | 
	
		
			
				|  |  |  	return serviceRegistry
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -func createTaskExecutionRole(service types.ServiceConfig, definition *ecs.TaskDefinition, 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(service, definition)
 | 
	
		
			
				|  |  | +	policies := b.createPolicies(project, service)
 | 
	
		
			
				|  |  |  	template.Resources[taskExecutionRole] = &iam.Role{
 | 
	
		
			
				|  |  |  		AssumeRolePolicyDocument: ecsTaskAssumeRolePolicyDocument,
 | 
	
		
			
				|  |  |  		Policies:                 policies,
 | 
	
	
		
			
				|  | @@ -503,7 +367,7 @@ func createTaskExecutionRole(service types.ServiceConfig, definition *ecs.TaskDe
 | 
	
		
			
				|  |  |  	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 {
 | 
	
	
		
			
				|  | @@ -528,99 +392,21 @@ 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, vpc string) {
 | 
	
		
			
				|  |  |  	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:         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),
 | 
	
		
			
				|  |  | +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))
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	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(service types.ServiceConfig, taskDef *ecs.TaskDefinition) []iam.Role_Policy {
 | 
	
		
			
				|  |  | -	arns := []string{}
 | 
	
		
			
				|  |  | -	for _, container := range taskDef.ContainerDefinitions {
 | 
	
		
			
				|  |  | -		if container.RepositoryCredentials != nil {
 | 
	
		
			
				|  |  | -			arns = append(arns, container.RepositoryCredentials.CredentialsParameter)
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		if len(container.Secrets) > 0 {
 | 
	
		
			
				|  |  | -			for _, s := range container.Secrets {
 | 
	
		
			
				|  |  | -				arns = append(arns, s.ValueFrom)
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +	for _, secret := range service.Secrets {
 | 
	
		
			
				|  |  | +		arns = append(arns, project.Secrets[secret.Source].Name)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  	if len(arns) > 0 {
 | 
	
		
			
				|  |  |  		return []iam.Role_Policy{
 | 
	
	
		
			
				|  | @@ -641,14 +427,14 @@ func createPolicies(service types.ServiceConfig, taskDef *ecs.TaskDefinition) []
 | 
	
		
			
				|  |  |  	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, ""))
 | 
	
		
			
				|  |  |  }
 |