|
|
@@ -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 <service>.<project>.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)
|