cloudformation.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package amazon
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "strings"
  7. "github.com/awslabs/goformation/v4/cloudformation/logs"
  8. ecsapi "github.com/aws/aws-sdk-go/service/ecs"
  9. "github.com/awslabs/goformation/v4/cloudformation"
  10. "github.com/awslabs/goformation/v4/cloudformation/ec2"
  11. "github.com/awslabs/goformation/v4/cloudformation/ecs"
  12. "github.com/awslabs/goformation/v4/cloudformation/iam"
  13. cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
  14. "github.com/docker/ecs-plugin/pkg/compose"
  15. )
  16. func (c client) Convert(ctx context.Context, project *compose.Project) (*cloudformation.Template, error) {
  17. template := cloudformation.NewTemplate()
  18. vpc, err := c.GetVPC(ctx, project)
  19. if err != nil {
  20. return nil, err
  21. }
  22. subnets, err := c.api.GetSubNets(ctx, vpc)
  23. if err != nil {
  24. return nil, err
  25. }
  26. var ingresses = []ec2.SecurityGroup_Ingress{}
  27. for _, service := range project.Services {
  28. for _, port := range service.Ports {
  29. ingresses = append(ingresses, ec2.SecurityGroup_Ingress{
  30. CidrIp: "0.0.0.0/0",
  31. Description: fmt.Sprintf("%s:%d/%s", service.Name, port.Target, port.Protocol),
  32. FromPort: int(port.Target),
  33. IpProtocol: strings.ToUpper(port.Protocol),
  34. ToPort: int(port.Target),
  35. })
  36. }
  37. }
  38. securityGroup := fmt.Sprintf("%s Security Group", project.Name)
  39. template.Resources["SecurityGroup"] = &ec2.SecurityGroup{
  40. GroupDescription: securityGroup,
  41. GroupName: securityGroup,
  42. SecurityGroupIngress: ingresses,
  43. VpcId: vpc,
  44. }
  45. logGroup := fmt.Sprintf("/docker-compose/%s", project.Name)
  46. template.Resources["LogGroup"] = &logs.LogGroup{
  47. LogGroupName: logGroup,
  48. }
  49. // Private DNS namespace will allow DNS name for the services to be <service>.<project>.local
  50. template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{
  51. Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name),
  52. Name: fmt.Sprintf("%s.local", project.Name),
  53. Vpc: vpc,
  54. }
  55. for _, service := range project.Services {
  56. definition, err := Convert(project, service)
  57. if err != nil {
  58. return nil, err
  59. }
  60. taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", service.Name)
  61. template.Resources[taskExecutionRole] = &iam.Role{
  62. AssumeRolePolicyDocument: assumeRolePolicyDocument,
  63. // Here we can grant access to secrets/configs using a Policy { Allow,ssm:GetParameters,secret|config ARN}
  64. ManagedPolicyArns: []string{
  65. ECSTaskExecutionPolicy,
  66. },
  67. }
  68. definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
  69. // FIXME definition.TaskRoleArn = ?
  70. taskDefinition := fmt.Sprintf("%sTaskDefinition", service.Name)
  71. template.Resources[taskDefinition] = definition
  72. template.Resources[fmt.Sprintf("%sService", service.Name)] = &ecs.Service{
  73. Cluster: c.Cluster,
  74. DesiredCount: 1,
  75. LaunchType: ecsapi.LaunchTypeFargate,
  76. NetworkConfiguration: &ecs.Service_NetworkConfiguration{
  77. AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
  78. AssignPublicIp: ecsapi.AssignPublicIpEnabled,
  79. SecurityGroups: []string{cloudformation.Ref("SecurityGroup")},
  80. Subnets: subnets,
  81. },
  82. },
  83. SchedulingStrategy: ecsapi.SchedulingStrategyReplica,
  84. ServiceName: service.Name,
  85. TaskDefinition: cloudformation.Ref(taskDefinition),
  86. }
  87. var healthCheck *cloudmap.Service_HealthCheckConfig
  88. if service.HealthCheck != nil && !service.HealthCheck.Disable {
  89. // FIXME ECS only support HTTP(s) health checks, while Docker only support CMD
  90. }
  91. serviceRegistration := fmt.Sprintf("%sServiceRegistration", service.Name)
  92. template.Resources[serviceRegistration] = &cloudmap.Service{
  93. Description: fmt.Sprintf("%q registration in Service Map", service.Name),
  94. HealthCheckConfig: healthCheck,
  95. Name: serviceRegistration,
  96. NamespaceId: cloudformation.Ref("CloudMap"),
  97. }
  98. }
  99. return template, nil
  100. }
  101. func (c client) GetVPC(ctx context.Context, project *compose.Project) (string, error) {
  102. //check compose file for the default external network
  103. if net, ok := project.Networks["default"]; ok {
  104. if net.External.External {
  105. vpc := net.Name
  106. ok, err := c.api.VpcExists(ctx, vpc)
  107. if err != nil {
  108. return "", err
  109. }
  110. if !ok {
  111. return "", errors.New("Vpc does not exist: " + vpc)
  112. }
  113. return vpc, nil
  114. }
  115. }
  116. defaultVPC, err := c.api.GetDefaultVPC(ctx)
  117. if err != nil {
  118. return "", err
  119. }
  120. return defaultVPC, nil
  121. }
  122. type convertAPI interface {
  123. GetDefaultVPC(ctx context.Context) (string, error)
  124. VpcExists(ctx context.Context, vpcID string) (bool, error)
  125. GetSubNets(ctx context.Context, vpcID string) ([]string, error)
  126. GetRoleArn(ctx context.Context, name string) (string, error)
  127. }