sdk.go 28 KB

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