cloudformation.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. /*
  2. Copyright 2020 Docker, Inc.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package ecs
  14. import (
  15. "context"
  16. "fmt"
  17. "io/ioutil"
  18. "regexp"
  19. "strings"
  20. "github.com/docker/compose-cli/api/compose"
  21. ecsapi "github.com/aws/aws-sdk-go/service/ecs"
  22. "github.com/aws/aws-sdk-go/service/elbv2"
  23. cloudmapapi "github.com/aws/aws-sdk-go/service/servicediscovery"
  24. "github.com/awslabs/goformation/v4/cloudformation"
  25. "github.com/awslabs/goformation/v4/cloudformation/ec2"
  26. "github.com/awslabs/goformation/v4/cloudformation/ecs"
  27. "github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2"
  28. "github.com/awslabs/goformation/v4/cloudformation/iam"
  29. "github.com/awslabs/goformation/v4/cloudformation/logs"
  30. "github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
  31. cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
  32. "github.com/awslabs/goformation/v4/cloudformation/tags"
  33. "github.com/compose-spec/compose-go/compatibility"
  34. "github.com/compose-spec/compose-go/errdefs"
  35. "github.com/compose-spec/compose-go/types"
  36. "github.com/sirupsen/logrus"
  37. )
  38. const (
  39. parameterClusterName = "ParameterClusterName"
  40. parameterVPCId = "ParameterVPCId"
  41. parameterSubnet1Id = "ParameterSubnet1Id"
  42. parameterSubnet2Id = "ParameterSubnet2Id"
  43. parameterLoadBalancerARN = "ParameterLoadBalancerARN"
  44. )
  45. func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project) ([]byte, error) {
  46. template, err := b.convert(project)
  47. if err != nil {
  48. return nil, err
  49. }
  50. return marshall(template)
  51. }
  52. // Convert a compose project into a CloudFormation template
  53. func (b *ecsAPIService) convert(project *types.Project) (*cloudformation.Template, error) { //nolint:gocyclo
  54. var checker compatibility.Checker = &fargateCompatibilityChecker{
  55. compatibility.AllowList{
  56. Supported: compatibleComposeAttributes,
  57. },
  58. }
  59. compatibility.Check(project, checker)
  60. for _, err := range checker.Errors() {
  61. if errdefs.IsIncompatibleError(err) {
  62. logrus.Error(err.Error())
  63. } else {
  64. logrus.Warn(err.Error())
  65. }
  66. }
  67. if !compatibility.IsCompatible(checker) {
  68. return nil, fmt.Errorf("compose file is incompatible with Amazon ECS")
  69. }
  70. template := cloudformation.NewTemplate()
  71. template.Description = "CloudFormation template created by Docker for deploying applications on Amazon ECS"
  72. template.Parameters[parameterClusterName] = cloudformation.Parameter{
  73. Type: "String",
  74. Description: "Name of the ECS cluster to deploy to (optional)",
  75. }
  76. template.Parameters[parameterVPCId] = cloudformation.Parameter{
  77. Type: "AWS::EC2::VPC::Id",
  78. Description: "ID of the VPC",
  79. }
  80. /*
  81. FIXME can't set subnets: Ref("SubnetIds") see https://github.com/awslabs/goformation/issues/282
  82. template.Parameters["SubnetIds"] = cloudformation.Parameter{
  83. Type: "List<AWS::EC2::Subnet::Id>",
  84. Description: "The list of SubnetIds, for at least two Availability Zones in the region in your VPC",
  85. }
  86. */
  87. template.Parameters[parameterSubnet1Id] = cloudformation.Parameter{
  88. Type: "AWS::EC2::Subnet::Id",
  89. Description: "SubnetId, for Availability Zone 1 in the region in your VPC",
  90. }
  91. template.Parameters[parameterSubnet2Id] = cloudformation.Parameter{
  92. Type: "AWS::EC2::Subnet::Id",
  93. Description: "SubnetId, for Availability Zone 2 in the region in your VPC",
  94. }
  95. template.Parameters[parameterLoadBalancerARN] = cloudformation.Parameter{
  96. Type: "String",
  97. Description: "Name of the LoadBalancer to connect to (optional)",
  98. }
  99. // Create Cluster is `ParameterClusterName` parameter is not set
  100. template.Conditions["CreateCluster"] = cloudformation.Equals("", cloudformation.Ref(parameterClusterName))
  101. cluster := createCluster(project, template)
  102. networks := map[string]string{}
  103. for _, net := range project.Networks {
  104. networks[net.Name] = convertNetwork(project, net, cloudformation.Ref(parameterVPCId), template)
  105. }
  106. for i, s := range project.Secrets {
  107. if s.External.External {
  108. continue
  109. }
  110. secret, err := ioutil.ReadFile(s.File)
  111. if err != nil {
  112. return nil, err
  113. }
  114. name := fmt.Sprintf("%sSecret", normalizeResourceName(s.Name))
  115. template.Resources[name] = &secretsmanager.Secret{
  116. Description: "",
  117. SecretString: string(secret),
  118. Tags: []tags.Tag{
  119. {
  120. Key: compose.ProjectTag,
  121. Value: project.Name,
  122. },
  123. },
  124. }
  125. s.Name = cloudformation.Ref(name)
  126. project.Secrets[i] = s
  127. }
  128. createLogGroup(project, template)
  129. // Private DNS namespace will allow DNS name for the services to be <service>.<project>.local
  130. createCloudMap(project, template)
  131. loadBalancerARN := createLoadBalancer(project, template)
  132. for _, service := range project.Services {
  133. definition, err := convert(project, service)
  134. if err != nil {
  135. return nil, err
  136. }
  137. taskExecutionRole := createTaskExecutionRole(service, definition, template)
  138. definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
  139. taskRole := createTaskRole(service, template)
  140. if taskRole != "" {
  141. definition.TaskRoleArn = cloudformation.Ref(taskRole)
  142. }
  143. taskDefinition := fmt.Sprintf("%sTaskDefinition", normalizeResourceName(service.Name))
  144. template.Resources[taskDefinition] = definition
  145. var healthCheck *cloudmap.Service_HealthCheckConfig
  146. serviceRegistry := createServiceRegistry(service, template, healthCheck)
  147. serviceSecurityGroups := []string{}
  148. for net := range service.Networks {
  149. serviceSecurityGroups = append(serviceSecurityGroups, networks[net])
  150. }
  151. dependsOn := []string{}
  152. serviceLB := []ecs.Service_LoadBalancer{}
  153. if len(service.Ports) > 0 {
  154. for _, port := range service.Ports {
  155. protocol := strings.ToUpper(port.Protocol)
  156. if getLoadBalancerType(project) == elbv2.LoadBalancerTypeEnumApplication {
  157. protocol = elbv2.ProtocolEnumHttps
  158. if port.Published == 80 {
  159. protocol = elbv2.ProtocolEnumHttp
  160. }
  161. }
  162. if loadBalancerARN != "" {
  163. targetGroupName := createTargetGroup(project, service, port, template, protocol)
  164. listenerName := createListener(service, port, template, targetGroupName, loadBalancerARN, protocol)
  165. dependsOn = append(dependsOn, listenerName)
  166. serviceLB = append(serviceLB, ecs.Service_LoadBalancer{
  167. ContainerName: service.Name,
  168. ContainerPort: int(port.Target),
  169. TargetGroupArn: cloudformation.Ref(targetGroupName),
  170. })
  171. }
  172. }
  173. }
  174. desiredCount := 1
  175. if service.Deploy != nil && service.Deploy.Replicas != nil {
  176. desiredCount = int(*service.Deploy.Replicas)
  177. }
  178. for dependency := range service.DependsOn {
  179. dependsOn = append(dependsOn, serviceResourceName(dependency))
  180. }
  181. minPercent, maxPercent, err := computeRollingUpdateLimits(service)
  182. if err != nil {
  183. return nil, err
  184. }
  185. template.Resources[serviceResourceName(service.Name)] = &ecs.Service{
  186. AWSCloudFormationDependsOn: dependsOn,
  187. Cluster: cluster,
  188. DesiredCount: desiredCount,
  189. DeploymentController: &ecs.Service_DeploymentController{
  190. Type: ecsapi.DeploymentControllerTypeEcs,
  191. },
  192. DeploymentConfiguration: &ecs.Service_DeploymentConfiguration{
  193. MaximumPercent: maxPercent,
  194. MinimumHealthyPercent: minPercent,
  195. },
  196. LaunchType: ecsapi.LaunchTypeFargate,
  197. LoadBalancers: serviceLB,
  198. NetworkConfiguration: &ecs.Service_NetworkConfiguration{
  199. AwsvpcConfiguration: &ecs.Service_AwsVpcConfiguration{
  200. AssignPublicIp: ecsapi.AssignPublicIpEnabled,
  201. SecurityGroups: serviceSecurityGroups,
  202. Subnets: []string{
  203. cloudformation.Ref(parameterSubnet1Id),
  204. cloudformation.Ref(parameterSubnet2Id),
  205. },
  206. },
  207. },
  208. PropagateTags: ecsapi.PropagateTagsService,
  209. SchedulingStrategy: ecsapi.SchedulingStrategyReplica,
  210. ServiceRegistries: []ecs.Service_ServiceRegistry{serviceRegistry},
  211. Tags: []tags.Tag{
  212. {
  213. Key: compose.ProjectTag,
  214. Value: project.Name,
  215. },
  216. {
  217. Key: compose.ServiceTag,
  218. Value: service.Name,
  219. },
  220. },
  221. TaskDefinition: cloudformation.Ref(normalizeResourceName(taskDefinition)),
  222. }
  223. }
  224. return template, nil
  225. }
  226. func createLogGroup(project *types.Project, template *cloudformation.Template) {
  227. retention := 0
  228. if v, ok := project.Extensions[extensionRetention]; ok {
  229. retention = v.(int)
  230. }
  231. logGroup := fmt.Sprintf("/docker-compose/%s", project.Name)
  232. template.Resources["LogGroup"] = &logs.LogGroup{
  233. LogGroupName: logGroup,
  234. RetentionInDays: retention,
  235. }
  236. }
  237. func computeRollingUpdateLimits(service types.ServiceConfig) (int, int, error) {
  238. maxPercent := 200
  239. minPercent := 100
  240. if service.Deploy == nil || service.Deploy.UpdateConfig == nil {
  241. return minPercent, maxPercent, nil
  242. }
  243. updateConfig := service.Deploy.UpdateConfig
  244. min, okMin := updateConfig.Extensions[extensionMinPercent]
  245. if okMin {
  246. minPercent = min.(int)
  247. }
  248. max, okMax := updateConfig.Extensions[extensionMaxPercent]
  249. if okMax {
  250. maxPercent = max.(int)
  251. }
  252. if okMin && okMax {
  253. return minPercent, maxPercent, nil
  254. }
  255. if updateConfig.Parallelism != nil {
  256. parallelism := int(*updateConfig.Parallelism)
  257. if service.Deploy.Replicas == nil {
  258. return minPercent, maxPercent,
  259. fmt.Errorf("rolling update configuration require deploy.replicas to be set")
  260. }
  261. replicas := int(*service.Deploy.Replicas)
  262. if replicas < parallelism {
  263. return minPercent, maxPercent,
  264. fmt.Errorf("deploy.replicas (%d) must be greater than deploy.update_config.parallelism (%d)", replicas, parallelism)
  265. }
  266. if !okMin {
  267. minPercent = (replicas - parallelism) * 100 / replicas
  268. }
  269. if !okMax {
  270. maxPercent = (replicas + parallelism) * 100 / replicas
  271. }
  272. }
  273. return minPercent, maxPercent, nil
  274. }
  275. func getLoadBalancerType(project *types.Project) string {
  276. for _, service := range project.Services {
  277. for _, port := range service.Ports {
  278. protocol := port.Protocol
  279. v, ok := port.Extensions[extensionProtocol]
  280. if ok {
  281. protocol = v.(string)
  282. }
  283. if protocol == "http" || protocol == "https" {
  284. continue
  285. }
  286. if port.Published != 80 && port.Published != 443 {
  287. return elbv2.LoadBalancerTypeEnumNetwork
  288. }
  289. }
  290. }
  291. return elbv2.LoadBalancerTypeEnumApplication
  292. }
  293. func getLoadBalancerSecurityGroups(project *types.Project, template *cloudformation.Template) []string {
  294. securityGroups := []string{}
  295. for _, network := range project.Networks {
  296. if !network.Internal {
  297. net := convertNetwork(project, network, cloudformation.Ref(parameterVPCId), template)
  298. securityGroups = append(securityGroups, net)
  299. }
  300. }
  301. return uniqueStrings(securityGroups)
  302. }
  303. func createLoadBalancer(project *types.Project, template *cloudformation.Template) string {
  304. ports := 0
  305. for _, service := range project.Services {
  306. ports += len(service.Ports)
  307. }
  308. if ports == 0 {
  309. // Project do not expose any port (batch jobs?)
  310. // So no need to create a PortPublisher
  311. return ""
  312. }
  313. // load balancer names are limited to 32 characters total
  314. loadBalancerName := fmt.Sprintf("%.32s", fmt.Sprintf("%sLoadBalancer", strings.Title(project.Name)))
  315. // Create PortPublisher if `ParameterLoadBalancerName` is not set
  316. template.Conditions["CreateLoadBalancer"] = cloudformation.Equals("", cloudformation.Ref(parameterLoadBalancerARN))
  317. loadBalancerType := getLoadBalancerType(project)
  318. securityGroups := []string{}
  319. if loadBalancerType == elbv2.LoadBalancerTypeEnumApplication {
  320. securityGroups = getLoadBalancerSecurityGroups(project, template)
  321. }
  322. template.Resources[loadBalancerName] = &elasticloadbalancingv2.LoadBalancer{
  323. Name: loadBalancerName,
  324. Scheme: elbv2.LoadBalancerSchemeEnumInternetFacing,
  325. SecurityGroups: securityGroups,
  326. Subnets: []string{
  327. cloudformation.Ref(parameterSubnet1Id),
  328. cloudformation.Ref(parameterSubnet2Id),
  329. },
  330. Tags: []tags.Tag{
  331. {
  332. Key: compose.ProjectTag,
  333. Value: project.Name,
  334. },
  335. },
  336. Type: loadBalancerType,
  337. AWSCloudFormationCondition: "CreateLoadBalancer",
  338. }
  339. return cloudformation.If("CreateLoadBalancer", cloudformation.Ref(loadBalancerName), cloudformation.Ref(parameterLoadBalancerARN))
  340. }
  341. func createListener(service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, targetGroupName string, loadBalancerARN string, protocol string) string {
  342. listenerName := fmt.Sprintf(
  343. "%s%s%dListener",
  344. normalizeResourceName(service.Name),
  345. strings.ToUpper(port.Protocol),
  346. port.Target,
  347. )
  348. //add listener to dependsOn
  349. //https://stackoverflow.com/questions/53971873/the-target-group-does-not-have-an-associated-load-balancer
  350. template.Resources[listenerName] = &elasticloadbalancingv2.Listener{
  351. DefaultActions: []elasticloadbalancingv2.Listener_Action{
  352. {
  353. ForwardConfig: &elasticloadbalancingv2.Listener_ForwardConfig{
  354. TargetGroups: []elasticloadbalancingv2.Listener_TargetGroupTuple{
  355. {
  356. TargetGroupArn: cloudformation.Ref(targetGroupName),
  357. },
  358. },
  359. },
  360. Type: elbv2.ActionTypeEnumForward,
  361. },
  362. },
  363. LoadBalancerArn: loadBalancerARN,
  364. Protocol: protocol,
  365. Port: int(port.Target),
  366. }
  367. return listenerName
  368. }
  369. func createTargetGroup(project *types.Project, service types.ServiceConfig, port types.ServicePortConfig, template *cloudformation.Template, protocol string) string {
  370. targetGroupName := fmt.Sprintf(
  371. "%s%s%dTargetGroup",
  372. normalizeResourceName(service.Name),
  373. strings.ToUpper(port.Protocol),
  374. port.Published,
  375. )
  376. template.Resources[targetGroupName] = &elasticloadbalancingv2.TargetGroup{
  377. Port: int(port.Target),
  378. Protocol: protocol,
  379. Tags: []tags.Tag{
  380. {
  381. Key: compose.ProjectTag,
  382. Value: project.Name,
  383. },
  384. },
  385. VpcId: cloudformation.Ref(parameterVPCId),
  386. TargetType: elbv2.TargetTypeEnumIp,
  387. }
  388. return targetGroupName
  389. }
  390. func createServiceRegistry(service types.ServiceConfig, template *cloudformation.Template, healthCheck *cloudmap.Service_HealthCheckConfig) ecs.Service_ServiceRegistry {
  391. serviceRegistration := fmt.Sprintf("%sServiceDiscoveryEntry", normalizeResourceName(service.Name))
  392. serviceRegistry := ecs.Service_ServiceRegistry{
  393. RegistryArn: cloudformation.GetAtt(serviceRegistration, "Arn"),
  394. }
  395. template.Resources[serviceRegistration] = &cloudmap.Service{
  396. Description: fmt.Sprintf("%q service discovery entry in Cloud Map", service.Name),
  397. HealthCheckConfig: healthCheck,
  398. HealthCheckCustomConfig: &cloudmap.Service_HealthCheckCustomConfig{
  399. FailureThreshold: 1,
  400. },
  401. Name: service.Name,
  402. NamespaceId: cloudformation.Ref("CloudMap"),
  403. DnsConfig: &cloudmap.Service_DnsConfig{
  404. DnsRecords: []cloudmap.Service_DnsRecord{
  405. {
  406. TTL: 60,
  407. Type: cloudmapapi.RecordTypeA,
  408. },
  409. },
  410. RoutingPolicy: cloudmapapi.RoutingPolicyMultivalue,
  411. },
  412. }
  413. return serviceRegistry
  414. }
  415. func createTaskExecutionRole(service types.ServiceConfig, definition *ecs.TaskDefinition, template *cloudformation.Template) string {
  416. taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", normalizeResourceName(service.Name))
  417. policies := createPolicies(service, definition)
  418. template.Resources[taskExecutionRole] = &iam.Role{
  419. AssumeRolePolicyDocument: assumeRolePolicyDocument,
  420. Policies: policies,
  421. ManagedPolicyArns: []string{
  422. ecsTaskExecutionPolicy,
  423. ecrReadOnlyPolicy,
  424. },
  425. }
  426. return taskExecutionRole
  427. }
  428. func createTaskRole(service types.ServiceConfig, template *cloudformation.Template) string {
  429. taskRole := fmt.Sprintf("%sTaskRole", normalizeResourceName(service.Name))
  430. rolePolicies := []iam.Role_Policy{}
  431. if roles, ok := service.Extensions[extensionRole]; ok {
  432. rolePolicies = append(rolePolicies, iam.Role_Policy{
  433. PolicyDocument: roles,
  434. })
  435. }
  436. managedPolicies := []string{}
  437. if v, ok := service.Extensions[extensionManagedPolicies]; ok {
  438. for _, s := range v.([]interface{}) {
  439. managedPolicies = append(managedPolicies, s.(string))
  440. }
  441. }
  442. if len(rolePolicies) == 0 && len(managedPolicies) == 0 {
  443. return ""
  444. }
  445. template.Resources[taskRole] = &iam.Role{
  446. AssumeRolePolicyDocument: assumeRolePolicyDocument,
  447. Policies: rolePolicies,
  448. ManagedPolicyArns: managedPolicies,
  449. }
  450. return taskRole
  451. }
  452. func createCluster(project *types.Project, template *cloudformation.Template) string {
  453. template.Resources["Cluster"] = &ecs.Cluster{
  454. ClusterName: project.Name,
  455. Tags: []tags.Tag{
  456. {
  457. Key: compose.ProjectTag,
  458. Value: project.Name,
  459. },
  460. },
  461. AWSCloudFormationCondition: "CreateCluster",
  462. }
  463. cluster := cloudformation.If("CreateCluster", cloudformation.Ref("Cluster"), cloudformation.Ref(parameterClusterName))
  464. return cluster
  465. }
  466. func createCloudMap(project *types.Project, template *cloudformation.Template) {
  467. template.Resources["CloudMap"] = &cloudmap.PrivateDnsNamespace{
  468. Description: fmt.Sprintf("Service Map for Docker Compose project %s", project.Name),
  469. Name: fmt.Sprintf("%s.local", project.Name),
  470. Vpc: cloudformation.Ref(parameterVPCId),
  471. }
  472. }
  473. func convertNetwork(project *types.Project, net types.NetworkConfig, vpc string, template *cloudformation.Template) string {
  474. if net.External.External {
  475. return net.Name
  476. }
  477. if sg, ok := net.Extensions[extensionSecurityGroup]; ok {
  478. logrus.Warn("to use an existing security-group, set `network.external` and `network.name` in your compose file")
  479. logrus.Debugf("Security Group for network %q set by user to %q", net.Name, sg)
  480. return sg.(string)
  481. }
  482. var ingresses []ec2.SecurityGroup_Ingress
  483. if !net.Internal {
  484. for _, service := range project.Services {
  485. if _, ok := service.Networks[net.Name]; ok {
  486. for _, port := range service.Ports {
  487. ingresses = append(ingresses, ec2.SecurityGroup_Ingress{
  488. CidrIp: "0.0.0.0/0",
  489. Description: fmt.Sprintf("%s:%d/%s", service.Name, port.Target, port.Protocol),
  490. FromPort: int(port.Target),
  491. IpProtocol: strings.ToUpper(port.Protocol),
  492. ToPort: int(port.Target),
  493. })
  494. }
  495. }
  496. }
  497. }
  498. securityGroup := networkResourceName(project, net.Name)
  499. template.Resources[securityGroup] = &ec2.SecurityGroup{
  500. GroupDescription: fmt.Sprintf("%s %s Security Group", project.Name, net.Name),
  501. GroupName: securityGroup,
  502. SecurityGroupIngress: ingresses,
  503. VpcId: vpc,
  504. Tags: []tags.Tag{
  505. {
  506. Key: compose.ProjectTag,
  507. Value: project.Name,
  508. },
  509. {
  510. Key: compose.NetworkTag,
  511. Value: net.Name,
  512. },
  513. },
  514. }
  515. ingress := securityGroup + "Ingress"
  516. template.Resources[ingress] = &ec2.SecurityGroupIngress{
  517. Description: fmt.Sprintf("Allow communication within network %s", net.Name),
  518. IpProtocol: "-1", // all protocols
  519. GroupId: cloudformation.Ref(securityGroup),
  520. SourceSecurityGroupId: cloudformation.Ref(securityGroup),
  521. }
  522. return cloudformation.Ref(securityGroup)
  523. }
  524. func networkResourceName(project *types.Project, network string) string {
  525. return fmt.Sprintf("%s%sNetwork", normalizeResourceName(project.Name), normalizeResourceName(network))
  526. }
  527. func serviceResourceName(dependency string) string {
  528. return fmt.Sprintf("%sService", normalizeResourceName(dependency))
  529. }
  530. func normalizeResourceName(s string) string {
  531. return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, ""))
  532. }
  533. func createPolicies(service types.ServiceConfig, taskDef *ecs.TaskDefinition) []iam.Role_Policy {
  534. arns := []string{}
  535. for _, container := range taskDef.ContainerDefinitions {
  536. if container.RepositoryCredentials != nil {
  537. arns = append(arns, container.RepositoryCredentials.CredentialsParameter)
  538. }
  539. if len(container.Secrets) > 0 {
  540. for _, s := range container.Secrets {
  541. arns = append(arns, s.ValueFrom)
  542. }
  543. }
  544. }
  545. if len(arns) > 0 {
  546. return []iam.Role_Policy{
  547. {
  548. PolicyDocument: &PolicyDocument{
  549. Statement: []PolicyStatement{
  550. {
  551. Effect: "Allow",
  552. Action: []string{actionGetSecretValue, actionGetParameters, actionDecrypt},
  553. Resource: arns,
  554. },
  555. },
  556. },
  557. PolicyName: fmt.Sprintf("%sGrantAccessToSecrets", service.Name),
  558. },
  559. }
  560. }
  561. return nil
  562. }
  563. func uniqueStrings(items []string) []string {
  564. keys := make(map[string]bool)
  565. unique := []string{}
  566. for _, item := range items {
  567. if _, val := keys[item]; !val {
  568. keys[item] = true
  569. unique = append(unique, item)
  570. }
  571. }
  572. return unique
  573. }