1
0

sdk.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  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. "strings"
  18. "time"
  19. "github.com/aws/aws-sdk-go/aws/client"
  20. "github.com/docker/compose-cli/api/compose"
  21. "github.com/docker/compose-cli/api/secrets"
  22. "github.com/aws/aws-sdk-go/aws"
  23. "github.com/aws/aws-sdk-go/service/cloudformation"
  24. "github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
  25. "github.com/aws/aws-sdk-go/service/cloudwatchlogs"
  26. "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface"
  27. "github.com/aws/aws-sdk-go/service/ec2"
  28. "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
  29. "github.com/aws/aws-sdk-go/service/ecs"
  30. "github.com/aws/aws-sdk-go/service/ecs/ecsiface"
  31. "github.com/aws/aws-sdk-go/service/elbv2"
  32. "github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
  33. "github.com/aws/aws-sdk-go/service/iam"
  34. "github.com/aws/aws-sdk-go/service/iam/iamiface"
  35. "github.com/aws/aws-sdk-go/service/secretsmanager"
  36. "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
  37. cf "github.com/awslabs/goformation/v4/cloudformation"
  38. "github.com/sirupsen/logrus"
  39. )
  40. type sdk struct {
  41. ECS ecsiface.ECSAPI
  42. EC2 ec2iface.EC2API
  43. ELB elbv2iface.ELBV2API
  44. CW cloudwatchlogsiface.CloudWatchLogsAPI
  45. IAM iamiface.IAMAPI
  46. CF cloudformationiface.CloudFormationAPI
  47. SM secretsmanageriface.SecretsManagerAPI
  48. }
  49. func newSDK(sess client.ConfigProvider) sdk {
  50. return sdk{
  51. ECS: ecs.New(sess),
  52. EC2: ec2.New(sess),
  53. ELB: elbv2.New(sess),
  54. CW: cloudwatchlogs.New(sess),
  55. IAM: iam.New(sess),
  56. CF: cloudformation.New(sess),
  57. SM: secretsmanager.New(sess),
  58. }
  59. }
  60. func (s sdk) CheckRequirements(ctx context.Context, region string) error {
  61. settings, err := s.ECS.ListAccountSettingsWithContext(ctx, &ecs.ListAccountSettingsInput{
  62. EffectiveSettings: aws.Bool(true),
  63. Name: aws.String("serviceLongArnFormat"),
  64. })
  65. if err != nil {
  66. return err
  67. }
  68. serviceLongArnFormat := settings.Settings[0].Value
  69. if *serviceLongArnFormat != "enabled" {
  70. return fmt.Errorf("this tool requires the \"new ARN resource ID format\".\n"+
  71. "Check https://%s.console.aws.amazon.com/ecs/home#/settings\n"+
  72. "Learn more: https://aws.amazon.com/blogs/compute/migrating-your-amazon-ecs-deployment-to-the-new-arn-and-resource-id-format-2", region)
  73. }
  74. return nil
  75. }
  76. func (s sdk) ClusterExists(ctx context.Context, name string) (bool, error) {
  77. logrus.Debug("CheckRequirements if cluster was already created: ", name)
  78. clusters, err := s.ECS.DescribeClustersWithContext(ctx, &ecs.DescribeClustersInput{
  79. Clusters: []*string{aws.String(name)},
  80. })
  81. if err != nil {
  82. return false, err
  83. }
  84. return len(clusters.Clusters) > 0, nil
  85. }
  86. func (s sdk) CreateCluster(ctx context.Context, name string) (string, error) {
  87. logrus.Debug("Create cluster ", name)
  88. response, err := s.ECS.CreateClusterWithContext(ctx, &ecs.CreateClusterInput{ClusterName: aws.String(name)})
  89. if err != nil {
  90. return "", err
  91. }
  92. return *response.Cluster.Status, nil
  93. }
  94. func (s sdk) CheckVPC(ctx context.Context, vpcID string) error {
  95. logrus.Debug("CheckRequirements on VPC : ", vpcID)
  96. output, err := s.EC2.DescribeVpcAttributeWithContext(ctx, &ec2.DescribeVpcAttributeInput{
  97. VpcId: aws.String(vpcID),
  98. Attribute: aws.String("enableDnsSupport"),
  99. })
  100. if err != nil {
  101. return err
  102. }
  103. if !*output.EnableDnsSupport.Value {
  104. return fmt.Errorf("VPC %q doesn't have DNS resolution enabled", vpcID)
  105. }
  106. return err
  107. }
  108. func (s sdk) GetDefaultVPC(ctx context.Context) (string, error) {
  109. logrus.Debug("Retrieve default VPC")
  110. vpcs, err := s.EC2.DescribeVpcsWithContext(ctx, &ec2.DescribeVpcsInput{
  111. Filters: []*ec2.Filter{
  112. {
  113. Name: aws.String("isDefault"),
  114. Values: []*string{aws.String("true")},
  115. },
  116. },
  117. })
  118. if err != nil {
  119. return "", err
  120. }
  121. if len(vpcs.Vpcs) == 0 {
  122. return "", fmt.Errorf("account has not default VPC")
  123. }
  124. return *vpcs.Vpcs[0].VpcId, nil
  125. }
  126. func (s sdk) GetSubNets(ctx context.Context, vpcID string) ([]string, error) {
  127. logrus.Debug("Retrieve SubNets")
  128. subnets, err := s.EC2.DescribeSubnetsWithContext(ctx, &ec2.DescribeSubnetsInput{
  129. DryRun: nil,
  130. Filters: []*ec2.Filter{
  131. {
  132. Name: aws.String("vpc-id"),
  133. Values: []*string{aws.String(vpcID)},
  134. },
  135. },
  136. })
  137. if err != nil {
  138. return nil, err
  139. }
  140. ids := []string{}
  141. for _, subnet := range subnets.Subnets {
  142. ids = append(ids, *subnet.SubnetId)
  143. }
  144. return ids, nil
  145. }
  146. func (s sdk) GetRoleArn(ctx context.Context, name string) (string, error) {
  147. role, err := s.IAM.GetRoleWithContext(ctx, &iam.GetRoleInput{
  148. RoleName: aws.String(name),
  149. })
  150. if err != nil {
  151. return "", err
  152. }
  153. return *role.Role.Arn, nil
  154. }
  155. func (s sdk) StackExists(ctx context.Context, name string) (bool, error) {
  156. stacks, err := s.CF.DescribeStacksWithContext(ctx, &cloudformation.DescribeStacksInput{
  157. StackName: aws.String(name),
  158. })
  159. if err != nil {
  160. if strings.HasPrefix(err.Error(), fmt.Sprintf("ValidationError: Stack with id %s does not exist", name)) {
  161. return false, nil
  162. }
  163. return false, nil
  164. }
  165. return len(stacks.Stacks) > 0, nil
  166. }
  167. func (s sdk) CreateStack(ctx context.Context, name string, template *cf.Template, parameters map[string]string) error {
  168. logrus.Debug("Create CloudFormation stack")
  169. json, err := marshall(template)
  170. if err != nil {
  171. return err
  172. }
  173. param := []*cloudformation.Parameter{}
  174. for name, value := range parameters {
  175. param = append(param, &cloudformation.Parameter{
  176. ParameterKey: aws.String(name),
  177. ParameterValue: aws.String(value),
  178. })
  179. }
  180. _, err = s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{
  181. OnFailure: aws.String("DELETE"),
  182. StackName: aws.String(name),
  183. TemplateBody: aws.String(string(json)),
  184. Parameters: param,
  185. TimeoutInMinutes: nil,
  186. Capabilities: []*string{
  187. aws.String(cloudformation.CapabilityCapabilityIam),
  188. },
  189. Tags: []*cloudformation.Tag{
  190. {
  191. Key: aws.String(compose.ProjectTag),
  192. Value: aws.String(name),
  193. },
  194. },
  195. })
  196. return err
  197. }
  198. func (s sdk) CreateChangeSet(ctx context.Context, name string, template *cf.Template, parameters map[string]string) (string, error) {
  199. logrus.Debug("Create CloudFormation Changeset")
  200. json, err := marshall(template)
  201. if err != nil {
  202. return "", err
  203. }
  204. param := []*cloudformation.Parameter{}
  205. for name := range parameters {
  206. param = append(param, &cloudformation.Parameter{
  207. ParameterKey: aws.String(name),
  208. UsePreviousValue: aws.Bool(true),
  209. })
  210. }
  211. update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05"))
  212. changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{
  213. ChangeSetName: aws.String(update),
  214. ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate),
  215. StackName: aws.String(name),
  216. TemplateBody: aws.String(string(json)),
  217. Parameters: param,
  218. Capabilities: []*string{
  219. aws.String(cloudformation.CapabilityCapabilityIam),
  220. },
  221. })
  222. if err != nil {
  223. return "", err
  224. }
  225. err = s.CF.WaitUntilChangeSetCreateCompleteWithContext(ctx, &cloudformation.DescribeChangeSetInput{
  226. ChangeSetName: changeset.Id,
  227. })
  228. return *changeset.Id, err
  229. }
  230. func (s sdk) UpdateStack(ctx context.Context, changeset string) error {
  231. desc, err := s.CF.DescribeChangeSetWithContext(ctx, &cloudformation.DescribeChangeSetInput{
  232. ChangeSetName: aws.String(changeset),
  233. })
  234. if err != nil {
  235. return err
  236. }
  237. if strings.HasPrefix(aws.StringValue(desc.StatusReason), "The submitted information didn't contain changes.") {
  238. return nil
  239. }
  240. _, err = s.CF.ExecuteChangeSet(&cloudformation.ExecuteChangeSetInput{
  241. ChangeSetName: aws.String(changeset),
  242. })
  243. return err
  244. }
  245. const (
  246. stackCreate = iota
  247. stackUpdate
  248. stackDelete
  249. )
  250. func (s sdk) WaitStackComplete(ctx context.Context, name string, operation int) error {
  251. input := &cloudformation.DescribeStacksInput{
  252. StackName: aws.String(name),
  253. }
  254. switch operation {
  255. case stackCreate:
  256. return s.CF.WaitUntilStackCreateCompleteWithContext(ctx, input)
  257. case stackDelete:
  258. return s.CF.WaitUntilStackDeleteCompleteWithContext(ctx, input)
  259. default:
  260. return fmt.Errorf("internal error: unexpected stack operation %d", operation)
  261. }
  262. }
  263. func (s sdk) GetStackID(ctx context.Context, name string) (string, error) {
  264. stacks, err := s.CF.DescribeStacksWithContext(ctx, &cloudformation.DescribeStacksInput{
  265. StackName: aws.String(name),
  266. })
  267. if err != nil {
  268. return "", err
  269. }
  270. return *stacks.Stacks[0].StackId, nil
  271. }
  272. func (s sdk) ListStacks(ctx context.Context, name string) ([]compose.Stack, error) {
  273. params := cloudformation.DescribeStacksInput{}
  274. if name != "" {
  275. params.StackName = &name
  276. }
  277. cfStacks, err := s.CF.DescribeStacksWithContext(ctx, &params)
  278. if err != nil {
  279. return nil, err
  280. }
  281. stacks := []compose.Stack{}
  282. for _, stack := range cfStacks.Stacks {
  283. for _, t := range stack.Tags {
  284. if *t.Key == compose.ProjectTag {
  285. status := compose.RUNNING
  286. switch aws.StringValue(stack.StackStatus) {
  287. case "CREATE_IN_PROGRESS":
  288. status = compose.STARTING
  289. case "DELETE_IN_PROGRESS":
  290. status = compose.REMOVING
  291. case "UPDATE_IN_PROGRESS":
  292. status = compose.UPDATING
  293. }
  294. stacks = append(stacks, compose.Stack{
  295. ID: aws.StringValue(stack.StackId),
  296. Name: aws.StringValue(stack.StackName),
  297. Status: status,
  298. })
  299. break
  300. }
  301. }
  302. }
  303. return stacks, nil
  304. }
  305. func (s sdk) DescribeStackEvents(ctx context.Context, stackID string) ([]*cloudformation.StackEvent, error) {
  306. // Fixme implement Paginator on Events and return as a chan(events)
  307. events := []*cloudformation.StackEvent{}
  308. var nextToken *string
  309. for {
  310. resp, err := s.CF.DescribeStackEventsWithContext(ctx, &cloudformation.DescribeStackEventsInput{
  311. StackName: aws.String(stackID),
  312. NextToken: nextToken,
  313. })
  314. if err != nil {
  315. return nil, err
  316. }
  317. events = append(events, resp.StackEvents...)
  318. if resp.NextToken == nil {
  319. return events, nil
  320. }
  321. nextToken = resp.NextToken
  322. }
  323. }
  324. func (s sdk) ListStackParameters(ctx context.Context, name string) (map[string]string, error) {
  325. st, err := s.CF.DescribeStacksWithContext(ctx, &cloudformation.DescribeStacksInput{
  326. NextToken: nil,
  327. StackName: aws.String(name),
  328. })
  329. if err != nil {
  330. return nil, err
  331. }
  332. parameters := map[string]string{}
  333. for _, parameter := range st.Stacks[0].Parameters {
  334. parameters[aws.StringValue(parameter.ParameterKey)] = aws.StringValue(parameter.ParameterValue)
  335. }
  336. return parameters, nil
  337. }
  338. type stackResource struct {
  339. LogicalID string
  340. Type string
  341. ARN string
  342. Status string
  343. }
  344. func (s sdk) ListStackResources(ctx context.Context, name string) ([]stackResource, error) {
  345. // FIXME handle pagination
  346. res, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{
  347. StackName: aws.String(name),
  348. })
  349. if err != nil {
  350. return nil, err
  351. }
  352. resources := []stackResource{}
  353. for _, r := range res.StackResourceSummaries {
  354. resources = append(resources, stackResource{
  355. LogicalID: aws.StringValue(r.LogicalResourceId),
  356. Type: aws.StringValue(r.ResourceType),
  357. ARN: aws.StringValue(r.PhysicalResourceId),
  358. Status: aws.StringValue(r.ResourceStatus),
  359. })
  360. }
  361. return resources, nil
  362. }
  363. func (s sdk) DeleteStack(ctx context.Context, name string) error {
  364. logrus.Debug("Delete CloudFormation stack")
  365. _, err := s.CF.DeleteStackWithContext(ctx, &cloudformation.DeleteStackInput{
  366. StackName: aws.String(name),
  367. })
  368. return err
  369. }
  370. func (s sdk) CreateSecret(ctx context.Context, secret secrets.Secret) (string, error) {
  371. logrus.Debug("Create secret " + secret.Name)
  372. secretStr, err := secret.GetCredString()
  373. if err != nil {
  374. return "", err
  375. }
  376. response, err := s.SM.CreateSecret(&secretsmanager.CreateSecretInput{
  377. Name: &secret.Name,
  378. SecretString: &secretStr,
  379. Description: &secret.Description,
  380. })
  381. if err != nil {
  382. return "", err
  383. }
  384. return aws.StringValue(response.ARN), nil
  385. }
  386. func (s sdk) InspectSecret(ctx context.Context, id string) (secrets.Secret, error) {
  387. logrus.Debug("Inspect secret " + id)
  388. response, err := s.SM.DescribeSecret(&secretsmanager.DescribeSecretInput{SecretId: &id})
  389. if err != nil {
  390. return secrets.Secret{}, err
  391. }
  392. labels := map[string]string{}
  393. for _, tag := range response.Tags {
  394. labels[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value)
  395. }
  396. secret := secrets.Secret{
  397. ID: aws.StringValue(response.ARN),
  398. Name: aws.StringValue(response.Name),
  399. Labels: labels,
  400. }
  401. if response.Description != nil {
  402. secret.Description = *response.Description
  403. }
  404. return secret, nil
  405. }
  406. func (s sdk) ListSecrets(ctx context.Context) ([]secrets.Secret, error) {
  407. logrus.Debug("List secrets ...")
  408. response, err := s.SM.ListSecrets(&secretsmanager.ListSecretsInput{})
  409. if err != nil {
  410. return nil, err
  411. }
  412. var ls []secrets.Secret
  413. for _, sec := range response.SecretList {
  414. labels := map[string]string{}
  415. for _, tag := range sec.Tags {
  416. labels[*tag.Key] = *tag.Value
  417. }
  418. description := ""
  419. if sec.Description != nil {
  420. description = *sec.Description
  421. }
  422. ls = append(ls, secrets.Secret{
  423. ID: *sec.ARN,
  424. Name: *sec.Name,
  425. Labels: labels,
  426. Description: description,
  427. })
  428. }
  429. return ls, nil
  430. }
  431. func (s sdk) DeleteSecret(ctx context.Context, id string, recover bool) error {
  432. logrus.Debug("List secrets ...")
  433. force := !recover
  434. _, err := s.SM.DeleteSecret(&secretsmanager.DeleteSecretInput{SecretId: &id, ForceDeleteWithoutRecovery: &force})
  435. return err
  436. }
  437. func (s sdk) GetLogs(ctx context.Context, name string, consumer func(service, container, message string)) error {
  438. logGroup := fmt.Sprintf("/docker-compose/%s", name)
  439. var startTime int64
  440. for {
  441. select {
  442. case <-ctx.Done():
  443. return nil
  444. default:
  445. var hasMore = true
  446. var token *string
  447. for hasMore {
  448. events, err := s.CW.FilterLogEvents(&cloudwatchlogs.FilterLogEventsInput{
  449. LogGroupName: aws.String(logGroup),
  450. NextToken: token,
  451. StartTime: aws.Int64(startTime),
  452. })
  453. if err != nil {
  454. return err
  455. }
  456. if events.NextToken == nil {
  457. hasMore = false
  458. } else {
  459. token = events.NextToken
  460. }
  461. for _, event := range events.Events {
  462. p := strings.Split(aws.StringValue(event.LogStreamName), "/")
  463. consumer(p[1], p[2], aws.StringValue(event.Message))
  464. startTime = *event.IngestionTime
  465. }
  466. }
  467. }
  468. time.Sleep(500 * time.Millisecond)
  469. }
  470. }
  471. func (s sdk) DescribeServices(ctx context.Context, cluster string, arns []string) ([]compose.ServiceStatus, error) {
  472. services, err := s.ECS.DescribeServicesWithContext(ctx, &ecs.DescribeServicesInput{
  473. Cluster: aws.String(cluster),
  474. Services: aws.StringSlice(arns),
  475. Include: aws.StringSlice([]string{"TAGS"}),
  476. })
  477. if err != nil {
  478. return nil, err
  479. }
  480. status := []compose.ServiceStatus{}
  481. for _, service := range services.Services {
  482. var name string
  483. for _, t := range service.Tags {
  484. if *t.Key == compose.ServiceTag {
  485. name = aws.StringValue(t.Value)
  486. }
  487. }
  488. if name == "" {
  489. return nil, fmt.Errorf("service %s doesn't have a %s tag", *service.ServiceArn, compose.ServiceTag)
  490. }
  491. targetGroupArns := []string{}
  492. for _, lb := range service.LoadBalancers {
  493. targetGroupArns = append(targetGroupArns, *lb.TargetGroupArn)
  494. }
  495. // getURLwithPortMapping makes 2 queries
  496. // one to get the target groups and another for load balancers
  497. loadBalancers, err := s.getURLWithPortMapping(ctx, targetGroupArns)
  498. if err != nil {
  499. return nil, err
  500. }
  501. status = append(status, compose.ServiceStatus{
  502. ID: aws.StringValue(service.ServiceName),
  503. Name: name,
  504. Replicas: int(aws.Int64Value(service.RunningCount)),
  505. Desired: int(aws.Int64Value(service.DesiredCount)),
  506. Publishers: loadBalancers,
  507. })
  508. }
  509. return status, nil
  510. }
  511. func (s sdk) getURLWithPortMapping(ctx context.Context, targetGroupArns []string) ([]compose.PortPublisher, error) {
  512. if len(targetGroupArns) == 0 {
  513. return nil, nil
  514. }
  515. groups, err := s.ELB.DescribeTargetGroups(&elbv2.DescribeTargetGroupsInput{
  516. TargetGroupArns: aws.StringSlice(targetGroupArns),
  517. })
  518. if err != nil {
  519. return nil, err
  520. }
  521. lbarns := []*string{}
  522. for _, tg := range groups.TargetGroups {
  523. lbarns = append(lbarns, tg.LoadBalancerArns...)
  524. }
  525. lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
  526. LoadBalancerArns: lbarns,
  527. })
  528. if err != nil {
  529. return nil, err
  530. }
  531. filterLB := func(arn *string, lbs []*elbv2.LoadBalancer) *elbv2.LoadBalancer {
  532. if aws.StringValue(arn) == "" {
  533. // load balancer arn is nil/""
  534. return nil
  535. }
  536. for _, lb := range lbs {
  537. if aws.StringValue(lb.LoadBalancerArn) == aws.StringValue(arn) {
  538. return lb
  539. }
  540. }
  541. return nil
  542. }
  543. loadBalancers := []compose.PortPublisher{}
  544. for _, tg := range groups.TargetGroups {
  545. for _, lbarn := range tg.LoadBalancerArns {
  546. lb := filterLB(lbarn, lbs.LoadBalancers)
  547. if lb == nil {
  548. continue
  549. }
  550. loadBalancers = append(loadBalancers, compose.PortPublisher{
  551. URL: aws.StringValue(lb.DNSName),
  552. TargetPort: int(aws.Int64Value(tg.Port)),
  553. PublishedPort: int(aws.Int64Value(tg.Port)),
  554. Protocol: aws.StringValue(tg.Protocol),
  555. })
  556. }
  557. }
  558. return loadBalancers, nil
  559. }
  560. func (s sdk) ListTasks(ctx context.Context, cluster string, family string) ([]string, error) {
  561. tasks, err := s.ECS.ListTasksWithContext(ctx, &ecs.ListTasksInput{
  562. Cluster: aws.String(cluster),
  563. Family: aws.String(family),
  564. })
  565. if err != nil {
  566. return nil, err
  567. }
  568. arns := []string{}
  569. for _, arn := range tasks.TaskArns {
  570. arns = append(arns, *arn)
  571. }
  572. return arns, nil
  573. }
  574. func (s sdk) GetPublicIPs(ctx context.Context, interfaces ...string) (map[string]string, error) {
  575. desc, err := s.EC2.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{
  576. NetworkInterfaceIds: aws.StringSlice(interfaces),
  577. })
  578. if err != nil {
  579. return nil, err
  580. }
  581. publicIPs := map[string]string{}
  582. for _, interf := range desc.NetworkInterfaces {
  583. if interf.Association != nil {
  584. publicIPs[aws.StringValue(interf.NetworkInterfaceId)] = aws.StringValue(interf.Association.PublicIp)
  585. }
  586. }
  587. return publicIPs, nil
  588. }
  589. func (s sdk) LoadBalancerExists(ctx context.Context, arn string) (bool, error) {
  590. logrus.Debug("CheckRequirements if PortPublisher exists: ", arn)
  591. lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
  592. LoadBalancerArns: []*string{aws.String(arn)},
  593. })
  594. if err != nil {
  595. return false, err
  596. }
  597. return len(lbs.LoadBalancers) > 0, nil
  598. }
  599. func (s sdk) GetLoadBalancerURL(ctx context.Context, arn string) (string, error) {
  600. logrus.Debug("Retrieve load balancer URL: ", arn)
  601. lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
  602. LoadBalancerArns: []*string{aws.String(arn)},
  603. })
  604. if err != nil {
  605. return "", err
  606. }
  607. dnsName := aws.StringValue(lbs.LoadBalancers[0].DNSName)
  608. if dnsName == "" {
  609. return "", fmt.Errorf("Load balancer %s doesn't have a dns name", aws.StringValue(lbs.LoadBalancers[0].LoadBalancerArn))
  610. }
  611. return dnsName, nil
  612. }