diff --git a/ecs/pkg/amazon/cloudformation.go b/ecs/pkg/amazon/cloudformation.go index 98970753c..0e6966bb1 100644 --- a/ecs/pkg/amazon/cloudformation.go +++ b/ecs/pkg/amazon/cloudformation.go @@ -69,17 +69,7 @@ func (c client) Convert(project *compose.Project) (*cloudformation.Template, err // Create Cluster is `ParameterClusterName` parameter is not set template.Conditions["CreateCluster"] = cloudformation.Equals("", cloudformation.Ref(ParameterClusterName)) - template.Resources["Cluster"] = &ecs.Cluster{ - ClusterName: project.Name, - Tags: []tags.Tag{ - { - Key: ProjectTag, - Value: project.Name, - }, - }, - AWSCloudFormationCondition: "CreateCluster", - } - cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(ParameterClusterName)) + cluster := c.createCluster(project, template) networks := map[string]string{} for _, net := range project.Networks { @@ -92,34 +82,8 @@ func (c client) Convert(project *compose.Project) (*cloudformation.Template, err } // Private DNS namespace will allow DNS name for the services to be ..local - 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), - } - - loadBalancerType := "network" - loadBalancerName := fmt.Sprintf( - "%s%sLB", - strings.Title(project.Name), - strings.ToUpper(loadBalancerType[0:1]), - ) - loadBalancer := &elasticloadbalancingv2.LoadBalancer{ - Name: loadBalancerName, - Scheme: "internet-facing", - Subnets: []string{ - cloudformation.Ref(ParameterSubnet1Id), - cloudformation.Ref(ParameterSubnet2Id), - }, - Tags: []tags.Tag{ - { - Key: ProjectTag, - Value: project.Name, - }, - }, - Type: loadBalancerType, - } - template.Resources[loadBalancerName] = loadBalancer + c.createCloudMap(project, template) + loadBalancer := c.createLoadBalancer(project, template) for _, service := range project.Services { definition, err := Convert(project, service) @@ -127,30 +91,13 @@ func (c client) Convert(project *compose.Project) (*cloudformation.Template, err return nil, err } - taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name)) - policy, err := c.getPolicy(definition) + taskExecutionRole, err := c.createTaskExecutionRole(service, err, definition, template) if err != nil { - return nil, err - } - rolePolicies := []iam.Role_Policy{} - if policy != nil { - rolePolicies = append(rolePolicies, iam.Role_Policy{ - PolicyDocument: policy, - PolicyName: fmt.Sprintf("%sGrantAccessToSecrets", service.Name), - }) - + return template, err } definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole) taskDefinition := fmt.Sprintf("%sTaskDefinition", normalizeResourceName(service.Name)) - template.Resources[taskExecutionRole] = &iam.Role{ - AssumeRolePolicyDocument: assumeRolePolicyDocument, - Policies: rolePolicies, - ManagedPolicyArns: []string{ - ECSTaskExecutionPolicy, - ECRReadOnlyPolicy, - }, - } template.Resources[taskDefinition] = definition var healthCheck *cloudmap.Service_HealthCheckConfig @@ -158,26 +105,7 @@ func (c client) Convert(project *compose.Project) (*cloudformation.Template, err // FIXME ECS only support HTTP(s) health checks, while Docker only support CMD } - 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, - Name: service.Name, - NamespaceId: cloudformation.Ref("CloudMap"), - DnsConfig: &cloudmap.Service_DnsConfig{ - DnsRecords: []cloudmap.Service_DnsRecord{ - { - TTL: 60, - Type: cloudmapapi.RecordTypeA, - }, - }, - RoutingPolicy: cloudmapapi.RoutingPolicyMultivalue, - }, - } + serviceRegistry := c.createServiceRegistry(service, template, healthCheck) serviceSecurityGroups := []string{} for net := range service.Networks { @@ -188,54 +116,10 @@ func (c client) Convert(project *compose.Project) (*cloudformation.Template, err serviceLB := []ecs.Service_LoadBalancer{} if len(service.Ports) > 0 { for _, port := range service.Ports { - - protocolType := strings.ToUpper(port.Protocol) - targetGroupName := fmt.Sprintf( - "%s%s%sTargetGroup", - normalizeResourceName(service.Name), - strings.ToUpper(port.Protocol), - string(port.Published), - ) - template.Resources[targetGroupName] = &elasticloadbalancingv2.TargetGroup{ - Name: targetGroupName, - Port: int(port.Target), - Protocol: protocolType, - Tags: []tags.Tag{ - { - Key: ProjectTag, - Value: project.Name, - }, - }, - VpcId: cloudformation.Ref(ParameterVPCId), - TargetType: elbv2.TargetTypeEnumIp, - } - listenerName := fmt.Sprintf( - "%s%s%sListener", - normalizeResourceName(service.Name), - strings.ToUpper(port.Protocol), - string(port.Published), - ) - //add listener to dependsOn - //https://stackoverflow.com/questions/53971873/the-target-group-does-not-have-an-associated-load-balancer + protocol := strings.ToUpper(port.Protocol) + targetGroupName := c.createTargetGroup(project, service, port, template, protocol) + listenerName := c.createListener(service, port, template, targetGroupName, loadBalancer, protocol) dependsOn = append(dependsOn, listenerName) - 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: cloudformation.Ref(loadBalancerName), - Protocol: protocolType, - Port: int(port.Published), - } - serviceLB = append(serviceLB, ecs.Service_LoadBalancer{ ContainerName: service.Name, ContainerPort: int(port.Published), @@ -287,6 +171,156 @@ func (c client) Convert(project *compose.Project) (*cloudformation.Template, err return template, nil } +func (c client) createLoadBalancer(project *compose.Project, template *cloudformation.Template) string { + loadBalancerType := "network" + loadBalancerName := fmt.Sprintf( + "%s%sLB", + strings.Title(project.Name), + strings.ToUpper(loadBalancerType[0:1]), + ) + loadBalancer := &elasticloadbalancingv2.LoadBalancer{ + Name: loadBalancerName, + Scheme: "internet-facing", + Subnets: []string{ + cloudformation.Ref(ParameterSubnet1Id), + cloudformation.Ref(ParameterSubnet2Id), + }, + Tags: []tags.Tag{ + { + Key: ProjectTag, + Value: project.Name, + }, + }, + Type: loadBalancerType, + } + template.Resources[loadBalancerName] = loadBalancer + return loadBalancerName +} + +func (c client) createListener(service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, targetGroupName string, loadBalancerName string, protocol string) string { + listenerName := fmt.Sprintf( + "%s%s%sListener", + normalizeResourceName(service.Name), + strings.ToUpper(port.Protocol), + string(port.Published), + ) + //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: cloudformation.Ref(loadBalancerName), + Protocol: protocol, + Port: int(port.Published), + } + return listenerName +} + +func (c client) createTargetGroup(project *compose.Project, service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, protocol string) string { + targetGroupName := fmt.Sprintf( + "%s%s%sTargetGroup", + normalizeResourceName(service.Name), + strings.ToUpper(port.Protocol), + string(port.Published), + ) + template.Resources[targetGroupName] = &elasticloadbalancingv2.TargetGroup{ + Name: targetGroupName, + Port: int(port.Target), + Protocol: protocol, + Tags: []tags.Tag{ + { + Key: ProjectTag, + Value: project.Name, + }, + }, + VpcId: cloudformation.Ref(ParameterVPCId), + TargetType: elbv2.TargetTypeEnumIp, + } + return targetGroupName +} + +func (c client) 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, + 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 (c client) createTaskExecutionRole(service types.ServiceConfig, err error, definition *ecs.TaskDefinition, template *cloudformation.Template) (string, error) { + taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name)) + policy, err := c.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), + }) + + } + template.Resources[taskExecutionRole] = &iam.Role{ + AssumeRolePolicyDocument: assumeRolePolicyDocument, + Policies: rolePolicies, + ManagedPolicyArns: []string{ + ECSTaskExecutionPolicy, + ECRReadOnlyPolicy, + }, + } + return taskExecutionRole, nil +} + +func (c client) createCluster(project *compose.Project, template *cloudformation.Template) string { + template.Resources["Cluster"] = &ecs.Cluster{ + ClusterName: project.Name, + Tags: []tags.Tag{ + { + Key: ProjectTag, + Value: project.Name, + }, + }, + AWSCloudFormationCondition: "CreateCluster", + } + cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(ParameterClusterName)) + return cluster +} + +func (c client) createCloudMap(project *compose.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 *compose.Project, net types.NetworkConfig, vpc string, template *cloudformation.Template) string { if sg, ok := net.Extras[ExtensionSecurityGroup]; ok { logrus.Debugf("Security Group for network %q set by user to %q", net.Name, sg)