sdk.go 27 KB

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