sdk.go 24 KB

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