sdk.go 35 KB

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