| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613 | 
							- /*
 
-    Copyright 2020 Docker, Inc.
 
-    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"
 
- 	"io/ioutil"
 
- 	"regexp"
 
- 	"strings"
 
- 	"github.com/docker/compose-cli/compose"
 
- 	ecsapi "github.com/aws/aws-sdk-go/service/ecs"
 
- 	"github.com/aws/aws-sdk-go/service/elbv2"
 
- 	cloudmapapi "github.com/aws/aws-sdk-go/service/servicediscovery"
 
- 	"github.com/awslabs/goformation/v4/cloudformation"
 
- 	"github.com/awslabs/goformation/v4/cloudformation/ec2"
 
- 	"github.com/awslabs/goformation/v4/cloudformation/ecs"
 
- 	"github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2"
 
- 	"github.com/awslabs/goformation/v4/cloudformation/iam"
 
- 	"github.com/awslabs/goformation/v4/cloudformation/logs"
 
- 	"github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
 
- 	cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
 
- 	"github.com/awslabs/goformation/v4/cloudformation/tags"
 
- 	"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, err := b.convert(project)
 
- 	if err != nil {
 
- 		return nil, err
 
- 	}
 
- 	return marshall(template)
 
- }
 
- // Convert a compose project into a CloudFormation template
 
- func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, 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, fmt.Errorf("compose file is incompatible with Amazon ECS")
 
- 	}
 
- 	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)",
 
- 	}
 
- 	// Create Cluster is `ParameterClusterName` parameter is not set
 
- 	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)
 
- 		if err != nil {
 
- 			return nil, err
 
- 		}
 
- 		name := fmt.Sprintf("%sSecret", normalizeResourceName(s.Name))
 
- 		template.Resources[name] = &secretsmanager.Secret{
 
- 			Description:  "",
 
- 			SecretString: string(secret),
 
- 			Tags: []tags.Tag{
 
- 				{
 
- 					Key:   compose.ProjectTag,
 
- 					Value: project.Name,
 
- 				},
 
- 			},
 
- 		}
 
- 		s.Name = cloudformation.Ref(name)
 
- 		project.Secrets[i] = s
 
- 	}
 
- 	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)
 
- 	for _, service := range project.Services {
 
- 		definition, err := convert(project, service)
 
- 		if err != nil {
 
- 			return nil, err
 
- 		}
 
- 		taskExecutionRole, err := createTaskExecutionRole(service, err, definition, template)
 
- 		if err != nil {
 
- 			return template, err
 
- 		}
 
- 		definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
 
- 		taskDefinition := fmt.Sprintf("%sTaskDefinition", normalizeResourceName(service.Name))
 
- 		template.Resources[taskDefinition] = definition
 
- 		var healthCheck *cloudmap.Service_HealthCheckConfig
 
- 		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 {
 
- 					protocol = elbv2.ProtocolEnumHttps
 
- 					if port.Published == 80 {
 
- 						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),
 
- 					})
 
- 				}
 
- 			}
 
- 		}
 
- 		desiredCount := 1
 
- 		if service.Deploy != nil && service.Deploy.Replicas != nil {
 
- 			desiredCount = int(*service.Deploy.Replicas)
 
- 		}
 
- 		for _, dependency := range service.DependsOn {
 
- 			dependsOn = append(dependsOn, serviceResourceName(dependency))
 
- 		}
 
- 		minPercent, maxPercent, err := computeRollingUpdateLimits(service)
 
- 		if err != nil {
 
- 			return nil, err
 
- 		}
 
- 		template.Resources[serviceResourceName(service.Name)] = &ecs.Service{
 
- 			AWSCloudFormationDependsOn: dependsOn,
 
- 			Cluster:                    cluster,
 
- 			DesiredCount:               desiredCount,
 
- 			DeploymentController: &ecs.Service_DeploymentController{
 
- 				Type: ecsapi.DeploymentControllerTypeEcs,
 
- 			},
 
- 			DeploymentConfiguration: &ecs.Service_DeploymentConfiguration{
 
- 				MaximumPercent:        maxPercent,
 
- 				MinimumHealthyPercent: minPercent,
 
- 			},
 
- 			LaunchType:    ecsapi.LaunchTypeFargate,
 
- 			LoadBalancers: serviceLB,
 
- 			NetworkConfiguration: &ecs.Service_NetworkConfiguration{
 
- 				AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
 
- 					AssignPublicIp: ecsapi.AssignPublicIpEnabled,
 
- 					SecurityGroups: serviceSecurityGroups,
 
- 					Subnets: []string{
 
- 						cloudformation.Ref(parameterSubnet1Id),
 
- 						cloudformation.Ref(parameterSubnet2Id),
 
- 					},
 
- 				},
 
- 			},
 
- 			PropagateTags:      ecsapi.PropagateTagsService,
 
- 			SchedulingStrategy: ecsapi.SchedulingStrategyReplica,
 
- 			ServiceRegistries:  []ecs.Service_ServiceRegistry{serviceRegistry},
 
- 			Tags: []tags.Tag{
 
- 				{
 
- 					Key:   compose.ProjectTag,
 
- 					Value: project.Name,
 
- 				},
 
- 				{
 
- 					Key:   compose.ServiceTag,
 
- 					Value: service.Name,
 
- 				},
 
- 			},
 
- 			TaskDefinition: cloudformation.Ref(normalizeResourceName(taskDefinition)),
 
- 		}
 
- 	}
 
- 	return template, nil
 
- }
 
- func createLogGroup(project *types.Project, template *cloudformation.Template) {
 
- 	retention := 0
 
- 	if v, ok := project.Extensions[extensionRetention]; ok {
 
- 		retention = v.(int)
 
- 	}
 
- 	logGroup := fmt.Sprintf("/docker-compose/%s", project.Name)
 
- 	template.Resources["LogGroup"] = &logs.LogGroup{
 
- 		LogGroupName:    logGroup,
 
- 		RetentionInDays: retention,
 
- 	}
 
- }
 
- func computeRollingUpdateLimits(service types.ServiceConfig) (int, int, error) {
 
- 	maxPercent := 200
 
- 	minPercent := 100
 
- 	if service.Deploy == nil || service.Deploy.UpdateConfig == nil {
 
- 		return minPercent, maxPercent, nil
 
- 	}
 
- 	updateConfig := service.Deploy.UpdateConfig
 
- 	min, okMin := updateConfig.Extensions[extensionMinPercent]
 
- 	if okMin {
 
- 		minPercent = min.(int)
 
- 	}
 
- 	max, okMax := updateConfig.Extensions[extensionMaxPercent]
 
- 	if okMax {
 
- 		maxPercent = max.(int)
 
- 	}
 
- 	if okMin && okMax {
 
- 		return minPercent, maxPercent, nil
 
- 	}
 
- 	if updateConfig.Parallelism != nil {
 
- 		parallelism := int(*updateConfig.Parallelism)
 
- 		if service.Deploy.Replicas == nil {
 
- 			return minPercent, maxPercent,
 
- 				fmt.Errorf("rolling update configuration require deploy.replicas to be set")
 
- 		}
 
- 		replicas := int(*service.Deploy.Replicas)
 
- 		if replicas < parallelism {
 
- 			return minPercent, maxPercent,
 
- 				fmt.Errorf("deploy.replicas (%d) must be greater than deploy.update_config.parallelism (%d)", replicas, parallelism)
 
- 		}
 
- 		if !okMin {
 
- 			minPercent = (replicas - parallelism) * 100 / replicas
 
- 		}
 
- 		if !okMax {
 
- 			maxPercent = (replicas + parallelism) * 100 / replicas
 
- 		}
 
- 	}
 
- 	return minPercent, maxPercent, nil
 
- }
 
- func getLoadBalancerType(project *types.Project) string {
 
- 	for _, service := range project.Services {
 
- 		for _, port := range service.Ports {
 
- 			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: []tags.Tag{
 
- 			{
 
- 				Key:   compose.ProjectTag,
 
- 				Value: project.Name,
 
- 			},
 
- 		},
 
- 		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 {
 
- 	listenerName := fmt.Sprintf(
 
- 		"%s%s%dListener",
 
- 		normalizeResourceName(service.Name),
 
- 		strings.ToUpper(port.Protocol),
 
- 		port.Target,
 
- 	)
 
- 	//add listener to dependsOn
 
- 	//https://stackoverflow.com/questions/53971873/the-target-group-does-not-have-an-associated-load-balancer
 
- 	template.Resources[listenerName] = &elasticloadbalancingv2.Listener{
 
- 		DefaultActions: []elasticloadbalancingv2.Listener_Action{
 
- 			{
 
- 				ForwardConfig: &elasticloadbalancingv2.Listener_ForwardConfig{
 
- 					TargetGroups: []elasticloadbalancingv2.Listener_TargetGroupTuple{
 
- 						{
 
- 							TargetGroupArn: cloudformation.Ref(targetGroupName),
 
- 						},
 
- 					},
 
- 				},
 
- 				Type: elbv2.ActionTypeEnumForward,
 
- 			},
 
- 		},
 
- 		LoadBalancerArn: loadBalancerARN,
 
- 		Protocol:        protocol,
 
- 		Port:            int(port.Target),
 
- 	}
 
- 	return listenerName
 
- }
 
- func 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),
 
- 		strings.ToUpper(port.Protocol),
 
- 		port.Published,
 
- 	)
 
- 	template.Resources[targetGroupName] = &elasticloadbalancingv2.TargetGroup{
 
- 		Port:     int(port.Target),
 
- 		Protocol: protocol,
 
- 		Tags: []tags.Tag{
 
- 			{
 
- 				Key:   compose.ProjectTag,
 
- 				Value: project.Name,
 
- 			},
 
- 		},
 
- 		VpcId:      cloudformation.Ref(parameterVPCId),
 
- 		TargetType: elbv2.TargetTypeEnumIp,
 
- 	}
 
- 	return targetGroupName
 
- }
 
- func 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"),
 
- 	}
 
- 	template.Resources[serviceRegistration] = &cloudmap.Service{
 
- 		Description:       fmt.Sprintf("%q service discovery entry in Cloud Map", service.Name),
 
- 		HealthCheckConfig: healthCheck,
 
- 		HealthCheckCustomConfig: &cloudmap.Service_HealthCheckCustomConfig{
 
- 			FailureThreshold: 1,
 
- 		},
 
- 		Name:        service.Name,
 
- 		NamespaceId: cloudformation.Ref("CloudMap"),
 
- 		DnsConfig: &cloudmap.Service_DnsConfig{
 
- 			DnsRecords: []cloudmap.Service_DnsRecord{
 
- 				{
 
- 					TTL:  60,
 
- 					Type: cloudmapapi.RecordTypeA,
 
- 				},
 
- 			},
 
- 			RoutingPolicy: cloudmapapi.RoutingPolicyMultivalue,
 
- 		},
 
- 	}
 
- 	return serviceRegistry
 
- }
 
- func createTaskExecutionRole(service types.ServiceConfig, err error, definition *ecs.TaskDefinition, template *cloudformation.Template) (string, error) {
 
- 	taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name))
 
- 	policy := getPolicy(definition)
 
- 	if err != nil {
 
- 		return taskExecutionRole, err
 
- 	}
 
- 	rolePolicies := []iam.Role_Policy{}
 
- 	if policy != nil {
 
- 		rolePolicies = append(rolePolicies, iam.Role_Policy{
 
- 			PolicyDocument: policy,
 
- 			PolicyName:     fmt.Sprintf("%sGrantAccessToSecrets", service.Name),
 
- 		})
 
- 	}
 
- 	if roles, ok := service.Extensions[extensionRole]; ok {
 
- 		rolePolicies = append(rolePolicies, iam.Role_Policy{
 
- 			PolicyDocument: roles,
 
- 		})
 
- 	}
 
- 	managedPolicies := []string{
 
- 		ecsTaskExecutionPolicy,
 
- 		ecrReadOnlyPolicy,
 
- 	}
 
- 	if v, ok := service.Extensions[extensionManagedPolicies]; ok {
 
- 		for _, s := range v.([]interface{}) {
 
- 			managedPolicies = append(managedPolicies, s.(string))
 
- 		}
 
- 	}
 
- 	template.Resources[taskExecutionRole] = &iam.Role{
 
- 		AssumeRolePolicyDocument: assumeRolePolicyDocument,
 
- 		Policies:                 rolePolicies,
 
- 		ManagedPolicyArns:        managedPolicies,
 
- 	}
 
- 	return taskExecutionRole, nil
 
- }
 
- func createCluster(project *types.Project, template *cloudformation.Template) string {
 
- 	template.Resources["Cluster"] = &ecs.Cluster{
 
- 		ClusterName: project.Name,
 
- 		Tags: []tags.Tag{
 
- 			{
 
- 				Key:   compose.ProjectTag,
 
- 				Value: project.Name,
 
- 			},
 
- 		},
 
- 		AWSCloudFormationCondition: "CreateCluster",
 
- 	}
 
- 	cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(parameterClusterName))
 
- 	return cluster
 
- }
 
- func 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),
 
- 	}
 
- }
 
- func convertNetwork(project *types.Project, net types.NetworkConfig, vpc string, template *cloudformation.Template) string {
 
- 	if sg, ok := net.Extensions[extensionSecurityGroup]; ok {
 
- 		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 {
 
- 					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:  strings.ToUpper(port.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: []tags.Tag{
 
- 			{
 
- 				Key:   compose.ProjectTag,
 
- 				Value: project.Name,
 
- 			},
 
- 			{
 
- 				Key:   compose.NetworkTag,
 
- 				Value: net.Name,
 
- 			},
 
- 		},
 
- 	}
 
- 	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(dependency string) string {
 
- 	return fmt.Sprintf("%sService", normalizeResourceName(dependency))
 
- }
 
- func normalizeResourceName(s string) string {
 
- 	return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, ""))
 
- }
 
- func getPolicy(taskDef *ecs.TaskDefinition) *PolicyDocument {
 
- 	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)
 
- 			}
 
- 		}
 
- 	}
 
- 	if len(arns) > 0 {
 
- 		return &PolicyDocument{
 
- 			Statement: []PolicyStatement{
 
- 				{
 
- 					Effect:   "Allow",
 
- 					Action:   []string{actionGetSecretValue, actionGetParameters, actionDecrypt},
 
- 					Resource: arns,
 
- 				}},
 
- 		}
 
- 	}
 
- 	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
 
- }
 
 
  |