sdk.go 25 KB

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