sdk.go 19 KB

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