sdk.go 20 KB

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