sdk.go 24 KB

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