sdk.go 26 KB

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