sdk.go 35 KB

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