sdk.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248
  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. "bytes"
  16. "context"
  17. "encoding/json"
  18. "fmt"
  19. "strings"
  20. "time"
  21. "github.com/docker/compose-cli/api/secrets"
  22. "github.com/docker/compose-cli/internal"
  23. "github.com/docker/compose-cli/pkg/api"
  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/s3"
  45. "github.com/aws/aws-sdk-go/service/s3/s3iface"
  46. "github.com/aws/aws-sdk-go/service/s3/s3manager"
  47. "github.com/aws/aws-sdk-go/service/secretsmanager"
  48. "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
  49. "github.com/aws/aws-sdk-go/service/ssm"
  50. "github.com/aws/aws-sdk-go/service/ssm/ssmiface"
  51. "github.com/hashicorp/go-multierror"
  52. "github.com/hashicorp/go-uuid"
  53. "github.com/pkg/errors"
  54. "github.com/sirupsen/logrus"
  55. )
  56. type sdk struct {
  57. ECS ecsiface.ECSAPI
  58. EC2 ec2iface.EC2API
  59. EFS efsiface.EFSAPI
  60. ELB elbv2iface.ELBV2API
  61. CW cloudwatchlogsiface.CloudWatchLogsAPI
  62. IAM iamiface.IAMAPI
  63. CF cloudformationiface.CloudFormationAPI
  64. SM secretsmanageriface.SecretsManagerAPI
  65. SSM ssmiface.SSMAPI
  66. AG autoscalingiface.AutoScalingAPI
  67. S3 s3iface.S3API
  68. uploader *s3manager.Uploader
  69. }
  70. // sdk implement API
  71. var _ API = sdk{}
  72. func newSDK(sess *session.Session) sdk {
  73. sess.Handlers.Build.PushBack(func(r *request.Request) {
  74. request.AddToUserAgent(r, internal.ECSUserAgentName+"/"+internal.Version)
  75. })
  76. return sdk{
  77. ECS: ecs.New(sess),
  78. EC2: ec2.New(sess),
  79. EFS: efs.New(sess),
  80. ELB: elbv2.New(sess),
  81. CW: cloudwatchlogs.New(sess),
  82. IAM: iam.New(sess),
  83. CF: cloudformation.New(sess),
  84. SM: secretsmanager.New(sess),
  85. SSM: ssm.New(sess),
  86. AG: autoscaling.New(sess),
  87. S3: s3.New(sess),
  88. uploader: s3manager.NewUploader(sess),
  89. }
  90. }
  91. func (s sdk) CheckRequirements(ctx context.Context, region string) error {
  92. settings, err := s.ECS.ListAccountSettingsWithContext(ctx, &ecs.ListAccountSettingsInput{
  93. EffectiveSettings: aws.Bool(true),
  94. Name: aws.String("serviceLongArnFormat"),
  95. })
  96. if err != nil {
  97. return err
  98. }
  99. serviceLongArnFormat := settings.Settings[0].Value
  100. if *serviceLongArnFormat != "enabled" {
  101. return fmt.Errorf("this tool requires the \"new ARN resource ID format\".\n"+
  102. "Check https://%s.console.aws.amazon.com/ecs/home#/settings\n"+
  103. "Learn more: https://aws.amazon.com/blogs/compute/migrating-your-amazon-ecs-deployment-to-the-new-arn-and-resource-id-format-2", region)
  104. }
  105. return nil
  106. }
  107. func (s sdk) ResolveCluster(ctx context.Context, nameOrArn string) (awsResource, error) {
  108. logrus.Debug("CheckRequirements if cluster was already created: ", nameOrArn)
  109. clusters, err := s.ECS.DescribeClustersWithContext(ctx, &ecs.DescribeClustersInput{
  110. Clusters: []*string{aws.String(nameOrArn)},
  111. })
  112. if err != nil {
  113. return nil, err
  114. }
  115. if len(clusters.Clusters) == 0 {
  116. return nil, errors.Wrapf(api.ErrNotFound, "cluster %q does not exist", nameOrArn)
  117. }
  118. it := clusters.Clusters[0]
  119. return existingAWSResource{
  120. arn: aws.StringValue(it.ClusterArn),
  121. id: aws.StringValue(it.ClusterName),
  122. }, nil
  123. }
  124. func (s sdk) CreateCluster(ctx context.Context, name string) (string, error) {
  125. logrus.Debug("Create cluster ", name)
  126. response, err := s.ECS.CreateClusterWithContext(ctx, &ecs.CreateClusterInput{ClusterName: aws.String(name)})
  127. if err != nil {
  128. return "", err
  129. }
  130. return *response.Cluster.Status, nil
  131. }
  132. func (s sdk) CheckVPC(ctx context.Context, vpcID string) error {
  133. logrus.Debug("CheckRequirements on VPC : ", vpcID)
  134. output, err := s.EC2.DescribeVpcAttributeWithContext(ctx, &ec2.DescribeVpcAttributeInput{
  135. VpcId: aws.String(vpcID),
  136. Attribute: aws.String("enableDnsSupport"),
  137. })
  138. if err != nil {
  139. return err
  140. }
  141. if !*output.EnableDnsSupport.Value {
  142. return fmt.Errorf("VPC %q doesn't have DNS resolution enabled", vpcID)
  143. }
  144. return nil
  145. }
  146. func (s sdk) GetDefaultVPC(ctx context.Context) (string, error) {
  147. logrus.Debug("Retrieve default VPC")
  148. vpcs, err := s.EC2.DescribeVpcsWithContext(ctx, &ec2.DescribeVpcsInput{
  149. Filters: []*ec2.Filter{
  150. {
  151. Name: aws.String("isDefault"),
  152. Values: []*string{aws.String("true")},
  153. },
  154. },
  155. })
  156. if err != nil {
  157. return "", err
  158. }
  159. if len(vpcs.Vpcs) == 0 {
  160. return "", fmt.Errorf("account has no default VPC. Set VPC to deploy to using 'x-aws-vpc'")
  161. }
  162. return *vpcs.Vpcs[0].VpcId, nil
  163. }
  164. func (s sdk) GetSubNets(ctx context.Context, vpcID string) ([]awsResource, error) {
  165. logrus.Debug("Retrieve SubNets")
  166. var ids []awsResource
  167. var token *string
  168. for {
  169. subnets, err := s.EC2.DescribeSubnetsWithContext(ctx, &ec2.DescribeSubnetsInput{
  170. Filters: []*ec2.Filter{
  171. {
  172. Name: aws.String("vpc-id"),
  173. Values: []*string{aws.String(vpcID)},
  174. },
  175. },
  176. NextToken: token,
  177. })
  178. if err != nil {
  179. return nil, err
  180. }
  181. for _, subnet := range subnets.Subnets {
  182. ids = append(ids, existingAWSResource{
  183. arn: aws.StringValue(subnet.SubnetArn),
  184. id: aws.StringValue(subnet.SubnetId),
  185. })
  186. }
  187. if subnets.NextToken == token {
  188. break
  189. }
  190. token = subnets.NextToken
  191. }
  192. return ids, nil
  193. }
  194. func (s sdk) IsPublicSubnet(ctx context.Context, subNetID string) (bool, error) {
  195. tables, err := s.EC2.DescribeRouteTablesWithContext(ctx, &ec2.DescribeRouteTablesInput{
  196. Filters: []*ec2.Filter{
  197. {
  198. Name: aws.String("association.subnet-id"),
  199. Values: []*string{aws.String(subNetID)},
  200. },
  201. },
  202. })
  203. if err != nil {
  204. return false, err
  205. }
  206. if len(tables.RouteTables) == 0 {
  207. // If a subnet is not explicitly associated with any route table, it is implicitly associated with the main route table.
  208. // https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-route-tables.html
  209. return true, nil
  210. }
  211. for _, routeTable := range tables.RouteTables {
  212. for _, route := range routeTable.Routes {
  213. if aws.StringValue(route.State) != "active" {
  214. continue
  215. }
  216. if strings.HasPrefix(aws.StringValue(route.GatewayId), "igw-") {
  217. // Connected to an internet Gateway
  218. return true, nil
  219. }
  220. }
  221. }
  222. return false, nil
  223. }
  224. func (s sdk) GetRoleArn(ctx context.Context, name string) (string, error) {
  225. role, err := s.IAM.GetRoleWithContext(ctx, &iam.GetRoleInput{
  226. RoleName: aws.String(name),
  227. })
  228. if err != nil {
  229. return "", err
  230. }
  231. return *role.Role.Arn, nil
  232. }
  233. func (s sdk) StackExists(ctx context.Context, name string) (bool, error) {
  234. stacks, err := s.CF.DescribeStacksWithContext(ctx, &cloudformation.DescribeStacksInput{
  235. StackName: aws.String(name),
  236. })
  237. if err != nil {
  238. if strings.HasPrefix(err.Error(), fmt.Sprintf("ValidationError: Stack with ID %s does not exist", name)) {
  239. return false, nil
  240. }
  241. return false, nil
  242. }
  243. return len(stacks.Stacks) > 0, nil
  244. }
  245. type uploadedTemplateFunc func(body *string, url *string) (string, error)
  246. const cloudformationBytesLimit = 51200
  247. func (s sdk) withTemplate(ctx context.Context, name string, template []byte, region string, fn uploadedTemplateFunc) (string, error) {
  248. if len(template) < cloudformationBytesLimit {
  249. return fn(aws.String(string(template)), nil)
  250. }
  251. key, err := uuid.GenerateUUID()
  252. if err != nil {
  253. return "", err
  254. }
  255. bucket := "com.docker.compose." + key
  256. logrus.Debugf("Create s3 bucket %q to store cloudformation template", bucket)
  257. var configuration *s3.CreateBucketConfiguration
  258. if region != "us-east-1" {
  259. configuration = &s3.CreateBucketConfiguration{
  260. LocationConstraint: aws.String(region),
  261. }
  262. }
  263. _, err = s.S3.CreateBucket(&s3.CreateBucketInput{
  264. Bucket: aws.String(bucket),
  265. CreateBucketConfiguration: configuration,
  266. })
  267. if err != nil {
  268. return "", err
  269. }
  270. upload, err := s.uploader.UploadWithContext(ctx, &s3manager.UploadInput{
  271. Key: aws.String("template.yaml"),
  272. Body: bytes.NewReader(template),
  273. Bucket: aws.String(bucket),
  274. ContentType: aws.String("application/x-yaml"),
  275. Tagging: aws.String(name),
  276. })
  277. if err != nil {
  278. return "", err
  279. }
  280. defer func() {
  281. _, err := s.S3.DeleteObjectWithContext(ctx, &s3.DeleteObjectInput{
  282. Bucket: aws.String(bucket),
  283. Key: aws.String("template.yaml"),
  284. VersionId: upload.VersionID,
  285. })
  286. if err != nil {
  287. logrus.Warnf("Failed to remove S3 bucket: %s", err)
  288. }
  289. _, err = s.S3.DeleteBucketWithContext(ctx, &s3.DeleteBucketInput{
  290. Bucket: aws.String(bucket),
  291. })
  292. if err != nil {
  293. logrus.Warnf("Failed to remove S3 bucket: %s", err)
  294. }
  295. }()
  296. return fn(nil, aws.String(upload.Location))
  297. }
  298. func (s sdk) CreateStack(ctx context.Context, name string, region string, template []byte) error {
  299. logrus.Debug("Create CloudFormation stack")
  300. stackID, err := s.withTemplate(ctx, name, template, region, func(body *string, url *string) (string, error) {
  301. stack, err := s.CF.CreateStackWithContext(ctx, &cloudformation.CreateStackInput{
  302. OnFailure: aws.String("DELETE"),
  303. StackName: aws.String(name),
  304. TemplateBody: body,
  305. TemplateURL: url,
  306. TimeoutInMinutes: nil,
  307. Capabilities: []*string{
  308. aws.String(cloudformation.CapabilityCapabilityIam),
  309. },
  310. Tags: []*cloudformation.Tag{
  311. {
  312. Key: aws.String(api.ProjectLabel),
  313. Value: aws.String(name),
  314. },
  315. },
  316. })
  317. if err != nil {
  318. return "", err
  319. }
  320. return aws.StringValue(stack.StackId), nil
  321. })
  322. logrus.Debugf("Stack %s created", stackID)
  323. return err
  324. }
  325. func (s sdk) CreateChangeSet(ctx context.Context, name string, region string, template []byte) (string, error) {
  326. logrus.Debug("Create CloudFormation Changeset")
  327. update := fmt.Sprintf("Update%s", time.Now().Format("2006-01-02-15-04-05"))
  328. changeset, err := s.withTemplate(ctx, name, template, region, func(body *string, url *string) (string, error) {
  329. changeset, err := s.CF.CreateChangeSetWithContext(ctx, &cloudformation.CreateChangeSetInput{
  330. ChangeSetName: aws.String(update),
  331. ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate),
  332. StackName: aws.String(name),
  333. TemplateBody: body,
  334. TemplateURL: url,
  335. Capabilities: []*string{
  336. aws.String(cloudformation.CapabilityCapabilityIam),
  337. },
  338. })
  339. if err != nil {
  340. return "", err
  341. }
  342. return aws.StringValue(changeset.Id), err
  343. })
  344. if err != nil {
  345. return "", err
  346. }
  347. // we have to WaitUntilChangeSetCreateComplete even this in fail with error `ResourceNotReady`
  348. // so that we can invoke DescribeChangeSet to check status, and then we can know about the actual creation failure cause.
  349. s.CF.WaitUntilChangeSetCreateCompleteWithContext(ctx, &cloudformation.DescribeChangeSetInput{ // nolint:errcheck
  350. ChangeSetName: aws.String(changeset),
  351. })
  352. desc, err := s.CF.DescribeChangeSetWithContext(ctx, &cloudformation.DescribeChangeSetInput{
  353. ChangeSetName: aws.String(update),
  354. StackName: aws.String(name),
  355. })
  356. if aws.StringValue(desc.Status) == "FAILED" {
  357. return changeset, fmt.Errorf(aws.StringValue(desc.StatusReason))
  358. }
  359. return changeset, err
  360. }
  361. func (s sdk) UpdateStack(ctx context.Context, changeset string) error {
  362. desc, err := s.CF.DescribeChangeSetWithContext(ctx, &cloudformation.DescribeChangeSetInput{
  363. ChangeSetName: aws.String(changeset),
  364. })
  365. if err != nil {
  366. return err
  367. }
  368. if strings.HasPrefix(aws.StringValue(desc.StatusReason), "The submitted information didn't contain changes.") {
  369. return nil
  370. }
  371. _, err = s.CF.ExecuteChangeSet(&cloudformation.ExecuteChangeSetInput{
  372. ChangeSetName: aws.String(changeset),
  373. })
  374. return err
  375. }
  376. const (
  377. stackCreate = iota
  378. stackUpdate
  379. stackDelete
  380. )
  381. func (s sdk) WaitStackComplete(ctx context.Context, name string, operation int) error {
  382. input := &cloudformation.DescribeStacksInput{
  383. StackName: aws.String(name),
  384. }
  385. switch operation {
  386. case stackCreate:
  387. return s.CF.WaitUntilStackCreateCompleteWithContext(ctx, input)
  388. case stackDelete:
  389. return s.CF.WaitUntilStackDeleteCompleteWithContext(ctx, input)
  390. default:
  391. return fmt.Errorf("internal error: unexpected stack operation %d", operation)
  392. }
  393. }
  394. func (s sdk) GetStackID(ctx context.Context, name string) (string, error) {
  395. stacks, err := s.CF.DescribeStacksWithContext(ctx, &cloudformation.DescribeStacksInput{
  396. StackName: aws.String(name),
  397. })
  398. if err != nil {
  399. return "", err
  400. }
  401. return *stacks.Stacks[0].StackId, nil
  402. }
  403. func (s sdk) ListStacks(ctx context.Context) ([]api.Stack, error) {
  404. params := cloudformation.DescribeStacksInput{}
  405. var token *string
  406. var stacks []api.Stack
  407. for {
  408. response, err := s.CF.DescribeStacksWithContext(ctx, &params)
  409. if err != nil {
  410. return nil, err
  411. }
  412. for _, stack := range response.Stacks {
  413. for _, t := range stack.Tags {
  414. if *t.Key == api.ProjectLabel {
  415. status := api.RUNNING
  416. switch aws.StringValue(stack.StackStatus) {
  417. case "CREATE_IN_PROGRESS":
  418. status = api.STARTING
  419. case "DELETE_IN_PROGRESS":
  420. status = api.REMOVING
  421. case "UPDATE_IN_PROGRESS":
  422. status = api.UPDATING
  423. default:
  424. }
  425. stacks = append(stacks, api.Stack{
  426. ID: aws.StringValue(stack.StackId),
  427. Name: aws.StringValue(stack.StackName),
  428. Status: status,
  429. })
  430. break
  431. }
  432. }
  433. }
  434. if token == response.NextToken {
  435. return stacks, nil
  436. }
  437. token = response.NextToken
  438. }
  439. }
  440. func (s sdk) GetStackClusterID(ctx context.Context, stack string) (string, error) {
  441. // Note: could use DescribeStackResource but we only can detect `does not exist` case by matching string error message
  442. var token *string
  443. for {
  444. response, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{
  445. StackName: aws.String(stack),
  446. })
  447. if err != nil {
  448. return "", err
  449. }
  450. for _, r := range response.StackResourceSummaries {
  451. if aws.StringValue(r.ResourceType) == "AWS::ECS::Cluster" {
  452. return aws.StringValue(r.PhysicalResourceId), nil
  453. }
  454. }
  455. if token == response.NextToken {
  456. break
  457. }
  458. token = response.NextToken
  459. }
  460. // stack is using user-provided cluster
  461. res, err := s.CF.GetTemplateSummaryWithContext(ctx, &cloudformation.GetTemplateSummaryInput{
  462. StackName: aws.String(stack),
  463. })
  464. if err != nil {
  465. return "", err
  466. }
  467. c := aws.StringValue(res.Metadata)
  468. var m templateMetadata
  469. err = json.Unmarshal([]byte(c), &m)
  470. if err != nil {
  471. return "", err
  472. }
  473. if m.Cluster == "" {
  474. return "", errors.Wrap(api.ErrNotFound, "CloudFormation is missing cluster metadata")
  475. }
  476. return m.Cluster, nil
  477. }
  478. type templateMetadata struct {
  479. Cluster string `json:",omitempty"`
  480. }
  481. func (s sdk) GetServiceTaskDefinition(ctx context.Context, cluster string, serviceArns []string) (map[string]string, error) {
  482. defs := map[string]string{}
  483. svc := []*string{}
  484. for _, s := range serviceArns {
  485. svc = append(svc, aws.String(s))
  486. }
  487. for i := 0; i < len(svc); i += 10 {
  488. end := i + 10
  489. if end > len(svc) {
  490. end = len(svc)
  491. }
  492. chunk := svc[i:end]
  493. services, err := s.ECS.DescribeServicesWithContext(ctx, &ecs.DescribeServicesInput{
  494. Cluster: aws.String(cluster),
  495. Services: chunk,
  496. })
  497. if err != nil {
  498. return nil, err
  499. }
  500. for _, s := range services.Services {
  501. defs[aws.StringValue(s.ServiceArn)] = aws.StringValue(s.TaskDefinition)
  502. }
  503. }
  504. return defs, nil
  505. }
  506. func (s sdk) ListStackServices(ctx context.Context, stack string) ([]string, error) {
  507. arns := []string{}
  508. var nextToken *string
  509. for {
  510. response, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{
  511. StackName: aws.String(stack),
  512. NextToken: nextToken,
  513. })
  514. if err != nil {
  515. return nil, err
  516. }
  517. for _, r := range response.StackResourceSummaries {
  518. if aws.StringValue(r.ResourceType) == "AWS::ECS::Service" {
  519. if r.PhysicalResourceId != nil {
  520. arns = append(arns, aws.StringValue(r.PhysicalResourceId))
  521. }
  522. }
  523. }
  524. nextToken = response.NextToken
  525. if nextToken == nil {
  526. break
  527. }
  528. }
  529. return arns, nil
  530. }
  531. func (s sdk) GetServiceTasks(ctx context.Context, cluster string, service string, stopped bool) ([]*ecs.Task, error) {
  532. state := "RUNNING"
  533. if stopped {
  534. state = "STOPPED"
  535. }
  536. var token *string
  537. var tasks []*ecs.Task
  538. for {
  539. response, err := s.ECS.ListTasksWithContext(ctx, &ecs.ListTasksInput{
  540. Cluster: aws.String(cluster),
  541. ServiceName: aws.String(service),
  542. DesiredStatus: aws.String(state),
  543. })
  544. if err != nil {
  545. return nil, err
  546. }
  547. if len(response.TaskArns) > 0 {
  548. taskDescriptions, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{
  549. Cluster: aws.String(cluster),
  550. Tasks: response.TaskArns,
  551. })
  552. if err != nil {
  553. return nil, err
  554. }
  555. tasks = append(tasks, taskDescriptions.Tasks...)
  556. }
  557. if token == response.NextToken {
  558. return tasks, nil
  559. }
  560. token = response.NextToken
  561. }
  562. }
  563. func (s sdk) GetTaskStoppedReason(ctx context.Context, cluster string, taskArn string) (string, error) {
  564. taskDescriptions, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{
  565. Cluster: aws.String(cluster),
  566. Tasks: []*string{aws.String(taskArn)},
  567. })
  568. if err != nil {
  569. return "", err
  570. }
  571. if len(taskDescriptions.Tasks) == 0 {
  572. return "", nil
  573. }
  574. task := taskDescriptions.Tasks[0]
  575. return fmt.Sprintf(
  576. "%s: %s",
  577. aws.StringValue(task.StopCode),
  578. aws.StringValue(task.StoppedReason)), nil
  579. }
  580. func (s sdk) DescribeStackEvents(ctx context.Context, stackID string) ([]*cloudformation.StackEvent, error) {
  581. // Fixme implement Paginator on Events and return as a chan(events)
  582. events := []*cloudformation.StackEvent{}
  583. var nextToken *string
  584. for {
  585. resp, err := s.CF.DescribeStackEventsWithContext(ctx, &cloudformation.DescribeStackEventsInput{
  586. StackName: aws.String(stackID),
  587. NextToken: nextToken,
  588. })
  589. if err != nil {
  590. return nil, err
  591. }
  592. events = append(events, resp.StackEvents...)
  593. if resp.NextToken == nil {
  594. return events, nil
  595. }
  596. nextToken = resp.NextToken
  597. }
  598. }
  599. func (s sdk) ListStackParameters(ctx context.Context, name string) (map[string]string, error) {
  600. st, err := s.CF.DescribeStacksWithContext(ctx, &cloudformation.DescribeStacksInput{
  601. NextToken: nil,
  602. StackName: aws.String(name),
  603. })
  604. if err != nil {
  605. return nil, err
  606. }
  607. parameters := map[string]string{}
  608. for _, parameter := range st.Stacks[0].Parameters {
  609. parameters[aws.StringValue(parameter.ParameterKey)] = aws.StringValue(parameter.ParameterValue)
  610. }
  611. return parameters, nil
  612. }
  613. type stackResource struct {
  614. LogicalID string
  615. Type string
  616. ARN string
  617. Status string
  618. }
  619. type stackResourceFn func(r stackResource) error
  620. type stackResources []stackResource
  621. func (resources stackResources) apply(awsType string, fn stackResourceFn) error {
  622. var errs *multierror.Error
  623. for _, r := range resources {
  624. if r.Type == awsType {
  625. err := fn(r)
  626. if err != nil {
  627. errs = multierror.Append(err)
  628. }
  629. }
  630. }
  631. return errs.ErrorOrNil()
  632. }
  633. func (s sdk) ListStackResources(ctx context.Context, name string) (stackResources, error) {
  634. var token *string
  635. var resources stackResources
  636. for {
  637. response, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{
  638. StackName: aws.String(name),
  639. })
  640. if err != nil {
  641. return nil, err
  642. }
  643. for _, r := range response.StackResourceSummaries {
  644. resources = append(resources, stackResource{
  645. LogicalID: aws.StringValue(r.LogicalResourceId),
  646. Type: aws.StringValue(r.ResourceType),
  647. ARN: aws.StringValue(r.PhysicalResourceId),
  648. Status: aws.StringValue(r.ResourceStatus),
  649. })
  650. }
  651. if token == response.NextToken {
  652. return resources, nil
  653. }
  654. token = response.NextToken
  655. }
  656. }
  657. func (s sdk) DeleteStack(ctx context.Context, name string) error {
  658. logrus.Debug("Delete CloudFormation stack")
  659. _, err := s.CF.DeleteStackWithContext(ctx, &cloudformation.DeleteStackInput{
  660. StackName: aws.String(name),
  661. })
  662. return err
  663. }
  664. func (s sdk) CreateSecret(ctx context.Context, secret secrets.Secret) (string, error) {
  665. logrus.Debug("Create secret " + secret.Name)
  666. var tags []*secretsmanager.Tag
  667. for k, v := range secret.Labels {
  668. tags = []*secretsmanager.Tag{
  669. {
  670. Key: aws.String(k),
  671. Value: aws.String(v),
  672. },
  673. }
  674. }
  675. // store the secret content as string
  676. content := string(secret.GetContent())
  677. response, err := s.SM.CreateSecret(&secretsmanager.CreateSecretInput{
  678. Name: &secret.Name,
  679. SecretString: &content,
  680. Tags: tags,
  681. })
  682. if err != nil {
  683. return "", err
  684. }
  685. return aws.StringValue(response.ARN), nil
  686. }
  687. func (s sdk) InspectSecret(ctx context.Context, id string) (secrets.Secret, error) {
  688. logrus.Debug("Inspect secret " + id)
  689. response, err := s.SM.DescribeSecret(&secretsmanager.DescribeSecretInput{SecretId: &id})
  690. if err != nil {
  691. return secrets.Secret{}, err
  692. }
  693. tags := map[string]string{}
  694. for _, tag := range response.Tags {
  695. tags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value)
  696. }
  697. secret := secrets.Secret{
  698. ID: aws.StringValue(response.ARN),
  699. Name: aws.StringValue(response.Name),
  700. Labels: tags,
  701. }
  702. return secret, nil
  703. }
  704. func (s sdk) ListSecrets(ctx context.Context) ([]secrets.Secret, error) {
  705. logrus.Debug("List secrets ...")
  706. var ls []secrets.Secret
  707. var token *string
  708. for {
  709. response, err := s.SM.ListSecrets(&secretsmanager.ListSecretsInput{})
  710. if err != nil {
  711. return nil, err
  712. }
  713. for _, sec := range response.SecretList {
  714. tags := map[string]string{}
  715. for _, tag := range sec.Tags {
  716. tags[*tag.Key] = *tag.Value
  717. }
  718. ls = append(ls, secrets.Secret{
  719. ID: *sec.ARN,
  720. Name: *sec.Name,
  721. Labels: tags,
  722. })
  723. }
  724. if token == response.NextToken {
  725. return ls, nil
  726. }
  727. token = response.NextToken
  728. }
  729. }
  730. func (s sdk) DeleteSecret(ctx context.Context, id string, recover bool) error {
  731. logrus.Debug("List secrets ...")
  732. force := !recover
  733. _, err := s.SM.DeleteSecret(&secretsmanager.DeleteSecretInput{SecretId: &id, ForceDeleteWithoutRecovery: &force})
  734. return err
  735. }
  736. func (s sdk) GetLogs(ctx context.Context, name string, consumer func(container string, service string, message string), follow bool) error {
  737. logGroup := fmt.Sprintf("/docker-compose/%s", name)
  738. var startTime int64
  739. for {
  740. select {
  741. case <-ctx.Done():
  742. return nil
  743. default:
  744. var hasMore = true
  745. var token *string
  746. for hasMore {
  747. events, err := s.CW.FilterLogEvents(&cloudwatchlogs.FilterLogEventsInput{
  748. LogGroupName: aws.String(logGroup),
  749. NextToken: token,
  750. StartTime: aws.Int64(startTime),
  751. })
  752. if err != nil {
  753. return err
  754. }
  755. if events.NextToken == nil {
  756. hasMore = false
  757. } else {
  758. token = events.NextToken
  759. }
  760. for _, event := range events.Events {
  761. p := strings.Split(aws.StringValue(event.LogStreamName), "/")
  762. consumer(p[1], p[2], aws.StringValue(event.Message))
  763. startTime = *event.IngestionTime
  764. }
  765. }
  766. }
  767. if !follow {
  768. return nil
  769. }
  770. time.Sleep(500 * time.Millisecond)
  771. }
  772. }
  773. func (s sdk) DescribeService(ctx context.Context, cluster string, arn string) (api.ServiceStatus, error) {
  774. services, err := s.ECS.DescribeServicesWithContext(ctx, &ecs.DescribeServicesInput{
  775. Cluster: aws.String(cluster),
  776. Services: []*string{aws.String(arn)},
  777. Include: aws.StringSlice([]string{"TAGS"}),
  778. })
  779. if err != nil {
  780. return api.ServiceStatus{}, err
  781. }
  782. for _, f := range services.Failures {
  783. return api.ServiceStatus{}, errors.Wrapf(api.ErrNotFound, "can't get service status %s: %s", aws.StringValue(f.Detail), aws.StringValue(f.Reason))
  784. }
  785. service := services.Services[0]
  786. var name string
  787. for _, t := range service.Tags {
  788. if *t.Key == api.ServiceLabel {
  789. name = aws.StringValue(t.Value)
  790. }
  791. }
  792. if name == "" {
  793. return api.ServiceStatus{}, fmt.Errorf("service %s doesn't have a %s tag", *service.ServiceArn, api.ServiceLabel)
  794. }
  795. targetGroupArns := []string{}
  796. for _, lb := range service.LoadBalancers {
  797. targetGroupArns = append(targetGroupArns, *lb.TargetGroupArn)
  798. }
  799. // getURLwithPortMapping makes 2 queries
  800. // one to get the target groups and another for load balancers
  801. loadBalancers, err := s.getURLWithPortMapping(ctx, targetGroupArns)
  802. if err != nil {
  803. return api.ServiceStatus{}, err
  804. }
  805. return api.ServiceStatus{
  806. ID: aws.StringValue(service.ServiceName),
  807. Name: name,
  808. Replicas: int(aws.Int64Value(service.RunningCount)),
  809. Desired: int(aws.Int64Value(service.DesiredCount)),
  810. Publishers: loadBalancers,
  811. }, nil
  812. }
  813. func (s sdk) DescribeServiceTasks(ctx context.Context, cluster string, project string, service string) ([]api.ContainerSummary, error) {
  814. var summary []api.ContainerSummary
  815. familly := fmt.Sprintf("%s-%s", project, service)
  816. var token *string
  817. for {
  818. list, err := s.ECS.ListTasks(&ecs.ListTasksInput{
  819. Cluster: aws.String(cluster),
  820. Family: aws.String(familly),
  821. LaunchType: nil,
  822. MaxResults: nil,
  823. NextToken: token,
  824. })
  825. if err != nil {
  826. return nil, err
  827. }
  828. if len(list.TaskArns) == 0 {
  829. break
  830. }
  831. tasks, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{
  832. Cluster: aws.String(cluster),
  833. Include: aws.StringSlice([]string{"TAGS"}),
  834. Tasks: list.TaskArns,
  835. })
  836. if err != nil {
  837. return nil, err
  838. }
  839. for _, t := range tasks.Tasks {
  840. var project string
  841. var service string
  842. for _, tag := range t.Tags {
  843. switch aws.StringValue(tag.Key) {
  844. case api.ProjectLabel:
  845. project = aws.StringValue(tag.Value)
  846. case api.ServiceLabel:
  847. service = aws.StringValue(tag.Value)
  848. }
  849. }
  850. id, err := arn.Parse(aws.StringValue(t.TaskArn))
  851. if err != nil {
  852. return nil, err
  853. }
  854. summary = append(summary, api.ContainerSummary{
  855. ID: id.String(),
  856. Name: id.Resource,
  857. Project: project,
  858. Service: service,
  859. State: strings.Title(strings.ToLower(aws.StringValue(t.LastStatus))),
  860. })
  861. }
  862. if list.NextToken == token {
  863. break
  864. }
  865. token = list.NextToken
  866. }
  867. return summary, nil
  868. }
  869. func (s sdk) getURLWithPortMapping(ctx context.Context, targetGroupArns []string) ([]api.PortPublisher, error) {
  870. if len(targetGroupArns) == 0 {
  871. return nil, nil
  872. }
  873. groups, err := s.ELB.DescribeTargetGroups(&elbv2.DescribeTargetGroupsInput{
  874. TargetGroupArns: aws.StringSlice(targetGroupArns),
  875. })
  876. if err != nil {
  877. return nil, err
  878. }
  879. lbarns := []*string{}
  880. for _, tg := range groups.TargetGroups {
  881. lbarns = append(lbarns, tg.LoadBalancerArns...)
  882. }
  883. lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
  884. LoadBalancerArns: lbarns,
  885. })
  886. if err != nil {
  887. return nil, err
  888. }
  889. filterLB := func(arn *string, lbs []*elbv2.LoadBalancer) *elbv2.LoadBalancer {
  890. if aws.StringValue(arn) == "" {
  891. // load balancer arn is nil/""
  892. return nil
  893. }
  894. for _, lb := range lbs {
  895. if aws.StringValue(lb.LoadBalancerArn) == aws.StringValue(arn) {
  896. return lb
  897. }
  898. }
  899. return nil
  900. }
  901. loadBalancers := []api.PortPublisher{}
  902. for _, tg := range groups.TargetGroups {
  903. for _, lbarn := range tg.LoadBalancerArns {
  904. lb := filterLB(lbarn, lbs.LoadBalancers)
  905. if lb == nil {
  906. continue
  907. }
  908. loadBalancers = append(loadBalancers, api.PortPublisher{
  909. URL: fmt.Sprintf("%s:%d", aws.StringValue(lb.DNSName), aws.Int64Value(tg.Port)),
  910. TargetPort: int(aws.Int64Value(tg.Port)),
  911. PublishedPort: int(aws.Int64Value(tg.Port)),
  912. Protocol: strings.ToLower(aws.StringValue(tg.Protocol)),
  913. })
  914. }
  915. }
  916. return loadBalancers, nil
  917. }
  918. func (s sdk) ListTasks(ctx context.Context, cluster string, family string) ([]string, error) {
  919. var token *string
  920. var arns []string
  921. for {
  922. response, err := s.ECS.ListTasksWithContext(ctx, &ecs.ListTasksInput{
  923. Cluster: aws.String(cluster),
  924. Family: aws.String(family),
  925. })
  926. if err != nil {
  927. return nil, err
  928. }
  929. for _, arn := range response.TaskArns {
  930. arns = append(arns, *arn)
  931. }
  932. if token == response.NextToken {
  933. return arns, nil
  934. }
  935. token = response.NextToken
  936. }
  937. }
  938. func (s sdk) GetPublicIPs(ctx context.Context, interfaces ...string) (map[string]string, error) {
  939. var token *string
  940. publicIPs := map[string]string{}
  941. for {
  942. response, err := s.EC2.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{
  943. NetworkInterfaceIds: aws.StringSlice(interfaces),
  944. })
  945. if err != nil {
  946. return nil, err
  947. }
  948. for _, interf := range response.NetworkInterfaces {
  949. if interf.Association != nil {
  950. publicIPs[aws.StringValue(interf.NetworkInterfaceId)] = aws.StringValue(interf.Association.PublicIp)
  951. }
  952. }
  953. if token == response.NextToken {
  954. return publicIPs, nil
  955. }
  956. token = response.NextToken
  957. }
  958. }
  959. func (s sdk) ResolveLoadBalancer(ctx context.Context, nameOrArn string) (awsResource, string, string, []awsResource, error) {
  960. logrus.Debug("Check if LoadBalancer exists: ", nameOrArn)
  961. var arns []*string
  962. var names []*string
  963. if arn.IsARN(nameOrArn) {
  964. arns = append(arns, aws.String(nameOrArn))
  965. } else {
  966. names = append(names, aws.String(nameOrArn))
  967. }
  968. lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
  969. LoadBalancerArns: arns,
  970. Names: names,
  971. })
  972. if err != nil {
  973. return nil, "", "", nil, err
  974. }
  975. if len(lbs.LoadBalancers) == 0 {
  976. return nil, "", "", nil, errors.Wrapf(api.ErrNotFound, "load balancer %q does not exist", nameOrArn)
  977. }
  978. it := lbs.LoadBalancers[0]
  979. var subNets []awsResource
  980. for _, az := range it.AvailabilityZones {
  981. subNets = append(subNets, existingAWSResource{
  982. id: aws.StringValue(az.SubnetId),
  983. })
  984. }
  985. return existingAWSResource{
  986. arn: aws.StringValue(it.LoadBalancerArn),
  987. id: aws.StringValue(it.LoadBalancerName),
  988. }, aws.StringValue(it.Type), aws.StringValue(it.VpcId), subNets, nil
  989. }
  990. func (s sdk) GetLoadBalancerURL(ctx context.Context, arn string) (string, error) {
  991. logrus.Debug("Retrieve load balancer URL: ", arn)
  992. lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
  993. LoadBalancerArns: []*string{aws.String(arn)},
  994. })
  995. if err != nil {
  996. return "", err
  997. }
  998. dnsName := aws.StringValue(lbs.LoadBalancers[0].DNSName)
  999. if dnsName == "" {
  1000. return "", fmt.Errorf("Load balancer %s doesn't have a dns name", aws.StringValue(lbs.LoadBalancers[0].LoadBalancerArn))
  1001. }
  1002. return dnsName, nil
  1003. }
  1004. func (s sdk) GetParameter(ctx context.Context, name string) (string, error) {
  1005. parameter, err := s.SSM.GetParameterWithContext(ctx, &ssm.GetParameterInput{
  1006. Name: aws.String(name),
  1007. })
  1008. if err != nil {
  1009. return "", err
  1010. }
  1011. value := *parameter.Parameter.Value
  1012. var ami struct {
  1013. SchemaVersion int `json:"schema_version"`
  1014. ImageName string `json:"image_name"`
  1015. ImageID string `json:"image_id"`
  1016. OS string `json:"os"`
  1017. ECSRuntimeVersion string `json:"ecs_runtime_verion"`
  1018. ECSAgentVersion string `json:"ecs_agent_version"`
  1019. }
  1020. err = json.Unmarshal([]byte(value), &ami)
  1021. if err != nil {
  1022. return "", err
  1023. }
  1024. return ami.ImageID, nil
  1025. }
  1026. func (s sdk) SecurityGroupExists(ctx context.Context, sg string) (bool, error) {
  1027. desc, err := s.EC2.DescribeSecurityGroupsWithContext(ctx, &ec2.DescribeSecurityGroupsInput{
  1028. GroupIds: aws.StringSlice([]string{sg}),
  1029. })
  1030. if err != nil {
  1031. return false, err
  1032. }
  1033. return len(desc.SecurityGroups) > 0, nil
  1034. }
  1035. func (s sdk) DeleteCapacityProvider(ctx context.Context, arn string) error {
  1036. _, err := s.ECS.DeleteCapacityProvider(&ecs.DeleteCapacityProviderInput{
  1037. CapacityProvider: aws.String(arn),
  1038. })
  1039. return err
  1040. }
  1041. func (s sdk) DeleteAutoscalingGroup(ctx context.Context, arn string) error {
  1042. _, err := s.AG.DeleteAutoScalingGroup(&autoscaling.DeleteAutoScalingGroupInput{
  1043. AutoScalingGroupName: aws.String(arn),
  1044. ForceDelete: aws.Bool(true),
  1045. })
  1046. return err
  1047. }
  1048. func (s sdk) ResolveFileSystem(ctx context.Context, id string) (awsResource, error) {
  1049. desc, err := s.EFS.DescribeFileSystemsWithContext(ctx, &efs.DescribeFileSystemsInput{
  1050. FileSystemId: aws.String(id),
  1051. })
  1052. if err != nil {
  1053. return nil, err
  1054. }
  1055. if len(desc.FileSystems) == 0 {
  1056. return nil, errors.Wrapf(api.ErrNotFound, "EFS file system %q doesn't exist", id)
  1057. }
  1058. it := desc.FileSystems[0]
  1059. return existingAWSResource{
  1060. arn: aws.StringValue(it.FileSystemArn),
  1061. id: aws.StringValue(it.FileSystemId),
  1062. }, nil
  1063. }
  1064. func (s sdk) ListFileSystems(ctx context.Context, tags map[string]string) ([]awsResource, error) {
  1065. var results []awsResource
  1066. var token *string
  1067. for {
  1068. desc, err := s.EFS.DescribeFileSystemsWithContext(ctx, &efs.DescribeFileSystemsInput{
  1069. Marker: token,
  1070. })
  1071. if err != nil {
  1072. return nil, err
  1073. }
  1074. for _, filesystem := range desc.FileSystems {
  1075. if containsAll(filesystem.Tags, tags) {
  1076. results = append(results, existingAWSResource{
  1077. arn: aws.StringValue(filesystem.FileSystemArn),
  1078. id: aws.StringValue(filesystem.FileSystemId),
  1079. })
  1080. }
  1081. }
  1082. if desc.NextMarker == token {
  1083. return results, nil
  1084. }
  1085. token = desc.NextMarker
  1086. }
  1087. }
  1088. func containsAll(tags []*efs.Tag, required map[string]string) bool {
  1089. TAGS:
  1090. for key, value := range required {
  1091. for _, t := range tags {
  1092. if aws.StringValue(t.Key) == key && aws.StringValue(t.Value) == value {
  1093. continue TAGS
  1094. }
  1095. }
  1096. return false
  1097. }
  1098. return true
  1099. }
  1100. func (s sdk) CreateFileSystem(ctx context.Context, tags map[string]string, options VolumeCreateOptions) (awsResource, error) {
  1101. var efsTags []*efs.Tag
  1102. for k, v := range tags {
  1103. efsTags = append(efsTags, &efs.Tag{
  1104. Key: aws.String(k),
  1105. Value: aws.String(v),
  1106. })
  1107. }
  1108. var (
  1109. k *string
  1110. p *string
  1111. f *float64
  1112. t *string
  1113. )
  1114. if options.ProvisionedThroughputInMibps > 1 {
  1115. f = aws.Float64(options.ProvisionedThroughputInMibps)
  1116. }
  1117. if options.KmsKeyID != "" {
  1118. k = aws.String(options.KmsKeyID)
  1119. }
  1120. if options.PerformanceMode != "" {
  1121. p = aws.String(options.PerformanceMode)
  1122. }
  1123. if options.ThroughputMode != "" {
  1124. t = aws.String(options.ThroughputMode)
  1125. }
  1126. res, err := s.EFS.CreateFileSystemWithContext(ctx, &efs.CreateFileSystemInput{
  1127. Encrypted: aws.Bool(true),
  1128. KmsKeyId: k,
  1129. PerformanceMode: p,
  1130. ProvisionedThroughputInMibps: f,
  1131. ThroughputMode: t,
  1132. Tags: efsTags,
  1133. })
  1134. if err != nil {
  1135. return nil, err
  1136. }
  1137. return existingAWSResource{
  1138. id: aws.StringValue(res.FileSystemId),
  1139. arn: aws.StringValue(res.FileSystemArn),
  1140. }, nil
  1141. }
  1142. func (s sdk) DeleteFileSystem(ctx context.Context, id string) error {
  1143. _, err := s.EFS.DeleteFileSystemWithContext(ctx, &efs.DeleteFileSystemInput{
  1144. FileSystemId: aws.String(id),
  1145. })
  1146. return err
  1147. }