sdk.go 21 KB

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