From a13cfe683412f81198f65aa7639d52100ce3aa17 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Mon, 30 Dec 2019 17:53:23 +0200 Subject: [PATCH] fix(eks): failures when creating or updating clusters (#5540) There were two causes of timeouts for EKS cluster creation: create time which is longer than the AWS Lambda timeout (15min) and lack of retry when applying kubectl after the cluster has been created. The change fixes the first issue by leveraging the custom resource provider framework to implement the cluster resource as an async resource. The custom resource providers are now bundled as nested stacks so they don't take up too many resources from users, and are also reused by multiple clusters within the same stack. This required that the creation role will not be the same as the lambda role, so we define this role separately and assume it within the providers. The second issue is fixed by adding 3 retries to "kubectl apply". **Backwards compatibility**: as described in #5544, since the resource provider handler of `Cluster` and `KubernetesResource` has been changed, this change requires a replacement of existing clusters (deployment fails with "service token cannot be changed" error). Since this can be disruptive to users, this change includes an exact copy of the previous version under a new module called `@aws-cdk/aws-eks-legacy`, which can be used as a drop-in replacement until users decide to upgrade to the new version. Using the legacy cluster will emit a synthesis warning that this module will no longer be released as part of the CDK starting March 1st, 2020. - Fixes #4087 - Fixes #4695 - Fixes #5259 - Fixes #5501 --- BREAKING CHANGE: (in experimental module) the providers behind the AWS EKS module have been rewritten to address multiple stability issues. Since this change requires cluster replacement, the old version of this module is available under `@aws-cdk/aws-eks-legacy`. Please read https://github.com/aws/aws-cdk/issues/5544 carefully for upgrade instructions. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- packages/@aws-cdk/aws-eks-legacy/.gitignore | 17 + packages/@aws-cdk/aws-eks-legacy/.npmignore | 20 + packages/@aws-cdk/aws-eks-legacy/LICENSE | 201 +++ packages/@aws-cdk/aws-eks-legacy/NOTICE | 2 + packages/@aws-cdk/aws-eks-legacy/README.md | 427 +++++ .../aws-eks-legacy/lib/aws-auth-mapping.ts | 15 + .../@aws-cdk/aws-eks-legacy/lib/aws-auth.ts | 119 ++ .../aws-eks-legacy/lib/cluster-resource.ts | 82 + .../lib/cluster-resource/index.py | 0 .../@aws-cdk/aws-eks-legacy/lib/cluster.ts | 876 ++++++++++ .../@aws-cdk/aws-eks-legacy/lib/helm-chart.ts | 123 ++ .../aws-eks-legacy/lib/helm-chart/index.py | 136 ++ packages/@aws-cdk/aws-eks-legacy/lib/index.ts | 8 + .../aws-eks-legacy/lib/k8s-resource.ts | 73 + .../lib/k8s-resource/index.py | 0 .../aws-eks-legacy/lib/kubectl-layer.ts | 78 + .../lib/spot-interrupt-handler.ts | 175 ++ .../@aws-cdk/aws-eks-legacy/lib/user-data.ts | 46 + packages/@aws-cdk/aws-eks-legacy/package.json | 118 ++ .../aws-eks-legacy/scripts/kube_bump.sh | 21 + .../aws-eks-legacy/test/MANUAL_TEST.md | 58 + .../test/example.ssh-into-nodes.lit.ts | 32 + .../integ.eks-cluster.defaults.expected.json | 1315 +++++++++++++++ .../test/integ.eks-cluster.defaults.ts | 19 + ...eks-cluster.kubectl-disabled.expected.json | 1044 ++++++++++++ .../integ.eks-cluster.kubectl-disabled.ts | 33 + .../test/integ.eks-cluster.lit.expected.json | 1315 +++++++++++++++ .../test/integ.eks-cluster.lit.ts | 32 + .../test/integ.eks-helm.lit.expected.json | 1336 +++++++++++++++ .../aws-eks-legacy/test/integ.eks-helm.lit.ts | 54 + .../test/integ.eks-kubectl.lit.expected.json | 1220 ++++++++++++++ .../test/integ.eks-kubectl.lit.ts | 88 + .../test/integ.eks-spot.expected.json | 1451 +++++++++++++++++ .../aws-eks-legacy/test/integ.eks-spot.ts | 33 + .../aws-eks-legacy/test/test.awsauth.ts | 137 ++ .../test/test.cluster-resource.ts | 0 .../aws-eks-legacy/test/test.cluster.ts | 583 +++++++ .../aws-eks-legacy/test/test.helm-chart.ts | 55 + .../aws-eks-legacy/test/test.manifest.ts | 77 + .../aws-eks-legacy/test/test.user-data.ts | 166 ++ packages/@aws-cdk/aws-eks-legacy/test/util.ts | 36 + packages/@aws-cdk/aws-eks/.gitignore | 2 + .../lib/cluster-resource-handler/handler.ts | 267 +++ .../lib/cluster-resource-handler/index.ts | 44 + .../aws-eks/lib/cluster-resource-provider.ts | 52 + .../@aws-cdk/aws-eks/lib/cluster-resource.ts | 85 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 65 +- packages/@aws-cdk/aws-eks/lib/helm-chart.ts | 84 +- .../@aws-cdk/aws-eks/lib/helm-chart/index.py | 140 +- .../aws-eks/lib/k8s-resource-handler/index.py | 66 + packages/@aws-cdk/aws-eks/lib/k8s-resource.ts | 69 +- packages/@aws-cdk/aws-eks/package.json | 7 +- .../test/cluster-resource-handler-mocks.ts | 132 ++ .../integ.eks-cluster.defaults.expected.json | 516 +++--- .../test/integ.eks-cluster.defaults.ts | 15 +- ...eks-cluster.kubectl-disabled.expected.json | 10 +- .../test/integ.eks-cluster.lit.expected.json | 421 ++--- .../test/integ.eks-helm.lit.expected.json | 610 +++---- .../test/integ.eks-kubectl.lit.expected.json | 452 ++--- .../aws-eks/test/integ.eks-spot.expected.json | 438 ++--- .../test/test.cluster-resource-provider.ts | 395 +++++ .../@aws-cdk/aws-eks/test/test.cluster.ts | 208 +++ packages/decdk/package.json | 3 +- 63 files changed, 14382 insertions(+), 1320 deletions(-) create mode 100644 packages/@aws-cdk/aws-eks-legacy/.gitignore create mode 100644 packages/@aws-cdk/aws-eks-legacy/.npmignore create mode 100644 packages/@aws-cdk/aws-eks-legacy/LICENSE create mode 100644 packages/@aws-cdk/aws-eks-legacy/NOTICE create mode 100644 packages/@aws-cdk/aws-eks-legacy/README.md create mode 100644 packages/@aws-cdk/aws-eks-legacy/lib/aws-auth-mapping.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/lib/aws-auth.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/lib/cluster-resource.ts rename packages/@aws-cdk/{aws-eks => aws-eks-legacy}/lib/cluster-resource/index.py (100%) create mode 100644 packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/lib/helm-chart/index.py create mode 100644 packages/@aws-cdk/aws-eks-legacy/lib/index.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts rename packages/@aws-cdk/{aws-eks => aws-eks-legacy}/lib/k8s-resource/index.py (100%) create mode 100644 packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/lib/spot-interrupt-handler.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/package.json create mode 100644 packages/@aws-cdk/aws-eks-legacy/scripts/kube_bump.sh create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/MANUAL_TEST.md create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/example.ssh-into-nodes.lit.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.defaults.expected.json create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.defaults.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.kubectl-disabled.expected.json create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.kubectl-disabled.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.lit.expected.json create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.lit.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-helm.lit.expected.json create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-helm.lit.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-kubectl.lit.expected.json create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-kubectl.lit.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-spot.expected.json create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/integ.eks-spot.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/test.awsauth.ts rename packages/@aws-cdk/{aws-eks => aws-eks-legacy}/test/test.cluster-resource.ts (100%) create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/test.cluster.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/test.helm-chart.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/test.manifest.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/test.user-data.ts create mode 100644 packages/@aws-cdk/aws-eks-legacy/test/util.ts create mode 100644 packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/handler.ts create mode 100644 packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts create mode 100644 packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts create mode 100644 packages/@aws-cdk/aws-eks/lib/k8s-resource-handler/index.py create mode 100644 packages/@aws-cdk/aws-eks/test/cluster-resource-handler-mocks.ts create mode 100644 packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts diff --git a/packages/@aws-cdk/aws-eks-legacy/.gitignore b/packages/@aws-cdk/aws-eks-legacy/.gitignore new file mode 100644 index 0000000000000..c1681abf99e81 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/.gitignore @@ -0,0 +1,17 @@ +*.js +*.js.map +*.d.ts +tsconfig.json +tslint.json +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk +nyc.config.js \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/.npmignore b/packages/@aws-cdk/aws-eks-legacy/.npmignore new file mode 100644 index 0000000000000..5f2fbb23042e4 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/.npmignore @@ -0,0 +1,20 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo + +tsconfig.json \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/LICENSE b/packages/@aws-cdk/aws-eks-legacy/LICENSE new file mode 100644 index 0000000000000..46c185646b439 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-eks-legacy/NOTICE b/packages/@aws-cdk/aws-eks-legacy/NOTICE new file mode 100644 index 0000000000000..8585168af8b7d --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-eks-legacy/README.md b/packages/@aws-cdk/aws-eks-legacy/README.md new file mode 100644 index 0000000000000..33aa05557a731 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/README.md @@ -0,0 +1,427 @@ +## Amazon EKS Construct Library + + + +--- + +![Stability: Deprecated](https://img.shields.io/badge/stability-Deprecated-critical.svg?style=for-the-badge) + +> This API may emit warnings. Backward compatibility is not guaranteed. + +--- + + +**This module is available for backwards compatibility purposes only ([details](https://github.com/aws/aws-cdk/pull/5540)). It will +no longer be released with the CDK starting March 1st, 2020. See [issue +#5544](https://github.com/aws/aws-cdk/issues/5544) for upgrade instructions.** + +--- + +This construct library allows you to define [Amazon Elastic Container Service +for Kubernetes (EKS)](https://aws.amazon.com/eks/) clusters programmatically. +This library also supports programmatically defining Kubernetes resource +manifests within EKS clusters. + +This example defines an Amazon EKS cluster with the following configuration: + +- 2x **m5.large** instances (this instance type suits most common use-cases, and is good value for money) +- Dedicated VPC with default configuration (see [ec2.Vpc](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-ec2-readme.html#vpc)) +- A Kubernetes pod with a container based on the [paulbouwer/hello-kubernetes](https://github.com/paulbouwer/hello-kubernetes) image. + +```ts +const cluster = new eks.Cluster(this, 'hello-eks'); + +cluster.addResource('mypod', { + apiVersion: 'v1', + kind: 'Pod', + metadata: { name: 'mypod' }, + spec: { + containers: [ + { + name: 'hello', + image: 'paulbouwer/hello-kubernetes:1.5', + ports: [ { containerPort: 8080 } ] + } + ] + } +}); +``` + +Here is a [complete sample](https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-eks/test/integ.eks-kubectl.lit.ts). + +### Capacity + +By default, `eks.Cluster` is created with x2 `m5.large` instances. + +```ts +new eks.Cluster(this, 'cluster-two-m5-large'); +``` + +The quantity and instance type for the default capacity can be specified through +the `defaultCapacity` and `defaultCapacityInstance` props: + +```ts +new eks.Cluster(this, 'cluster', { + defaultCapacity: 10, + defaultCapacityInstance: new ec2.InstanceType('m2.xlarge') +}); +``` + +To disable the default capacity, simply set `defaultCapacity` to `0`: + +```ts +new eks.Cluster(this, 'cluster-with-no-capacity', { defaultCapacity: 0 }); +``` + +The `cluster.defaultCapacity` property will reference the `AutoScalingGroup` +resource for the default capacity. It will be `undefined` if `defaultCapacity` +is set to `0`: + +```ts +const cluster = new eks.Cluster(this, 'my-cluster'); +cluster.defaultCapacity!.scaleOnCpuUtilization('up', { + targetUtilizationPercent: 80 +}); +``` + +You can add customized capacity through `cluster.addCapacity()` or +`cluster.addAutoScalingGroup()`: + +```ts +cluster.addCapacity('frontend-nodes', { + instanceType: new ec2.InstanceType('t2.medium'), + desiredCapacity: 3, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC } +}); +``` + +### Spot Capacity + +If `spotPrice` is specified, the capacity will be purchased from spot instances: + +```ts +cluster.addCapacity('spot', { + spotPrice: '0.1094', + instanceType: new ec2.InstanceType('t3.large'), + maxCapacity: 10 +}); +``` + +Spot instance nodes will be labeled with `lifecycle=Ec2Spot` and tainted with `PreferNoSchedule`. + +The [Spot Termination Handler](https://github.com/awslabs/ec2-spot-labs/tree/master/ec2-spot-eks-solution/spot-termination-handler) +DaemonSet will be installed on these nodes. The termination handler leverages +[EC2 Spot Instance Termination Notices](https://aws.amazon.com/blogs/aws/new-ec2-spot-instance-termination-notices/) +to gracefully stop all pods running on spot nodes that are about to be +terminated. + +### Bootstrapping + +When adding capacity, you can specify options for +[/etc/eks/boostrap.sh](https://github.com/awslabs/amazon-eks-ami/blob/master/files/bootstrap.sh) +which is responsible for associating the node to the EKS cluster. For example, +you can use `kubeletExtraArgs` to add custom node labels or taints. + + +```ts +// up to ten spot instances +cluster.addCapacity('spot', { + instanceType: new ec2.InstanceType('t3.large'), + desiredCapacity: 2, + bootstrapOptions: { + kubeletExtraArgs: '--node-labels foo=bar,goo=far', + awsApiRetryAttempts: 5 + } +}); +``` + +To disable bootstrapping altogether (i.e. to fully customize user-data), set `bootstrapEnabled` to `false` when you add +the capacity. + +### Masters Role + +The Amazon EKS construct library allows you to specify an IAM role that will be +granted `system:masters` privileges on your cluster. + +Without specifying a `mastersRole`, you will not be able to interact manually +with the cluster. + +The following example defines an IAM role that can be assumed by all users +in the account and shows how to use the `mastersRole` property to map this +role to the Kubernetes `system:masters` group: + +```ts +// first define the role +const clusterAdmin = new iam.Role(this, 'AdminRole', { + assumedBy: new iam.AccountRootPrincipal() +}); + +// now define the cluster and map role to "masters" RBAC group +new eks.Cluster(this, 'Cluster', { + mastersRole: clusterAdmin +}); +``` + +When you `cdk deploy` this CDK app, you will notice that an output will be printed +with the `update-kubeconfig` command. + +Something like this: + +``` +Outputs: +eks-integ-defaults.ClusterConfigCommand43AAE40F = aws eks update-kubeconfig --name cluster-ba7c166b-c4f3-421c-bf8a-6812e4036a33 --role-arn arn:aws:iam::112233445566:role/eks-integ-defaults-Role1ABCC5F0-1EFK2W5ZJD98Y +``` + +Copy & paste the "`aws eks update-kubeconfig ...`" command to your shell in +order to connect to your EKS cluster with the "masters" role. + +Now, given [AWS CLI](https://aws.amazon.com/cli/) is configured to use AWS +credentials for a user that is trusted by the masters role, you should be able +to interact with your cluster through `kubectl` (the above example will trust +all users in the account). + +For example: + +```console +$ aws eks update-kubeconfig --name cluster-ba7c166b-c4f3-421c-bf8a-6812e4036a33 --role-arn arn:aws:iam::112233445566:role/eks-integ-defaults-Role1ABCC5F0-1EFK2W5ZJD98Y +Added new context arn:aws:eks:eu-west-2:112233445566:cluster/cluster-ba7c166b-c4f3-421c-bf8a-6812e4036a33 to /Users/boom/.kube/config + +$ kubectl get nodes # list all nodes +NAME STATUS ROLES AGE VERSION +ip-10-0-147-66.eu-west-2.compute.internal Ready 21m v1.13.7-eks-c57ff8 +ip-10-0-169-151.eu-west-2.compute.internal Ready 21m v1.13.7-eks-c57ff8 + +$ kubectl get all -n kube-system +NAME READY STATUS RESTARTS AGE +pod/aws-node-fpmwv 1/1 Running 0 21m +pod/aws-node-m9htf 1/1 Running 0 21m +pod/coredns-5cb4fb54c7-q222j 1/1 Running 0 23m +pod/coredns-5cb4fb54c7-v9nxx 1/1 Running 0 23m +pod/kube-proxy-d4jrh 1/1 Running 0 21m +pod/kube-proxy-q7hh7 1/1 Running 0 21m + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/kube-dns ClusterIP 172.20.0.10 53/UDP,53/TCP 23m + +NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE +daemonset.apps/aws-node 2 2 2 2 2 23m +daemonset.apps/kube-proxy 2 2 2 2 2 23m + +NAME READY UP-TO-DATE AVAILABLE AGE +deployment.apps/coredns 2/2 2 2 23m + +NAME DESIRED CURRENT READY AGE +replicaset.apps/coredns-5cb4fb54c7 2 2 2 23m +``` + +For your convenience, an AWS CloudFormation output will automatically be +included in your template and will be printed when running `cdk deploy`. + +**NOTE**: if the cluster is configured with `kubectlEnabled: false`, it +will be created with the role/user that created the AWS CloudFormation +stack. See [Kubectl Support](#kubectl-support) for details. + +### Kubernetes Resources + +The `KubernetesResource` construct or `cluster.addResource` method can be used +to apply Kubernetes resource manifests to this cluster. + +The following examples will deploy the [paulbouwer/hello-kubernetes](https://github.com/paulbouwer/hello-kubernetes) +service on the cluster: + +```ts +const appLabel = { app: "hello-kubernetes" }; + +const deployment = { + apiVersion: "apps/v1", + kind: "Deployment", + metadata: { name: "hello-kubernetes" }, + spec: { + replicas: 3, + selector: { matchLabels: appLabel }, + template: { + metadata: { labels: appLabel }, + spec: { + containers: [ + { + name: "hello-kubernetes", + image: "paulbouwer/hello-kubernetes:1.5", + ports: [ { containerPort: 8080 } ] + } + ] + } + } + } +}; + +const service = { + apiVersion: "v1", + kind: "Service", + metadata: { name: "hello-kubernetes" }, + spec: { + type: "LoadBalancer", + ports: [ { port: 80, targetPort: 8080 } ], + selector: appLabel + } +}; + +// option 1: use a construct +new KubernetesResource(this, 'hello-kub', { + cluster, + manifest: [ deployment, service ] +}); + +// or, option2: use `addResource` +cluster.addResource('hello-kub', service, deployment); +``` + +Since Kubernetes resources are implemented as CloudFormation resources in the +CDK. This means that if the resource is deleted from your code (or the stack is +deleted), the next `cdk deploy` will issue a `kubectl delete` command and the +Kubernetes resources will be deleted. + +### AWS IAM Mapping + +As described in the [Amazon EKS User Guide](https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html), +you can map AWS IAM users and roles to [Kubernetes Role-based access control (RBAC)](https://kubernetes.io/docs/reference/access-authn-authz/rbac). + +The Amazon EKS construct manages the **aws-auth ConfigMap** Kubernetes resource +on your behalf and exposes an API through the `cluster.awsAuth` for mapping +users, roles and accounts. + +Furthermore, when auto-scaling capacity is added to the cluster (through +`cluster.addCapacity` or `cluster.addAutoScalingGroup`), the IAM instance role +of the auto-scaling group will be automatically mapped to RBAC so nodes can +connect to the cluster. No manual mapping is required any longer. + +> NOTE: `cluster.awsAuth` will throw an error if your cluster is created with `kubectlEnabled: false`. + +For example, let's say you want to grant an IAM user administrative privileges +on your cluster: + +```ts +const adminUser = new iam.User(this, 'Admin'); +cluster.awsAuth.addUserMapping(adminUser, { groups: [ 'system:masters' ]}); +``` + +A convenience method for mapping a role to the `system:masters` group is also available: + +```ts +cluster.awsAuth.addMastersRole(role) +``` + +### Node ssh Access + +If you want to be able to SSH into your worker nodes, you must already +have an SSH key in the region you're connecting to and pass it, and you must +be able to connect to the hosts (meaning they must have a public IP and you +should be allowed to connect to them on port 22): + +[ssh into nodes example](test/example.ssh-into-nodes.lit.ts) + +If you want to SSH into nodes in a private subnet, you should set up a +bastion host in a public subnet. That setup is recommended, but is +unfortunately beyond the scope of this documentation. + +### kubectl Support + +When you create an Amazon EKS cluster, the IAM entity user or role, such as a +[federated user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers.html) +that creates the cluster, is automatically granted `system:masters` permissions +in the cluster's RBAC configuration. + +In order to allow programmatically defining **Kubernetes resources** in your AWS +CDK app and provisioning them through AWS CloudFormation, we will need to assume +this "masters" role every time we want to issue `kubectl` operations against your +cluster. + +At the moment, the [AWS::EKS::Cluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html) +AWS CloudFormation resource does not support this behavior, so in order to +support "programmatic kubectl", such as applying manifests +and mapping IAM roles from within your CDK application, the Amazon EKS +construct library uses a custom resource for provisioning the cluster. +This custom resource is executed with an IAM role that we can then use +to issue `kubectl` commands. + +The default behavior of this library is to use this custom resource in order +to retain programmatic control over the cluster. In other words: to allow +you to define Kubernetes resources in your CDK code instead of having to +manage your Kubernetes applications through a separate system. + +One of the implications of this design is that, by default, the user who +provisioned the AWS CloudFormation stack (executed `cdk deploy`) will +not have administrative privileges on the EKS cluster. + +1. Additional resources will be synthesized into your template (the AWS Lambda + function, the role and policy). +2. As described in [Interacting with Your Cluster](#interacting-with-your-cluster), + if you wish to be able to manually interact with your cluster, you will need + to map an IAM role or user to the `system:masters` group. This can be either + done by specifying a `mastersRole` when the cluster is defined, calling + `cluster.awsAuth.addMastersRole` or explicitly mapping an IAM role or IAM user to the + relevant Kubernetes RBAC groups using `cluster.addRoleMapping` and/or + `cluster.addUserMapping`. + +If you wish to disable the programmatic kubectl behavior and use the standard +AWS::EKS::Cluster resource, you can specify `kubectlEnabled: false` when you define +the cluster: + +```ts +new eks.Cluster(this, 'cluster', { + kubectlEnabled: false +}); +``` + +**Take care**: a change in this property will cause the cluster to be destroyed +and a new cluster to be created. + +When kubectl is disabled, you should be aware of the following: + +1. When you log-in to your cluster, you don't need to specify `--role-arn` as + long as you are using the same user that created the cluster. +2. As described in the Amazon EKS User Guide, you will need to manually + edit the [aws-auth ConfigMap](https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html) + when you add capacity in order to map the IAM instance role to RBAC to allow nodes to join the cluster. +3. Any `eks.Cluster` APIs that depend on programmatic kubectl support will fail + with an error: `cluster.addResource`, `cluster.addChart`, `cluster.awsAuth`, `props.mastersRole`. + +### Helm Charts + +The `HelmChart` construct or `cluster.addChart` method can be used +to add Kubernetes resources to this cluster using Helm. + +The following example will install the [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/) +to you cluster using Helm. + +```ts +// option 1: use a construct +new HelmChart(this, 'NginxIngress', { + cluster, + chart: 'nginx-ingress', + repository: 'https://helm.nginx.com/stable', + namespace: 'kube-system' +}); + +// or, option2: use `addChart` +cluster.addChart('NginxIngress', { + chart: 'nginx-ingress', + repository: 'https://helm.nginx.com/stable', + namespace: 'kube-system' +}); +``` + +Helm charts will be installed and updated using `helm upgrade --install`. +This means that if the chart is added to CDK with the same release name, it will try to update +the chart in the cluster. The chart will exists as CloudFormation resource. + +Helm charts are implemented as CloudFormation resources in CDK. +This means that if the chart is deleted from your code (or the stack is +deleted), the next `cdk deploy` will issue a `helm uninstall` command and the +Helm chart will be deleted. + +When there is no `release` defined, the chart will be installed using the `node.uniqueId`, +which will be lower cassed and truncated to the last 63 characters. + +### Roadmap + +- [ ] AutoScaling (combine EC2 and Kubernetes scaling) diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/aws-auth-mapping.ts b/packages/@aws-cdk/aws-eks-legacy/lib/aws-auth-mapping.ts new file mode 100644 index 0000000000000..5b105e598bc15 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/lib/aws-auth-mapping.ts @@ -0,0 +1,15 @@ +export interface Mapping { + /** + * The user name within Kubernetes to map to the IAM role. + * + * @default - By default, the user name is the ARN of the IAM role. + */ + readonly username?: string; + + /** + * A list of groups within Kubernetes to which the role is mapped. + * + * @see https://kubernetes.io/docs/reference/access-authn-authz/rbac/#default-roles-and-role-bindings + */ + readonly groups: string[]; +} diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/aws-auth.ts b/packages/@aws-cdk/aws-eks-legacy/lib/aws-auth.ts new file mode 100644 index 0000000000000..55e001f871de6 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/lib/aws-auth.ts @@ -0,0 +1,119 @@ +import * as iam from '@aws-cdk/aws-iam'; +import { Construct, Lazy, Stack } from '@aws-cdk/core'; +import { Mapping } from './aws-auth-mapping'; +import { Cluster } from './cluster'; +import { KubernetesResource } from './k8s-resource'; + +export interface AwsAuthProps { + /** + * The EKS cluster to apply this configuration to. + * + * [disable-awslint:ref-via-interface] + */ + readonly cluster: Cluster; +} + +/** + * Manages mapping between IAM users and roles to Kubernetes RBAC configuration. + * + * @see https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html + */ +export class AwsAuth extends Construct { + private readonly stack: Stack; + private readonly roleMappings = new Array<{ role: iam.IRole, mapping: Mapping }>(); + private readonly userMappings = new Array<{ user: iam.IUser, mapping: Mapping }>(); + private readonly accounts = new Array(); + + constructor(scope: Construct, id: string, props: AwsAuthProps) { + super(scope, id); + + this.stack = Stack.of(this); + + new KubernetesResource(this, 'manifest', { + cluster: props.cluster, + manifest: [ + { + apiVersion: "v1", + kind: "ConfigMap", + metadata: { + name: "aws-auth", + namespace: "kube-system" + }, + data: { + mapRoles: this.synthesizeMapRoles(), + mapUsers: this.synthesizeMapUsers(), + mapAccounts: this.synthesizeMapAccounts(), + } + } + ] + }); + } + + /** + * Adds the specified IAM role to the `system:masters` RBAC group, which means + * that anyone that can assume it will be able to administer this Kubernetes system. + * + * @param role The IAM role to add + * @param username Optional user (defaults to the role ARN) + */ + public addMastersRole(role: iam.IRole, username?: string) { + this.addRoleMapping(role, { + username, + groups: [ 'system:masters' ] + }); + } + + /** + * Adds a mapping between an IAM role to a Kubernetes user and groups. + * + * @param role The IAM role to map + * @param mapping Mapping to k8s user name and groups + */ + public addRoleMapping(role: iam.IRole, mapping: Mapping) { + this.roleMappings.push({ role, mapping }); + } + + /** + * Adds a mapping between an IAM user to a Kubernetes user and groups. + * + * @param user The IAM user to map + * @param mapping Mapping to k8s user name and groups + */ + public addUserMapping(user: iam.IUser, mapping: Mapping) { + this.userMappings.push({ user, mapping }); + } + + /** + * Additional AWS account to add to the aws-auth configmap. + * @param accountId account number + */ + public addAccount(accountId: string) { + this.accounts.push(accountId); + } + + private synthesizeMapRoles() { + return Lazy.anyValue({ + produce: () => this.stack.toJsonString(this.roleMappings.map(m => ({ + rolearn: m.role.roleArn, + username: m.mapping.username, + groups: m.mapping.groups + }))) + }); + } + + private synthesizeMapUsers() { + return Lazy.anyValue({ + produce: () => this.stack.toJsonString(this.userMappings.map(m => ({ + userarn: m.user.userArn, + username: m.mapping.username, + groups: m.mapping.groups + }))) + }); + } + + private synthesizeMapAccounts() { + return Lazy.anyValue({ + produce: () => this.stack.toJsonString(this.accounts) + }); + } +} diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/cluster-resource.ts b/packages/@aws-cdk/aws-eks-legacy/lib/cluster-resource.ts new file mode 100644 index 0000000000000..2cfb1511703f3 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/lib/cluster-resource.ts @@ -0,0 +1,82 @@ +import * as cfn from '@aws-cdk/aws-cloudformation'; +import * as iam from '@aws-cdk/aws-iam'; +import * as lambda from '@aws-cdk/aws-lambda'; +import { Construct, Duration, Token } from '@aws-cdk/core'; +import * as path from 'path'; +import { CfnClusterProps } from './eks.generated'; +import { KubectlLayer } from './kubectl-layer'; + +/** + * A low-level CFN resource Amazon EKS cluster implemented through a custom + * resource. + * + * Implements EKS create/update/delete through a CloudFormation custom resource + * in order to allow us to control the IAM role which creates the cluster. This + * is required in order to be able to allow CloudFormation to interact with the + * cluster via `kubectl` to enable Kubernetes management capabilities like apply + * manifest and IAM role/user RBAC mapping. + */ +export class ClusterResource extends Construct { + /** + * The AWS CloudFormation resource type used for this resource. + */ + public static readonly RESOURCE_TYPE = 'Custom::AWSCDK-EKS-Cluster'; + + public readonly attrEndpoint: string; + public readonly attrArn: string; + public readonly attrCertificateAuthorityData: string; + public readonly ref: string; + + /** + * The IAM role which created the cluster. Initially this is the only IAM role + * that gets administrator privilages on the cluster (`system:masters`), and + * will be able to issue `kubectl` commands against it. + */ + public readonly creationRole: iam.IRole; + + constructor(scope: Construct, id: string, props: CfnClusterProps) { + super(scope, id); + + // each cluster resource will have it's own lambda handler since permissions + // are scoped to this cluster and related resources like it's role + const handler = new lambda.Function(this, 'ResourceHandler', { + code: lambda.Code.fromAsset(path.join(__dirname, 'cluster-resource')), + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + timeout: Duration.minutes(15), + memorySize: 512, + layers: [ KubectlLayer.getOrCreate(this) ], + }); + + if (!props.roleArn) { + throw new Error(`"roleArn" is required`); + } + + // since we don't know the cluster name at this point, we must give this role star resource permissions + handler.addToRolePolicy(new iam.PolicyStatement({ + actions: [ 'eks:CreateCluster', 'eks:DescribeCluster', 'eks:DeleteCluster', 'eks:UpdateClusterVersion' ], + resources: [ '*' ] + })); + + // the CreateCluster API will allow the cluster to assume this role, so we + // need to allow the lambda execution role to pass it. + handler.addToRolePolicy(new iam.PolicyStatement({ + actions: [ 'iam:PassRole' ], + resources: [ props.roleArn ] + })); + + const resource = new cfn.CustomResource(this, 'Resource', { + resourceType: ClusterResource.RESOURCE_TYPE, + provider: cfn.CustomResourceProvider.lambda(handler), + properties: { + Config: props + } + }); + + this.ref = resource.ref; + this.attrEndpoint = Token.asString(resource.getAtt('Endpoint')); + this.attrArn = Token.asString(resource.getAtt('Arn')); + this.attrCertificateAuthorityData = Token.asString(resource.getAtt('CertificateAuthorityData')); + this.creationRole = handler.role!; + } +} diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource/index.py b/packages/@aws-cdk/aws-eks-legacy/lib/cluster-resource/index.py similarity index 100% rename from packages/@aws-cdk/aws-eks/lib/cluster-resource/index.py rename to packages/@aws-cdk/aws-eks-legacy/lib/cluster-resource/index.py diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts new file mode 100644 index 0000000000000..4491aff79dfdd --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts @@ -0,0 +1,876 @@ +import * as autoscaling from '@aws-cdk/aws-autoscaling'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as ssm from '@aws-cdk/aws-ssm'; +import { CfnOutput, Construct, Duration, IResource, Resource, Stack, Tag, Token } from '@aws-cdk/core'; +import * as path from 'path'; +import { AwsAuth } from './aws-auth'; +import { ClusterResource } from './cluster-resource'; +import { CfnCluster, CfnClusterProps } from './eks.generated'; +import { HelmChart, HelmChartOptions } from './helm-chart'; +import { KubernetesResource } from './k8s-resource'; +import { KubectlLayer } from './kubectl-layer'; +import { spotInterruptHandler } from './spot-interrupt-handler'; +import { renderUserData } from './user-data'; + +// defaults are based on https://eksctl.io +const DEFAULT_CAPACITY_COUNT = 2; +const DEFAULT_CAPACITY_TYPE = ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE); + +/** + * An EKS cluster + */ +export interface ICluster extends IResource, ec2.IConnectable { + /** + * The VPC in which this Cluster was created + */ + readonly vpc: ec2.IVpc; + + /** + * The physical name of the Cluster + * @attribute + */ + readonly clusterName: string; + + /** + * The unique ARN assigned to the service by AWS + * in the form of arn:aws:eks: + * @attribute + */ + readonly clusterArn: string; + + /** + * The API Server endpoint URL + * @attribute + */ + readonly clusterEndpoint: string; + + /** + * The certificate-authority-data for your cluster. + * @attribute + */ + readonly clusterCertificateAuthorityData: string; +} + +export interface ClusterAttributes { + /** + * The VPC in which this Cluster was created + */ + readonly vpc: ec2.IVpc; + + /** + * The physical name of the Cluster + */ + readonly clusterName: string; + + /** + * The unique ARN assigned to the service by AWS + * in the form of arn:aws:eks: + */ + readonly clusterArn: string; + + /** + * The API Server endpoint URL + */ + readonly clusterEndpoint: string; + + /** + * The certificate-authority-data for your cluster. + */ + readonly clusterCertificateAuthorityData: string; + + /** + * The security groups associated with this cluster. + */ + readonly securityGroups: ec2.ISecurityGroup[]; +} + +/** + * Properties to instantiate the Cluster + */ +export interface ClusterProps { + /** + * The VPC in which to create the Cluster + * + * @default - a VPC with default configuration will be created and can be accessed through `cluster.vpc`. + */ + readonly vpc?: ec2.IVpc; + + /** + * Where to place EKS Control Plane ENIs + * + * If you want to create public load balancers, this must include public subnets. + * + * For example, to only select private subnets, supply the following: + * + * ```ts + * vpcSubnets: [ + * { subnetType: ec2.SubnetType.Private } + * ] + * ``` + * + * @default - All public and private subnets + */ + readonly vpcSubnets?: ec2.SubnetSelection[]; + + /** + * Role that provides permissions for the Kubernetes control plane to make calls to AWS API operations on your behalf. + * + * @default - A role is automatically created for you + */ + readonly role?: iam.IRole; + + /** + * Name for the cluster. + * + * @default - Automatically generated name + */ + readonly clusterName?: string; + + /** + * Security Group to use for Control Plane ENIs + * + * @default - A security group is automatically created + */ + readonly securityGroup?: ec2.ISecurityGroup; + + /** + * The Kubernetes version to run in the cluster + * + * @default - If not supplied, will use Amazon default version + */ + readonly version?: string; + + /** + * An IAM role that will be added to the `system:masters` Kubernetes RBAC + * group. + * + * @see https://kubernetes.io/docs/reference/access-authn-authz/rbac/#default-roles-and-role-bindings + * + * @default - By default, it will only possible to update this Kubernetes + * system by adding resources to this cluster via `addResource` or + * by defining `KubernetesResource` resources in your AWS CDK app. + * Use this if you wish to grant cluster administration privileges + * to another role. + */ + readonly mastersRole?: iam.IRole; + + /** + * Allows defining `kubectrl`-related resources on this cluster. + * + * If this is disabled, it will not be possible to use the following + * capabilities: + * - `addResource` + * - `addRoleMapping` + * - `addUserMapping` + * - `addMastersRole` and `props.mastersRole` + * + * If this is disabled, the cluster can only be managed by issuing `kubectl` + * commands from a session that uses the IAM role/user that created the + * account. + * + * _NOTE_: changing this value will destoy the cluster. This is because a + * managable cluster must be created using an AWS CloudFormation custom + * resource which executes with an IAM role owned by the CDK app. + * + * @default true The cluster can be managed by the AWS CDK application. + */ + readonly kubectlEnabled?: boolean; + + /** + * Number of instances to allocate as an initial capacity for this cluster. + * Instance type can be configured through `defaultCapacityInstanceType`, + * which defaults to `m5.large`. + * + * Use `cluster.addCapacity` to add additional customized capacity. Set this + * to `0` is you wish to avoid the initial capacity allocation. + * + * @default 2 + */ + readonly defaultCapacity?: number; + + /** + * The instance type to use for the default capacity. This will only be taken + * into account if `defaultCapacity` is > 0. + * + * @default m5.large + */ + readonly defaultCapacityInstance?: ec2.InstanceType; + + /** + * Determines whether a CloudFormation output with the name of the cluster + * will be synthesized. + * + * @default false + */ + readonly outputClusterName?: boolean; + + /** + * Determines whether a CloudFormation output with the ARN of the "masters" + * IAM role will be synthesized (if `mastersRole` is specified). + * + * @default false + */ + readonly outputMastersRoleArn?: boolean; + + /** + * Determines whether a CloudFormation output with the `aws eks + * update-kubeconfig` command will be synthesized. This command will include + * the cluster name and, if applicable, the ARN of the masters IAM role. + * + * @default true + */ + readonly outputConfigCommand?: boolean; +} + +/** + * A Cluster represents a managed Kubernetes Service (EKS) + * + * This is a fully managed cluster of API Servers (control-plane) + * The user is still required to create the worker nodes. + * + * @resource AWS::Eks-Legacy::Cluster + */ +export class Cluster extends Resource implements ICluster { + /** + * Import an existing cluster + * + * @param scope the construct scope, in most cases 'this' + * @param id the id or name to import as + * @param attrs the cluster properties to use for importing information + */ + public static fromClusterAttributes(scope: Construct, id: string, attrs: ClusterAttributes): ICluster { + return new ImportedCluster(scope, id, attrs); + } + + /** + * The VPC in which this Cluster was created + */ + public readonly vpc: ec2.IVpc; + + /** + * The Name of the created EKS Cluster + */ + public readonly clusterName: string; + + /** + * The AWS generated ARN for the Cluster resource + * + * @example arn:aws:eks:us-west-2:666666666666:cluster/prod + */ + public readonly clusterArn: string; + + /** + * The endpoint URL for the Cluster + * + * This is the URL inside the kubeconfig file to use with kubectl + * + * @example https://5E1D0CEXAMPLEA591B746AFC5AB30262.yl4.us-west-2.eks.amazonaws.com + */ + public readonly clusterEndpoint: string; + + /** + * The certificate-authority-data for your cluster. + */ + public readonly clusterCertificateAuthorityData: string; + + /** + * Manages connection rules (Security Group Rules) for the cluster + * + * @type {ec2.Connections} + * @memberof Cluster + */ + public readonly connections: ec2.Connections; + + /** + * IAM role assumed by the EKS Control Plane + */ + public readonly role: iam.IRole; + + /** + * Indicates if `kubectl` related operations can be performed on this cluster. + */ + public readonly kubectlEnabled: boolean; + + /** + * The CloudFormation custom resource handler that can apply Kubernetes + * manifests to this cluster. + * + * @internal + */ + public readonly _k8sResourceHandler?: lambda.Function; + + /** + * The auto scaling group that hosts the default capacity for this cluster. + * This will be `undefined` if the default capacity is set to 0. + */ + public readonly defaultCapacity?: autoscaling.AutoScalingGroup; + + /** + * The IAM role that was used to create this cluster. This role is + * automatically added by Amazon EKS to the `system:masters` RBAC group of the + * cluster. Use `addMastersRole` or `props.mastersRole` to define additional + * IAM roles as administrators. + * + * @internal + */ + public readonly _defaultMastersRole?: iam.IRole; + + /** + * Manages the aws-auth config map. + */ + private _awsAuth?: AwsAuth; + + private readonly version: string | undefined; + + /** + * Initiates an EKS Cluster with the supplied arguments + * + * @param scope a Construct, most likely a cdk.Stack created + * @param name the name of the Construct to create + * @param props properties in the IClusterProps interface + */ + constructor(scope: Construct, id: string, props: ClusterProps = { }) { + super(scope, id, { + physicalName: props.clusterName, + }); + + this.node.addWarning(`The @aws-cdk/aws-eks-legacy module will no longer be released as part of the AWS CDK starting March 1st, 2020. Please refer to https://github.com/aws/aws-cdk/issues/5544 for upgrade instructions`); + + const stack = Stack.of(this); + + this.vpc = props.vpc || new ec2.Vpc(this, 'DefaultVpc'); + this.version = props.version; + + this.tagSubnets(); + + this.role = props.role || new iam.Role(this, 'ClusterRole', { + assumedBy: new iam.ServicePrincipal('eks.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSClusterPolicy'), + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSServicePolicy'), + ], + }); + + const securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'ControlPlaneSecurityGroup', { + vpc: this.vpc, + description: 'EKS Control Plane Security Group', + }); + + this.connections = new ec2.Connections({ + securityGroups: [securityGroup], + defaultPort: ec2.Port.tcp(443), // Control Plane has an HTTPS API + }); + + // Get subnetIds for all selected subnets + const placements = props.vpcSubnets || [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE }]; + const subnetIds = [...new Set(Array().concat(...placements.map(s => this.vpc.selectSubnets(s).subnetIds)))]; + + const clusterProps: CfnClusterProps = { + name: this.physicalName, + roleArn: this.role.roleArn, + version: props.version, + resourcesVpcConfig: { + securityGroupIds: [securityGroup.securityGroupId], + subnetIds + } + }; + + let resource; + this.kubectlEnabled = props.kubectlEnabled === undefined ? true : props.kubectlEnabled; + if (this.kubectlEnabled) { + resource = new ClusterResource(this, 'Resource', clusterProps); + this._defaultMastersRole = resource.creationRole; + } else { + resource = new CfnCluster(this, 'Resource', clusterProps); + } + + this.clusterName = this.getResourceNameAttribute(resource.ref); + this.clusterArn = this.getResourceArnAttribute(resource.attrArn, { + service: 'eks', + resource: 'cluster', + resourceName: this.physicalName, + }); + + this.clusterEndpoint = resource.attrEndpoint; + this.clusterCertificateAuthorityData = resource.attrCertificateAuthorityData; + + const updateConfigCommandPrefix = `aws eks update-kubeconfig --name ${this.clusterName}`; + const getTokenCommandPrefix = `aws eks get-token --cluster-name ${this.clusterName}`; + const commonCommandOptions = [ `--region ${stack.region}` ]; + + if (props.outputClusterName) { + new CfnOutput(this, 'ClusterName', { value: this.clusterName }); + } + + // we maintain a single manifest custom resource handler per cluster since + // permissions and role are scoped. This will return `undefined` if kubectl + // is not enabled for this cluster. + this._k8sResourceHandler = this.createKubernetesResourceHandler(); + + // map the IAM role to the `system:masters` group. + if (props.mastersRole) { + if (!this.kubectlEnabled) { + throw new Error(`Cannot specify a "masters" role if kubectl is disabled`); + } + + this.awsAuth.addMastersRole(props.mastersRole); + + if (props.outputMastersRoleArn) { + new CfnOutput(this, 'MastersRoleArn', { value: props.mastersRole.roleArn }); + } + + commonCommandOptions.push(`--role-arn ${props.mastersRole.roleArn}`); + } + + // allocate default capacity if non-zero (or default). + const desiredCapacity = props.defaultCapacity === undefined ? DEFAULT_CAPACITY_COUNT : props.defaultCapacity; + if (desiredCapacity > 0) { + const instanceType = props.defaultCapacityInstance || DEFAULT_CAPACITY_TYPE; + this.defaultCapacity = this.addCapacity('DefaultCapacity', { instanceType, desiredCapacity }); + } + + const outputConfigCommand = props.outputConfigCommand === undefined ? true : props.outputConfigCommand; + if (outputConfigCommand) { + const postfix = commonCommandOptions.join(' '); + new CfnOutput(this, 'ConfigCommand', { value: `${updateConfigCommandPrefix} ${postfix}` }); + new CfnOutput(this, 'GetTokenCommand', { value: `${getTokenCommandPrefix} ${postfix}` }); + } + } + + /** + * Add nodes to this EKS cluster + * + * The nodes will automatically be configured with the right VPC and AMI + * for the instance type and Kubernetes version. + * + * Spot instances will be labeled `lifecycle=Ec2Spot` and tainted with `PreferNoSchedule`. + * If kubectl is enabled, the + * [spot interrupt handler](https://github.com/awslabs/ec2-spot-labs/tree/master/ec2-spot-eks-solution/spot-termination-handler) + * daemon will be installed on all spot instances to handle + * [EC2 Spot Instance Termination Notices](https://aws.amazon.com/blogs/aws/new-ec2-spot-instance-termination-notices/). + */ + public addCapacity(id: string, options: CapacityOptions): autoscaling.AutoScalingGroup { + const asg = new autoscaling.AutoScalingGroup(this, id, { + ...options, + vpc: this.vpc, + machineImage: new EksOptimizedImage({ + nodeType: nodeTypeForInstanceType(options.instanceType), + kubernetesVersion: this.version, + }), + updateType: options.updateType || autoscaling.UpdateType.ROLLING_UPDATE, + instanceType: options.instanceType, + }); + + this.addAutoScalingGroup(asg, { + mapRole: options.mapRole, + bootstrapOptions: options.bootstrapOptions, + bootstrapEnabled: options.bootstrapEnabled + }); + + return asg; + } + + /** + * Add compute capacity to this EKS cluster in the form of an AutoScalingGroup + * + * The AutoScalingGroup must be running an EKS-optimized AMI containing the + * /etc/eks/bootstrap.sh script. This method will configure Security Groups, + * add the right policies to the instance role, apply the right tags, and add + * the required user data to the instance's launch configuration. + * + * Spot instances will be labeled `lifecycle=Ec2Spot` and tainted with `PreferNoSchedule`. + * If kubectl is enabled, the + * [spot interrupt handler](https://github.com/awslabs/ec2-spot-labs/tree/master/ec2-spot-eks-solution/spot-termination-handler) + * daemon will be installed on all spot instances to handle + * [EC2 Spot Instance Termination Notices](https://aws.amazon.com/blogs/aws/new-ec2-spot-instance-termination-notices/). + * + * Prefer to use `addCapacity` if possible. + * + * @see https://docs.aws.amazon.com/eks/latest/userguide/launch-workers.html + * @param autoScalingGroup [disable-awslint:ref-via-interface] + * @param options options for adding auto scaling groups, like customizing the bootstrap script + */ + public addAutoScalingGroup(autoScalingGroup: autoscaling.AutoScalingGroup, options: AutoScalingGroupOptions) { + // self rules + autoScalingGroup.connections.allowInternally(ec2.Port.allTraffic()); + + // Cluster to:nodes rules + autoScalingGroup.connections.allowFrom(this, ec2.Port.tcp(443)); + autoScalingGroup.connections.allowFrom(this, ec2.Port.tcpRange(1025, 65535)); + + // Allow HTTPS from Nodes to Cluster + autoScalingGroup.connections.allowTo(this, ec2.Port.tcp(443)); + + // Allow all node outbound traffic + autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allTcp()); + autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allUdp()); + autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allIcmp()); + + const bootstrapEnabled = options.bootstrapEnabled !== undefined ? options.bootstrapEnabled : true; + if (options.bootstrapOptions && !bootstrapEnabled) { + throw new Error(`Cannot specify "bootstrapOptions" if "bootstrapEnabled" is false`); + } + + if (bootstrapEnabled) { + const userData = renderUserData(this.clusterName, autoScalingGroup, options.bootstrapOptions); + autoScalingGroup.addUserData(...userData); + } + + autoScalingGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSWorkerNodePolicy')); + autoScalingGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKS_CNI_Policy')); + autoScalingGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryReadOnly')); + + // EKS Required Tags + Tag.add(autoScalingGroup, `kubernetes.io/cluster/${this.clusterName}`, 'owned', { + applyToLaunchedInstances: true + }); + + if (options.mapRole === true && !this.kubectlEnabled) { + throw new Error(`Cannot map instance IAM role to RBAC if kubectl is disabled for the cluster`); + } + + // do not attempt to map the role if `kubectl` is not enabled for this + // cluster or if `mapRole` is set to false. By default this should happen. + const mapRole = options.mapRole === undefined ? true : options.mapRole; + if (mapRole && this.kubectlEnabled) { + // see https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html + this.awsAuth.addRoleMapping(autoScalingGroup.role, { + username: 'system:node:{{EC2PrivateDNSName}}', + groups: [ + 'system:bootstrappers', + 'system:nodes' + ] + }); + } else { + // since we are not mapping the instance role to RBAC, synthesize an + // output so it can be pasted into `aws-auth-cm.yaml` + new CfnOutput(autoScalingGroup, 'InstanceRoleARN', { + value: autoScalingGroup.role.roleArn + }); + } + + // if this is an ASG with spot instances, install the spot interrupt handler (only if kubectl is enabled). + if (autoScalingGroup.spotPrice && this.kubectlEnabled) { + this.addResource('spot-interrupt-handler', ...spotInterruptHandler()); + } + } + + /** + * Lazily creates the AwsAuth resource, which manages AWS authentication mapping. + */ + public get awsAuth() { + if (!this.kubectlEnabled) { + throw new Error(`Cannot define aws-auth mappings if kubectl is disabled`); + } + + if (!this._awsAuth) { + this._awsAuth = new AwsAuth(this, 'AwsAuth', { cluster: this }); + } + + return this._awsAuth; + } + + /** + * Defines a Kubernetes resource in this cluster. + * + * The manifest will be applied/deleted using kubectl as needed. + * + * @param id logical id of this manifest + * @param manifest a list of Kubernetes resource specifications + * @returns a `KubernetesResource` object. + * @throws If `kubectlEnabled` is `false` + */ + public addResource(id: string, ...manifest: any[]) { + return new KubernetesResource(this, `manifest-${id}`, { cluster: this, manifest }); + } + + /** + * Defines a Helm chart in this cluster. + * + * @param id logical id of this chart. + * @param options options of this chart. + * @returns a `HelmChart` object + * @throws If `kubectlEnabled` is `false` + */ + public addChart(id: string, options: HelmChartOptions) { + return new HelmChart(this, `chart-${id}`, { cluster: this, ...options }); + } + + private createKubernetesResourceHandler() { + if (!this.kubectlEnabled) { + return undefined; + } + + return new lambda.Function(this, 'KubernetesResourceHandler', { + code: lambda.Code.fromAsset(path.join(__dirname, 'k8s-resource')), + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + timeout: Duration.minutes(15), + layers: [ KubectlLayer.getOrCreate(this) ], + memorySize: 256, + environment: { + CLUSTER_NAME: this.clusterName, + }, + + // NOTE: we must use the default IAM role that's mapped to "system:masters" + // as the execution role of this custom resource handler. This is the only + // way to be able to interact with the cluster after it's been created. + role: this._defaultMastersRole, + }); + } + + /** + * Opportunistically tag subnets with the required tags. + * + * If no subnets could be found (because this is an imported VPC), add a warning. + * + * @see https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html + */ + private tagSubnets() { + const tagAllSubnets = (type: string, subnets: ec2.ISubnet[], tag: string) => { + for (const subnet of subnets) { + // if this is not a concrete subnet, attach a construct warning + if (!ec2.Subnet.isVpcSubnet(subnet)) { + // message (if token): "could not auto-tag public/private subnet with tag..." + // message (if not token): "count not auto-tag public/private subnet xxxxx with tag..." + const subnetID = Token.isUnresolved(subnet.subnetId) ? '' : ` ${subnet.subnetId}`; + this.node.addWarning(`Could not auto-tag ${type} subnet${subnetID} with "${tag}=1", please remember to do this manually`); + continue; + } + + subnet.node.applyAspect(new Tag(tag, "1")); + } + }; + + // https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html + tagAllSubnets('private', this.vpc.privateSubnets, "kubernetes.io/role/internal-elb"); + tagAllSubnets('public', this.vpc.publicSubnets, "kubernetes.io/role/elb"); + } +} + +/** + * Options for adding worker nodes + */ +export interface CapacityOptions extends autoscaling.CommonAutoScalingGroupProps { + /** + * Instance type of the instances to start + */ + readonly instanceType: ec2.InstanceType; + + /** + * Will automatically update the aws-auth ConfigMap to map the IAM instance + * role to RBAC. + * + * This cannot be explicitly set to `true` if the cluster has kubectl disabled. + * + * @default - true if the cluster has kubectl enabled (which is the default). + */ + readonly mapRole?: boolean; + + /** + * Configures the EC2 user-data script for instances in this autoscaling group + * to bootstrap the node (invoke `/etc/eks/bootstrap.sh`) and associate it + * with the EKS cluster. + * + * If you wish to provide a custom user data script, set this to `false` and + * manually invoke `autoscalingGroup.addUserData()`. + * + * @default true + */ + readonly bootstrapEnabled?: boolean; + + /** + * EKS node bootstrapping options. + * + * @default - none + */ + readonly bootstrapOptions?: BootstrapOptions; +} + +export interface BootstrapOptions { + /** + * Sets `--max-pods` for the kubelet based on the capacity of the EC2 instance. + * + * @default true + */ + readonly useMaxPods?: boolean; + + /** + * Restores the docker default bridge network. + * + * @default false + */ + readonly enableDockerBridge?: boolean; + + /** + * Number of retry attempts for AWS API call (DescribeCluster). + * + * @default 3 + */ + readonly awsApiRetryAttempts?: number; + + /** + * The contents of the `/etc/docker/daemon.json` file. Useful if you want a + * custom config differing from the default one in the EKS AMI. + * + * @default - none + */ + readonly dockerConfigJson?: string; + + /** + * Extra arguments to add to the kubelet. Useful for adding labels or taints. + * + * @example --node-labels foo=bar,goo=far + * @default - none + */ + readonly kubeletExtraArgs?: string; + + /** + * Additional command line arguments to pass to the `/etc/eks/bootstrap.sh` + * command. + * + * @see https://github.com/awslabs/amazon-eks-ami/blob/master/files/bootstrap.sh + * @default - none + */ + readonly additionalArgs?: string; +} + +/** + * Options for adding an AutoScalingGroup as capacity + */ +export interface AutoScalingGroupOptions { + /** + * Will automatically update the aws-auth ConfigMap to map the IAM instance + * role to RBAC. + * + * This cannot be explicitly set to `true` if the cluster has kubectl disabled. + * + * @default - true if the cluster has kubectl enabled (which is the default). + */ + readonly mapRole?: boolean; + + /** + * Configures the EC2 user-data script for instances in this autoscaling group + * to bootstrap the node (invoke `/etc/eks/bootstrap.sh`) and associate it + * with the EKS cluster. + * + * If you wish to provide a custom user data script, set this to `false` and + * manually invoke `autoscalingGroup.addUserData()`. + * + * @default true + */ + readonly bootstrapEnabled?: boolean; + + /** + * Allows options for node bootstrapping through EC2 user data. + */ + readonly bootstrapOptions?: BootstrapOptions; +} + +/** + * Import a cluster to use in another stack + */ +class ImportedCluster extends Resource implements ICluster { + public readonly vpc: ec2.IVpc; + public readonly clusterCertificateAuthorityData: string; + public readonly clusterName: string; + public readonly clusterArn: string; + public readonly clusterEndpoint: string; + public readonly connections = new ec2.Connections(); + + constructor(scope: Construct, id: string, props: ClusterAttributes) { + super(scope, id); + + this.vpc = ec2.Vpc.fromVpcAttributes(this, "VPC", props.vpc); + this.clusterName = props.clusterName; + this.clusterEndpoint = props.clusterEndpoint; + this.clusterArn = props.clusterArn; + this.clusterCertificateAuthorityData = props.clusterCertificateAuthorityData; + + let i = 1; + for (const sgProps of props.securityGroups) { + this.connections.addSecurityGroup(ec2.SecurityGroup.fromSecurityGroupId(this, `SecurityGroup${i}`, sgProps.securityGroupId)); + i++; + } + } +} + +/** + * Properties for EksOptimizedImage + */ +export interface EksOptimizedImageProps { + /** + * What instance type to retrieve the image for (standard or GPU-optimized) + * + * @default NodeType.STANDARD + */ + readonly nodeType?: NodeType; + + /** + * The Kubernetes version to use + * + * @default - The latest version + */ + readonly kubernetesVersion?: string; +} + +/** + * Construct an Amazon Linux 2 image from the latest EKS Optimized AMI published in SSM + */ +export class EksOptimizedImage implements ec2.IMachineImage { + private readonly nodeType?: NodeType; + private readonly kubernetesVersion?: string; + + private readonly amiParameterName: string; + + /** + * Constructs a new instance of the EcsOptimizedAmi class. + */ + public constructor(props: EksOptimizedImageProps) { + this.nodeType = props && props.nodeType; + this.kubernetesVersion = props && props.kubernetesVersion || LATEST_KUBERNETES_VERSION; + + // set the SSM parameter name + this.amiParameterName = `/aws/service/eks/optimized-ami/${this.kubernetesVersion}/` + + ( this.nodeType === NodeType.STANDARD ? "amazon-linux-2/" : "" ) + + ( this.nodeType === NodeType.GPU ? "amazon-linux2-gpu/" : "" ) + + "recommended/image_id"; + } + + /** + * Return the correct image + */ + public getImage(scope: Construct): ec2.MachineImageConfig { + const ami = ssm.StringParameter.valueForStringParameter(scope, this.amiParameterName); + return { + imageId: ami, + osType: ec2.OperatingSystemType.LINUX + }; + } +} + +// MAINTAINERS: use ./scripts/kube_bump.sh to update LATEST_KUBERNETES_VERSION +const LATEST_KUBERNETES_VERSION = '1.14'; + +/** + * Whether the worker nodes should support GPU or just standard instances + */ +export enum NodeType { + /** + * Standard instances + */ + STANDARD = 'Standard', + + /** + * GPU instances + */ + GPU = 'GPU', +} + +const GPU_INSTANCETYPES = ['p2', 'p3', 'g4']; + +export function nodeTypeForInstanceType(instanceType: ec2.InstanceType) { + return GPU_INSTANCETYPES.includes(instanceType.toString().substring(0, 2)) ? NodeType.GPU : NodeType.STANDARD; +} diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts new file mode 100644 index 0000000000000..051b13774a3cf --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts @@ -0,0 +1,123 @@ +import { CustomResource, CustomResourceProvider } from '@aws-cdk/aws-cloudformation'; +import * as lambda from '@aws-cdk/aws-lambda'; +import { Construct, Duration, Stack } from '@aws-cdk/core'; +import * as path from 'path'; +import { Cluster } from './cluster'; +import { KubectlLayer } from './kubectl-layer'; + +/** + * Helm Chart options. + */ + +export interface HelmChartOptions { + /** + * The name of the chart. + */ + readonly chart: string; + + /** + * The name of the release. + * @default - If no release name is given, it will use the last 63 characters of the node's unique id. + */ + readonly release?: string; + + /** + * The chart version to install. + * @default - If this is not specified, the latest version is installed + */ + readonly version?: string; + + /** + * The repository which contains the chart. For example: https://kubernetes-charts.storage.googleapis.com/ + * @default - No repository will be used, which means that the chart needs to be an absolute URL. + */ + readonly repository?: string; + + /** + * The Kubernetes namespace scope of the requests. + * @default default + */ + readonly namespace?: string; + + /** + * The values to be used by the chart. + * @default - No values are provided to the chart. + */ + readonly values?: {[key: string]: any}; +} + +/** + * Helm Chart properties. + */ +export interface HelmChartProps extends HelmChartOptions { + /** + * The EKS cluster to apply this configuration to. + * + * [disable-awslint:ref-via-interface] + */ + readonly cluster: Cluster; +} + +/** + * Represents a helm chart within the Kubernetes system. + * + * Applies/deletes the resources using `kubectl` in sync with the resource. + */ +export class HelmChart extends Construct { + /** + * The CloudFormation reosurce type. + */ + public static readonly RESOURCE_TYPE = 'Custom::AWSCDK-EKS-HelmChart'; + + constructor(scope: Construct, id: string, props: HelmChartProps) { + super(scope, id); + + const stack = Stack.of(this); + + // we maintain a single manifest custom resource handler for each cluster + const handler = this.getOrCreateHelmChartHandler(props.cluster); + if (!handler) { + throw new Error(`Cannot define a Helm chart on a cluster with kubectl disabled`); + } + + new CustomResource(this, 'Resource', { + provider: CustomResourceProvider.lambda(handler), + resourceType: HelmChart.RESOURCE_TYPE, + properties: { + Release: props.release || this.node.uniqueId.slice(-63).toLowerCase(), // Helm has a 63 character limit for the name + Chart: props.chart, + Version: props.version, + Values: (props.values ? stack.toJsonString(props.values) : undefined), + Namespace: props.namespace || 'default', + Repository: props.repository + } + }); + } + + private getOrCreateHelmChartHandler(cluster: Cluster): lambda.IFunction | undefined { + if (!cluster.kubectlEnabled) { + return undefined; + } + + let handler = cluster.node.tryFindChild('HelmChartHandler') as lambda.IFunction; + if (!handler) { + handler = new lambda.Function(cluster, 'HelmChartHandler', { + code: lambda.Code.fromAsset(path.join(__dirname, 'helm-chart')), + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + timeout: Duration.minutes(15), + layers: [ KubectlLayer.getOrCreate(this, { version: "2.0.0-beta1" }) ], + memorySize: 256, + environment: { + CLUSTER_NAME: cluster.clusterName, + }, + + // NOTE: we must use the default IAM role that's mapped to "system:masters" + // as the execution role of this custom resource handler. This is the only + // way to be able to interact with the cluster after it's been created. + role: cluster._defaultMastersRole, + }); + } + return handler; + } +} diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart/index.py b/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart/index.py new file mode 100644 index 0000000000000..0b311f61e0fcd --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart/index.py @@ -0,0 +1,136 @@ +import subprocess +import os +import json +import logging +import boto3 +from uuid import uuid4 +from botocore.vendored import requests + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# these are coming from the kubectl layer +os.environ['PATH'] = '/opt/helm:/opt/awscli:' + os.environ['PATH'] + +outdir = os.environ.get('TEST_OUTDIR', '/tmp') +kubeconfig = os.path.join(outdir, 'kubeconfig') + +CFN_SUCCESS = "SUCCESS" +CFN_FAILED = "FAILED" + +def handler(event, context): + + def cfn_error(message=None): + logger.error("| cfn_error: %s" % message) + cfn_send(event, context, CFN_FAILED, reason=message) + + try: + logger.info(json.dumps(event)) + + request_type = event['RequestType'] + props = event['ResourceProperties'] + physical_id = event.get('PhysicalResourceId', None) + release = props['Release'] + chart = props['Chart'] + version = props.get('Version', None) + namespace = props.get('Namespace', None) + repository = props.get('Repository', None) + values_text = props.get('Values', None) + + cluster_name = os.environ.get('CLUSTER_NAME', None) + if cluster_name is None: + cfn_error("CLUSTER_NAME is missing in environment") + return + + subprocess.check_call([ 'aws', 'eks', 'update-kubeconfig', + '--name', cluster_name, + '--kubeconfig', kubeconfig + ]) + + # Write out the values to a file and include them with the install and upgrade + values_file = None + if not request_type == "Delete" and not values_text is None: + values = json.loads(values_text) + values_file = os.path.join(outdir, 'values.yaml') + with open(values_file, "w") as f: + f.write(json.dumps(values, indent=2)) + + if request_type == 'Create' or request_type == 'Update': + helm('upgrade', release, chart, repository, values_file, namespace, version) + elif request_type == "Delete": + try: + helm('uninstall', release, namespace=namespace) + except Exception as e: + logger.info("delete error: %s" % e) + + # if we are creating a new resource, allocate a physical id for it + # otherwise, we expect physical id to be relayed by cloudformation + if request_type == 'Create': + physical_id = "%s/%s" % (cluster_name, str(uuid4())) + else: + if not physical_id: + cfn_error("invalid request: request type is '%s' but 'PhysicalResourceId' is not defined" % request_type) + return + + cfn_send(event, context, CFN_SUCCESS, physicalResourceId=physical_id) + return + + except KeyError as e: + cfn_error("invalid request. Missing '%s'" % str(e)) + except Exception as e: + logger.exception(e) + cfn_error(str(e)) + +def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None): + import subprocess + try: + cmnd = ['helm', verb, release] + if not chart is None: + cmnd.append(chart) + if verb == 'upgrade': + cmnd.append('--install') + if not repo is None: + cmnd.extend(['--repo', repo]) + if not file is None: + cmnd.extend(['--values', file]) + if not version is None: + cmnd.extend(['--version', version]) + if not namespace is None: + cmnd.extend(['--namespace', namespace]) + cmnd.extend(['--kubeconfig', kubeconfig]) + output = subprocess.check_output(cmnd, stderr=subprocess.STDOUT, cwd=outdir) + logger.info(output) + except subprocess.CalledProcessError as exc: + raise Exception(exc.output) + +#--------------------------------------------------------------------------------------------------- +# sends a response to cloudformation +def cfn_send(event, context, responseStatus, responseData={}, physicalResourceId=None, noEcho=False, reason=None): + + responseUrl = event['ResponseURL'] + logger.info(responseUrl) + + responseBody = {} + responseBody['Status'] = responseStatus + responseBody['Reason'] = reason or ('See the details in CloudWatch Log Stream: ' + context.log_stream_name) + responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name + responseBody['StackId'] = event['StackId'] + responseBody['RequestId'] = event['RequestId'] + responseBody['LogicalResourceId'] = event['LogicalResourceId'] + responseBody['NoEcho'] = noEcho + responseBody['Data'] = responseData + + body = json.dumps(responseBody) + logger.info("| response body:\n" + body) + + headers = { + 'content-type' : '', + 'content-length' : str(len(body)) + } + + try: + response = requests.put(responseUrl, data=body, headers=headers) + logger.info("| status code: " + response.reason) + except Exception as e: + logger.error("| unable to send response to CloudFormation") + logger.exception(e) diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/index.ts b/packages/@aws-cdk/aws-eks-legacy/lib/index.ts new file mode 100644 index 0000000000000..166d5bd35e5fc --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/lib/index.ts @@ -0,0 +1,8 @@ +export * from './cluster'; +export * from './aws-auth-mapping'; +export * from './k8s-resource'; +export * from './helm-chart'; +export * from './aws-auth'; + +// AWS::EKS CloudFormation Resources: +export * from './eks.generated'; diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts b/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts new file mode 100644 index 0000000000000..23fde579d6b75 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts @@ -0,0 +1,73 @@ +import * as cfn from '@aws-cdk/aws-cloudformation'; +import { Construct, Stack } from '@aws-cdk/core'; +import { Cluster } from './cluster'; + +export interface KubernetesResourceProps { + /** + * The EKS cluster to apply this configuration to. + * + * [disable-awslint:ref-via-interface] + */ + readonly cluster: Cluster; + + /** + * The resource manifest. + * + * Consists of any number of child resources. + * + * When the resource is created/updated, this manifest will be applied to the + * cluster through `kubectl apply` and when the resource or the stack is + * deleted, the manifest will be deleted through `kubectl delete`. + * + * @example + * + * { + * apiVersion: 'v1', + * kind: 'Pod', + * metadata: { name: 'mypod' }, + * spec: { + * containers: [ { name: 'hello', image: 'paulbouwer/hello-kubernetes:1.5', ports: [ { containerPort: 8080 } ] } ] + * } + * } + * + */ + readonly manifest: any[]; +} + +/** + * Represents a resource within the Kubernetes system. + * + * Alternatively, you can use `cluster.addResource(resource[, resource, ...])` + * to define resources on this cluster. + * + * Applies/deletes the resources using `kubectl` in sync with the resource. + */ +export class KubernetesResource extends Construct { + /** + * The CloudFormation reosurce type. + */ + public static readonly RESOURCE_TYPE = 'Custom::AWSCDK-EKS-KubernetesResource'; + + constructor(scope: Construct, id: string, props: KubernetesResourceProps) { + super(scope, id); + + const stack = Stack.of(this); + + // we maintain a single manifest custom resource handler for each cluster + const handler = props.cluster._k8sResourceHandler; + if (!handler) { + throw new Error(`Cannot define a KubernetesManifest resource on a cluster with kubectl disabled`); + } + + new cfn.CustomResource(this, 'Resource', { + provider: cfn.CustomResourceProvider.lambda(handler), + resourceType: KubernetesResource.RESOURCE_TYPE, + properties: { + // `toJsonString` enables embedding CDK tokens in the manifest and will + // render a CloudFormation-compatible JSON string (similar to + // StepFunctions, CloudWatch Dashboards etc). + Manifest: stack.toJsonString(props.manifest), + } + }); + } +} diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-resource/index.py b/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource/index.py similarity index 100% rename from packages/@aws-cdk/aws-eks/lib/k8s-resource/index.py rename to packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource/index.py diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts b/packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts new file mode 100644 index 0000000000000..211f6d8b36abd --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts @@ -0,0 +1,78 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import { CfnResource, Construct, Stack, Token } from '@aws-cdk/core'; +import * as crypto from 'crypto'; + +const KUBECTL_APP_ARN = 'arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl'; +const KUBECTL_APP_VERSION = '1.13.7'; + +export interface KubectlLayerProps { + /** + * The semantic version of the kubectl AWS Lambda Layer SAR app to use. + * + * @default '1.13.7' + */ + readonly version?: string; +} + +/** + * An AWS Lambda layer that includes kubectl and the AWS CLI. + * + * @see https://github.com/aws-samples/aws-lambda-layer-kubectl + */ +export class KubectlLayer extends Construct implements lambda.ILayerVersion { + + /** + * Gets or create a singleton instance of this construct. + */ + public static getOrCreate(scope: Construct, props: KubectlLayerProps = {}): KubectlLayer { + const stack = Stack.of(scope); + const id = 'kubectl-layer-' + (props.version ? props.version : "8C2542BC-BF2B-4DFE-B765-E181FD30A9A0"); + const exists = stack.node.tryFindChild(id) as KubectlLayer; + if (exists) { + return exists; + } + + return new KubectlLayer(stack, id, props); + } + + /** + * The ARN of the AWS Lambda layer version. + */ + public readonly layerVersionArn: string; + + /** + * All runtimes are compatible. + */ + public readonly compatibleRuntimes?: lambda.Runtime[] = undefined; + + constructor(scope: Construct, id: string, props: KubectlLayerProps = {}) { + super(scope, id); + + const uniqueId = crypto.createHash('md5').update(this.node.path).digest("hex"); + const version = props.version || KUBECTL_APP_VERSION; + + this.stack.templateOptions.transforms = [ 'AWS::Serverless-2016-10-31' ]; // required for AWS::Serverless + const resource = new CfnResource(this, 'Resource', { + type: 'AWS::Serverless::Application', + properties: { + Location: { + ApplicationId: KUBECTL_APP_ARN, + SemanticVersion: version + }, + Parameters: { + LayerName: `kubectl-${uniqueId}` + } + } + }); + + this.layerVersionArn = Token.asString(resource.getAtt('Outputs.LayerVersionArn')); + } + + public get stack() { + return Stack.of(this); + } + + public addPermission(_id: string, _permission: lambda.LayerVersionPermission): void { + return; + } +} diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/spot-interrupt-handler.ts b/packages/@aws-cdk/aws-eks-legacy/lib/spot-interrupt-handler.ts new file mode 100644 index 0000000000000..df60b906da449 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/lib/spot-interrupt-handler.ts @@ -0,0 +1,175 @@ +export enum LifecycleLabel { + ON_DEMAND = 'OnDemand', + SPOT = 'Ec2Spot', +} + +const DEFAULT_NODE_SELECTOR = { lifecycle: LifecycleLabel.SPOT }; + +export function spotInterruptHandler(nodeSelector: { [name: string]: string } = DEFAULT_NODE_SELECTOR) { + return [ + { + kind: "ClusterRole", + apiVersion: "rbac.authorization.k8s.io/v1", + metadata: { + name: "node-termination-handler", + namespace: "default" + }, + rules: [ + { + apiGroups: [ + "apps" + ], + resources: [ + "daemonsets" + ], + verbs: [ + "get", + "delete" + ] + }, + { + apiGroups: [ + "" + ], + resources: [ + "*" + ], + verbs: [ + "*" + ] + }, + { + apiGroups: [ + "rbac.authorization.k8s.io" + ], + resources: [ + "*" + ], + verbs: [ + "*" + ] + }, + { + apiGroups: [ + "apiextensions.k8s.io" + ], + resources: [ + "customresourcedefinitions" + ], + verbs: [ + "get", + "list", + "watch", + "create", + "delete" + ] + } + ] + }, + { + apiVersion: "v1", + kind: "ServiceAccount", + metadata: { + name: "node-termination-handler" + } + }, + { + kind: "ClusterRoleBinding", + apiVersion: "rbac.authorization.k8s.io/v1", + metadata: { + name: "node-termination-handler", + namespace: "default" + }, + subjects: [ + { + kind: "ServiceAccount", + name: "node-termination-handler", + namespace: "default" + } + ], + roleRef: { + kind: "ClusterRole", + name: "node-termination-handler", + apiGroup: "rbac.authorization.k8s.io" + } + }, + { + apiVersion: "apps/v1beta2", + kind: "DaemonSet", + metadata: { + name: "node-termination-handler", + namespace: "default" + }, + spec: { + selector: { + matchLabels: { + app: "node-termination-handler" + } + }, + template: { + metadata: { + labels: { + app: "node-termination-handler" + } + }, + spec: { + serviceAccountName: "node-termination-handler", + containers: [ + { + name: "node-termination-handler", + image: "amazon/aws-node-termination-handler:v1.0.0", + imagePullPolicy: "Always", + env: [ + { + name: "NODE_NAME", + valueFrom: { + fieldRef: { + fieldPath: "spec.nodeName" + } + } + }, + { + name: "POD_NAME", + valueFrom: { + fieldRef: { + fieldPath: "metadata.name" + } + } + }, + { + name: "NAMESPACE", + valueFrom: { + fieldRef: { + fieldPath: "metadata.namespace" + } + } + }, + { + name: "SPOT_POD_IP", + valueFrom: { + fieldRef: { + fieldPath: "status.podIP" + } + } + } + ], + resources: { + requests: { + memory: "64Mi", + cpu: "50m" + }, + limits: { + memory: "128Mi", + cpu: "100m" + } + } + } + ], + nodeSelector + } + } + } + } + + ]; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts b/packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts new file mode 100644 index 0000000000000..5a50dc52a8882 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts @@ -0,0 +1,46 @@ +import * as autoscaling from '@aws-cdk/aws-autoscaling'; +import { Stack } from '@aws-cdk/core'; +import { BootstrapOptions } from './cluster'; +import { LifecycleLabel } from './spot-interrupt-handler'; + +export function renderUserData(clusterName: string, autoScalingGroup: autoscaling.AutoScalingGroup, options: BootstrapOptions = { }): string[] { + const stack = Stack.of(autoScalingGroup); + + // determine logical id of ASG so we can signal cloudformation + const cfn = autoScalingGroup.node.defaultChild as autoscaling.CfnAutoScalingGroup; + const asgLogicalId = cfn.logicalId; + + const extraArgs = new Array(); + + extraArgs.push(`--use-max-pods ${options.useMaxPods === undefined ? true : options.useMaxPods}`); + + if (options.awsApiRetryAttempts) { + extraArgs.push(`--aws-api-retry-attempts ${options.awsApiRetryAttempts}`); + } + + if (options.enableDockerBridge) { + extraArgs.push(`--enable-docker-bridge`); + } + + if (options.dockerConfigJson) { + extraArgs.push(`--docker-config-json '${options.dockerConfigJson}'`); + } + + if (options.additionalArgs) { + extraArgs.push(options.additionalArgs); + } + + const commandLineSuffix = extraArgs.join(' '); + const kubeletExtraArgsSuffix = options.kubeletExtraArgs || ''; + + // determine lifecycle label based on whether the ASG has a spot price. + const lifecycleLabel = autoScalingGroup.spotPrice ? LifecycleLabel.SPOT : LifecycleLabel.ON_DEMAND; + const withTaints = autoScalingGroup.spotPrice ? '--register-with-taints=spotInstance=true:PreferNoSchedule' : ''; + const kubeletExtraArgs = `--node-labels lifecycle=${lifecycleLabel} ${withTaints} ${kubeletExtraArgsSuffix}`.trim(); + + return [ + `set -o xtrace`, + `/etc/eks/bootstrap.sh ${clusterName} --kubelet-extra-args "${kubeletExtraArgs}" ${commandLineSuffix}`.trim(), + `/opt/aws/bin/cfn-signal --exit-code $? --stack ${stack.stackName} --resource ${asgLogicalId} --region ${stack.region}` + ]; +} diff --git a/packages/@aws-cdk/aws-eks-legacy/package.json b/packages/@aws-cdk/aws-eks-legacy/package.json new file mode 100644 index 0000000000000..fa2694dae1197 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/package.json @@ -0,0 +1,118 @@ +{ + "name": "@aws-cdk/aws-eks-legacy", + "version": "1.19.0", + "description": "The CDK Construct Library for AWS::EKS (Legacy)", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.services.eks.legacy", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "eks-legacy" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.EKS.Legacy", + "packageId": "Amazon.CDK.AWS.EKS.Legacy", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-cdk.aws-eks-legacy", + "module": "aws_cdk.aws_eks_legacy" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-eks-legacy" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts", + "build+test+package": "npm run build+test && npm run package", + "build+test": "npm run build && npm test", + "compat": "cdk-compat" + }, + "cdk-build": { + "cloudformation": "AWS::EKS" + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "eks" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "1.19.0", + "@types/nodeunit": "^0.0.30", + "cdk-build-tools": "1.19.0", + "cdk-integ-tools": "1.19.0", + "cfn2ts": "1.19.0", + "nodeunit": "^0.11.3", + "pkglint": "1.19.0" + }, + "dependencies": { + "@aws-cdk/aws-autoscaling": "1.19.0", + "@aws-cdk/aws-cloudformation": "1.19.0", + "@aws-cdk/aws-ec2": "1.19.0", + "@aws-cdk/aws-iam": "1.19.0", + "@aws-cdk/aws-lambda": "1.19.0", + "@aws-cdk/aws-ssm": "1.19.0", + "@aws-cdk/core": "1.19.0" + }, + "homepage": "https://github.com/aws/aws-cdk", + "peerDependencies": { + "@aws-cdk/aws-autoscaling": "1.19.0", + "@aws-cdk/aws-cloudformation": "1.19.0", + "@aws-cdk/aws-ec2": "1.19.0", + "@aws-cdk/aws-iam": "1.19.0", + "@aws-cdk/aws-lambda": "1.19.0", + "@aws-cdk/aws-ssm": "1.19.0", + "@aws-cdk/core": "1.19.0" + }, + "engines": { + "node": ">= 10.3.0" + }, + "awslint": { + "exclude": [ + "awslint:module-name:@aws-cdk/aws-eks-legacy", + "props-no-arn-refs:@aws-cdk/aws-eks.ClusterProps.outputMastersRoleArn", + "props-default-doc:@aws-cdk/aws-eks.AutoScalingGroupOptions.bootstrapOptions", + "resource-attribute:@aws-cdk/aws-eks.Cluster.clusterSecurityGroupId", + "docs-public-apis:@aws-cdk/aws-eks.AwsAuthProps", + "docs-public-apis:@aws-cdk/aws-eks.BootstrapOptions", + "docs-public-apis:@aws-cdk/aws-eks.ClusterAttributes", + "docs-public-apis:@aws-cdk/aws-eks.KubernetesResourceProps", + "docs-public-apis:@aws-cdk/aws-eks.Mapping", + "module-name:@aws-cdk/aws-eks-legacy", + "props-no-arn-refs:@aws-cdk/aws-eks-legacy.ClusterProps.outputMastersRoleArn", + "resource-attribute:@aws-cdk/aws-eks-legacy.Cluster.clusterSecurityGroupId", + "props-default-doc:@aws-cdk/aws-eks-legacy.AutoScalingGroupOptions.bootstrapOptions", + "docs-public-apis:@aws-cdk/aws-eks-legacy.AwsAuthProps", + "docs-public-apis:@aws-cdk/aws-eks-legacy.BootstrapOptions", + "docs-public-apis:@aws-cdk/aws-eks-legacy.ClusterAttributes", + "docs-public-apis:@aws-cdk/aws-eks-legacy.KubernetesResourceProps", + "docs-public-apis:@aws-cdk/aws-eks-legacy.Mapping" + ] + }, + "stability": "deprecated" +} diff --git a/packages/@aws-cdk/aws-eks-legacy/scripts/kube_bump.sh b/packages/@aws-cdk/aws-eks-legacy/scripts/kube_bump.sh new file mode 100644 index 0000000000000..1a051886a86db --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/scripts/kube_bump.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +if [ $# -lt 2 ]; then + echo "usage: scripts/kube_bump.sh " + exit 1 +fi + +PREVIOUS_KUBE_VERSION=$1 +LATEST_KUBE_VERSION=$2 + +SCRIPT_PATH="$( cd "$(dirname "$0")" ; pwd -P )" +EKS_PATH="$SCRIPT_PATH/.." + +sed -i "s/const LATEST_KUBERNETES_VERSION = '${PREVIOUS_KUBE_VERSION}/const LATEST_KUBERNETES_VERSION = '${LATEST_KUBE_VERSION}/" "$EKS_PATH/lib/cluster.ts" + +INTEG_FILES=$(find "$EKS_PATH/test" -type f -name 'integ.*.json') +echo "$INTEG_FILES" | xargs sed -i "s#eks/optimized-ami/${PREVIOUS_KUBE_VERSION}#eks/optimized-ami/${LATEST_KUBE_VERSION}#g" + +NUMERIC_PREVIOUS_VERSION=$(sed 's/[^0-9]*//g' <<< "$PREVIOUS_KUBE_VERSION") +NUMERIC_LATEST_VERSION=$(sed 's/[^0-9]*//g' <<< "$LATEST_KUBE_VERSION") +echo "$INTEG_FILES" | xargs sed -i "s#awsserviceeksoptimizedami${NUMERIC_PREVIOUS_VERSION}amazonlinux2#awsserviceeksoptimizedami${NUMERIC_LATEST_VERSION}amazonlinux2#g" \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/test/MANUAL_TEST.md b/packages/@aws-cdk/aws-eks-legacy/test/MANUAL_TEST.md new file mode 100644 index 0000000000000..a552429343e08 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/MANUAL_TEST.md @@ -0,0 +1,58 @@ +# Manual verification + +Following https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html + +After starting the cluster and installing `kubectl` and `aws-iam-authenticator`: + +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: aws-auth + namespace: kube-system +data: + mapRoles: | + - rolearn: + username: system:node:{{EC2PrivateDNSName}} + groups: + - system:bootstrappers + - system:nodes +``` + +``` +aws eks update-kubeconfig --name {{ClusterName}} + +# File above, with substitutions +kubectl apply -f aws-auth-cm.yaml + +# Check that nodes joined (may take a while) +kubectl get nodes + +# Start services (will autocreate a load balancer) +kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-master-controller.json +kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-master-service.json +kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-slave-controller.json +kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/redis-slave-service.json +kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/guestbook-controller.json +kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/master/guestbook-go/guestbook-service.json + +# Check up on service status +kubectl get services -o wide +``` + +Visit the website that appears under LoadBalancer on port 3000. The Amazon corporate network will block this +port, in which case you add this: + +``` +ssh -L 3000::3000 ssh-box-somewhere.example.com + +# Visit http://localhost:3000/ +``` + +Clean the services before you stop the cluster to get rid of the load balancer +(otherwise you won't be able to delete the stack): + +``` +kubectl delete --all services + +``` diff --git a/packages/@aws-cdk/aws-eks-legacy/test/example.ssh-into-nodes.lit.ts b/packages/@aws-cdk/aws-eks-legacy/test/example.ssh-into-nodes.lit.ts new file mode 100644 index 0000000000000..797e8a9247812 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/example.ssh-into-nodes.lit.ts @@ -0,0 +1,32 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import * as eks from '../lib'; + +class EksClusterStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const vpc = new ec2.Vpc(this, 'VPC'); + + const cluster = new eks.Cluster(this, 'EKSCluster', { + vpc + }); + + /// !show + const asg = cluster.addCapacity('Nodes', { + instanceType: new ec2.InstanceType('t2.medium'), + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, + keyName: 'my-key-name', + }); + + // Replace with desired IP + asg.connections.allowFrom(ec2.Peer.ipv4('1.2.3.4/32'), ec2.Port.tcp(22)); + /// !hide + } +} + +const app = new cdk.App(); + +new EksClusterStack(app, 'eks-integ-test'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.defaults.expected.json b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.defaults.expected.json new file mode 100644 index 0000000000000..7ff2bc1aa7024 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.defaults.expected.json @@ -0,0 +1,1315 @@ +{ + "Transform": "AWS::Serverless-2016-10-31", + "Resources": { + "ClusterDefaultVpcFA9F2722": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet1Subnet3BFE1BDA": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet1RouteTable1DCCDD98": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet1RouteTableAssociationAFBE6789": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPublicSubnet1RouteTable1DCCDD98" + }, + "SubnetId": { + "Ref": "ClusterDefaultVpcPublicSubnet1Subnet3BFE1BDA" + } + } + }, + "ClusterDefaultVpcPublicSubnet1DefaultRouteCF22EF6E": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPublicSubnet1RouteTable1DCCDD98" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "ClusterDefaultVpcIGW756BE43E" + } + }, + "DependsOn": [ + "ClusterDefaultVpcVPCGWC1D00388" + ] + }, + "ClusterDefaultVpcPublicSubnet1EIP498E2BD2": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet1NATGateway6E21013E": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "ClusterDefaultVpcPublicSubnet1EIP498E2BD2", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "ClusterDefaultVpcPublicSubnet1Subnet3BFE1BDA" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet2SubnetC4E9A966": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet2RouteTable6F1F5F47": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet2RouteTableAssociationA8539C50": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPublicSubnet2RouteTable6F1F5F47" + }, + "SubnetId": { + "Ref": "ClusterDefaultVpcPublicSubnet2SubnetC4E9A966" + } + } + }, + "ClusterDefaultVpcPublicSubnet2DefaultRoute1FA8621E": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPublicSubnet2RouteTable6F1F5F47" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "ClusterDefaultVpcIGW756BE43E" + } + }, + "DependsOn": [ + "ClusterDefaultVpcVPCGWC1D00388" + ] + }, + "ClusterDefaultVpcPublicSubnet2EIP265F4810": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet2NATGateway4AF4B728": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "ClusterDefaultVpcPublicSubnet2EIP265F4810", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "ClusterDefaultVpcPublicSubnet2SubnetC4E9A966" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet3Subnet1A46184A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet3RouteTableC81F99EF": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet3" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet3RouteTableAssociation7C5D21CC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPublicSubnet3RouteTableC81F99EF" + }, + "SubnetId": { + "Ref": "ClusterDefaultVpcPublicSubnet3Subnet1A46184A" + } + } + }, + "ClusterDefaultVpcPublicSubnet3DefaultRouteB6080504": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPublicSubnet3RouteTableC81F99EF" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "ClusterDefaultVpcIGW756BE43E" + } + }, + "DependsOn": [ + "ClusterDefaultVpcVPCGWC1D00388" + ] + }, + "ClusterDefaultVpcPublicSubnet3EIP0CBF6D05": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet3" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPublicSubnet3NATGatewayEF4BA49A": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "ClusterDefaultVpcPublicSubnet3EIP0CBF6D05", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "ClusterDefaultVpcPublicSubnet3Subnet1A46184A" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet3" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPrivateSubnet1Subnet03F39409": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPrivateSubnet1RouteTable7844020C": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet1" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPrivateSubnet1RouteTableAssociationF8A67D95": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPrivateSubnet1RouteTable7844020C" + }, + "SubnetId": { + "Ref": "ClusterDefaultVpcPrivateSubnet1Subnet03F39409" + } + } + }, + "ClusterDefaultVpcPrivateSubnet1DefaultRouteD624C8BD": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPrivateSubnet1RouteTable7844020C" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "ClusterDefaultVpcPublicSubnet1NATGateway6E21013E" + } + } + }, + "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPrivateSubnet2RouteTable1F9A5298": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet2" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPrivateSubnet2RouteTableAssociationE1240DF2": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPrivateSubnet2RouteTable1F9A5298" + }, + "SubnetId": { + "Ref": "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7" + } + } + }, + "ClusterDefaultVpcPrivateSubnet2DefaultRouteAB55737C": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPrivateSubnet2RouteTable1F9A5298" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "ClusterDefaultVpcPublicSubnet2NATGateway4AF4B728" + } + } + }, + "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPrivateSubnet3RouteTableF71314D0": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet3" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "ClusterDefaultVpcPrivateSubnet3RouteTableAssociation3007DC36": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPrivateSubnet3RouteTableF71314D0" + }, + "SubnetId": { + "Ref": "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839" + } + } + }, + "ClusterDefaultVpcPrivateSubnet3DefaultRoute932EDFF0": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "ClusterDefaultVpcPrivateSubnet3RouteTableF71314D0" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "ClusterDefaultVpcPublicSubnet3NATGatewayEF4BA49A" + } + } + }, + "ClusterDefaultVpcIGW756BE43E": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultVpc" + } + ] + } + }, + "ClusterDefaultVpcVPCGWC1D00388": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + }, + "InternetGatewayId": { + "Ref": "ClusterDefaultVpcIGW756BE43E" + } + } + }, + "ClusterClusterRoleCE5C05DD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSServicePolicy" + ] + ] + } + ] + } + }, + "ClusterControlPlaneSecurityGroupD274242C": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "EKS Control Plane Security Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + } + } + }, + "ClusterControlPlaneSecurityGroupfromeksintegdefaultsClusterDefaultCapacityInstanceSecurityGroup913A261E44376C54A34": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from eksintegdefaultsClusterDefaultCapacityInstanceSecurityGroup913A261E:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "ClusterControlPlaneSecurityGroupD274242C", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ClusterDefaultCapacityInstanceSecurityGroup8FDF4D48", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "ClusterResourceHandlerServiceRole7FB16465": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "ClusterResourceHandlerServiceRoleDefaultPolicy333D0E3A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ClusterClusterRoleCE5C05DD", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ClusterResourceHandlerServiceRoleDefaultPolicy333D0E3A", + "Roles": [ + { + "Ref": "ClusterResourceHandlerServiceRole7FB16465" + } + ] + } + }, + "ClusterResourceHandler28BF924D": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "ClusterResourceHandlerServiceRole7FB16465", + "Arn" + ] + }, + "Runtime": "python3.7", + "Layers": [ + { + "Fn::GetAtt": [ + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", + "Outputs.LayerVersionArn" + ] + } + ], + "MemorySize": 512, + "Timeout": 900 + }, + "DependsOn": [ + "ClusterResourceHandlerServiceRoleDefaultPolicy333D0E3A", + "ClusterResourceHandlerServiceRole7FB16465" + ] + }, + "Cluster9EE0221C": { + "Type": "Custom::AWSCDK-EKS-Cluster", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "ClusterResourceHandler28BF924D", + "Arn" + ] + }, + "Config": { + "roleArn": { + "Fn::GetAtt": [ + "ClusterClusterRoleCE5C05DD", + "Arn" + ] + }, + "resourcesVpcConfig": { + "securityGroupIds": [ + { + "Fn::GetAtt": [ + "ClusterControlPlaneSecurityGroupD274242C", + "GroupId" + ] + } + ], + "subnetIds": [ + { + "Ref": "ClusterDefaultVpcPublicSubnet1Subnet3BFE1BDA" + }, + { + "Ref": "ClusterDefaultVpcPublicSubnet2SubnetC4E9A966" + }, + { + "Ref": "ClusterDefaultVpcPublicSubnet3Subnet1A46184A" + }, + { + "Ref": "ClusterDefaultVpcPrivateSubnet1Subnet03F39409" + }, + { + "Ref": "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7" + }, + { + "Ref": "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839" + } + ] + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ClusterKubernetesResourceHandler81C19BC8": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "ClusterResourceHandlerServiceRole7FB16465", + "Arn" + ] + }, + "Runtime": "python3.7", + "Environment": { + "Variables": { + "CLUSTER_NAME": { + "Ref": "Cluster9EE0221C" + } + } + }, + "Layers": [ + { + "Fn::GetAtt": [ + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", + "Outputs.LayerVersionArn" + ] + } + ], + "MemorySize": 256, + "Timeout": 900 + }, + "DependsOn": [ + "ClusterResourceHandlerServiceRoleDefaultPolicy333D0E3A", + "ClusterResourceHandlerServiceRole7FB16465" + ] + }, + "ClusterDefaultCapacityInstanceSecurityGroup8FDF4D48": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "eks-integ-defaults/Cluster/DefaultCapacity/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultCapacity" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "Cluster9EE0221C" + } + ] + ] + }, + "Value": "owned" + } + ], + "VpcId": { + "Ref": "ClusterDefaultVpcFA9F2722" + } + } + }, + "ClusterDefaultCapacityInstanceSecurityGroupfromeksintegdefaultsClusterDefaultCapacityInstanceSecurityGroup913A261EALLTRAFFICA8163873": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "-1", + "Description": "from eksintegdefaultsClusterDefaultCapacityInstanceSecurityGroup913A261E:ALL TRAFFIC", + "GroupId": { + "Fn::GetAtt": [ + "ClusterDefaultCapacityInstanceSecurityGroup8FDF4D48", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ClusterDefaultCapacityInstanceSecurityGroup8FDF4D48", + "GroupId" + ] + } + } + }, + "ClusterDefaultCapacityInstanceSecurityGroupfromeksintegdefaultsClusterControlPlaneSecurityGroup0FA4E3AB4436B585189": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from eksintegdefaultsClusterControlPlaneSecurityGroup0FA4E3AB:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "ClusterDefaultCapacityInstanceSecurityGroup8FDF4D48", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ClusterControlPlaneSecurityGroupD274242C", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "ClusterDefaultCapacityInstanceSecurityGroupfromeksintegdefaultsClusterControlPlaneSecurityGroup0FA4E3AB102565535C02D6CB8": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from eksintegdefaultsClusterControlPlaneSecurityGroup0FA4E3AB:1025-65535", + "FromPort": 1025, + "GroupId": { + "Fn::GetAtt": [ + "ClusterDefaultCapacityInstanceSecurityGroup8FDF4D48", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ClusterControlPlaneSecurityGroupD274242C", + "GroupId" + ] + }, + "ToPort": 65535 + } + }, + "ClusterDefaultCapacityInstanceRole3E209969": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-defaults/Cluster/DefaultCapacity" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "Cluster9EE0221C" + } + ] + ] + }, + "Value": "owned" + } + ] + } + }, + "ClusterDefaultCapacityInstanceProfile70387741": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "ClusterDefaultCapacityInstanceRole3E209969" + } + ] + } + }, + "ClusterDefaultCapacityLaunchConfig72790CF7": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "m5.large", + "IamInstanceProfile": { + "Ref": "ClusterDefaultCapacityInstanceProfile70387741" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ClusterDefaultCapacityInstanceSecurityGroup8FDF4D48", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ", + { + "Ref": "Cluster9EE0221C" + }, + " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack eks-integ-defaults --resource ClusterDefaultCapacityASG00CC9431 --region test-region" + ] + ] + } + } + }, + "DependsOn": [ + "ClusterDefaultCapacityInstanceRole3E209969" + ] + }, + "ClusterDefaultCapacityASG00CC9431": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "2", + "MinSize": "1", + "DesiredCapacity": "2", + "LaunchConfigurationName": { + "Ref": "ClusterDefaultCapacityLaunchConfig72790CF7" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "eks-integ-defaults/Cluster/DefaultCapacity" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "Cluster9EE0221C" + } + ] + ] + }, + "PropagateAtLaunch": true, + "Value": "owned" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "ClusterDefaultVpcPrivateSubnet1Subnet03F39409" + }, + { + "Ref": "ClusterDefaultVpcPrivateSubnet2SubnetA526AEA7" + }, + { + "Ref": "ClusterDefaultVpcPrivateSubnet3SubnetB64BC839" + } + ] + }, + "UpdatePolicy": { + "AutoScalingRollingUpdate": { + "WaitOnResourceSignals": false, + "PauseTime": "PT0S", + "SuspendProcesses": [ + "HealthCheck", + "ReplaceUnhealthy", + "AZRebalance", + "AlarmNotification", + "ScheduledActions" + ] + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "ClusterAwsAuthmanifestFE51F8AE": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "ClusterKubernetesResourceHandler81C19BC8", + "Arn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\"},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterDefaultCapacityInstanceRole3E209969", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA": { + "Type": "AWS::Serverless::Application", + "Properties": { + "Location": { + "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", + "SemanticVersion": "1.13.7" + }, + "Parameters": { + "LayerName": "kubectl-459230f5f24751b9afdd68c6a69be4c7" + } + } + } + }, + "Outputs": { + "ClusterConfigCommand43AAE40F": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks update-kubeconfig --name ", + { + "Ref": "Cluster9EE0221C" + }, + " --region test-region" + ] + ] + } + }, + "ClusterGetTokenCommand06AE992E": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks get-token --cluster-name ", + { + "Ref": "Cluster9EE0221C" + }, + " --region test-region" + ] + ] + } + } + }, + "Parameters": { + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8": { + "Type": "String", + "Description": "S3 bucket for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD": { + "Type": "String", + "Description": "S3 key for asset version \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204ArtifactHashB80B497F": { + "Type": "String", + "Description": "Artifact hash for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB": { + "Type": "String", + "Description": "S3 bucket for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54": { + "Type": "String", + "Description": "S3 key for asset version \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444ArtifactHash606C8127": { + "Type": "String", + "Description": "Artifact hash for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/eks/optimized-ami/1.14/amazon-linux-2/recommended/image_id" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.defaults.ts b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.defaults.ts new file mode 100644 index 0000000000000..25d6ffcadb834 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.defaults.ts @@ -0,0 +1,19 @@ +import * as cdk from '@aws-cdk/core'; +import * as eks from '../lib'; +import { TestStack } from './util'; + +class EksClusterStack extends TestStack { + constructor(scope: cdk.App, id: string) { + super(scope, id); + + new eks.Cluster(this, 'Cluster'); + } +} + +const app = new cdk.App(); + +// since the EKS optimized AMI is hard-coded here based on the region, +// we need to actually pass in a specific region. +new EksClusterStack(app, 'eks-integ-defaults'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.kubectl-disabled.expected.json b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.kubectl-disabled.expected.json new file mode 100644 index 0000000000000..4c00d1f93bd8a --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.kubectl-disabled.expected.json @@ -0,0 +1,1044 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet3Subnet631C5E25": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet3RouteTable98AE0E14": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet3" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet3RouteTableAssociation427FE0C6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + } + } + }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet3EIPAD4BC883": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet3" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet3NATGatewayD3048F5C": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet3EIPAD4BC883", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PublicSubnet3" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PrivateSubnet1" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PrivateSubnet2" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCPrivateSubnet3Subnet3EDCD457": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PrivateSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet3RouteTable192186F8": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC/PrivateSubnet3" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet3RouteTableAssociationC28D144E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + } + }, + "VPCPrivateSubnet3DefaultRoute27F311AE": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet3NATGatewayD3048F5C" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "EKSClusterClusterRoleB72F3251": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSServicePolicy" + ] + ] + } + ] + } + }, + "EKSClusterControlPlaneSecurityGroup580AD1FE": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "EKS Control Plane Security Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "EKSClusterControlPlaneSecurityGroupfromeksintegkubectldisabledEKSClusterNodesInstanceSecurityGroup1E8EEB07443828A1FF0": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from eksintegkubectldisabledEKSClusterNodesInstanceSecurityGroup1E8EEB07:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "EKSClusterControlPlaneSecurityGroup580AD1FE", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "EKSClusterBA6ECF8F": { + "Type": "AWS::EKS::Cluster", + "Properties": { + "ResourcesVpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "EKSClusterControlPlaneSecurityGroup580AD1FE", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "EKSClusterClusterRoleB72F3251", + "Arn" + ] + } + } + }, + "EKSClusterNodesInstanceSecurityGroup460A275E": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "eks-integ-kubectl-disabled/EKSCluster/Nodes/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/EKSCluster/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "EKSClusterBA6ECF8F" + } + ] + ] + }, + "Value": "owned" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "EKSClusterNodesInstanceSecurityGroupfromeksintegkubectldisabledEKSClusterNodesInstanceSecurityGroup1E8EEB07ALLTRAFFIC813BA9BB": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "-1", + "Description": "from eksintegkubectldisabledEKSClusterNodesInstanceSecurityGroup1E8EEB07:ALL TRAFFIC", + "GroupId": { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + } + } + }, + "EKSClusterNodesInstanceSecurityGroupfromeksintegkubectldisabledEKSClusterControlPlaneSecurityGroupA8D847C7443405A887C": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from eksintegkubectldisabledEKSClusterControlPlaneSecurityGroupA8D847C7:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "EKSClusterControlPlaneSecurityGroup580AD1FE", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "EKSClusterNodesInstanceSecurityGroupfromeksintegkubectldisabledEKSClusterControlPlaneSecurityGroupA8D847C71025655350C1AD63E": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from eksintegkubectldisabledEKSClusterControlPlaneSecurityGroupA8D847C7:1025-65535", + "FromPort": 1025, + "GroupId": { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "EKSClusterControlPlaneSecurityGroup580AD1FE", + "GroupId" + ] + }, + "ToPort": 65535 + } + }, + "EKSClusterNodesInstanceRoleEE5595D6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-kubectl-disabled/EKSCluster/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "EKSClusterBA6ECF8F" + } + ] + ] + }, + "Value": "owned" + } + ] + } + }, + "EKSClusterNodesInstanceProfile0F2DB3B9": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "EKSClusterNodesInstanceRoleEE5595D6" + } + ] + } + }, + "EKSClusterNodesLaunchConfig921F1106": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t2.medium", + "IamInstanceProfile": { + "Ref": "EKSClusterNodesInstanceProfile0F2DB3B9" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ", + { + "Ref": "EKSClusterBA6ECF8F" + }, + " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack eks-integ-kubectl-disabled --resource EKSClusterNodesASGC2597E34 --region test-region" + ] + ] + } + } + }, + "DependsOn": [ + "EKSClusterNodesInstanceRoleEE5595D6" + ] + }, + "EKSClusterNodesASGC2597E34": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "DesiredCapacity": "1", + "LaunchConfigurationName": { + "Ref": "EKSClusterNodesLaunchConfig921F1106" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "eks-integ-kubectl-disabled/EKSCluster/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "EKSClusterBA6ECF8F" + } + ] + ] + }, + "PropagateAtLaunch": true, + "Value": "owned" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ] + }, + "UpdatePolicy": { + "AutoScalingRollingUpdate": { + "WaitOnResourceSignals": false, + "PauseTime": "PT0S", + "SuspendProcesses": [ + "HealthCheck", + "ReplaceUnhealthy", + "AZRebalance", + "AlarmNotification", + "ScheduledActions" + ] + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + } + }, + "Outputs": { + "EKSClusterConfigCommand3809C9C9": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks update-kubeconfig --name ", + { + "Ref": "EKSClusterBA6ECF8F" + }, + " --region test-region" + ] + ] + } + }, + "EKSClusterGetTokenCommand10DBF41A": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks get-token --cluster-name ", + { + "Ref": "EKSClusterBA6ECF8F" + }, + " --region test-region" + ] + ] + } + }, + "EKSClusterNodesInstanceRoleARN10992C84": { + "Value": { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceRoleEE5595D6", + "Arn" + ] + } + } + }, + "Parameters": { + "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/eks/optimized-ami/1.14/amazon-linux-2/recommended/image_id" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.kubectl-disabled.ts b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.kubectl-disabled.ts new file mode 100644 index 0000000000000..5cdab928f90cf --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.kubectl-disabled.ts @@ -0,0 +1,33 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import * as eks from '../lib'; +import { TestStack } from './util'; + +class EksClusterStack extends TestStack { + constructor(scope: cdk.App, id: string) { + super(scope, id); + + /// !show + const vpc = new ec2.Vpc(this, 'VPC'); + + const cluster = new eks.Cluster(this, 'EKSCluster', { + vpc, + kubectlEnabled: false, + defaultCapacity: 0, + }); + + cluster.addCapacity('Nodes', { + instanceType: new ec2.InstanceType('t2.medium'), + desiredCapacity: 1, // Raise this number to add more nodes + }); + /// !hide + } +} + +const app = new cdk.App(); + +// since the EKS optimized AMI is hard-coded here based on the region, +// we need to actually pass in a specific region. +new EksClusterStack(app, 'eks-integ-kubectl-disabled'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.lit.expected.json b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.lit.expected.json new file mode 100644 index 0000000000000..ad3e616b39bb6 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.lit.expected.json @@ -0,0 +1,1315 @@ +{ + "Transform": "AWS::Serverless-2016-10-31", + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet3Subnet631C5E25": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet3RouteTable98AE0E14": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet3" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet3RouteTableAssociation427FE0C6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + } + } + }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet3EIPAD4BC883": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet3" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPublicSubnet3NATGatewayD3048F5C": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet3EIPAD4BC883", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PublicSubnet3" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PrivateSubnet1" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PrivateSubnet2" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCPrivateSubnet3Subnet3EDCD457": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PrivateSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet3RouteTable192186F8": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC/PrivateSubnet3" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "VPCPrivateSubnet3RouteTableAssociationC28D144E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + } + }, + "VPCPrivateSubnet3DefaultRoute27F311AE": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet3NATGatewayD3048F5C" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "EKSClusterClusterRoleB72F3251": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSServicePolicy" + ] + ] + } + ] + } + }, + "EKSClusterControlPlaneSecurityGroup580AD1FE": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "EKS Control Plane Security Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "EKSClusterControlPlaneSecurityGroupfromeksintegtestbasicEKSClusterNodesInstanceSecurityGroup5B890E254434E08C84B": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from eksintegtestbasicEKSClusterNodesInstanceSecurityGroup5B890E25:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "EKSClusterControlPlaneSecurityGroup580AD1FE", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "EKSClusterResourceHandlerServiceRoleFD631254": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "EKSClusterResourceHandlerServiceRoleDefaultPolicy4D087A98": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EKSClusterClusterRoleB72F3251", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "EKSClusterResourceHandlerServiceRoleDefaultPolicy4D087A98", + "Roles": [ + { + "Ref": "EKSClusterResourceHandlerServiceRoleFD631254" + } + ] + } + }, + "EKSClusterResourceHandler31198B21": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "EKSClusterResourceHandlerServiceRoleFD631254", + "Arn" + ] + }, + "Runtime": "python3.7", + "Layers": [ + { + "Fn::GetAtt": [ + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", + "Outputs.LayerVersionArn" + ] + } + ], + "MemorySize": 512, + "Timeout": 900 + }, + "DependsOn": [ + "EKSClusterResourceHandlerServiceRoleDefaultPolicy4D087A98", + "EKSClusterResourceHandlerServiceRoleFD631254" + ] + }, + "EKSClusterE11008B6": { + "Type": "Custom::AWSCDK-EKS-Cluster", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "EKSClusterResourceHandler31198B21", + "Arn" + ] + }, + "Config": { + "roleArn": { + "Fn::GetAtt": [ + "EKSClusterClusterRoleB72F3251", + "Arn" + ] + }, + "resourcesVpcConfig": { + "securityGroupIds": [ + { + "Fn::GetAtt": [ + "EKSClusterControlPlaneSecurityGroup580AD1FE", + "GroupId" + ] + } + ], + "subnetIds": [ + { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ] + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "EKSClusterKubernetesResourceHandler90E6DD64": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "EKSClusterResourceHandlerServiceRoleFD631254", + "Arn" + ] + }, + "Runtime": "python3.7", + "Environment": { + "Variables": { + "CLUSTER_NAME": { + "Ref": "EKSClusterE11008B6" + } + } + }, + "Layers": [ + { + "Fn::GetAtt": [ + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", + "Outputs.LayerVersionArn" + ] + } + ], + "MemorySize": 256, + "Timeout": 900 + }, + "DependsOn": [ + "EKSClusterResourceHandlerServiceRoleDefaultPolicy4D087A98", + "EKSClusterResourceHandlerServiceRoleFD631254" + ] + }, + "EKSClusterNodesInstanceSecurityGroup460A275E": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "eks-integ-test-basic/EKSCluster/Nodes/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/EKSCluster/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "EKSClusterE11008B6" + } + ] + ] + }, + "Value": "owned" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "EKSClusterNodesInstanceSecurityGroupfromeksintegtestbasicEKSClusterNodesInstanceSecurityGroup5B890E25ALLTRAFFIC17050541": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "-1", + "Description": "from eksintegtestbasicEKSClusterNodesInstanceSecurityGroup5B890E25:ALL TRAFFIC", + "GroupId": { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + } + } + }, + "EKSClusterNodesInstanceSecurityGroupfromeksintegtestbasicEKSClusterControlPlaneSecurityGroup389B14F14436EFF5343": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from eksintegtestbasicEKSClusterControlPlaneSecurityGroup389B14F1:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "EKSClusterControlPlaneSecurityGroup580AD1FE", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "EKSClusterNodesInstanceSecurityGroupfromeksintegtestbasicEKSClusterControlPlaneSecurityGroup389B14F1102565535BB0D6C6D": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from eksintegtestbasicEKSClusterControlPlaneSecurityGroup389B14F1:1025-65535", + "FromPort": 1025, + "GroupId": { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "EKSClusterControlPlaneSecurityGroup580AD1FE", + "GroupId" + ] + }, + "ToPort": 65535 + } + }, + "EKSClusterNodesInstanceRoleEE5595D6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "eks-integ-test-basic/EKSCluster/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "EKSClusterE11008B6" + } + ] + ] + }, + "Value": "owned" + } + ] + } + }, + "EKSClusterNodesInstanceProfile0F2DB3B9": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "EKSClusterNodesInstanceRoleEE5595D6" + } + ] + } + }, + "EKSClusterNodesLaunchConfig921F1106": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t2.medium", + "IamInstanceProfile": { + "Ref": "EKSClusterNodesInstanceProfile0F2DB3B9" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceSecurityGroup460A275E", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ", + { + "Ref": "EKSClusterE11008B6" + }, + " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack eks-integ-test-basic --resource EKSClusterNodesASGC2597E34 --region test-region" + ] + ] + } + } + }, + "DependsOn": [ + "EKSClusterNodesInstanceRoleEE5595D6" + ] + }, + "EKSClusterNodesASGC2597E34": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "DesiredCapacity": "1", + "LaunchConfigurationName": { + "Ref": "EKSClusterNodesLaunchConfig921F1106" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "eks-integ-test-basic/EKSCluster/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "EKSClusterE11008B6" + } + ] + ] + }, + "PropagateAtLaunch": true, + "Value": "owned" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ] + }, + "UpdatePolicy": { + "AutoScalingRollingUpdate": { + "WaitOnResourceSignals": false, + "PauseTime": "PT0S", + "SuspendProcesses": [ + "HealthCheck", + "ReplaceUnhealthy", + "AZRebalance", + "AlarmNotification", + "ScheduledActions" + ] + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "EKSClusterAwsAuthmanifestA4E0796C": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "EKSClusterKubernetesResourceHandler90E6DD64", + "Arn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\"},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "EKSClusterNodesInstanceRoleEE5595D6", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA": { + "Type": "AWS::Serverless::Application", + "Properties": { + "Location": { + "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", + "SemanticVersion": "1.13.7" + }, + "Parameters": { + "LayerName": "kubectl-de6ff3f9a59243920be5aeee7fc888a7" + } + } + } + }, + "Outputs": { + "EKSClusterConfigCommand3809C9C9": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks update-kubeconfig --name ", + { + "Ref": "EKSClusterE11008B6" + }, + " --region test-region" + ] + ] + } + }, + "EKSClusterGetTokenCommand10DBF41A": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks get-token --cluster-name ", + { + "Ref": "EKSClusterE11008B6" + }, + " --region test-region" + ] + ] + } + } + }, + "Parameters": { + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8": { + "Type": "String", + "Description": "S3 bucket for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD": { + "Type": "String", + "Description": "S3 key for asset version \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204ArtifactHashB80B497F": { + "Type": "String", + "Description": "Artifact hash for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB": { + "Type": "String", + "Description": "S3 bucket for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54": { + "Type": "String", + "Description": "S3 key for asset version \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444ArtifactHash606C8127": { + "Type": "String", + "Description": "Artifact hash for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/eks/optimized-ami/1.14/amazon-linux-2/recommended/image_id" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.lit.ts b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.lit.ts new file mode 100644 index 0000000000000..5ab3956b1ba96 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-cluster.lit.ts @@ -0,0 +1,32 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import * as eks from '../lib'; +import { TestStack } from './util'; + +class EksClusterStack extends TestStack { + constructor(scope: cdk.App, id: string) { + super(scope, id); + + /// !show + const vpc = new ec2.Vpc(this, 'VPC'); + + const cluster = new eks.Cluster(this, 'EKSCluster', { + vpc, + defaultCapacity: 0, + }); + + cluster.addCapacity('Nodes', { + instanceType: new ec2.InstanceType('t2.medium'), + desiredCapacity: 1, // Raise this number to add more nodes + }); + /// !hide + } +} + +const app = new cdk.App(); + +// since the EKS optimized AMI is hard-coded here based on the region, +// we need to actually pass in a specific region. +new EksClusterStack(app, 'eks-integ-test-basic'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-helm.lit.expected.json b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-helm.lit.expected.json new file mode 100644 index 0000000000000..10172624f8f2e --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-helm.lit.expected.json @@ -0,0 +1,1336 @@ +[ + { + "Resources": { + "vpcA2121C38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc" + } + ] + } + }, + "vpcPublicSubnet1Subnet2E65531E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet1RouteTable48A2DF9B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet1RouteTableAssociation5D3F4579": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet1RouteTable48A2DF9B" + }, + "SubnetId": { + "Ref": "vpcPublicSubnet1Subnet2E65531E" + } + } + }, + "vpcPublicSubnet1DefaultRoute10708846": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet1RouteTable48A2DF9B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + }, + "DependsOn": [ + "vpcVPCGW7984C166" + ] + }, + "vpcPublicSubnet1EIPDA49DCBE": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet1NATGateway9C16659E": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "vpcPublicSubnet1EIPDA49DCBE", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "vpcPublicSubnet1Subnet2E65531E" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2Subnet009B674F": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2RouteTableEB40D4CB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2RouteTableAssociation21F81B59": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet2RouteTableEB40D4CB" + }, + "SubnetId": { + "Ref": "vpcPublicSubnet2Subnet009B674F" + } + } + }, + "vpcPublicSubnet2DefaultRouteA1EC0F60": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet2RouteTableEB40D4CB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + }, + "DependsOn": [ + "vpcVPCGW7984C166" + ] + }, + "vpcPublicSubnet2EIP9B3743B1": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2NATGateway9B8AE11A": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "vpcPublicSubnet2EIP9B3743B1", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "vpcPublicSubnet2Subnet009B674F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet1Subnet934893E8": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet1RouteTableB41A48CC": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PrivateSubnet1" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet1RouteTableAssociation67945127": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet1RouteTableB41A48CC" + }, + "SubnetId": { + "Ref": "vpcPrivateSubnet1Subnet934893E8" + } + } + }, + "vpcPrivateSubnet1DefaultRoute1AA8E2E5": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet1RouteTableB41A48CC" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "vpcPublicSubnet1NATGateway9C16659E" + } + } + }, + "vpcPrivateSubnet2Subnet7031C2BA": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet2RouteTable7280F23E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PrivateSubnet2" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet2RouteTableAssociation007E94D3": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet2RouteTable7280F23E" + }, + "SubnetId": { + "Ref": "vpcPrivateSubnet2Subnet7031C2BA" + } + } + }, + "vpcPrivateSubnet2DefaultRouteB0E07F99": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet2RouteTable7280F23E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "vpcPublicSubnet2NATGateway9B8AE11A" + } + } + }, + "vpcIGWE57CBDCA": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc" + } + ] + } + }, + "vpcVPCGW7984C166": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "InternetGatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + } + } + }, + "Outputs": { + "ExportsOutputRefvpcA2121C384D1B3CDE": { + "Value": { + "Ref": "vpcA2121C38" + }, + "Export": { + "Name": "k8s-vpc:ExportsOutputRefvpcA2121C384D1B3CDE" + } + }, + "ExportsOutputRefvpcPublicSubnet1Subnet2E65531ECCB85041": { + "Value": { + "Ref": "vpcPublicSubnet1Subnet2E65531E" + }, + "Export": { + "Name": "k8s-vpc:ExportsOutputRefvpcPublicSubnet1Subnet2E65531ECCB85041" + } + }, + "ExportsOutputRefvpcPublicSubnet2Subnet009B674FB900C242": { + "Value": { + "Ref": "vpcPublicSubnet2Subnet009B674F" + }, + "Export": { + "Name": "k8s-vpc:ExportsOutputRefvpcPublicSubnet2Subnet009B674FB900C242" + } + }, + "ExportsOutputRefvpcPrivateSubnet1Subnet934893E8236E2271": { + "Value": { + "Ref": "vpcPrivateSubnet1Subnet934893E8" + }, + "Export": { + "Name": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet1Subnet934893E8236E2271" + } + }, + "ExportsOutputRefvpcPrivateSubnet2Subnet7031C2BA60DCB1EE": { + "Value": { + "Ref": "vpcPrivateSubnet2Subnet7031C2BA" + }, + "Export": { + "Name": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet2Subnet7031C2BA60DCB1EE" + } + } + } + }, + { + "Transform": "AWS::Serverless-2016-10-31", + "Resources": { + "cluster22ClusterRole5FC933B4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSServicePolicy" + ] + ] + } + ] + } + }, + "cluster22ControlPlaneSecurityGroup2648B9CD": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "EKS Control Plane Security Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcA2121C384D1B3CDE" + } + } + }, + "cluster22ControlPlaneSecurityGroupfromk8sclustercluster22NodesInstanceSecurityGroupF903AE86443C3EDA943": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from k8sclustercluster22NodesInstanceSecurityGroupF903AE86:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "cluster22ControlPlaneSecurityGroup2648B9CD", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "cluster22ResourceHandlerServiceRoleC2E4F327": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "cluster22ClusterRole5FC933B4", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", + "Roles": [ + { + "Ref": "cluster22ResourceHandlerServiceRoleC2E4F327" + } + ] + } + }, + "cluster22ResourceHandler6227579A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "cluster22ResourceHandlerServiceRoleC2E4F327", + "Arn" + ] + }, + "Runtime": "python3.7", + "Layers": [ + { + "Fn::GetAtt": [ + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", + "Outputs.LayerVersionArn" + ] + } + ], + "MemorySize": 512, + "Timeout": 900 + }, + "DependsOn": [ + "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", + "cluster22ResourceHandlerServiceRoleC2E4F327" + ] + }, + "cluster227BD1CB20": { + "Type": "Custom::AWSCDK-EKS-Cluster", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "cluster22ResourceHandler6227579A", + "Arn" + ] + }, + "Config": { + "roleArn": { + "Fn::GetAtt": [ + "cluster22ClusterRole5FC933B4", + "Arn" + ] + }, + "resourcesVpcConfig": { + "securityGroupIds": [ + { + "Fn::GetAtt": [ + "cluster22ControlPlaneSecurityGroup2648B9CD", + "GroupId" + ] + } + ], + "subnetIds": [ + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPublicSubnet1Subnet2E65531ECCB85041" + }, + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPublicSubnet2Subnet009B674FB900C242" + }, + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet1Subnet934893E8236E2271" + }, + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet2Subnet7031C2BA60DCB1EE" + } + ] + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "cluster22KubernetesResourceHandler599F07E6": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "cluster22ResourceHandlerServiceRoleC2E4F327", + "Arn" + ] + }, + "Runtime": "python3.7", + "Environment": { + "Variables": { + "CLUSTER_NAME": { + "Ref": "cluster227BD1CB20" + } + } + }, + "Layers": [ + { + "Fn::GetAtt": [ + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", + "Outputs.LayerVersionArn" + ] + } + ], + "MemorySize": 256, + "Timeout": 900 + }, + "DependsOn": [ + "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", + "cluster22ResourceHandlerServiceRoleC2E4F327" + ] + }, + "cluster22AwsAuthmanifest4685C84D": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "cluster22KubernetesResourceHandler599F07E6", + "Arn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\"},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "AdminRole38563C57", + "Arn" + ] + }, + "\\\",\\\"groups\\\":[\\\"system:masters\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "cluster22NodesInstanceRole51CD052F", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "cluster22NodesInstanceSecurityGroup4A3CDC24": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "k8s-cluster/cluster22/Nodes/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "k8s-cluster/cluster22/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "cluster227BD1CB20" + } + ] + ] + }, + "Value": "owned" + } + ], + "VpcId": { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcA2121C384D1B3CDE" + } + } + }, + "cluster22NodesInstanceSecurityGroupfromk8sclustercluster22NodesInstanceSecurityGroupF903AE86ALLTRAFFIC774C7781": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "-1", + "Description": "from k8sclustercluster22NodesInstanceSecurityGroupF903AE86:ALL TRAFFIC", + "GroupId": { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + } + } + }, + "cluster22NodesInstanceSecurityGroupfromk8sclustercluster22ControlPlaneSecurityGroup3B5F21B44434A6E344D": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from k8sclustercluster22ControlPlaneSecurityGroup3B5F21B4:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "cluster22ControlPlaneSecurityGroup2648B9CD", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "cluster22NodesInstanceSecurityGroupfromk8sclustercluster22ControlPlaneSecurityGroup3B5F21B41025655355658FCAA": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from k8sclustercluster22ControlPlaneSecurityGroup3B5F21B4:1025-65535", + "FromPort": 1025, + "GroupId": { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "cluster22ControlPlaneSecurityGroup2648B9CD", + "GroupId" + ] + }, + "ToPort": 65535 + } + }, + "cluster22NodesInstanceRole51CD052F": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "k8s-cluster/cluster22/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "cluster227BD1CB20" + } + ] + ] + }, + "Value": "owned" + } + ] + } + }, + "cluster22NodesInstanceProfile3D4963ED": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "cluster22NodesInstanceRole51CD052F" + } + ] + } + }, + "cluster22NodesLaunchConfig184BF3BA": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t2.medium", + "IamInstanceProfile": { + "Ref": "cluster22NodesInstanceProfile3D4963ED" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ", + { + "Ref": "cluster227BD1CB20" + }, + " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack k8s-cluster --resource cluster22NodesASGC0A97398 --region test-region" + ] + ] + } + } + }, + "DependsOn": [ + "cluster22NodesInstanceRole51CD052F" + ] + }, + "cluster22NodesASGC0A97398": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "3", + "MinSize": "1", + "DesiredCapacity": "3", + "LaunchConfigurationName": { + "Ref": "cluster22NodesLaunchConfig184BF3BA" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "k8s-cluster/cluster22/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "cluster227BD1CB20" + } + ] + ] + }, + "PropagateAtLaunch": true, + "Value": "owned" + } + ], + "VPCZoneIdentifier": [ + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet1Subnet934893E8236E2271" + }, + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet2Subnet7031C2BA60DCB1EE" + } + ] + }, + "UpdatePolicy": { + "AutoScalingRollingUpdate": { + "WaitOnResourceSignals": false, + "PauseTime": "PT0S", + "SuspendProcesses": [ + "HealthCheck", + "ReplaceUnhealthy", + "AZRebalance", + "AlarmNotification", + "ScheduledActions" + ] + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "cluster22chartdashboard616811AB": { + "Type": "Custom::AWSCDK-EKS-HelmChart", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "cluster22HelmChartHandler0BAF302E", + "Arn" + ] + }, + "Release": "k8sclustercluster22chartdashboard3844c297", + "Chart": "kubernetes-dashboard", + "Namespace": "default", + "Repository": "https://kubernetes-charts.storage.googleapis.com" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "cluster22HelmChartHandler0BAF302E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653S3BucketD01BFA78" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653S3VersionKeyD67E9179" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653S3VersionKeyD67E9179" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "cluster22ResourceHandlerServiceRoleC2E4F327", + "Arn" + ] + }, + "Runtime": "python3.7", + "Environment": { + "Variables": { + "CLUSTER_NAME": { + "Ref": "cluster227BD1CB20" + } + } + }, + "Layers": [ + { + "Fn::GetAtt": [ + "kubectllayer200beta1B9303363", + "Outputs.LayerVersionArn" + ] + } + ], + "MemorySize": 256, + "Timeout": 900 + }, + "DependsOn": [ + "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", + "cluster22ResourceHandlerServiceRoleC2E4F327" + ] + }, + "cluster22chartnginxingress90C2D506": { + "Type": "Custom::AWSCDK-EKS-HelmChart", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "cluster22HelmChartHandler0BAF302E", + "Arn" + ] + }, + "Release": "k8sclustercluster22chartnginxingress8b03389e", + "Chart": "nginx-ingress", + "Namespace": "kube-system", + "Repository": "https://helm.nginx.com/stable" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA": { + "Type": "AWS::Serverless::Application", + "Properties": { + "Location": { + "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", + "SemanticVersion": "1.13.7" + }, + "Parameters": { + "LayerName": "kubectl-bedb92f2e70f45155fba70d3425dd148" + } + } + }, + "AdminRole38563C57": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "kubectllayer200beta1B9303363": { + "Type": "AWS::Serverless::Application", + "Properties": { + "Location": { + "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", + "SemanticVersion": "2.0.0-beta1" + }, + "Parameters": { + "LayerName": "kubectl-aa3d1881d348da39094e6b1ce165f580" + } + } + } + }, + "Outputs": { + "cluster22ConfigCommand96B20279": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks update-kubeconfig --name ", + { + "Ref": "cluster227BD1CB20" + }, + " --region test-region" + ] + ] + } + }, + "cluster22GetTokenCommand99DB9B02": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks get-token --cluster-name ", + { + "Ref": "cluster227BD1CB20" + }, + " --region test-region" + ] + ] + } + } + }, + "Parameters": { + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8": { + "Type": "String", + "Description": "S3 bucket for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD": { + "Type": "String", + "Description": "S3 key for asset version \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204ArtifactHashB80B497F": { + "Type": "String", + "Description": "Artifact hash for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB": { + "Type": "String", + "Description": "S3 bucket for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54": { + "Type": "String", + "Description": "S3 key for asset version \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444ArtifactHash606C8127": { + "Type": "String", + "Description": "Artifact hash for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653S3BucketD01BFA78": { + "Type": "String", + "Description": "S3 bucket for asset \"8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653\"" + }, + "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653S3VersionKeyD67E9179": { + "Type": "String", + "Description": "S3 key for asset version \"8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653\"" + }, + "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653ArtifactHash77099D9F": { + "Type": "String", + "Description": "Artifact hash for asset \"8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653\"" + }, + "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/eks/optimized-ami/1.14/amazon-linux-2/recommended/image_id" + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-helm.lit.ts b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-helm.lit.ts new file mode 100644 index 0000000000000..35113435a740b --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-helm.lit.ts @@ -0,0 +1,54 @@ +/// !cdk-integ * + +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import { App, Construct } from '@aws-cdk/core'; +import { Cluster } from '../lib'; +import { TestStack } from './util'; + +class VpcStack extends TestStack { + public readonly vpc: ec2.Vpc; + + constructor(scope: Construct, id: string) { + super(scope, id); + this.vpc = new ec2.Vpc(this, 'vpc', { maxAzs: 2 }); + } +} + +class ClusterStack extends TestStack { + public readonly cluster: Cluster; + + constructor(scope: Construct, id: string, props: { vpc: ec2.Vpc }) { + super(scope, id); + + /// !show + // define the cluster. kubectl is enabled by default. + this.cluster = new Cluster(this, 'cluster22', { + vpc: props.vpc, + defaultCapacity: 0, + }); + + // define an IAM role assumable by anyone in the account and map it to the k8s + // `system:masters` group this is required if you want to be able to issue + // manual `kubectl` commands against the cluster. + const mastersRole = new iam.Role(this, 'AdminRole', { assumedBy: new iam.AccountRootPrincipal() }); + this.cluster.awsAuth.addMastersRole(mastersRole); + + // add some capacity to the cluster. The IAM instance role will + // automatically be mapped via aws-auth to allow nodes to join the cluster. + this.cluster.addCapacity('Nodes', { + instanceType: new ec2.InstanceType('t2.medium'), + desiredCapacity: 3, + }); + + // add two Helm charts to the cluster. This will be the Kubernetes dashboard and the Nginx Ingress Controller + this.cluster.addChart('dashboard', { chart: 'kubernetes-dashboard', repository: 'https://kubernetes-charts.storage.googleapis.com' }); + this.cluster.addChart('nginx-ingress', { chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', namespace: 'kube-system' }); + /// !hide + } +} + +const app = new App(); +const vpcStack = new VpcStack(app, 'k8s-vpc'); +new ClusterStack(app, 'k8s-cluster', { vpc: vpcStack.vpc }); +app.synth(); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-kubectl.lit.expected.json b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-kubectl.lit.expected.json new file mode 100644 index 0000000000000..720d8f76704e9 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-kubectl.lit.expected.json @@ -0,0 +1,1220 @@ +[ + { + "Resources": { + "vpcA2121C38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc" + } + ] + } + }, + "vpcPublicSubnet1Subnet2E65531E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet1RouteTable48A2DF9B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet1RouteTableAssociation5D3F4579": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet1RouteTable48A2DF9B" + }, + "SubnetId": { + "Ref": "vpcPublicSubnet1Subnet2E65531E" + } + } + }, + "vpcPublicSubnet1DefaultRoute10708846": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet1RouteTable48A2DF9B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + }, + "DependsOn": [ + "vpcVPCGW7984C166" + ] + }, + "vpcPublicSubnet1EIPDA49DCBE": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet1NATGateway9C16659E": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "vpcPublicSubnet1EIPDA49DCBE", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "vpcPublicSubnet1Subnet2E65531E" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2Subnet009B674F": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2RouteTableEB40D4CB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2RouteTableAssociation21F81B59": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet2RouteTableEB40D4CB" + }, + "SubnetId": { + "Ref": "vpcPublicSubnet2Subnet009B674F" + } + } + }, + "vpcPublicSubnet2DefaultRouteA1EC0F60": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet2RouteTableEB40D4CB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + }, + "DependsOn": [ + "vpcVPCGW7984C166" + ] + }, + "vpcPublicSubnet2EIP9B3743B1": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2NATGateway9B8AE11A": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "vpcPublicSubnet2EIP9B3743B1", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "vpcPublicSubnet2Subnet009B674F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet1Subnet934893E8": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet1RouteTableB41A48CC": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PrivateSubnet1" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet1RouteTableAssociation67945127": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet1RouteTableB41A48CC" + }, + "SubnetId": { + "Ref": "vpcPrivateSubnet1Subnet934893E8" + } + } + }, + "vpcPrivateSubnet1DefaultRoute1AA8E2E5": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet1RouteTableB41A48CC" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "vpcPublicSubnet1NATGateway9C16659E" + } + } + }, + "vpcPrivateSubnet2Subnet7031C2BA": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet2RouteTable7280F23E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc/PrivateSubnet2" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet2RouteTableAssociation007E94D3": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet2RouteTable7280F23E" + }, + "SubnetId": { + "Ref": "vpcPrivateSubnet2Subnet7031C2BA" + } + } + }, + "vpcPrivateSubnet2DefaultRouteB0E07F99": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet2RouteTable7280F23E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "vpcPublicSubnet2NATGateway9B8AE11A" + } + } + }, + "vpcIGWE57CBDCA": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "k8s-vpc/vpc" + } + ] + } + }, + "vpcVPCGW7984C166": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "InternetGatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + } + } + }, + "Outputs": { + "ExportsOutputRefvpcA2121C384D1B3CDE": { + "Value": { + "Ref": "vpcA2121C38" + }, + "Export": { + "Name": "k8s-vpc:ExportsOutputRefvpcA2121C384D1B3CDE" + } + }, + "ExportsOutputRefvpcPublicSubnet1Subnet2E65531ECCB85041": { + "Value": { + "Ref": "vpcPublicSubnet1Subnet2E65531E" + }, + "Export": { + "Name": "k8s-vpc:ExportsOutputRefvpcPublicSubnet1Subnet2E65531ECCB85041" + } + }, + "ExportsOutputRefvpcPublicSubnet2Subnet009B674FB900C242": { + "Value": { + "Ref": "vpcPublicSubnet2Subnet009B674F" + }, + "Export": { + "Name": "k8s-vpc:ExportsOutputRefvpcPublicSubnet2Subnet009B674FB900C242" + } + }, + "ExportsOutputRefvpcPrivateSubnet1Subnet934893E8236E2271": { + "Value": { + "Ref": "vpcPrivateSubnet1Subnet934893E8" + }, + "Export": { + "Name": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet1Subnet934893E8236E2271" + } + }, + "ExportsOutputRefvpcPrivateSubnet2Subnet7031C2BA60DCB1EE": { + "Value": { + "Ref": "vpcPrivateSubnet2Subnet7031C2BA" + }, + "Export": { + "Name": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet2Subnet7031C2BA60DCB1EE" + } + } + } + }, + { + "Transform": "AWS::Serverless-2016-10-31", + "Resources": { + "cluster22ClusterRole5FC933B4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSServicePolicy" + ] + ] + } + ] + } + }, + "cluster22ControlPlaneSecurityGroup2648B9CD": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "EKS Control Plane Security Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcA2121C384D1B3CDE" + } + } + }, + "cluster22ControlPlaneSecurityGroupfromk8sclustercluster22NodesInstanceSecurityGroupF903AE86443C3EDA943": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from k8sclustercluster22NodesInstanceSecurityGroupF903AE86:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "cluster22ControlPlaneSecurityGroup2648B9CD", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "cluster22ResourceHandlerServiceRoleC2E4F327": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "cluster22ClusterRole5FC933B4", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", + "Roles": [ + { + "Ref": "cluster22ResourceHandlerServiceRoleC2E4F327" + } + ] + } + }, + "cluster22ResourceHandler6227579A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "cluster22ResourceHandlerServiceRoleC2E4F327", + "Arn" + ] + }, + "Runtime": "python3.7", + "Layers": [ + { + "Fn::GetAtt": [ + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", + "Outputs.LayerVersionArn" + ] + } + ], + "MemorySize": 512, + "Timeout": 900 + }, + "DependsOn": [ + "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", + "cluster22ResourceHandlerServiceRoleC2E4F327" + ] + }, + "cluster227BD1CB20": { + "Type": "Custom::AWSCDK-EKS-Cluster", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "cluster22ResourceHandler6227579A", + "Arn" + ] + }, + "Config": { + "roleArn": { + "Fn::GetAtt": [ + "cluster22ClusterRole5FC933B4", + "Arn" + ] + }, + "resourcesVpcConfig": { + "securityGroupIds": [ + { + "Fn::GetAtt": [ + "cluster22ControlPlaneSecurityGroup2648B9CD", + "GroupId" + ] + } + ], + "subnetIds": [ + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPublicSubnet1Subnet2E65531ECCB85041" + }, + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPublicSubnet2Subnet009B674FB900C242" + }, + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet1Subnet934893E8236E2271" + }, + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet2Subnet7031C2BA60DCB1EE" + } + ] + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "cluster22KubernetesResourceHandler599F07E6": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "cluster22ResourceHandlerServiceRoleC2E4F327", + "Arn" + ] + }, + "Runtime": "python3.7", + "Environment": { + "Variables": { + "CLUSTER_NAME": { + "Ref": "cluster227BD1CB20" + } + } + }, + "Layers": [ + { + "Fn::GetAtt": [ + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", + "Outputs.LayerVersionArn" + ] + } + ], + "MemorySize": 256, + "Timeout": 900 + }, + "DependsOn": [ + "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", + "cluster22ResourceHandlerServiceRoleC2E4F327" + ] + }, + "cluster22AwsAuthmanifest4685C84D": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "cluster22KubernetesResourceHandler599F07E6", + "Arn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\"},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "AdminRole38563C57", + "Arn" + ] + }, + "\\\",\\\"groups\\\":[\\\"system:masters\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "cluster22NodesInstanceRole51CD052F", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "cluster22NodesInstanceSecurityGroup4A3CDC24": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "k8s-cluster/cluster22/Nodes/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "k8s-cluster/cluster22/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "cluster227BD1CB20" + } + ] + ] + }, + "Value": "owned" + } + ], + "VpcId": { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcA2121C384D1B3CDE" + } + } + }, + "cluster22NodesInstanceSecurityGroupfromk8sclustercluster22NodesInstanceSecurityGroupF903AE86ALLTRAFFIC774C7781": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "-1", + "Description": "from k8sclustercluster22NodesInstanceSecurityGroupF903AE86:ALL TRAFFIC", + "GroupId": { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + } + } + }, + "cluster22NodesInstanceSecurityGroupfromk8sclustercluster22ControlPlaneSecurityGroup3B5F21B44434A6E344D": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from k8sclustercluster22ControlPlaneSecurityGroup3B5F21B4:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "cluster22ControlPlaneSecurityGroup2648B9CD", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "cluster22NodesInstanceSecurityGroupfromk8sclustercluster22ControlPlaneSecurityGroup3B5F21B41025655355658FCAA": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from k8sclustercluster22ControlPlaneSecurityGroup3B5F21B4:1025-65535", + "FromPort": 1025, + "GroupId": { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "cluster22ControlPlaneSecurityGroup2648B9CD", + "GroupId" + ] + }, + "ToPort": 65535 + } + }, + "cluster22NodesInstanceRole51CD052F": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "k8s-cluster/cluster22/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "cluster227BD1CB20" + } + ] + ] + }, + "Value": "owned" + } + ] + } + }, + "cluster22NodesInstanceProfile3D4963ED": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "cluster22NodesInstanceRole51CD052F" + } + ] + } + }, + "cluster22NodesLaunchConfig184BF3BA": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t2.medium", + "IamInstanceProfile": { + "Ref": "cluster22NodesInstanceProfile3D4963ED" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "cluster22NodesInstanceSecurityGroup4A3CDC24", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ", + { + "Ref": "cluster227BD1CB20" + }, + " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack k8s-cluster --resource cluster22NodesASGC0A97398 --region test-region" + ] + ] + } + } + }, + "DependsOn": [ + "cluster22NodesInstanceRole51CD052F" + ] + }, + "cluster22NodesASGC0A97398": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "3", + "MinSize": "1", + "DesiredCapacity": "3", + "LaunchConfigurationName": { + "Ref": "cluster22NodesLaunchConfig184BF3BA" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "k8s-cluster/cluster22/Nodes" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "cluster227BD1CB20" + } + ] + ] + }, + "PropagateAtLaunch": true, + "Value": "owned" + } + ], + "VPCZoneIdentifier": [ + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet1Subnet934893E8236E2271" + }, + { + "Fn::ImportValue": "k8s-vpc:ExportsOutputRefvpcPrivateSubnet2Subnet7031C2BA60DCB1EE" + } + ] + }, + "UpdatePolicy": { + "AutoScalingRollingUpdate": { + "WaitOnResourceSignals": false, + "PauseTime": "PT0S", + "SuspendProcesses": [ + "HealthCheck", + "ReplaceUnhealthy", + "AZRebalance", + "AlarmNotification", + "ScheduledActions" + ] + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "cluster22manifesthellokubernetes849F52EA": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "cluster22KubernetesResourceHandler599F07E6", + "Arn" + ] + }, + "Manifest": "[{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"name\":\"hello-kubernetes\"},\"spec\":{\"type\":\"LoadBalancer\",\"ports\":[{\"port\":80,\"targetPort\":8080}],\"selector\":{\"app\":\"hello-kubernetes\"}}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"hello-kubernetes\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"hello-kubernetes\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"hello-kubernetes\"}},\"spec\":{\"containers\":[{\"name\":\"hello-kubernetes\",\"image\":\"paulbouwer/hello-kubernetes:1.5\",\"ports\":[{\"containerPort\":8080}]}]}}}}]" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA": { + "Type": "AWS::Serverless::Application", + "Properties": { + "Location": { + "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", + "SemanticVersion": "1.13.7" + }, + "Parameters": { + "LayerName": "kubectl-bedb92f2e70f45155fba70d3425dd148" + } + } + }, + "AdminRole38563C57": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + } + }, + "Outputs": { + "cluster22ConfigCommand96B20279": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks update-kubeconfig --name ", + { + "Ref": "cluster227BD1CB20" + }, + " --region test-region" + ] + ] + } + }, + "cluster22GetTokenCommand99DB9B02": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks get-token --cluster-name ", + { + "Ref": "cluster227BD1CB20" + }, + " --region test-region" + ] + ] + } + } + }, + "Parameters": { + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8": { + "Type": "String", + "Description": "S3 bucket for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD": { + "Type": "String", + "Description": "S3 key for asset version \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204ArtifactHashB80B497F": { + "Type": "String", + "Description": "Artifact hash for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB": { + "Type": "String", + "Description": "S3 bucket for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54": { + "Type": "String", + "Description": "S3 key for asset version \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444ArtifactHash606C8127": { + "Type": "String", + "Description": "Artifact hash for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/eks/optimized-ami/1.14/amazon-linux-2/recommended/image_id" + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-kubectl.lit.ts b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-kubectl.lit.ts new file mode 100644 index 0000000000000..16821dc79cfd3 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-kubectl.lit.ts @@ -0,0 +1,88 @@ +/// !cdk-integ * + +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import { App, Construct } from '@aws-cdk/core'; +import { Cluster } from '../lib'; +import { TestStack } from './util'; + +class VpcStack extends TestStack { + public readonly vpc: ec2.Vpc; + + constructor(scope: Construct, id: string) { + super(scope, id); + this.vpc = new ec2.Vpc(this, 'vpc', { maxAzs: 2 }); + } +} + +class ClusterStack extends TestStack { + public readonly cluster: Cluster; + + constructor(scope: Construct, id: string, props: { vpc: ec2.Vpc }) { + super(scope, id); + + /// !show + // define the cluster. kubectl is enabled by default. + this.cluster = new Cluster(this, 'cluster22', { + vpc: props.vpc, + defaultCapacity: 0, + }); + + // define an IAM role assumable by anyone in the account and map it to the k8s + // `system:masters` group this is required if you want to be able to issue + // manual `kubectl` commands against the cluster. + const mastersRole = new iam.Role(this, 'AdminRole', { assumedBy: new iam.AccountRootPrincipal() }); + this.cluster.awsAuth.addMastersRole(mastersRole); + + // add some capacity to the cluster. The IAM instance role will + // automatically be mapped via aws-auth to allow nodes to join the cluster. + this.cluster.addCapacity('Nodes', { + instanceType: new ec2.InstanceType('t2.medium'), + desiredCapacity: 3, + }); + + // add an arbitrary k8s manifest to the cluster. This will `kubectl apply` + // these resources upon creation or `kubectl delete` upon removal. + this.cluster.addResource('hello-kubernetes', + { + apiVersion: "v1", + kind: "Service", + metadata: { name: "hello-kubernetes" }, + spec: { + type: "LoadBalancer", + ports: [ { port: 80, targetPort: 8080 } ], + selector: { app: "hello-kubernetes" } + } + }, + { + apiVersion: "apps/v1", + kind: "Deployment", + metadata: { name: "hello-kubernetes" }, + spec: { + replicas: 1, + selector: { matchLabels: { app: "hello-kubernetes" } }, + template: { + metadata: { + labels: { app: "hello-kubernetes" } + }, + spec: { + containers: [ + { + name: "hello-kubernetes", + image: "paulbouwer/hello-kubernetes:1.5", + ports: [ { containerPort: 8080 } ] + } + ] + } + } + } + } + ); + /// !hide + } +} + +const app = new App(); +const vpcStack = new VpcStack(app, 'k8s-vpc'); +new ClusterStack(app, 'k8s-cluster', { vpc: vpcStack.vpc }); +app.synth(); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-spot.expected.json b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-spot.expected.json new file mode 100644 index 0000000000000..982e65e5d7d63 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-spot.expected.json @@ -0,0 +1,1451 @@ +{ + "Transform": "AWS::Serverless-2016-10-31", + "Resources": { + "vpcA2121C38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc" + } + ] + } + }, + "vpcPublicSubnet1Subnet2E65531E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet1RouteTable48A2DF9B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet1RouteTableAssociation5D3F4579": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet1RouteTable48A2DF9B" + }, + "SubnetId": { + "Ref": "vpcPublicSubnet1Subnet2E65531E" + } + } + }, + "vpcPublicSubnet1DefaultRoute10708846": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet1RouteTable48A2DF9B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + }, + "DependsOn": [ + "vpcVPCGW7984C166" + ] + }, + "vpcPublicSubnet1EIPDA49DCBE": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet1NATGateway9C16659E": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "vpcPublicSubnet1EIPDA49DCBE", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "vpcPublicSubnet1Subnet2E65531E" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PublicSubnet1" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2Subnet009B674F": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2RouteTableEB40D4CB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2RouteTableAssociation21F81B59": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet2RouteTableEB40D4CB" + }, + "SubnetId": { + "Ref": "vpcPublicSubnet2Subnet009B674F" + } + } + }, + "vpcPublicSubnet2DefaultRouteA1EC0F60": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPublicSubnet2RouteTableEB40D4CB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + }, + "DependsOn": [ + "vpcVPCGW7984C166" + ] + }, + "vpcPublicSubnet2EIP9B3743B1": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPublicSubnet2NATGateway9B8AE11A": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "vpcPublicSubnet2EIP9B3743B1", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "vpcPublicSubnet2Subnet009B674F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PublicSubnet2" + }, + { + "Key": "kubernetes.io/role/elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet1Subnet934893E8": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet1RouteTableB41A48CC": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PrivateSubnet1" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet1RouteTableAssociation67945127": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet1RouteTableB41A48CC" + }, + "SubnetId": { + "Ref": "vpcPrivateSubnet1Subnet934893E8" + } + } + }, + "vpcPrivateSubnet1DefaultRoute1AA8E2E5": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet1RouteTableB41A48CC" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "vpcPublicSubnet1NATGateway9C16659E" + } + } + }, + "vpcPrivateSubnet2Subnet7031C2BA": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "vpcA2121C38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet2RouteTable7280F23E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc/PrivateSubnet2" + }, + { + "Key": "kubernetes.io/role/internal-elb", + "Value": "1" + } + ] + } + }, + "vpcPrivateSubnet2RouteTableAssociation007E94D3": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet2RouteTable7280F23E" + }, + "SubnetId": { + "Ref": "vpcPrivateSubnet2Subnet7031C2BA" + } + } + }, + "vpcPrivateSubnet2DefaultRouteB0E07F99": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "vpcPrivateSubnet2RouteTable7280F23E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "vpcPublicSubnet2NATGateway9B8AE11A" + } + } + }, + "vpcIGWE57CBDCA": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/vpc" + } + ] + } + }, + "vpcVPCGW7984C166": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "vpcA2121C38" + }, + "InternetGatewayId": { + "Ref": "vpcIGWE57CBDCA" + } + } + }, + "myClusterClusterRoleF3B08D5F": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSServicePolicy" + ] + ] + } + ] + } + }, + "myClusterControlPlaneSecurityGroupD42800D0": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "EKS Control Plane Security Group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "vpcA2121C38" + } + } + }, + "myClusterControlPlaneSecurityGroupfromintegeksspotmyClusterDefaultCapacityInstanceSecurityGroup8EBC6D914435857A9D2": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from integeksspotmyClusterDefaultCapacityInstanceSecurityGroup8EBC6D91:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "myClusterControlPlaneSecurityGroupD42800D0", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "myClusterDefaultCapacityInstanceSecurityGroup22595F6B", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "myClusterControlPlaneSecurityGroupfromintegeksspotmyClusterspotInstanceSecurityGroup4D0BAA4D443BF12370D": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from integeksspotmyClusterspotInstanceSecurityGroup4D0BAA4D:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "myClusterControlPlaneSecurityGroupD42800D0", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "myClusterspotInstanceSecurityGroupE76CC584", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "myClusterResourceHandlerServiceRole95F554E2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "myClusterResourceHandlerServiceRoleDefaultPolicyB0BF3AD3": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "myClusterClusterRoleF3B08D5F", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "myClusterResourceHandlerServiceRoleDefaultPolicyB0BF3AD3", + "Roles": [ + { + "Ref": "myClusterResourceHandlerServiceRole95F554E2" + } + ] + } + }, + "myClusterResourceHandler19D131C9": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "myClusterResourceHandlerServiceRole95F554E2", + "Arn" + ] + }, + "Runtime": "python3.7", + "Layers": [ + { + "Fn::GetAtt": [ + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", + "Outputs.LayerVersionArn" + ] + } + ], + "MemorySize": 512, + "Timeout": 900 + }, + "DependsOn": [ + "myClusterResourceHandlerServiceRoleDefaultPolicyB0BF3AD3", + "myClusterResourceHandlerServiceRole95F554E2" + ] + }, + "myClusterE51CD07F": { + "Type": "Custom::AWSCDK-EKS-Cluster", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "myClusterResourceHandler19D131C9", + "Arn" + ] + }, + "Config": { + "roleArn": { + "Fn::GetAtt": [ + "myClusterClusterRoleF3B08D5F", + "Arn" + ] + }, + "resourcesVpcConfig": { + "securityGroupIds": [ + { + "Fn::GetAtt": [ + "myClusterControlPlaneSecurityGroupD42800D0", + "GroupId" + ] + } + ], + "subnetIds": [ + { + "Ref": "vpcPublicSubnet1Subnet2E65531E" + }, + { + "Ref": "vpcPublicSubnet2Subnet009B674F" + }, + { + "Ref": "vpcPrivateSubnet1Subnet934893E8" + }, + { + "Ref": "vpcPrivateSubnet2Subnet7031C2BA" + } + ] + } + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "myClusterKubernetesResourceHandler50297E32": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "myClusterResourceHandlerServiceRole95F554E2", + "Arn" + ] + }, + "Runtime": "python3.7", + "Environment": { + "Variables": { + "CLUSTER_NAME": { + "Ref": "myClusterE51CD07F" + } + } + }, + "Layers": [ + { + "Fn::GetAtt": [ + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", + "Outputs.LayerVersionArn" + ] + } + ], + "MemorySize": 256, + "Timeout": 900 + }, + "DependsOn": [ + "myClusterResourceHandlerServiceRoleDefaultPolicyB0BF3AD3", + "myClusterResourceHandlerServiceRole95F554E2" + ] + }, + "myClusterDefaultCapacityInstanceSecurityGroup22595F6B": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "integ-eks-spot/myCluster/DefaultCapacity/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/myCluster/DefaultCapacity" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "myClusterE51CD07F" + } + ] + ] + }, + "Value": "owned" + } + ], + "VpcId": { + "Ref": "vpcA2121C38" + } + } + }, + "myClusterDefaultCapacityInstanceSecurityGroupfromintegeksspotmyClusterDefaultCapacityInstanceSecurityGroup8EBC6D91ALLTRAFFIC50C0DBE7": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "-1", + "Description": "from integeksspotmyClusterDefaultCapacityInstanceSecurityGroup8EBC6D91:ALL TRAFFIC", + "GroupId": { + "Fn::GetAtt": [ + "myClusterDefaultCapacityInstanceSecurityGroup22595F6B", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "myClusterDefaultCapacityInstanceSecurityGroup22595F6B", + "GroupId" + ] + } + } + }, + "myClusterDefaultCapacityInstanceSecurityGroupfromintegeksspotmyClusterControlPlaneSecurityGroupC4434A844430734956F": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from integeksspotmyClusterControlPlaneSecurityGroupC4434A84:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "myClusterDefaultCapacityInstanceSecurityGroup22595F6B", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "myClusterControlPlaneSecurityGroupD42800D0", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "myClusterDefaultCapacityInstanceSecurityGroupfromintegeksspotmyClusterControlPlaneSecurityGroupC4434A84102565535234C3C38": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from integeksspotmyClusterControlPlaneSecurityGroupC4434A84:1025-65535", + "FromPort": 1025, + "GroupId": { + "Fn::GetAtt": [ + "myClusterDefaultCapacityInstanceSecurityGroup22595F6B", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "myClusterControlPlaneSecurityGroupD42800D0", + "GroupId" + ] + }, + "ToPort": 65535 + } + }, + "myClusterDefaultCapacityInstanceRoleA36E0984": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/myCluster/DefaultCapacity" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "myClusterE51CD07F" + } + ] + ] + }, + "Value": "owned" + } + ] + } + }, + "myClusterDefaultCapacityInstanceProfileE7E48198": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "myClusterDefaultCapacityInstanceRoleA36E0984" + } + ] + } + }, + "myClusterDefaultCapacityLaunchConfigCF6D4B81": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "m5.large", + "IamInstanceProfile": { + "Ref": "myClusterDefaultCapacityInstanceProfileE7E48198" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "myClusterDefaultCapacityInstanceSecurityGroup22595F6B", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ", + { + "Ref": "myClusterE51CD07F" + }, + " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack integ-eks-spot --resource myClusterDefaultCapacityASGF3FE3A19 --region test-region" + ] + ] + } + } + }, + "DependsOn": [ + "myClusterDefaultCapacityInstanceRoleA36E0984" + ] + }, + "myClusterDefaultCapacityASGF3FE3A19": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "2", + "MinSize": "1", + "DesiredCapacity": "2", + "LaunchConfigurationName": { + "Ref": "myClusterDefaultCapacityLaunchConfigCF6D4B81" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "integ-eks-spot/myCluster/DefaultCapacity" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "myClusterE51CD07F" + } + ] + ] + }, + "PropagateAtLaunch": true, + "Value": "owned" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "vpcPrivateSubnet1Subnet934893E8" + }, + { + "Ref": "vpcPrivateSubnet2Subnet7031C2BA" + } + ] + }, + "UpdatePolicy": { + "AutoScalingRollingUpdate": { + "WaitOnResourceSignals": false, + "PauseTime": "PT0S", + "SuspendProcesses": [ + "HealthCheck", + "ReplaceUnhealthy", + "AZRebalance", + "AlarmNotification", + "ScheduledActions" + ] + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "myClusterAwsAuthmanifest66DDDCBC": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "myClusterKubernetesResourceHandler50297E32", + "Arn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\"},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "myClusterDefaultCapacityInstanceRoleA36E0984", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "myClusterspotInstanceRole03AE80B5", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "myClusterspotInstanceSecurityGroupE76CC584": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "integ-eks-spot/myCluster/spot/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/myCluster/spot" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "myClusterE51CD07F" + } + ] + ] + }, + "Value": "owned" + } + ], + "VpcId": { + "Ref": "vpcA2121C38" + } + } + }, + "myClusterspotInstanceSecurityGroupfromintegeksspotmyClusterspotInstanceSecurityGroup4D0BAA4DALLTRAFFIC6AB5F7A7": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "-1", + "Description": "from integeksspotmyClusterspotInstanceSecurityGroup4D0BAA4D:ALL TRAFFIC", + "GroupId": { + "Fn::GetAtt": [ + "myClusterspotInstanceSecurityGroupE76CC584", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "myClusterspotInstanceSecurityGroupE76CC584", + "GroupId" + ] + } + } + }, + "myClusterspotInstanceSecurityGroupfromintegeksspotmyClusterControlPlaneSecurityGroupC4434A84443CAF82847": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from integeksspotmyClusterControlPlaneSecurityGroupC4434A84:443", + "FromPort": 443, + "GroupId": { + "Fn::GetAtt": [ + "myClusterspotInstanceSecurityGroupE76CC584", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "myClusterControlPlaneSecurityGroupD42800D0", + "GroupId" + ] + }, + "ToPort": 443 + } + }, + "myClusterspotInstanceSecurityGroupfromintegeksspotmyClusterControlPlaneSecurityGroupC4434A8410256553577BCEBCC": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from integeksspotmyClusterControlPlaneSecurityGroupC4434A84:1025-65535", + "FromPort": 1025, + "GroupId": { + "Fn::GetAtt": [ + "myClusterspotInstanceSecurityGroupE76CC584", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "myClusterControlPlaneSecurityGroupD42800D0", + "GroupId" + ] + }, + "ToPort": 65535 + } + }, + "myClusterspotInstanceRole03AE80B5": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSWorkerNodePolicy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKS_CNI_Policy" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + ] + ] + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "integ-eks-spot/myCluster/spot" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "myClusterE51CD07F" + } + ] + ] + }, + "Value": "owned" + } + ] + } + }, + "myClusterspotInstanceProfile93D80EE5": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "myClusterspotInstanceRole03AE80B5" + } + ] + } + }, + "myClusterspotLaunchConfig6681F311": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t3.large", + "IamInstanceProfile": { + "Ref": "myClusterspotInstanceProfile93D80EE5" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "myClusterspotInstanceSecurityGroupE76CC584", + "GroupId" + ] + } + ], + "SpotPrice": "0.1094", + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ", + { + "Ref": "myClusterE51CD07F" + }, + " --kubelet-extra-args \"--node-labels lifecycle=Ec2Spot --register-with-taints=spotInstance=true:PreferNoSchedule --node-labels foo=bar,goo=far\" --use-max-pods true --aws-api-retry-attempts 5\n/opt/aws/bin/cfn-signal --exit-code $? --stack integ-eks-spot --resource myClusterspotASG5D95FD2F --region test-region" + ] + ] + } + } + }, + "DependsOn": [ + "myClusterspotInstanceRole03AE80B5" + ] + }, + "myClusterspotASG5D95FD2F": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "10", + "MinSize": "1", + "LaunchConfigurationName": { + "Ref": "myClusterspotLaunchConfig6681F311" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "integ-eks-spot/myCluster/spot" + }, + { + "Key": { + "Fn::Join": [ + "", + [ + "kubernetes.io/cluster/", + { + "Ref": "myClusterE51CD07F" + } + ] + ] + }, + "PropagateAtLaunch": true, + "Value": "owned" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "vpcPrivateSubnet1Subnet934893E8" + }, + { + "Ref": "vpcPrivateSubnet2Subnet7031C2BA" + } + ] + }, + "UpdatePolicy": { + "AutoScalingRollingUpdate": { + "WaitOnResourceSignals": false, + "PauseTime": "PT0S", + "SuspendProcesses": [ + "HealthCheck", + "ReplaceUnhealthy", + "AZRebalance", + "AlarmNotification", + "ScheduledActions" + ] + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "myClustermanifestspotinterrupthandler0542CCD2": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "myClusterKubernetesResourceHandler50297E32", + "Arn" + ] + }, + "Manifest": "[{\"kind\":\"ClusterRole\",\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"metadata\":{\"name\":\"node-termination-handler\",\"namespace\":\"default\"},\"rules\":[{\"apiGroups\":[\"apps\"],\"resources\":[\"daemonsets\"],\"verbs\":[\"get\",\"delete\"]},{\"apiGroups\":[\"\"],\"resources\":[\"*\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"rbac.authorization.k8s.io\"],\"resources\":[\"*\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apiextensions.k8s.io\"],\"resources\":[\"customresourcedefinitions\"],\"verbs\":[\"get\",\"list\",\"watch\",\"create\",\"delete\"]}]},{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"name\":\"node-termination-handler\"}},{\"kind\":\"ClusterRoleBinding\",\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"metadata\":{\"name\":\"node-termination-handler\",\"namespace\":\"default\"},\"subjects\":[{\"kind\":\"ServiceAccount\",\"name\":\"node-termination-handler\",\"namespace\":\"default\"}],\"roleRef\":{\"kind\":\"ClusterRole\",\"name\":\"node-termination-handler\",\"apiGroup\":\"rbac.authorization.k8s.io\"}},{\"apiVersion\":\"apps/v1beta2\",\"kind\":\"DaemonSet\",\"metadata\":{\"name\":\"node-termination-handler\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"node-termination-handler\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"node-termination-handler\"}},\"spec\":{\"serviceAccountName\":\"node-termination-handler\",\"containers\":[{\"name\":\"node-termination-handler\",\"image\":\"amazon/aws-node-termination-handler:v1.0.0\",\"imagePullPolicy\":\"Always\",\"env\":[{\"name\":\"NODE_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"spec.nodeName\"}}},{\"name\":\"POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}},{\"name\":\"NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"SPOT_POD_IP\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"status.podIP\"}}}],\"resources\":{\"requests\":{\"memory\":\"64Mi\",\"cpu\":\"50m\"},\"limits\":{\"memory\":\"128Mi\",\"cpu\":\"100m\"}}}],\"nodeSelector\":{\"lifecycle\":\"Ec2Spot\"}}}}}]" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA": { + "Type": "AWS::Serverless::Application", + "Properties": { + "Location": { + "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", + "SemanticVersion": "1.13.7" + }, + "Parameters": { + "LayerName": "kubectl-e3c1a5897fab23abec558d991fea218c" + } + } + } + }, + "Outputs": { + "myClusterConfigCommandAC521B60": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks update-kubeconfig --name ", + { + "Ref": "myClusterE51CD07F" + }, + " --region test-region" + ] + ] + } + }, + "myClusterGetTokenCommandF3F07390": { + "Value": { + "Fn::Join": [ + "", + [ + "aws eks get-token --cluster-name ", + { + "Ref": "myClusterE51CD07F" + }, + " --region test-region" + ] + ] + } + } + }, + "Parameters": { + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8": { + "Type": "String", + "Description": "S3 bucket for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD": { + "Type": "String", + "Description": "S3 key for asset version \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204ArtifactHashB80B497F": { + "Type": "String", + "Description": "Artifact hash for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB": { + "Type": "String", + "Description": "S3 bucket for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54": { + "Type": "String", + "Description": "S3 key for asset version \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444ArtifactHash606C8127": { + "Type": "String", + "Description": "Artifact hash for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + }, + "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/eks/optimized-ami/1.14/amazon-linux-2/recommended/image_id" + } + } +} diff --git a/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-spot.ts b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-spot.ts new file mode 100644 index 0000000000000..bf99f9c62a4f7 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/integ.eks-spot.ts @@ -0,0 +1,33 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import { App, Construct } from '@aws-cdk/core'; +import * as eks from '../lib'; +import { TestStack } from './util'; + +class MyStack extends TestStack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const vpc = new ec2.Vpc(this, 'vpc', { maxAzs: 2 }); + + // two on-demand instances + const cluster = new eks.Cluster(this, 'myCluster', { + defaultCapacity: 2, + vpc, + }); + + // up to ten spot instances + cluster.addCapacity('spot', { + spotPrice: '0.1094', + instanceType: new ec2.InstanceType('t3.large'), + maxCapacity: 10, + bootstrapOptions: { + kubeletExtraArgs: '--node-labels foo=bar,goo=far', + awsApiRetryAttempts: 5 + } + }); + } +} + +const app = new App(); +new MyStack(app, 'integ-eks-spot'); +app.synth(); diff --git a/packages/@aws-cdk/aws-eks-legacy/test/test.awsauth.ts b/packages/@aws-cdk/aws-eks-legacy/test/test.awsauth.ts new file mode 100644 index 0000000000000..af65da9e1b91c --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/test.awsauth.ts @@ -0,0 +1,137 @@ +import { countResources, expect, haveResource } from '@aws-cdk/assert'; +import * as iam from '@aws-cdk/aws-iam'; +import { Test } from 'nodeunit'; +import { Cluster, KubernetesResource } from '../lib'; +import { AwsAuth } from '../lib/aws-auth'; +import { testFixtureNoVpc } from './util'; + +// tslint:disable:max-line-length + +export = { + 'empty aws-auth'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + const cluster = new Cluster(stack, 'cluster'); + + // WHEN + new AwsAuth(stack, 'AwsAuth', { cluster }); + + // THEN + expect(stack).to(haveResource(KubernetesResource.RESOURCE_TYPE, { + Manifest: JSON.stringify([{ + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { name: 'aws-auth', namespace: 'kube-system' }, + data: { mapRoles: '[]', mapUsers: '[]', mapAccounts: '[]' } + }]) + })); + test.done(); + }, + + 'addRoleMapping and addUserMapping can be used to define the aws-auth ConfigMap'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + const cluster = new Cluster(stack, 'Cluster'); + const role = new iam.Role(stack, 'role', { assumedBy: new iam.AnyPrincipal() }); + const user = new iam.User(stack, 'user'); + + // WHEN + cluster.awsAuth.addRoleMapping(role, { groups: ['role-group1'], username: 'roleuser' }); + cluster.awsAuth.addRoleMapping(role, { groups: ['role-group2', 'role-group3'] }); + cluster.awsAuth.addUserMapping(user, { groups: ['user-group1', 'user-group2'] }); + cluster.awsAuth.addUserMapping(user, { groups: ['user-group1', 'user-group2'], username: 'foo' }); + cluster.awsAuth.addAccount('112233'); + cluster.awsAuth.addAccount('5566776655'); + + // THEN + expect(stack).to(countResources(KubernetesResource.RESOURCE_TYPE, 1)); + expect(stack).to(haveResource(KubernetesResource.RESOURCE_TYPE, { + Manifest: { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\"},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterDefaultCapacityInstanceRole3E209969", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "roleC7B7E775", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"roleuser\\\",\\\"groups\\\":[\\\"role-group1\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "roleC7B7E775", + "Arn" + ] + }, + "\\\",\\\"groups\\\":[\\\"role-group2\\\",\\\"role-group3\\\"]}]\",\"mapUsers\":\"[{\\\"userarn\\\":\\\"", + { + "Fn::GetAtt": [ + "user2C2B57AE", + "Arn" + ] + }, + "\\\",\\\"groups\\\":[\\\"user-group1\\\",\\\"user-group2\\\"]},{\\\"userarn\\\":\\\"", + { + "Fn::GetAtt": [ + "user2C2B57AE", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"foo\\\",\\\"groups\\\":[\\\"user-group1\\\",\\\"user-group2\\\"]}]\",\"mapAccounts\":\"[\\\"112233\\\",\\\"5566776655\\\"]\"}}]" + ] + ] + } + })); + + test.done(); + }, + + 'imported users and roles can be also be used'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + const cluster = new Cluster(stack, 'Cluster'); + const role = iam.Role.fromRoleArn(stack, 'imported-role', 'arn:aws:iam::123456789012:role/S3Access'); + const user = iam.User.fromUserName(stack, 'import-user', 'MyUserName'); + + // WHEN + cluster.awsAuth.addRoleMapping(role, { groups: ['group1'] }); + cluster.awsAuth.addUserMapping(user, { groups: ['group2'] }); + + // THEN + expect(stack).to(haveResource(KubernetesResource.RESOURCE_TYPE, { + Manifest: { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\"},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterDefaultCapacityInstanceRole3E209969", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]},{\\\"rolearn\\\":\\\"arn:aws:iam::123456789012:role/S3Access\\\",\\\"groups\\\":[\\\"group1\\\"]}]\",\"mapUsers\":\"[{\\\"userarn\\\":\\\"arn:", + { + Ref: "AWS::Partition" + }, + ":iam::", + { + Ref: "AWS::AccountId" + }, + ":user/MyUserName\\\",\\\"groups\\\":[\\\"group2\\\"]}]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + } + })); + + test.done(); + } +}; diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster-resource.ts b/packages/@aws-cdk/aws-eks-legacy/test/test.cluster-resource.ts similarity index 100% rename from packages/@aws-cdk/aws-eks/test/test.cluster-resource.ts rename to packages/@aws-cdk/aws-eks-legacy/test/test.cluster-resource.ts diff --git a/packages/@aws-cdk/aws-eks-legacy/test/test.cluster.ts b/packages/@aws-cdk/aws-eks-legacy/test/test.cluster.ts new file mode 100644 index 0000000000000..63934c1319046 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/test.cluster.ts @@ -0,0 +1,583 @@ +import { expect, haveResource, haveResourceLike, not } from '@aws-cdk/assert'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import * as eks from '../lib'; +import { spotInterruptHandler } from '../lib/spot-interrupt-handler'; +import { testFixture, testFixtureNoVpc } from './util'; + +// tslint:disable:max-line-length + +export = { + 'a default cluster spans all subnets'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); + + // THEN + expect(stack).to(haveResourceLike('AWS::EKS::Cluster', { + ResourcesVpcConfig: { + SubnetIds: [ + { Ref: "VPCPublicSubnet1SubnetB4246D30" }, + { Ref: "VPCPublicSubnet2Subnet74179F39" }, + { Ref: "VPCPrivateSubnet1Subnet8BCA10E0" }, + { Ref: "VPCPrivateSubnet2SubnetCFCDAA7A" }, + ] + } + })); + + test.done(); + }, + + 'if "vpc" is not specified, vpc with default configuration will be created'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + + // WHEN + new eks.Cluster(stack, 'cluster'); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPC')); + test.done(); + }, + + 'default capacity': { + + 'x2 m5.large by default'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + + // WHEN + const cluster = new eks.Cluster(stack, 'cluster'); + + // THEN + test.ok(cluster.defaultCapacity); + expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', { DesiredCapacity: '2' })); + expect(stack).to(haveResource('AWS::AutoScaling::LaunchConfiguration', { InstanceType: 'm5.large' })); + test.done(); + }, + + 'quantity and type can be customized'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + + // WHEN + const cluster = new eks.Cluster(stack, 'cluster', { + defaultCapacity: 10, + defaultCapacityInstance: new ec2.InstanceType('m2.xlarge') + }); + + // THEN + test.ok(cluster.defaultCapacity); + expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', { DesiredCapacity: '10' })); + expect(stack).to(haveResource('AWS::AutoScaling::LaunchConfiguration', { InstanceType: 'm2.xlarge' })); + test.done(); + }, + + 'defaultCapacity=0 will not allocate at all'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + + // WHEN + const cluster = new eks.Cluster(stack, 'cluster', { defaultCapacity: 0 }); + + // THEN + test.ok(!cluster.defaultCapacity); + expect(stack).notTo(haveResource('AWS::AutoScaling::AutoScalingGroup')); + expect(stack).notTo(haveResource('AWS::AutoScaling::LaunchConfiguration')); + test.done(); + } + }, + + 'creating a cluster tags the private VPC subnets'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::Subnet', { + Tags: [ + { Key: "Name", Value: "Stack/VPC/PrivateSubnet1" }, + { Key: "aws-cdk:subnet-name", Value: "Private" }, + { Key: "aws-cdk:subnet-type", Value: "Private" }, + { Key: "kubernetes.io/role/internal-elb", Value: "1" } + ] + })); + + test.done(); + }, + + 'creating a cluster tags the public VPC subnets'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::Subnet', { + MapPublicIpOnLaunch: true, + Tags: [ + { Key: "Name", Value: "Stack/VPC/PublicSubnet1" }, + { Key: "aws-cdk:subnet-name", Value: "Public" }, + { Key: "aws-cdk:subnet-type", Value: "Public" }, + { Key: "kubernetes.io/role/elb", Value: "1" } + ] + })); + + test.done(); + }, + + 'adding capacity creates an ASG with tags'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); + + // WHEN + cluster.addCapacity('Default', { + instanceType: new ec2.InstanceType('t2.medium'), + }); + + // THEN + expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', { + Tags: [ + { + Key: "Name", + PropagateAtLaunch: true, + Value: "Stack/Cluster/Default" + }, + { + Key: { "Fn::Join": [ "", [ "kubernetes.io/cluster/", { Ref: "ClusterEB0386A7" } ] ] }, + PropagateAtLaunch: true, + Value: "owned" + } + ] + })); + + test.done(); + }, + + 'exercise export/import'(test: Test) { + // GIVEN + const { stack: stack1, vpc, app } = testFixture(); + const stack2 = new cdk.Stack(app, 'stack2', { env: { region: 'us-east-1' } }); + const cluster = new eks.Cluster(stack1, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); + + // WHEN + const imported = eks.Cluster.fromClusterAttributes(stack2, 'Imported', { + clusterArn: cluster.clusterArn, + vpc: cluster.vpc, + clusterEndpoint: cluster.clusterEndpoint, + clusterName: cluster.clusterName, + securityGroups: cluster.connections.securityGroups, + clusterCertificateAuthorityData: cluster.clusterCertificateAuthorityData + }); + + // this should cause an export/import + new cdk.CfnOutput(stack2, 'ClusterARN', { value: imported.clusterArn }); + + // THEN + expect(stack2).toMatch({ + Outputs: { + ClusterARN: { + Value: { + "Fn::ImportValue": "Stack:ExportsOutputFnGetAttClusterEB0386A7Arn2F2E3C3F" + } + } + } + }); + test.done(); + }, + + 'disabled features when kubectl is disabled'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); + + test.throws(() => cluster.awsAuth, /Cannot define aws-auth mappings if kubectl is disabled/); + test.throws(() => cluster.addResource('foo', {}), /Cannot define a KubernetesManifest resource on a cluster with kubectl disabled/); + test.throws(() => cluster.addCapacity('boo', { instanceType: new ec2.InstanceType('r5d.24xlarge'), mapRole: true }), + /Cannot map instance IAM role to RBAC if kubectl is disabled for the cluster/); + test.throws(() => new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart' }), /Cannot define a Helm chart on a cluster with kubectl disabled/); + test.done(); + }, + + 'mastersRole can be used to map an IAM role to "system:masters" (required kubectl)'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + const role = new iam.Role(stack, 'role', { assumedBy: new iam.AnyPrincipal() }); + + // WHEN + new eks.Cluster(stack, 'Cluster', { vpc, mastersRole: role, defaultCapacity: 0 }); + + // THEN + expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Manifest: { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\"},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "roleC7B7E775", + "Arn" + ] + }, + "\\\",\\\"groups\\\":[\\\"system:masters\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + } + })); + + test.done(); + }, + + 'addResource can be used to apply k8s manifests on this cluster'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + const cluster = new eks.Cluster(stack, 'Cluster', { vpc, defaultCapacity: 0 }); + + // WHEN + cluster.addResource('manifest1', { foo: 123 }); + cluster.addResource('manifest2', { bar: 123 }, { boor: [ 1, 2, 3 ] }); + + // THEN + expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Manifest: "[{\"foo\":123}]" + })); + + expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Manifest: "[{\"bar\":123},{\"boor\":[1,2,3]}]" + })); + + test.done(); + }, + + 'when kubectl is enabled (default) adding capacity will automatically map its IAM role'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + const cluster = new eks.Cluster(stack, 'Cluster', { vpc, defaultCapacity: 0 }); + + // WHEN + cluster.addCapacity('default', { + instanceType: new ec2.InstanceType('t2.nano'), + }); + + // THEN + expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + Manifest: { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"aws-auth\",\"namespace\":\"kube-system\"},\"data\":{\"mapRoles\":\"[{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterdefaultInstanceRoleF20A29CD", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" + ] + ] + } + })); + + test.done(); + }, + + 'addCapacity will *not* map the IAM role if mapRole is false'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + const cluster = new eks.Cluster(stack, 'Cluster', { vpc, defaultCapacity: 0 }); + + // WHEN + cluster.addCapacity('default', { + instanceType: new ec2.InstanceType('t2.nano'), + mapRole: false + }); + + // THEN + expect(stack).to(not(haveResource(eks.KubernetesResource.RESOURCE_TYPE))); + test.done(); + }, + + 'addCapacity will *not* map the IAM role if kubectl is disabled'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); + + // WHEN + cluster.addCapacity('default', { + instanceType: new ec2.InstanceType('t2.nano') + }); + + // THEN + expect(stack).to(not(haveResource(eks.KubernetesResource.RESOURCE_TYPE))); + test.done(); + }, + + 'outputs': { + 'aws eks update-kubeconfig is the only output synthesized by default'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + + // WHEN + new eks.Cluster(stack, 'Cluster'); + + // THEN + const assembly = app.synth(); + const template = assembly.getStackByName(stack.stackName).template; + test.deepEqual(template.Outputs, { + ClusterConfigCommand43AAE40F: { Value: { 'Fn::Join': [ '', [ 'aws eks update-kubeconfig --name ', { Ref: 'Cluster9EE0221C' }, ' --region us-east-1' ] ] } }, + ClusterGetTokenCommand06AE992E: { Value: { 'Fn::Join': [ '', [ 'aws eks get-token --cluster-name ', { Ref: 'Cluster9EE0221C' }, ' --region us-east-1' ] ] } } + }); + test.done(); + }, + + 'if masters role is defined, it should be included in the config command'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + + // WHEN + const mastersRole = new iam.Role(stack, 'masters', { assumedBy: new iam.AccountRootPrincipal() }); + new eks.Cluster(stack, 'Cluster', { mastersRole }); + + // THEN + const assembly = app.synth(); + const template = assembly.getStackByName(stack.stackName).template; + test.deepEqual(template.Outputs, { + ClusterConfigCommand43AAE40F: { Value: { 'Fn::Join': [ '', [ 'aws eks update-kubeconfig --name ', { Ref: 'Cluster9EE0221C' }, ' --region us-east-1 --role-arn ', { 'Fn::GetAtt': [ 'masters0D04F23D', 'Arn' ] } ] ] } }, + ClusterGetTokenCommand06AE992E: { Value: { 'Fn::Join': [ '', [ 'aws eks get-token --cluster-name ', { Ref: 'Cluster9EE0221C' }, ' --region us-east-1 --role-arn ', { 'Fn::GetAtt': [ 'masters0D04F23D', 'Arn' ] } ] ] } } + }); + test.done(); + }, + + 'if `outputConfigCommand=false` will disabled the output'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + + // WHEN + const mastersRole = new iam.Role(stack, 'masters', { assumedBy: new iam.AccountRootPrincipal() }); + new eks.Cluster(stack, 'Cluster', { + mastersRole, + outputConfigCommand: false, + }); + + // THEN + const assembly = app.synth(); + const template = assembly.getStackByName(stack.stackName).template; + test.ok(!template.Outputs); // no outputs + test.done(); + }, + + '`outputClusterName` can be used to synthesize an output with the cluster name'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + + // WHEN + new eks.Cluster(stack, 'Cluster', { + outputConfigCommand: false, + outputClusterName: true + }); + + // THEN + const assembly = app.synth(); + const template = assembly.getStackByName(stack.stackName).template; + test.deepEqual(template.Outputs, { + ClusterClusterNameEB26049E: { Value: { Ref: 'Cluster9EE0221C' } } + }); + test.done(); + }, + + '`outputMastersRoleArn` can be used to synthesize an output with the arn of the masters role if defined'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + + // WHEN + new eks.Cluster(stack, 'Cluster', { + outputConfigCommand: false, + outputMastersRoleArn: true, + mastersRole: new iam.Role(stack, 'masters', { assumedBy: new iam.AccountRootPrincipal() }) + }); + + // THEN + const assembly = app.synth(); + const template = assembly.getStackByName(stack.stackName).template; + test.deepEqual(template.Outputs, { + ClusterMastersRoleArnB15964B1: { Value: { 'Fn::GetAtt': [ 'masters0D04F23D', 'Arn' ] } } + }); + test.done(); + }, + + 'when adding capacity, instance role ARN will not be outputed only if we do not auto-map aws-auth'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + + // WHEN + new eks.Cluster(stack, 'Cluster', { + outputConfigCommand: false, + kubectlEnabled: false + }); + + // THEN + const assembly = app.synth(); + const template = assembly.getStackByName(stack.stackName).template; + test.deepEqual(template.Outputs, { + ClusterDefaultCapacityInstanceRoleARN7DADF219: { + Value: { 'Fn::GetAtt': [ 'ClusterDefaultCapacityInstanceRole3E209969', 'Arn' ] } + } + }); + test.done(); + }, + }, + + 'boostrap user-data': { + + 'rendered by default for ASGs'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + + // WHEN + cluster.addCapacity('MyCapcity', { instanceType: new ec2.InstanceType('m3.xlargs') }); + + // THEN + const template = app.synth().getStackByName(stack.stackName).template; + const userData = template.Resources.ClusterMyCapcityLaunchConfig58583345.Properties.UserData; + test.deepEqual(userData, { 'Fn::Base64': { 'Fn::Join': [ '', [ '#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ', { Ref: 'Cluster9EE0221C' }, ' --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack Stack --resource ClusterMyCapcityASGD4CD8B97 --region us-east-1' ] ] } }); + test.done(); + }, + + 'not rendered if bootstrap is disabled'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + + // WHEN + cluster.addCapacity('MyCapcity', { + instanceType: new ec2.InstanceType('m3.xlargs'), + bootstrapEnabled: false + }); + + // THEN + const template = app.synth().getStackByName(stack.stackName).template; + const userData = template.Resources.ClusterMyCapcityLaunchConfig58583345.Properties.UserData; + test.deepEqual(userData, { "Fn::Base64": "#!/bin/bash" }); + test.done(); + }, + + // cursory test for options: see test.user-data.ts for full suite + 'bootstrap options'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + + // WHEN + cluster.addCapacity('MyCapcity', { + instanceType: new ec2.InstanceType('m3.xlargs'), + bootstrapOptions: { + kubeletExtraArgs: '--node-labels FOO=42' + } + }); + + // THEN + const template = app.synth().getStackByName(stack.stackName).template; + const userData = template.Resources.ClusterMyCapcityLaunchConfig58583345.Properties.UserData; + test.deepEqual(userData, { 'Fn::Base64': { 'Fn::Join': [ '', [ '#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ', { Ref: 'Cluster9EE0221C' }, ' --kubelet-extra-args "--node-labels lifecycle=OnDemand --node-labels FOO=42" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack Stack --resource ClusterMyCapcityASGD4CD8B97 --region us-east-1' ] ] } }); + test.done(); + }, + + 'spot instances': { + + 'nodes labeled an tainted accordingly'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + + // WHEN + cluster.addCapacity('MyCapcity', { + instanceType: new ec2.InstanceType('m3.xlargs'), + spotPrice: '0.01' + }); + + // THEN + const template = app.synth().getStackByName(stack.stackName).template; + const userData = template.Resources.ClusterMyCapcityLaunchConfig58583345.Properties.UserData; + test.deepEqual(userData, { 'Fn::Base64': { 'Fn::Join': [ '', [ '#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ', { Ref: 'Cluster9EE0221C' }, ' --kubelet-extra-args "--node-labels lifecycle=Ec2Spot --register-with-taints=spotInstance=true:PreferNoSchedule" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack Stack --resource ClusterMyCapcityASGD4CD8B97 --region us-east-1' ] ] } }); + test.done(); + }, + + 'if kubectl is enabled, the interrupt handler is added'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + + // WHEN + cluster.addCapacity('MyCapcity', { + instanceType: new ec2.InstanceType('m3.xlargs'), + spotPrice: '0.01' + }); + + // THEN + expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { Manifest: JSON.stringify(spotInterruptHandler()) })); + test.done(); + }, + + 'if kubectl is disabled, interrupt handler is not added'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, kubectlEnabled: false }); + + // WHEN + cluster.addCapacity('MyCapcity', { + instanceType: new ec2.InstanceType('m3.xlargs'), + spotPrice: '0.01' + }); + + // THEN + expect(stack).notTo(haveResource(eks.KubernetesResource.RESOURCE_TYPE)); + test.done(); + } + + } + + }, + + 'if bootstrap is disabled cannot specify options'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + + // THEN + test.throws(() => cluster.addCapacity('MyCapcity', { + instanceType: new ec2.InstanceType('m3.xlargs'), + bootstrapEnabled: false, + bootstrapOptions: { awsApiRetryAttempts: 10 } + }), /Cannot specify "bootstrapOptions" if "bootstrapEnabled" is false/); + test.done(); + }, + + 'EKS-Optimized AMI with GPU support'(test: Test) { + // GIVEN + const { app, stack } = testFixtureNoVpc(); + + // WHEN + new eks.Cluster(stack, 'cluster', { + defaultCapacity: 2, + defaultCapacityInstance: new ec2.InstanceType('g4dn.xlarge'), + }); + + // THEN + const assembly = app.synth(); + const parameters = assembly.getStackByName(stack.stackName).template.Parameters; + test.ok(Object.entries(parameters).some( + ([k, v]) => k.startsWith('SsmParameterValueawsserviceeksoptimizedami') && (v as any).Default.includes('amazon-linux2-gpu') + ), 'EKS AMI with GPU should be in ssm parameters'); + test.done(); + }, +}; diff --git a/packages/@aws-cdk/aws-eks-legacy/test/test.helm-chart.ts b/packages/@aws-cdk/aws-eks-legacy/test/test.helm-chart.ts new file mode 100644 index 0000000000000..8d36452d2be07 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/test.helm-chart.ts @@ -0,0 +1,55 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import { Test } from 'nodeunit'; +import * as eks from '../lib'; +import { testFixtureCluster } from './util'; + +// tslint:disable:max-line-length + +export = { + 'add Helm chart': { + 'should have default namespace'(test: Test) { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + + // WHEN + new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart' }); + + // THEN + expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Namespace: 'default' })); + test.done(); + }, + 'should have a lowercase default release name'(test: Test) { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + + // WHEN + new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart' }); + + // THEN + expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Release: 'stackmychartff398361' })); + test.done(); + }, + 'should trim the last 63 of the default release name'(test: Test) { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + + // WHEN + new eks.HelmChart(stack, 'MyChartNameWhichISMostProbablyLongerThenSixtyThreeCharacters', { cluster, chart: 'chart' }); + + // THEN + expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Release: 'rtnamewhichismostprobablylongerthensixtythreecharactersb800614d' })); + test.done(); + }, + 'with values'(test: Test) { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + + // WHEN + new eks.HelmChart(stack, 'MyChart', { cluster, chart: 'chart', values: { foo: 123 } }); + + // THEN + expect(stack).to(haveResource(eks.HelmChart.RESOURCE_TYPE, { Values: '{\"foo\":123}' })); + test.done(); + } + } +}; diff --git a/packages/@aws-cdk/aws-eks-legacy/test/test.manifest.ts b/packages/@aws-cdk/aws-eks-legacy/test/test.manifest.ts new file mode 100644 index 0000000000000..ca038ba148cc1 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/test.manifest.ts @@ -0,0 +1,77 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import { Test } from 'nodeunit'; +import { Cluster, KubernetesResource } from '../lib'; +import { testFixtureNoVpc } from './util'; + +// tslint:disable:max-line-length + +export = { + 'basic usage'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + const cluster = new Cluster(stack, 'cluster'); + + const manifest = [ + { + apiVersion: 'v1', + kind: 'Service', + metadata: { + name: 'hello-kubernetes', + }, + spec: { + type: 'LoadBalancer', + ports: [ + { port: 80, targetPort: 8080 } + ], + selector: { + app: 'hello-kubernetes' + } + } + }, + { + apiVersion: 'apps/v1', + kind: 'Deployment', + metadata: { + name: 'hello-kubernetes' + }, + spec: { + replicas: 2, + selector: { + matchLabels: { + app: 'hello-kubernetes' + } + }, + template: { + metadata: { + labels: { + app: 'hello-kubernetes' + } + }, + spec: { + containers: [ + { + name: 'hello-kubernetes', + image: 'paulbouwer/hello-kubernetes:1.5', + ports: [ + { containerPort: 8080 } + ] + } + ] + } + } + } + } + ]; + + // WHEN + new KubernetesResource(stack, 'manifest', { + cluster, + manifest + }); + + expect(stack).to(haveResource(KubernetesResource.RESOURCE_TYPE, { + Manifest: JSON.stringify(manifest) + })); + test.done(); + } +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks-legacy/test/test.user-data.ts b/packages/@aws-cdk/aws-eks-legacy/test/test.user-data.ts new file mode 100644 index 0000000000000..93cfb85dbfcb0 --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/test.user-data.ts @@ -0,0 +1,166 @@ +import * as autoscaling from '@aws-cdk/aws-autoscaling'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import { App, Stack } from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { renderUserData } from '../lib/user-data'; + +// tslint:disable:max-line-length + +export = { + 'default user data'(test: Test) { + // GIVEN + const { asg, stack } = newFixtures(); + + // WHEN + const userData = stack.resolve(renderUserData('my-cluster-name', asg)); + + // THEN + test.deepEqual(userData, [ + 'set -o xtrace', + '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true', + '/opt/aws/bin/cfn-signal --exit-code $? --stack my-stack --resource ASG46ED3070 --region us-west-33' + ]); + + test.done(); + }, + + '--use-max-pods=true'(test: Test) { + // GIVEN + const { asg, stack } = newFixtures(); + + // WHEN + const userData = stack.resolve(renderUserData('my-cluster-name', asg, { + useMaxPods: true + })); + + // THEN + test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true'); + test.done(); + }, + + '--use-max-pods=false'(test: Test) { + // GIVEN + const { asg, stack } = newFixtures(); + + // WHEN + const userData = stack.resolve(renderUserData('my-cluster-name', asg, { + useMaxPods: false + })); + + // THEN + test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods false'); + test.done(); + }, + + '--aws-api-retry-attempts'(test: Test) { + // GIVEN + const { asg, stack } = newFixtures(); + + // WHEN + const userData = stack.resolve(renderUserData('my-cluster-name', asg, { + awsApiRetryAttempts: 123 + })); + + // THEN + test.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true --aws-api-retry-attempts 123'); + test.done(); + }, + + '--docker-config-json'(test: Test) { + // GIVEN + const { asg } = newFixtures(); + + // WHEN + const userData = renderUserData('my-cluster-name', asg, { + dockerConfigJson: '{"docker":123}' + }); + + // THEN + test.deepEqual(userData[1], `/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true --docker-config-json '{"docker":123}'`); + test.done(); + }, + + '--enable-docker-bridge=true'(test: Test) { + // GIVEN + const { asg, stack } = newFixtures(); + + // WHEN + const userData = stack.resolve(renderUserData('my-cluster-name', asg, { + enableDockerBridge: true + })); + + // THEN + test.deepEqual(userData[1], `/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true --enable-docker-bridge`); + test.done(); + }, + + '--enable-docker-bridge=false'(test: Test) { + // GIVEN + const { asg, stack } = newFixtures(); + + // WHEN + const userData = stack.resolve(renderUserData('my-cluster-name', asg, { + enableDockerBridge: false + })); + + // THEN + test.deepEqual(userData[1], `/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true`); + test.done(); + }, + + '--kubelet-extra-args'(test: Test) { + // GIVEN + const { asg, stack } = newFixtures(); + + // WHEN + const userData = stack.resolve(renderUserData('my-cluster-name', asg, { + kubeletExtraArgs: '--extra-args-for --kubelet' + })); + + // THEN + test.deepEqual(userData[1], `/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand --extra-args-for --kubelet" --use-max-pods true`); + test.done(); + }, + + 'arbitrary additional bootstrap arguments can be passed through "additionalArgs"'(test: Test) { + // GIVEN + const { asg, stack } = newFixtures(); + + // WHEN + const userData = stack.resolve(renderUserData('my-cluster-name', asg, { + additionalArgs: '--apiserver-endpoint 1111 --foo-bar' + })); + + // THEN + test.deepEqual(userData[1], `/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true --apiserver-endpoint 1111 --foo-bar`); + test.done(); + }, + + 'if asg has spot instances, the correct label and taint is used'(test: Test) { + // GIVEN + const { asg, stack } = newFixtures(true); + + // WHEN + const userData = stack.resolve(renderUserData('my-cluster-name', asg, { + kubeletExtraArgs: '--node-labels X=y' + })); + + // THEN + test.deepEqual(userData[1], `/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=Ec2Spot --register-with-taints=spotInstance=true:PreferNoSchedule --node-labels X=y" --use-max-pods true`); + test.done(); + } +}; + +function newFixtures(spot = false) { + const app = new App(); + const stack = new Stack(app, 'my-stack', { env: { region: 'us-west-33' }}); + const vpc = new ec2.Vpc(stack, 'vpc'); + const asg = new autoscaling.AutoScalingGroup(stack, 'ASG', { + instanceType: new ec2.InstanceType('m4.xlarge'), + machineImage: new ec2.AmazonLinuxImage(), + spotPrice: spot ? '0.01' : undefined, + vpc + }); + + return { stack, vpc, asg }; +} diff --git a/packages/@aws-cdk/aws-eks-legacy/test/util.ts b/packages/@aws-cdk/aws-eks-legacy/test/util.ts new file mode 100644 index 0000000000000..6406e746d0a3e --- /dev/null +++ b/packages/@aws-cdk/aws-eks-legacy/test/util.ts @@ -0,0 +1,36 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import { App, Construct, Stack } from '@aws-cdk/core'; +import { Cluster } from '../lib'; + +export function testFixture() { + const { stack, app } = testFixtureNoVpc(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + return { stack, vpc, app }; +} + +export function testFixtureNoVpc() { + const app = new App(); + const stack = new Stack(app, 'Stack', { env: { region: 'us-east-1' }}); + return { stack, app }; +} + +export function testFixtureCluster() { + const { stack, app } = testFixtureNoVpc(); + const cluster = new Cluster(stack, 'Cluster'); + + return { stack, app, cluster }; +} + +// we must specify an explicit environment because we have an AMI map that is +// keyed from the target region. +const env = { + region: process.env.CDK_INTEG_REGION || process.env.CDK_DEFAULT_REGION, + account: process.env.CDK_INTEG_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT +}; + +export class TestStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id, { env }); + } +} diff --git a/packages/@aws-cdk/aws-eks/.gitignore b/packages/@aws-cdk/aws-eks/.gitignore index 274a65cde238c..b6948ea7869c0 100644 --- a/packages/@aws-cdk/aws-eks/.gitignore +++ b/packages/@aws-cdk/aws-eks/.gitignore @@ -14,3 +14,5 @@ coverage nyc.config.js .LAST_PACKAGE *.snk + +.nycrc \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/handler.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/handler.ts new file mode 100644 index 0000000000000..5db46ab5bd344 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/handler.ts @@ -0,0 +1,267 @@ +// tslint:disable:no-console + +import { IsCompleteResponse, OnEventResponse } from '@aws-cdk/custom-resources/lib/provider-framework/types'; +// eslint-disable-next-line import/no-extraneous-dependencies +import * as aws from 'aws-sdk'; + +export class ClusterResourceHandler { + + public get clusterName() { + if (!this.physicalResourceId) { + throw new Error(`Cannot determie cluster name without physical resource ID`); + } + + return this.physicalResourceId; + } + + private readonly requestType: string; + private readonly requestId: string; + private readonly logicalResourceId: string; + private readonly physicalResourceId?: string; + private readonly newProps: aws.EKS.CreateClusterRequest; + private readonly oldProps: Partial; + + constructor(private readonly eks: EksClient, event: any) { + this.requestId = event.RequestId; + this.logicalResourceId = event.LogicalResourceId; + this.newProps = parseProps(event.ResourceProperties); + this.oldProps = event.RequestType === 'Update' ? parseProps(event.OldResourceProperties) : { }; + this.physicalResourceId = event.PhysicalResourceId; + + const roleToAssume = event.ResourceProperties.AssumeRoleArn; + if (!roleToAssume) { + throw new Error(`AssumeRoleArn must be provided`); + } + + eks.configureAssumeRole({ + RoleArn: roleToAssume, + RoleSessionName: `AWSCDK.EKSCluster.${event.RequestType}.${this.requestId}` + }); + + this.requestType = event.RequestType; + } + + public onEvent() { + switch (this.requestType) { + case 'Create': return this.onCreate(); + case 'Update': return this.onUpdate(); + case 'Delete': return this.onDelete(); + } + + throw new Error(`Invalid request type ${this.requestType}`); + } + + public isComplete() { + switch (this.requestType) { + case 'Create': return this.isCreateComplete(); + case 'Update': return this.isUpdateComplete(); + case 'Delete': return this.isDeleteComplete(); + } + + throw new Error(`Invalid request type ${this.requestType}`); + } + + // ------ + // CREATE + // ------ + + private async onCreate(): Promise { + console.log('onCreate: creating cluster with options:', JSON.stringify(this.newProps, undefined, 2)); + if (!this.newProps.roleArn) { + throw new Error('"roleArn" is required'); + } + + const clusterName = this.newProps.name || `${this.logicalResourceId}-${this.requestId}`; + + const resp = await this.eks.createCluster({ + ...this.newProps, + name: clusterName, + }); + + if (!resp.cluster) { + throw new Error(`Error when trying to create cluster ${clusterName}: CreateCluster returned without cluster information`); + } + + return { + PhysicalResourceId: resp.cluster.name + }; + } + + private async isCreateComplete() { + return this.isActive(); + } + + // ------ + // DELETE + // ------ + + private async onDelete(): Promise { + console.log(`onDelete: deleting cluster ${this.clusterName}`); + try { + await this.eks.deleteCluster({ name: this.clusterName }); + } catch (e) { + if (e.code !== 'ResourceNotFoundException') { + throw e; + } else { + console.log(`cluster ${this.clusterName} not found, idempotently succeeded`); + } + } + return { + PhysicalResourceId: this.clusterName + }; + } + + private async isDeleteComplete(): Promise { + console.log(`isDeleteComplete: waiting for cluster ${this.clusterName} to be deleted`); + + try { + const resp = await this.eks.describeCluster({ name: this.clusterName }); + console.log('describeCluster returned:', JSON.stringify(resp, undefined, 2)); + } catch (e) { + if (e.code === 'ResourceNotFoundException') { + console.log('received ResourceNotFoundException, this means the cluster has been deleted (or never existed)'); + return { IsComplete: true }; + } + + console.log('describeCluster error:', e); + throw e; + } + + return { + IsComplete: false + }; + } + + // ------ + // UPDATE + // ------ + + private async onUpdate() { + const updates = analyzeUpdate(this.oldProps, this.newProps); + console.log(`onUpdate:`, JSON.stringify({ updates }, undefined, 2)); + + // if there is an update that requires replacement, go ahead and just create + // a new cluster with the new config. The old cluster will automatically be + // deleted by cloudformation upon success. + if (updates.replaceName || updates.replaceRole || updates.replaceVpc) { + + // if we are replacing this cluster and the cluster has an explicit + // physical name, the creation of the new cluster will fail with "there is + // already a cluster with that name". this is a common behavior for + // CloudFormation resources that support specifying a physical name. + if (this.oldProps.name === this.newProps.name && this.oldProps.name) { + throw new Error(`Cannot replace cluster "${this.oldProps.name}" since it has an explicit physical name. Either rename the cluster or remove the "name" configuration`); + } + + return await this.onCreate(); + } + + // if a version update is required, issue the version update + if (updates.updateVersion) { + if (!this.newProps.version) { + throw new Error(`Cannot remove cluster version configuration. Current version is ${this.oldProps.version}`); + } + + await this.updateClusterVersion(this.newProps.version); + } + + if (updates.updateLogging || updates.updateAccess) { + return await this.eks.updateClusterConfig({ + name: this.clusterName, + logging: this.newProps.logging, + resourcesVpcConfig: this.newProps.resourcesVpcConfig + }); + } + + // no updates + return; + } + + private async isUpdateComplete() { + console.log(`isUpdateComplete`); + return this.isActive(); + } + + private async updateClusterVersion(newVersion: string) { + console.log(`updating cluster version to ${newVersion}`); + + // update-cluster-version will fail if we try to update to the same version, + // so skip in this case. + const cluster = (await this.eks.describeCluster({ name: this.clusterName })).cluster; + if (cluster?.version === newVersion) { + console.log(`cluster already at version ${cluster.version}, skipping version update`); + return; + } + + await this.eks.updateClusterVersion({ name: this.clusterName, version: newVersion }); + } + + private async isActive(): Promise { + console.log('waiting for cluster to become ACTIVE'); + const resp = await this.eks.describeCluster({ name: this.clusterName }); + console.log('describeCluster result:', JSON.stringify(resp, undefined, 2)); + const cluster = resp.cluster; + + // if cluster is undefined (shouldnt happen) or status is not ACTIVE, we are + // not complete. note that the custom resource provider framework forbids + // returning attributes (Data) if isComplete is false. + if (cluster?.status !== 'ACTIVE') { + return { + IsComplete: false + }; + } else { + return { + IsComplete: true, + Data: { + Name: cluster.name, + Endpoint: cluster.endpoint, + Arn: cluster.arn, + CertificateAuthorityData: cluster.certificateAuthority?.data + } + }; + } + } +} + +export interface EksClient { + configureAssumeRole(request: aws.STS.AssumeRoleRequest): void; + createCluster(request: aws.EKS.CreateClusterRequest): Promise; + deleteCluster(request: aws.EKS.DeleteClusterRequest): Promise; + describeCluster(request: aws.EKS.DescribeClusterRequest): Promise; + updateClusterConfig(request: aws.EKS.UpdateClusterConfigRequest): Promise; + updateClusterVersion(request: aws.EKS.UpdateClusterVersionRequest): Promise; +} + +function parseProps(props: any): aws.EKS.CreateClusterRequest { + return props?.Config ?? { }; +} + +interface UpdateMap { + replaceName: boolean; // name + replaceVpc: boolean; // resourcesVpcConfig.subnetIds and securityGroupIds + replaceRole: boolean; // roleArn + updateVersion: boolean; // version + updateLogging: boolean; // logging + updateAccess: boolean; // resourcesVpcConfig.endpointPrivateAccess and endpointPublicAccess +} + +function analyzeUpdate(oldProps: Partial, newProps: aws.EKS.CreateClusterRequest): UpdateMap { + console.log('old props: ', JSON.stringify(oldProps)); + console.log('new props: ', JSON.stringify(newProps)); + + const newVpcProps = newProps.resourcesVpcConfig || { }; + const oldVpcProps = oldProps.resourcesVpcConfig || { }; + + return { + replaceName: newProps.name !== oldProps.name, + replaceVpc: + JSON.stringify(newVpcProps.subnetIds) !== JSON.stringify(oldVpcProps.subnetIds) || + JSON.stringify(newVpcProps.securityGroupIds) !== JSON.stringify(oldVpcProps.securityGroupIds), + updateAccess: + newVpcProps.endpointPrivateAccess !== oldVpcProps.endpointPrivateAccess || + newVpcProps.endpointPublicAccess !== oldVpcProps.endpointPublicAccess, + replaceRole: newProps.roleArn !== oldProps.roleArn, + updateVersion: newProps.version !== oldProps.version, + updateLogging: JSON.stringify(newProps.logging) !== JSON.stringify(oldProps.logging), + }; +} diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts new file mode 100644 index 0000000000000..0749214326636 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts @@ -0,0 +1,44 @@ +// tslint:disable:no-console + +import { IsCompleteResponse } from '@aws-cdk/custom-resources/lib/provider-framework/types'; +// eslint-disable-next-line import/no-extraneous-dependencies +import * as aws from 'aws-sdk'; +import { ClusterResourceHandler, EksClient } from './handler'; + +aws.config.logger = console; + +let eks: aws.EKS | undefined; + +const defaultEksClient: EksClient = { + createCluster: req => getEksClient().createCluster(req).promise(), + deleteCluster: req => getEksClient().deleteCluster(req).promise(), + describeCluster: req => getEksClient().describeCluster(req).promise(), + updateClusterConfig: req => getEksClient().updateClusterConfig(req).promise(), + updateClusterVersion: req => getEksClient().updateClusterVersion(req).promise(), + configureAssumeRole: req => { + console.log(JSON.stringify({ assumeRole: req }, undefined, 2)); + const creds = new aws.ChainableTemporaryCredentials({ + params: req + }); + + eks = new aws.EKS({ credentials: creds }); + } +}; + +function getEksClient() { + if (!eks) { + throw new Error(`EKS client not initialized (call "configureAssumeRole")`); + } + + return eks; +} + +export async function onEvent(event: AWSLambda.CloudFormationCustomResourceEvent) { + const provider = new ClusterResourceHandler(defaultEksClient, event); + return provider.onEvent(); +} + +export async function isComplete(event: AWSLambda.CloudFormationCustomResourceEvent): Promise { + const provider = new ClusterResourceHandler(defaultEksClient, event); + return provider.isComplete(); +} diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts new file mode 100644 index 0000000000000..b789b4a656657 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-provider.ts @@ -0,0 +1,52 @@ +import { NestedStack } from '@aws-cdk/aws-cloudformation'; +import * as iam from '@aws-cdk/aws-iam'; +import * as lambda from '@aws-cdk/aws-lambda'; +import { Construct, Duration, Stack } from '@aws-cdk/core'; +import * as cr from '@aws-cdk/custom-resources'; +import * as path from 'path'; + +const HANDLER_DIR = path.join(__dirname, 'cluster-resource-handler'); +const HANDLER_RUNTIME = lambda.Runtime.NODEJS_12_X; + +export class ClusterResourceProvider extends NestedStack { + public static getOrCreate(scope: Construct) { + const stack = Stack.of(scope); + const uid = '@aws-cdk/aws-eks.ClusterResourceProvider'; + return stack.node.tryFindChild(uid) as ClusterResourceProvider || new ClusterResourceProvider(stack, uid); + } + + public readonly provider: cr.Provider; + public readonly roles: iam.IRole[]; + + private constructor(scope: Construct, id: string) { + super(scope, id); + + const onEvent = new lambda.Function(this, 'OnEventHandler', { + code: lambda.Code.fromAsset(HANDLER_DIR), + description: 'onEvent handler for EKS cluster resource provider', + runtime: HANDLER_RUNTIME, + handler: 'index.onEvent', + timeout: Duration.minutes(1) + }); + + const isComplete = new lambda.Function(this, 'IsCompleteHandler', { + code: lambda.Code.fromAsset(HANDLER_DIR), + description: 'isComplete handler for EKS cluster resource provider', + runtime: HANDLER_RUNTIME, + handler: 'index.isComplete', + timeout: Duration.minutes(1) + }); + + this.provider = new cr.Provider(this, 'Provider', { + onEventHandler: onEvent, + isCompleteHandler: isComplete, + totalTimeout: Duration.hours(1), + queryInterval: Duration.minutes(5) + }); + + this.roles = [ + onEvent.role!, + isComplete.role!, + ]; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts index 2cfb1511703f3..31d8f3de51fe3 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts @@ -1,10 +1,8 @@ import * as cfn from '@aws-cdk/aws-cloudformation'; import * as iam from '@aws-cdk/aws-iam'; -import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, Duration, Token } from '@aws-cdk/core'; -import * as path from 'path'; +import { ArnComponents, Construct, Lazy, Stack, Token } from '@aws-cdk/core'; +import { ClusterResourceProvider } from './cluster-resource-provider'; import { CfnClusterProps } from './eks.generated'; -import { KubectlLayer } from './kubectl-layer'; /** * A low-level CFN resource Amazon EKS cluster implemented through a custom @@ -32,51 +30,90 @@ export class ClusterResource extends Construct { * that gets administrator privilages on the cluster (`system:masters`), and * will be able to issue `kubectl` commands against it. */ - public readonly creationRole: iam.IRole; + private readonly creationRole: iam.Role; + private readonly trustedPrincipals: string[] = []; constructor(scope: Construct, id: string, props: CfnClusterProps) { super(scope, id); - // each cluster resource will have it's own lambda handler since permissions - // are scoped to this cluster and related resources like it's role - const handler = new lambda.Function(this, 'ResourceHandler', { - code: lambda.Code.fromAsset(path.join(__dirname, 'cluster-resource')), - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - timeout: Duration.minutes(15), - memorySize: 512, - layers: [ KubectlLayer.getOrCreate(this) ], - }); + const stack = Stack.of(this); + const provider = ClusterResourceProvider.getOrCreate(this); if (!props.roleArn) { throw new Error(`"roleArn" is required`); } - // since we don't know the cluster name at this point, we must give this role star resource permissions - handler.addToRolePolicy(new iam.PolicyStatement({ - actions: [ 'eks:CreateCluster', 'eks:DescribeCluster', 'eks:DeleteCluster', 'eks:UpdateClusterVersion' ], - resources: [ '*' ] - })); + // the role used to create the cluster. this becomes the administrator role + // of the cluster. + this.creationRole = new iam.Role(this, 'CreationRole', { + assumedBy: new iam.CompositePrincipal(...provider.roles.map(x => new iam.ArnPrincipal(x.roleArn))) + }); // the CreateCluster API will allow the cluster to assume this role, so we // need to allow the lambda execution role to pass it. - handler.addToRolePolicy(new iam.PolicyStatement({ + this.creationRole.addToPolicy(new iam.PolicyStatement({ actions: [ 'iam:PassRole' ], resources: [ props.roleArn ] })); + // if we know the cluster name, restrict the policy to only allow + // interacting with this specific cluster otherwise, we will have to grant + // this role to manage all clusters in the account. this must be lazy since + // `props.name` may contain a lazy value that conditionally resolves to a + // physical name. + const resourceArn = Lazy.stringValue({ + produce: () => stack.resolve(props.name) + ? stack.formatArn(clusterArnComponents(stack.resolve(props.name))) + : '*' + }); + + this.creationRole.addToPolicy(new iam.PolicyStatement({ + actions: [ 'eks:CreateCluster', 'eks:DescribeCluster', 'eks:DeleteCluster', 'eks:UpdateClusterVersion', 'eks:UpdateClusterConfig' ], + resources: [ resourceArn ] + })); + const resource = new cfn.CustomResource(this, 'Resource', { resourceType: ClusterResource.RESOURCE_TYPE, - provider: cfn.CustomResourceProvider.lambda(handler), + provider: provider.provider, properties: { - Config: props + Config: props, + AssumeRoleArn: this.creationRole.roleArn } }); + resource.node.addDependency(this.creationRole); + this.ref = resource.ref; this.attrEndpoint = Token.asString(resource.getAtt('Endpoint')); this.attrArn = Token.asString(resource.getAtt('Arn')); this.attrCertificateAuthorityData = Token.asString(resource.getAtt('CertificateAuthorityData')); - this.creationRole = handler.role!; + } + + /** + * Returns the ARN of the cluster creation role and grants `trustedRole` + * permissions to assume this role. + */ + public getCreationRoleArn(trustedRole: iam.IRole): string { + if (!this.trustedPrincipals.includes(trustedRole.roleArn)) { + if (!this.creationRole.assumeRolePolicy) { + throw new Error(`unexpected: cluster creation role must have trust policy`); + } + + this.creationRole.assumeRolePolicy.addStatements(new iam.PolicyStatement({ + actions: [ 'sts:AssumeRole' ], + principals: [ new iam.ArnPrincipal(trustedRole.roleArn) ] + })); + + this.trustedPrincipals.push(trustedRole.roleArn); + } + return this.creationRole.roleArn; } } + +export function clusterArnComponents(clusterName: string): ArnComponents { + return { + service: 'eks', + resource: 'cluster', + resourceName: clusterName, + }; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 97d11787ad45c..04ed3df18954a 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -1,16 +1,13 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import * as lambda from '@aws-cdk/aws-lambda'; import * as ssm from '@aws-cdk/aws-ssm'; -import { CfnOutput, Construct, Duration, IResource, Resource, Stack, Tag, Token } from '@aws-cdk/core'; -import * as path from 'path'; +import { CfnOutput, Construct, IResource, Resource, Stack, Tag, Token } from '@aws-cdk/core'; import { AwsAuth } from './aws-auth'; -import { ClusterResource } from './cluster-resource'; +import { clusterArnComponents, ClusterResource } from './cluster-resource'; import { CfnCluster, CfnClusterProps } from './eks.generated'; import { HelmChart, HelmChartOptions } from './helm-chart'; import { KubernetesResource } from './k8s-resource'; -import { KubectlLayer } from './kubectl-layer'; import { spotInterruptHandler } from './spot-interrupt-handler'; import { renderUserData } from './user-data'; @@ -291,14 +288,6 @@ export class Cluster extends Resource implements ICluster { */ public readonly kubectlEnabled: boolean; - /** - * The CloudFormation custom resource handler that can apply Kubernetes - * manifests to this cluster. - * - * @internal - */ - public readonly _k8sResourceHandler?: lambda.Function; - /** * The auto scaling group that hosts the default capacity for this cluster. * This will be `undefined` if the default capacity is set to 0. @@ -306,14 +295,13 @@ export class Cluster extends Resource implements ICluster { public readonly defaultCapacity?: autoscaling.AutoScalingGroup; /** - * The IAM role that was used to create this cluster. This role is - * automatically added by Amazon EKS to the `system:masters` RBAC group of the - * cluster. Use `addMastersRole` or `props.mastersRole` to define additional - * IAM roles as administrators. + * If this cluster is kubectl-enabled, returns the `ClusterResource` object + * that manages it. If this cluster is not kubectl-enabled (i.e. uses the + * stock `CfnCluster`), this is `undefined`. * * @internal */ - public readonly _defaultMastersRole?: iam.IRole; + public readonly _clusterResource?: ClusterResource; /** * Manages the aws-auth config map. @@ -341,7 +329,8 @@ export class Cluster extends Resource implements ICluster { this.tagSubnets(); - this.role = props.role || new iam.Role(this, 'ClusterRole', { + // this is the role used by EKS when interacting with AWS resources + this.role = props.role || new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('eks.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSClusterPolicy'), @@ -377,17 +366,13 @@ export class Cluster extends Resource implements ICluster { this.kubectlEnabled = props.kubectlEnabled === undefined ? true : props.kubectlEnabled; if (this.kubectlEnabled) { resource = new ClusterResource(this, 'Resource', clusterProps); - this._defaultMastersRole = resource.creationRole; + this._clusterResource = resource; } else { resource = new CfnCluster(this, 'Resource', clusterProps); } this.clusterName = this.getResourceNameAttribute(resource.ref); - this.clusterArn = this.getResourceArnAttribute(resource.attrArn, { - service: 'eks', - resource: 'cluster', - resourceName: this.physicalName, - }); + this.clusterArn = this.getResourceArnAttribute(resource.attrArn, clusterArnComponents(this.physicalName)); this.clusterEndpoint = resource.attrEndpoint; this.clusterCertificateAuthorityData = resource.attrCertificateAuthorityData; @@ -400,11 +385,6 @@ export class Cluster extends Resource implements ICluster { new CfnOutput(this, 'ClusterName', { value: this.clusterName }); } - // we maintain a single manifest custom resource handler per cluster since - // permissions and role are scoped. This will return `undefined` if kubectl - // is not enabled for this cluster. - this._k8sResourceHandler = this.createKubernetesResourceHandler(); - // map the IAM role to the `system:masters` group. if (props.mastersRole) { if (!this.kubectlEnabled) { @@ -594,29 +574,6 @@ export class Cluster extends Resource implements ICluster { return new HelmChart(this, `chart-${id}`, { cluster: this, ...options }); } - private createKubernetesResourceHandler() { - if (!this.kubectlEnabled) { - return undefined; - } - - return new lambda.Function(this, 'KubernetesResourceHandler', { - code: lambda.Code.fromAsset(path.join(__dirname, 'k8s-resource')), - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - timeout: Duration.minutes(15), - layers: [ KubectlLayer.getOrCreate(this) ], - memorySize: 256, - environment: { - CLUSTER_NAME: this.clusterName, - }, - - // NOTE: we must use the default IAM role that's mapped to "system:masters" - // as the execution role of this custom resource handler. This is the only - // way to be able to interact with the cluster after it's been created. - role: this._defaultMastersRole, - }); - } - /** * Opportunistically tag subnets with the required tags. * @@ -869,4 +826,4 @@ const GPU_INSTANCETYPES = ['p2', 'p3', 'g4']; export function nodeTypeForInstanceType(instanceType: ec2.InstanceType) { return GPU_INSTANCETYPES.includes(instanceType.toString().substring(0, 2)) ? NodeType.GPU : NodeType.STANDARD; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts index 051b13774a3cf..b9e2eaf1db545 100644 --- a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts @@ -1,6 +1,8 @@ -import { CustomResource, CustomResourceProvider } from '@aws-cdk/aws-cloudformation'; +import { CustomResource, NestedStack } from '@aws-cdk/aws-cloudformation'; +import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, Duration, Stack } from '@aws-cdk/core'; +import * as cr from '@aws-cdk/custom-resources'; import * as path from 'path'; import { Cluster } from './cluster'; import { KubectlLayer } from './kubectl-layer'; @@ -72,18 +74,20 @@ export class HelmChart extends Construct { constructor(scope: Construct, id: string, props: HelmChartProps) { super(scope, id); - const stack = Stack.of(this); - - // we maintain a single manifest custom resource handler for each cluster - const handler = this.getOrCreateHelmChartHandler(props.cluster); - if (!handler) { + if (!props.cluster._clusterResource) { throw new Error(`Cannot define a Helm chart on a cluster with kubectl disabled`); } + const stack = Stack.of(this); + + const provider = HelmResourceProvider.getOrCreate(this); + new CustomResource(this, 'Resource', { - provider: CustomResourceProvider.lambda(handler), + provider: provider.provider, resourceType: HelmChart.RESOURCE_TYPE, properties: { + ClusterName: props.cluster.clusterName, + RoleArn: props.cluster._clusterResource.getCreationRoleArn(provider.role), Release: props.release || this.node.uniqueId.slice(-63).toLowerCase(), // Helm has a 63 character limit for the name Chart: props.chart, Version: props.version, @@ -93,31 +97,49 @@ export class HelmChart extends Construct { } }); } +} - private getOrCreateHelmChartHandler(cluster: Cluster): lambda.IFunction | undefined { - if (!cluster.kubectlEnabled) { - return undefined; - } +class HelmResourceProvider extends NestedStack { + /** + * Creates a stack-singleton resource provider nested stack. + */ + public static getOrCreate(scope: Construct) { + const stack = Stack.of(scope); + const uid = '@aws-cdk/aws-eks.HelmResourceProvider'; + return stack.node.tryFindChild(uid) as HelmResourceProvider || new HelmResourceProvider(stack, uid); + } - let handler = cluster.node.tryFindChild('HelmChartHandler') as lambda.IFunction; - if (!handler) { - handler = new lambda.Function(cluster, 'HelmChartHandler', { - code: lambda.Code.fromAsset(path.join(__dirname, 'helm-chart')), - runtime: lambda.Runtime.PYTHON_3_7, - handler: 'index.handler', - timeout: Duration.minutes(15), - layers: [ KubectlLayer.getOrCreate(this, { version: "2.0.0-beta1" }) ], - memorySize: 256, - environment: { - CLUSTER_NAME: cluster.clusterName, - }, - - // NOTE: we must use the default IAM role that's mapped to "system:masters" - // as the execution role of this custom resource handler. This is the only - // way to be able to interact with the cluster after it's been created. - role: cluster._defaultMastersRole, - }); - } - return handler; + /** + * The custom resource provider. + */ + public readonly provider: cr.Provider; + + /** + * The IAM role used to execute this provider. + */ + public readonly role: iam.IRole; + + private constructor(scope: Construct, id: string) { + super(scope, id); + + const handler = new lambda.Function(this, 'Handler', { + code: lambda.Code.fromAsset(path.join(__dirname, 'helm-chart')), + runtime: lambda.Runtime.PYTHON_3_7, + handler: 'index.handler', + timeout: Duration.minutes(15), + layers: [ KubectlLayer.getOrCreate(this, { version: "2.0.0-beta1" }) ], + memorySize: 256, + }); + + this.provider = new cr.Provider(this, 'Provider', { + onEventHandler: handler + }); + + this.role = handler.role!; + + this.role.addToPolicy(new iam.PolicyStatement({ + actions: [ 'eks:DescribeCluster' ], + resources: [ '*' ] + })); } } diff --git a/packages/@aws-cdk/aws-eks/lib/helm-chart/index.py b/packages/@aws-cdk/aws-eks/lib/helm-chart/index.py index 0b311f61e0fcd..604c46a73b2b8 100644 --- a/packages/@aws-cdk/aws-eks/lib/helm-chart/index.py +++ b/packages/@aws-cdk/aws-eks/lib/helm-chart/index.py @@ -1,10 +1,7 @@ -import subprocess -import os import json import logging -import boto3 -from uuid import uuid4 -from botocore.vendored import requests +import os +import subprocess logger = logging.getLogger() logger.setLevel(logging.INFO) @@ -15,71 +12,44 @@ outdir = os.environ.get('TEST_OUTDIR', '/tmp') kubeconfig = os.path.join(outdir, 'kubeconfig') -CFN_SUCCESS = "SUCCESS" -CFN_FAILED = "FAILED" - def handler(event, context): - - def cfn_error(message=None): - logger.error("| cfn_error: %s" % message) - cfn_send(event, context, CFN_FAILED, reason=message) - - try: - logger.info(json.dumps(event)) - - request_type = event['RequestType'] - props = event['ResourceProperties'] - physical_id = event.get('PhysicalResourceId', None) - release = props['Release'] - chart = props['Chart'] - version = props.get('Version', None) - namespace = props.get('Namespace', None) - repository = props.get('Repository', None) - values_text = props.get('Values', None) - - cluster_name = os.environ.get('CLUSTER_NAME', None) - if cluster_name is None: - cfn_error("CLUSTER_NAME is missing in environment") - return - - subprocess.check_call([ 'aws', 'eks', 'update-kubeconfig', - '--name', cluster_name, - '--kubeconfig', kubeconfig - ]) - - # Write out the values to a file and include them with the install and upgrade - values_file = None - if not request_type == "Delete" and not values_text is None: - values = json.loads(values_text) - values_file = os.path.join(outdir, 'values.yaml') - with open(values_file, "w") as f: - f.write(json.dumps(values, indent=2)) - - if request_type == 'Create' or request_type == 'Update': - helm('upgrade', release, chart, repository, values_file, namespace, version) - elif request_type == "Delete": - try: - helm('uninstall', release, namespace=namespace) - except Exception as e: - logger.info("delete error: %s" % e) - - # if we are creating a new resource, allocate a physical id for it - # otherwise, we expect physical id to be relayed by cloudformation - if request_type == 'Create': - physical_id = "%s/%s" % (cluster_name, str(uuid4())) - else: - if not physical_id: - cfn_error("invalid request: request type is '%s' but 'PhysicalResourceId' is not defined" % request_type) - return - - cfn_send(event, context, CFN_SUCCESS, physicalResourceId=physical_id) - return - - except KeyError as e: - cfn_error("invalid request. Missing '%s'" % str(e)) - except Exception as e: - logger.exception(e) - cfn_error(str(e)) + logger.info(json.dumps(event)) + + request_type = event['RequestType'] + props = event['ResourceProperties'] + + # resource properties + cluster_name = props['ClusterName'] + role_arn = props['RoleArn'] + release = props['Release'] + chart = props['Chart'] + version = props.get('Version', None) + namespace = props.get('Namespace', None) + repository = props.get('Repository', None) + values_text = props.get('Values', None) + + # "log in" to the cluster + subprocess.check_call([ 'aws', 'eks', 'update-kubeconfig', + '--role-arn', role_arn, + '--name', cluster_name, + '--kubeconfig', kubeconfig + ]) + + # Write out the values to a file and include them with the install and upgrade + values_file = None + if not request_type == "Delete" and not values_text is None: + values = json.loads(values_text) + values_file = os.path.join(outdir, 'values.yaml') + with open(values_file, "w") as f: + f.write(json.dumps(values, indent=2)) + + if request_type == 'Create' or request_type == 'Update': + helm('upgrade', release, chart, repository, values_file, namespace, version) + elif request_type == "Delete": + try: + helm('uninstall', release, namespace=namespace) + except Exception as e: + logger.info("delete error: %s" % e) def helm(verb, release, chart = None, repo = None, file = None, namespace = None, version = None): import subprocess @@ -102,35 +72,3 @@ def helm(verb, release, chart = None, repo = None, file = None, namespace = None logger.info(output) except subprocess.CalledProcessError as exc: raise Exception(exc.output) - -#--------------------------------------------------------------------------------------------------- -# sends a response to cloudformation -def cfn_send(event, context, responseStatus, responseData={}, physicalResourceId=None, noEcho=False, reason=None): - - responseUrl = event['ResponseURL'] - logger.info(responseUrl) - - responseBody = {} - responseBody['Status'] = responseStatus - responseBody['Reason'] = reason or ('See the details in CloudWatch Log Stream: ' + context.log_stream_name) - responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name - responseBody['StackId'] = event['StackId'] - responseBody['RequestId'] = event['RequestId'] - responseBody['LogicalResourceId'] = event['LogicalResourceId'] - responseBody['NoEcho'] = noEcho - responseBody['Data'] = responseData - - body = json.dumps(responseBody) - logger.info("| response body:\n" + body) - - headers = { - 'content-type' : '', - 'content-length' : str(len(body)) - } - - try: - response = requests.put(responseUrl, data=body, headers=headers) - logger.info("| status code: " + response.reason) - except Exception as e: - logger.error("| unable to send response to CloudFormation") - logger.exception(e) diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-resource-handler/index.py b/packages/@aws-cdk/aws-eks/lib/k8s-resource-handler/index.py new file mode 100644 index 0000000000000..14c67c9beb1ee --- /dev/null +++ b/packages/@aws-cdk/aws-eks/lib/k8s-resource-handler/index.py @@ -0,0 +1,66 @@ +import json +import logging +import os +import subprocess + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +# these are coming from the kubectl layer +os.environ['PATH'] = '/opt/kubectl:/opt/awscli:' + os.environ['PATH'] + +outdir = os.environ.get('TEST_OUTDIR', '/tmp') +kubeconfig = os.path.join(outdir, 'kubeconfig') + +def handler(event, context): + logger.info(json.dumps(event)) + + request_type = event['RequestType'] + props = event['ResourceProperties'] + + # resource properties (all required) + cluster_name = props['ClusterName'] + manifest_text = props['Manifest'] + role_arn = props['RoleArn'] + + # "log in" to the cluster + subprocess.check_call([ 'aws', 'eks', 'update-kubeconfig', + '--role-arn', role_arn, + '--name', cluster_name, + '--kubeconfig', kubeconfig + ]) + + # write resource manifests in sequence: { r1 }{ r2 }{ r3 } (this is how + # a stream of JSON objects can be included in a k8s manifest). + manifest_list = json.loads(manifest_text) + manifest_file = os.path.join(outdir, 'manifest.yaml') + with open(manifest_file, "w") as f: + f.writelines(map(lambda obj: json.dumps(obj), manifest_list)) + + logger.info("manifest written to: %s" % manifest_file) + + if request_type == 'Create' or request_type == 'Update': + kubectl('apply', manifest_file) + elif request_type == "Delete": + try: + kubectl('delete', manifest_file) + except Exception as e: + logger.info("delete error: %s" % e) + + +def kubectl(verb, file): + retry = 3 + while retry > 0: + try: + cmd = ['kubectl', verb, '--kubeconfig', kubeconfig, '-f', file] + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as exc: + output = exc.output + if 'i/o timeout' in output and retry > 0: + logger.info("kubectl timed out, retries left: %s" % retry) + retry = retry - 1 + else: + raise Exception(output) + else: + logger.info(output) + return diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts b/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts index 23fde579d6b75..776b8444cae69 100644 --- a/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts @@ -1,6 +1,11 @@ -import * as cfn from '@aws-cdk/aws-cloudformation'; -import { Construct, Stack } from '@aws-cdk/core'; +import { CustomResource, NestedStack } from '@aws-cdk/aws-cloudformation'; +import * as iam from '@aws-cdk/aws-iam'; +import * as lambda from '@aws-cdk/aws-lambda'; +import { Construct, Duration, Stack } from '@aws-cdk/core'; +import * as cr from '@aws-cdk/custom-resources'; +import * as path from 'path'; import { Cluster } from './cluster'; +import { KubectlLayer } from './kubectl-layer'; export interface KubernetesResourceProps { /** @@ -51,23 +56,69 @@ export class KubernetesResource extends Construct { constructor(scope: Construct, id: string, props: KubernetesResourceProps) { super(scope, id); - const stack = Stack.of(this); - - // we maintain a single manifest custom resource handler for each cluster - const handler = props.cluster._k8sResourceHandler; - if (!handler) { + if (!props.cluster._clusterResource) { throw new Error(`Cannot define a KubernetesManifest resource on a cluster with kubectl disabled`); } - new cfn.CustomResource(this, 'Resource', { - provider: cfn.CustomResourceProvider.lambda(handler), + const stack = Stack.of(this); + const provider = KubernetesResourceProvider.getOrCreate(this); + + new CustomResource(this, 'Resource', { + provider: provider.provider, resourceType: KubernetesResource.RESOURCE_TYPE, properties: { // `toJsonString` enables embedding CDK tokens in the manifest and will // render a CloudFormation-compatible JSON string (similar to // StepFunctions, CloudWatch Dashboards etc). Manifest: stack.toJsonString(props.manifest), + ClusterName: props.cluster.clusterName, + RoleArn: props.cluster._clusterResource.getCreationRoleArn(provider.role) } }); } } + +class KubernetesResourceProvider extends NestedStack { + /** + * Creates a stack-singleton resource provider nested stack. + */ + public static getOrCreate(scope: Construct) { + const stack = Stack.of(scope); + const uid = '@aws-cdk/aws-eks.KubernetesResourceProvider'; + return stack.node.tryFindChild(uid) as KubernetesResourceProvider || new KubernetesResourceProvider(stack, uid); + } + + /** + * The custom resource provider. + */ + public readonly provider: cr.Provider; + + /** + * The IAM role used to execute this provider. + */ + public readonly role: iam.IRole; + + private constructor(scope: Construct, id: string) { + super(scope, id); + + const handler = new lambda.Function(this, 'Handler', { + runtime: lambda.Runtime.PYTHON_2_7, + handler: 'index.handler', + timeout: Duration.minutes(15), + layers: [ KubectlLayer.getOrCreate(this) ], + memorySize: 256, + code: lambda.Code.fromAsset(path.join(__dirname, 'k8s-resource-handler')) + }); + + this.provider = new cr.Provider(this, 'Provider', { + onEventHandler: handler + }); + + this.role = handler.role!; + + this.role.addToPolicy(new iam.PolicyStatement({ + actions: [ 'eks:DescribeCluster' ], + resources: [ '*' ] + })); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 92dd56719ae82..4c035d28656b3 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -64,6 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "1.19.0", "@types/nodeunit": "^0.0.30", + "aws-sdk": "^2.595.0", "cdk-build-tools": "1.19.0", "cdk-integ-tools": "1.19.0", "cfn2ts": "1.19.0", @@ -77,7 +78,8 @@ "@aws-cdk/aws-iam": "1.19.0", "@aws-cdk/aws-lambda": "1.19.0", "@aws-cdk/aws-ssm": "1.19.0", - "@aws-cdk/core": "1.19.0" + "@aws-cdk/core": "1.19.0", + "@aws-cdk/custom-resources": "1.19.0" }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { @@ -87,7 +89,8 @@ "@aws-cdk/aws-iam": "1.19.0", "@aws-cdk/aws-lambda": "1.19.0", "@aws-cdk/aws-ssm": "1.19.0", - "@aws-cdk/core": "1.19.0" + "@aws-cdk/core": "1.19.0", + "@aws-cdk/custom-resources": "1.19.0" }, "engines": { "node": ">= 10.3.0" diff --git a/packages/@aws-cdk/aws-eks/test/cluster-resource-handler-mocks.ts b/packages/@aws-cdk/aws-eks/test/cluster-resource-handler-mocks.ts new file mode 100644 index 0000000000000..00eeb244dde49 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/cluster-resource-handler-mocks.ts @@ -0,0 +1,132 @@ +import * as sdk from 'aws-sdk'; +import { EksClient } from '../lib/cluster-resource-handler/handler'; + +/** + * Request objects will be assigned when a request of the relevant type will be + * made. + */ +export let actualRequest: { + configureAssumeRoleRequest?: sdk.STS.AssumeRoleRequest; + createClusterRequest?: sdk.EKS.CreateClusterRequest; + describeClusterRequest?: sdk.EKS.DescribeClusterRequest; + deleteClusterRequest?: sdk.EKS.DeleteClusterRequest; + updateClusterConfigRequest?: sdk.EKS.UpdateClusterConfigRequest; + updateClusterVersionRequest?: sdk.EKS.UpdateClusterVersionRequest; +} = { }; + +/** + * Responses can be simulated by assigning values here. + */ +export let simulateResponse: { + describeClusterResponseMockStatus?: string; + deleteClusterErrorCode?: string; + describeClusterExceptionCode?: string; +} = { }; + +export function reset() { + actualRequest = { }; + simulateResponse = { }; +} + +export const client: EksClient = { + + configureAssumeRole: req => { + actualRequest.configureAssumeRoleRequest = req; + }, + + createCluster: async req => { + actualRequest.createClusterRequest = req; + return { + cluster: { + name: req.name, + roleArn: req.roleArn, + version: '1.0', + arn: `arn:${req.name}`, + certificateAuthority: { data: 'certificateAuthority-data' }, + status: 'CREATING' + } + }; + }, + + deleteCluster: async req => { + actualRequest.deleteClusterRequest = req; + if (simulateResponse.deleteClusterErrorCode) { + const e = new Error('mock error'); + (e as any).code = simulateResponse.deleteClusterErrorCode; + throw e; + } + return { + cluster: { + name: req.name + } + }; + }, + + describeCluster: async req => { + actualRequest.describeClusterRequest = req; + + if (simulateResponse.describeClusterExceptionCode) { + const e = new Error('mock exception'); + (e as any).code = simulateResponse.describeClusterExceptionCode; + throw e; + } + + return { + cluster: { + name: req.name, + version: '1.0', + roleArn: 'arn:role', + arn: `arn:cluster-arn`, + certificateAuthority: { data: 'certificateAuthority-data' }, + endpoint: 'http://endpoint', + status: simulateResponse.describeClusterResponseMockStatus || 'ACTIVE' + } + }; + }, + + updateClusterConfig: async req => { + actualRequest.updateClusterConfigRequest = req; + return { }; + }, + + updateClusterVersion: async req => { + actualRequest.updateClusterVersionRequest = req; + return { }; + } + +}; + +export const MOCK_PROPS = { + roleArn: 'arn:of:role', + resourcesVpcConfig: { + subnetIds: [ 'subnet1', 'subnet2' ], + securityGroupIds: [ 'sg1', 'sg2', 'sg3' ] + } +}; + +export const MOCK_ASSUME_ROLE_ARN = 'assume:role:arn'; + +export function newRequest( + requestType: T, + props?: Partial, + oldProps?: Partial) { + return { + StackId: 'fake-stack-id', + RequestId: 'fake-request-id', + ResourceType: 'Custom::EKSCluster', + ServiceToken: 'boom', + LogicalResourceId: 'MyResourceId', + PhysicalResourceId: 'physical-resource-id', + ResponseURL: 'http://response-url', + RequestType: requestType, + OldResourceProperties: { + Config: oldProps, + AssumeRoleArn: MOCK_ASSUME_ROLE_ARN + }, + ResourceProperties: { + ServiceToken: 'boom', + Config: props, + AssumeRoleArn: MOCK_ASSUME_ROLE_ARN + } + }; +} diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.defaults.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.defaults.expected.json index 7ff2bc1aa7024..884d24d0bafe4 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.defaults.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.defaults.expected.json @@ -1,5 +1,4 @@ { - "Transform": "AWS::Serverless-2016-10-31", "Resources": { "ClusterDefaultVpcFA9F2722": { "Type": "AWS::EC2::VPC", @@ -11,7 +10,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc" } ] } @@ -28,7 +27,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet1" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet1" }, { "Key": "aws-cdk:subnet-name", @@ -54,7 +53,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet1" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet1" }, { "Key": "kubernetes.io/role/elb", @@ -96,7 +95,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet1" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet1" }, { "Key": "kubernetes.io/role/elb", @@ -120,7 +119,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet1" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet1" }, { "Key": "kubernetes.io/role/elb", @@ -141,7 +140,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet2" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet2" }, { "Key": "aws-cdk:subnet-name", @@ -167,7 +166,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet2" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet2" }, { "Key": "kubernetes.io/role/elb", @@ -209,7 +208,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet2" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet2" }, { "Key": "kubernetes.io/role/elb", @@ -233,7 +232,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet2" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet2" }, { "Key": "kubernetes.io/role/elb", @@ -254,7 +253,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet3" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet3" }, { "Key": "aws-cdk:subnet-name", @@ -280,7 +279,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet3" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet3" }, { "Key": "kubernetes.io/role/elb", @@ -322,7 +321,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet3" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet3" }, { "Key": "kubernetes.io/role/elb", @@ -346,7 +345,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PublicSubnet3" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PublicSubnet3" }, { "Key": "kubernetes.io/role/elb", @@ -367,7 +366,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet1" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PrivateSubnet1" }, { "Key": "aws-cdk:subnet-name", @@ -393,7 +392,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet1" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PrivateSubnet1" }, { "Key": "kubernetes.io/role/internal-elb", @@ -437,7 +436,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet2" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PrivateSubnet2" }, { "Key": "aws-cdk:subnet-name", @@ -463,7 +462,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet2" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PrivateSubnet2" }, { "Key": "kubernetes.io/role/internal-elb", @@ -507,7 +506,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet3" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PrivateSubnet3" }, { "Key": "aws-cdk:subnet-name", @@ -533,7 +532,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc/PrivateSubnet3" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc/PrivateSubnet3" }, { "Key": "kubernetes.io/role/internal-elb", @@ -571,7 +570,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultVpc" + "Value": "eks-integ-defaults-2/Cluster/DefaultVpc" } ] } @@ -587,7 +586,7 @@ } } }, - "ClusterClusterRoleCE5C05DD": { + "ClusterRoleFA261979": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -646,11 +645,11 @@ } } }, - "ClusterControlPlaneSecurityGroupfromeksintegdefaultsClusterDefaultCapacityInstanceSecurityGroup913A261E44376C54A34": { + "ClusterControlPlaneSecurityGroupfromeksintegdefaults2ClusterDefaultCapacityInstanceSecurityGroupF57DD9BE443C12103E7": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { "IpProtocol": "tcp", - "Description": "from eksintegdefaultsClusterDefaultCapacityInstanceSecurityGroup913A261E:443", + "Description": "from eksintegdefaults2ClusterDefaultCapacityInstanceSecurityGroupF57DD9BE:443", "FromPort": 443, "GroupId": { "Fn::GetAtt": [ @@ -667,7 +666,7 @@ "ToPort": 443 } }, - "ClusterResourceHandlerServiceRole7FB16465": { + "ClusterCreationRole360249B6": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -676,142 +675,89 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "lambda.amazonaws.com" + "AWS": [ + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.eksintegdefaults2awscdkawseksClusterResourceProviderOnEventHandlerServiceRoleFABA4092Arn" + ] + }, + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.eksintegdefaults2awscdkawseksClusterResourceProviderIsCompleteHandlerServiceRole3A491B05Arn" + ] + } + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.eksintegdefaults2awscdkawseksKubernetesResourceProviderHandlerServiceRole4872080FArn" + ] + } } } ], "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] + } } }, - "ClusterResourceHandlerServiceRoleDefaultPolicy333D0E3A": { + "ClusterCreationRoleDefaultPolicyE8BDFC7B": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { "Statement": [ - { - "Action": [ - "eks:CreateCluster", - "eks:DescribeCluster", - "eks:DeleteCluster", - "eks:UpdateClusterVersion" - ], - "Effect": "Allow", - "Resource": "*" - }, { "Action": "iam:PassRole", "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "ClusterClusterRoleCE5C05DD", + "ClusterRoleFA261979", "Arn" ] } + }, + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion", + "eks:UpdateClusterConfig" + ], + "Effect": "Allow", + "Resource": "*" } ], "Version": "2012-10-17" }, - "PolicyName": "ClusterResourceHandlerServiceRoleDefaultPolicy333D0E3A", + "PolicyName": "ClusterCreationRoleDefaultPolicyE8BDFC7B", "Roles": [ { - "Ref": "ClusterResourceHandlerServiceRole7FB16465" + "Ref": "ClusterCreationRole360249B6" } ] } }, - "ClusterResourceHandler28BF924D": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "ClusterResourceHandlerServiceRole7FB16465", - "Arn" - ] - }, - "Runtime": "python3.7", - "Layers": [ - { - "Fn::GetAtt": [ - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", - "Outputs.LayerVersionArn" - ] - } - ], - "MemorySize": 512, - "Timeout": 900 - }, - "DependsOn": [ - "ClusterResourceHandlerServiceRoleDefaultPolicy333D0E3A", - "ClusterResourceHandlerServiceRole7FB16465" - ] - }, "Cluster9EE0221C": { "Type": "Custom::AWSCDK-EKS-Cluster", "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "ClusterResourceHandler28BF924D", - "Arn" + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.eksintegdefaults2awscdkawseksClusterResourceProviderframeworkonEvent08F2BEB1Arn" ] }, "Config": { "roleArn": { "Fn::GetAtt": [ - "ClusterClusterRoleCE5C05DD", + "ClusterRoleFA261979", "Arn" ] }, @@ -845,87 +791,25 @@ } ] } - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "ClusterKubernetesResourceHandler81C19BC8": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" - } - ] - } - ] - } - ] - ] - } }, - "Handler": "index.handler", - "Role": { + "AssumeRoleArn": { "Fn::GetAtt": [ - "ClusterResourceHandlerServiceRole7FB16465", + "ClusterCreationRole360249B6", "Arn" ] - }, - "Runtime": "python3.7", - "Environment": { - "Variables": { - "CLUSTER_NAME": { - "Ref": "Cluster9EE0221C" - } - } - }, - "Layers": [ - { - "Fn::GetAtt": [ - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", - "Outputs.LayerVersionArn" - ] - } - ], - "MemorySize": 256, - "Timeout": 900 + } }, "DependsOn": [ - "ClusterResourceHandlerServiceRoleDefaultPolicy333D0E3A", - "ClusterResourceHandlerServiceRole7FB16465" - ] + "ClusterCreationRoleDefaultPolicyE8BDFC7B", + "ClusterCreationRole360249B6" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "ClusterDefaultCapacityInstanceSecurityGroup8FDF4D48": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "eks-integ-defaults/Cluster/DefaultCapacity/InstanceSecurityGroup", + "GroupDescription": "eks-integ-defaults-2/Cluster/DefaultCapacity/InstanceSecurityGroup", "SecurityGroupEgress": [ { "CidrIp": "0.0.0.0/0", @@ -936,7 +820,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultCapacity" + "Value": "eks-integ-defaults-2/Cluster/DefaultCapacity" }, { "Key": { @@ -958,11 +842,11 @@ } } }, - "ClusterDefaultCapacityInstanceSecurityGroupfromeksintegdefaultsClusterDefaultCapacityInstanceSecurityGroup913A261EALLTRAFFICA8163873": { + "ClusterDefaultCapacityInstanceSecurityGroupfromeksintegdefaults2ClusterDefaultCapacityInstanceSecurityGroupF57DD9BEALLTRAFFIC38BFC934": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { "IpProtocol": "-1", - "Description": "from eksintegdefaultsClusterDefaultCapacityInstanceSecurityGroup913A261E:ALL TRAFFIC", + "Description": "from eksintegdefaults2ClusterDefaultCapacityInstanceSecurityGroupF57DD9BE:ALL TRAFFIC", "GroupId": { "Fn::GetAtt": [ "ClusterDefaultCapacityInstanceSecurityGroup8FDF4D48", @@ -977,11 +861,11 @@ } } }, - "ClusterDefaultCapacityInstanceSecurityGroupfromeksintegdefaultsClusterControlPlaneSecurityGroup0FA4E3AB4436B585189": { + "ClusterDefaultCapacityInstanceSecurityGroupfromeksintegdefaults2ClusterControlPlaneSecurityGroup11B762614438EAFCC4C": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { "IpProtocol": "tcp", - "Description": "from eksintegdefaultsClusterControlPlaneSecurityGroup0FA4E3AB:443", + "Description": "from eksintegdefaults2ClusterControlPlaneSecurityGroup11B76261:443", "FromPort": 443, "GroupId": { "Fn::GetAtt": [ @@ -998,11 +882,11 @@ "ToPort": 443 } }, - "ClusterDefaultCapacityInstanceSecurityGroupfromeksintegdefaultsClusterControlPlaneSecurityGroup0FA4E3AB102565535C02D6CB8": { + "ClusterDefaultCapacityInstanceSecurityGroupfromeksintegdefaults2ClusterControlPlaneSecurityGroup11B76261102565535AFFD2324": { "Type": "AWS::EC2::SecurityGroupIngress", "Properties": { "IpProtocol": "tcp", - "Description": "from eksintegdefaultsClusterControlPlaneSecurityGroup0FA4E3AB:1025-65535", + "Description": "from eksintegdefaults2ClusterControlPlaneSecurityGroup11B76261:1025-65535", "FromPort": 1025, "GroupId": { "Fn::GetAtt": [ @@ -1085,7 +969,7 @@ "Tags": [ { "Key": "Name", - "Value": "eks-integ-defaults/Cluster/DefaultCapacity" + "Value": "eks-integ-defaults-2/Cluster/DefaultCapacity" }, { "Key": { @@ -1141,7 +1025,7 @@ { "Ref": "Cluster9EE0221C" }, - " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack eks-integ-defaults --resource ClusterDefaultCapacityASG00CC9431 --region test-region" + " --kubelet-extra-args \"--node-labels lifecycle=OnDemand\" --use-max-pods true\n/opt/aws/bin/cfn-signal --exit-code $? --stack eks-integ-defaults-2 --resource ClusterDefaultCapacityASG00CC9431 --region test-region" ] ] } @@ -1164,7 +1048,7 @@ { "Key": "Name", "PropagateAtLaunch": true, - "Value": "eks-integ-defaults/Cluster/DefaultCapacity" + "Value": "eks-integ-defaults-2/Cluster/DefaultCapacity" }, { "Key": { @@ -1216,8 +1100,8 @@ "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "ClusterKubernetesResourceHandler81C19BC8", - "Arn" + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.eksintegdefaults2awscdkawseksKubernetesResourceProviderframeworkonEvent6CA18B28Arn" ] }, "Manifest": { @@ -1234,20 +1118,139 @@ "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" ] ] + }, + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA": { - "Type": "AWS::Serverless::Application", + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454": { + "Type": "AWS::CloudFormation::Stack", "Properties": { - "Location": { - "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", - "SemanticVersion": "1.13.7" + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters209b080b138756cffeb08c815ba4ccfc21ca95800e8cd2bd26c0e5ea0a4fbb55S3Bucket67889A58" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters209b080b138756cffeb08c815ba4ccfc21ca95800e8cd2bd26c0e5ea0a4fbb55S3VersionKey4A356825" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters209b080b138756cffeb08c815ba4ccfc21ca95800e8cd2bd26c0e5ea0a4fbb55S3VersionKey4A356825" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetoeksintegdefaults2AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3Bucket61EFA364Ref": { + "Ref": "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3BucketE861A468" + }, + "referencetoeksintegdefaults2AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKey99475258Ref": { + "Ref": "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyB62DD0C4" + }, + "referencetoeksintegdefaults2AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3Bucket45B8FC93Ref": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE" + }, + "referencetoeksintegdefaults2AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKey17E8CEA1Ref": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8" + } + } + } + }, + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters9c326f06202274fde286cdfb04b4d8be6f58ef01451f732630a5fb719e047b17S3BucketC7A27D79" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters9c326f06202274fde286cdfb04b4d8be6f58ef01451f732630a5fb719e047b17S3VersionKey96773BD1" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters9c326f06202274fde286cdfb04b4d8be6f58ef01451f732630a5fb719e047b17S3VersionKey96773BD1" + } + ] + } + ] + } + ] + ] }, "Parameters": { - "LayerName": "kubectl-459230f5f24751b9afdd68c6a69be4c7" + "referencetoeksintegdefaults2AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketBDF1BF98Ref": { + "Ref": "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketB6E15294" + }, + "referencetoeksintegdefaults2AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey8EF2741FRef": { + "Ref": "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey5071432F" + }, + "referencetoeksintegdefaults2AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3Bucket45B8FC93Ref": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE" + }, + "referencetoeksintegdefaults2AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKey17E8CEA1Ref": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8" + } } } } @@ -1280,32 +1283,97 @@ ] ] } + }, + "ClusterEndpoint": { + "Value": { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "Endpoint" + ] + } + }, + "ClusterArn": { + "Value": { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "Arn" + ] + } + }, + "ClusterCertificateAuthorityData": { + "Value": { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "CertificateAuthorityData" + ] + } + }, + "ClusterName": { + "Value": { + "Ref": "Cluster9EE0221C" + } } }, "Parameters": { - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8": { + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3BucketE861A468": { + "Type": "String", + "Description": "S3 bucket for asset \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyB62DD0C4": { + "Type": "String", + "Description": "S3 key for asset version \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cArtifactHash1871DA56": { + "Type": "String", + "Description": "Artifact hash for asset \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE": { + "Type": "String", + "Description": "S3 bucket for asset \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8": { + "Type": "String", + "Description": "S3 key for asset version \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0ArtifactHashDD0FF81A": { + "Type": "String", + "Description": "Artifact hash for asset \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketB6E15294": { + "Type": "String", + "Description": "S3 bucket for asset \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey5071432F": { + "Type": "String", + "Description": "S3 key for asset version \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2ArtifactHash1FC307C8": { + "Type": "String", + "Description": "Artifact hash for asset \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters9c326f06202274fde286cdfb04b4d8be6f58ef01451f732630a5fb719e047b17S3BucketC7A27D79": { "Type": "String", - "Description": "S3 bucket for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "S3 bucket for asset \"9c326f06202274fde286cdfb04b4d8be6f58ef01451f732630a5fb719e047b17\"" }, - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD": { + "AssetParameters9c326f06202274fde286cdfb04b4d8be6f58ef01451f732630a5fb719e047b17S3VersionKey96773BD1": { "Type": "String", - "Description": "S3 key for asset version \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "S3 key for asset version \"9c326f06202274fde286cdfb04b4d8be6f58ef01451f732630a5fb719e047b17\"" }, - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204ArtifactHashB80B497F": { + "AssetParameters9c326f06202274fde286cdfb04b4d8be6f58ef01451f732630a5fb719e047b17ArtifactHashD5617B98": { "Type": "String", - "Description": "Artifact hash for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "Artifact hash for asset \"9c326f06202274fde286cdfb04b4d8be6f58ef01451f732630a5fb719e047b17\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB": { + "AssetParameters209b080b138756cffeb08c815ba4ccfc21ca95800e8cd2bd26c0e5ea0a4fbb55S3Bucket67889A58": { "Type": "String", - "Description": "S3 bucket for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "S3 bucket for asset \"209b080b138756cffeb08c815ba4ccfc21ca95800e8cd2bd26c0e5ea0a4fbb55\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54": { + "AssetParameters209b080b138756cffeb08c815ba4ccfc21ca95800e8cd2bd26c0e5ea0a4fbb55S3VersionKey4A356825": { "Type": "String", - "Description": "S3 key for asset version \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "S3 key for asset version \"209b080b138756cffeb08c815ba4ccfc21ca95800e8cd2bd26c0e5ea0a4fbb55\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444ArtifactHash606C8127": { + "AssetParameters209b080b138756cffeb08c815ba4ccfc21ca95800e8cd2bd26c0e5ea0a4fbb55ArtifactHashB9F5917F": { "Type": "String", - "Description": "Artifact hash for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "Artifact hash for asset \"209b080b138756cffeb08c815ba4ccfc21ca95800e8cd2bd26c0e5ea0a4fbb55\"" }, "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.defaults.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.defaults.ts index 25d6ffcadb834..ac10abda4a41a 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.defaults.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.defaults.ts @@ -1,19 +1,24 @@ -import * as cdk from '@aws-cdk/core'; +import { App, CfnOutput } from '@aws-cdk/core'; import * as eks from '../lib'; import { TestStack } from './util'; class EksClusterStack extends TestStack { - constructor(scope: cdk.App, id: string) { + constructor(scope: App, id: string) { super(scope, id); - new eks.Cluster(this, 'Cluster'); + const cluster = new eks.Cluster(this, 'Cluster'); + + new CfnOutput(this, 'ClusterEndpoint', { value: cluster.clusterEndpoint }); + new CfnOutput(this, 'ClusterArn', { value: cluster.clusterArn }); + new CfnOutput(this, 'ClusterCertificateAuthorityData', { value: cluster.clusterCertificateAuthorityData }); + new CfnOutput(this, 'ClusterName', { value: cluster.clusterName }); } } -const app = new cdk.App(); +const app = new App(); // since the EKS optimized AMI is hard-coded here based on the region, // we need to actually pass in a specific region. -new EksClusterStack(app, 'eks-integ-defaults'); +new EksClusterStack(app, 'eks-integ-defaults-2'); app.synth(); diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json index 4c00d1f93bd8a..794734b570960 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.expected.json @@ -58,7 +58,7 @@ { "Key": "kubernetes.io/role/elb", "Value": "1" - } + } ] } }, @@ -100,7 +100,7 @@ { "Key": "kubernetes.io/role/elb", "Value": "1" - } + } ] } }, @@ -153,7 +153,7 @@ { "Key": "kubernetes.io/role/elb", "Value": "1" - } + } ] } }, @@ -586,7 +586,7 @@ } } }, - "EKSClusterClusterRoleB72F3251": { + "EKSClusterRoleC0AEAC3D": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -701,7 +701,7 @@ }, "RoleArn": { "Fn::GetAtt": [ - "EKSClusterClusterRoleB72F3251", + "EKSClusterRoleC0AEAC3D", "Arn" ] } diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.lit.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.lit.expected.json index ad3e616b39bb6..b064204bf80bd 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.lit.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.lit.expected.json @@ -1,5 +1,4 @@ { - "Transform": "AWS::Serverless-2016-10-31", "Resources": { "VPCB9E5F0B4": { "Type": "AWS::EC2::VPC", @@ -587,7 +586,7 @@ } } }, - "EKSClusterClusterRoleB72F3251": { + "EKSClusterRoleC0AEAC3D": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -667,7 +666,7 @@ "ToPort": 443 } }, - "EKSClusterResourceHandlerServiceRoleFD631254": { + "EKSClusterCreationRoleB865C9E8": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -676,142 +675,89 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "lambda.amazonaws.com" + "AWS": [ + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.eksintegtestbasicawscdkawseksClusterResourceProviderOnEventHandlerServiceRoleA72FE2EBArn" + ] + }, + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.eksintegtestbasicawscdkawseksClusterResourceProviderIsCompleteHandlerServiceRole7B1EF602Arn" + ] + } + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.eksintegtestbasicawscdkawseksKubernetesResourceProviderHandlerServiceRoleB114CC36Arn" + ] + } } } ], "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] + } } }, - "EKSClusterResourceHandlerServiceRoleDefaultPolicy4D087A98": { + "EKSClusterCreationRoleDefaultPolicy27A5F6BE": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { "Statement": [ - { - "Action": [ - "eks:CreateCluster", - "eks:DescribeCluster", - "eks:DeleteCluster", - "eks:UpdateClusterVersion" - ], - "Effect": "Allow", - "Resource": "*" - }, { "Action": "iam:PassRole", "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "EKSClusterClusterRoleB72F3251", + "EKSClusterRoleC0AEAC3D", "Arn" ] } + }, + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion", + "eks:UpdateClusterConfig" + ], + "Effect": "Allow", + "Resource": "*" } ], "Version": "2012-10-17" }, - "PolicyName": "EKSClusterResourceHandlerServiceRoleDefaultPolicy4D087A98", + "PolicyName": "EKSClusterCreationRoleDefaultPolicy27A5F6BE", "Roles": [ { - "Ref": "EKSClusterResourceHandlerServiceRoleFD631254" + "Ref": "EKSClusterCreationRoleB865C9E8" } ] } }, - "EKSClusterResourceHandler31198B21": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "EKSClusterResourceHandlerServiceRoleFD631254", - "Arn" - ] - }, - "Runtime": "python3.7", - "Layers": [ - { - "Fn::GetAtt": [ - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", - "Outputs.LayerVersionArn" - ] - } - ], - "MemorySize": 512, - "Timeout": 900 - }, - "DependsOn": [ - "EKSClusterResourceHandlerServiceRoleDefaultPolicy4D087A98", - "EKSClusterResourceHandlerServiceRoleFD631254" - ] - }, "EKSClusterE11008B6": { "Type": "Custom::AWSCDK-EKS-Cluster", "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "EKSClusterResourceHandler31198B21", - "Arn" + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.eksintegtestbasicawscdkawseksClusterResourceProviderframeworkonEvent5B69E138Arn" ] }, "Config": { "roleArn": { "Fn::GetAtt": [ - "EKSClusterClusterRoleB72F3251", + "EKSClusterRoleC0AEAC3D", "Arn" ] }, @@ -845,82 +791,20 @@ } ] } - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "EKSClusterKubernetesResourceHandler90E6DD64": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" - } - ] - } - ] - } - ] - ] - } }, - "Handler": "index.handler", - "Role": { + "AssumeRoleArn": { "Fn::GetAtt": [ - "EKSClusterResourceHandlerServiceRoleFD631254", + "EKSClusterCreationRoleB865C9E8", "Arn" ] - }, - "Runtime": "python3.7", - "Environment": { - "Variables": { - "CLUSTER_NAME": { - "Ref": "EKSClusterE11008B6" - } - } - }, - "Layers": [ - { - "Fn::GetAtt": [ - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", - "Outputs.LayerVersionArn" - ] - } - ], - "MemorySize": 256, - "Timeout": 900 + } }, "DependsOn": [ - "EKSClusterResourceHandlerServiceRoleDefaultPolicy4D087A98", - "EKSClusterResourceHandlerServiceRoleFD631254" - ] + "EKSClusterCreationRoleDefaultPolicy27A5F6BE", + "EKSClusterCreationRoleB865C9E8" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "EKSClusterNodesInstanceSecurityGroup460A275E": { "Type": "AWS::EC2::SecurityGroup", @@ -1216,8 +1100,8 @@ "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "EKSClusterKubernetesResourceHandler90E6DD64", - "Arn" + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.eksintegtestbasicawscdkawseksKubernetesResourceProviderframeworkonEvent1C1AB494Arn" ] }, "Manifest": { @@ -1234,20 +1118,139 @@ "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" ] ] + }, + "ClusterName": { + "Ref": "EKSClusterE11008B6" + }, + "RoleArn": { + "Fn::GetAtt": [ + "EKSClusterCreationRoleB865C9E8", + "Arn" + ] } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA": { - "Type": "AWS::Serverless::Application", + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters0dbfc75e2973c6dc284120b2ea80dd6df7c704e57ed3d90672ee28131aef83aaS3Bucket8A1FC920" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0dbfc75e2973c6dc284120b2ea80dd6df7c704e57ed3d90672ee28131aef83aaS3VersionKeyA377CBC7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0dbfc75e2973c6dc284120b2ea80dd6df7c704e57ed3d90672ee28131aef83aaS3VersionKeyA377CBC7" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetoeksintegtestbasicAssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3BucketACC4B323Ref": { + "Ref": "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3BucketE861A468" + }, + "referencetoeksintegtestbasicAssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKey84768DF0Ref": { + "Ref": "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyB62DD0C4" + }, + "referencetoeksintegtestbasicAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE44A33FBRef": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE" + }, + "referencetoeksintegtestbasicAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKey6C24DDDERef": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8" + } + } + } + }, + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66": { + "Type": "AWS::CloudFormation::Stack", "Properties": { - "Location": { - "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", - "SemanticVersion": "1.13.7" + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters9356e02536cba133d17d8dc3b3b37541377d2e95d4d33433ea90e79ab0d859feS3Bucket6AE969C2" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters9356e02536cba133d17d8dc3b3b37541377d2e95d4d33433ea90e79ab0d859feS3VersionKeyC3B7FB97" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters9356e02536cba133d17d8dc3b3b37541377d2e95d4d33433ea90e79ab0d859feS3VersionKeyC3B7FB97" + } + ] + } + ] + } + ] + ] }, "Parameters": { - "LayerName": "kubectl-de6ff3f9a59243920be5aeee7fc888a7" + "referencetoeksintegtestbasicAssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3Bucket5516AF8BRef": { + "Ref": "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketB6E15294" + }, + "referencetoeksintegtestbasicAssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey4FABA150Ref": { + "Ref": "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey5071432F" + }, + "referencetoeksintegtestbasicAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE44A33FBRef": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE" + }, + "referencetoeksintegtestbasicAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKey6C24DDDERef": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8" + } } } } @@ -1283,29 +1286,65 @@ } }, "Parameters": { - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8": { + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3BucketE861A468": { + "Type": "String", + "Description": "S3 bucket for asset \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyB62DD0C4": { + "Type": "String", + "Description": "S3 key for asset version \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cArtifactHash1871DA56": { + "Type": "String", + "Description": "Artifact hash for asset \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE": { + "Type": "String", + "Description": "S3 bucket for asset \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8": { + "Type": "String", + "Description": "S3 key for asset version \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0ArtifactHashDD0FF81A": { + "Type": "String", + "Description": "Artifact hash for asset \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketB6E15294": { + "Type": "String", + "Description": "S3 bucket for asset \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey5071432F": { + "Type": "String", + "Description": "S3 key for asset version \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2ArtifactHash1FC307C8": { + "Type": "String", + "Description": "Artifact hash for asset \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters9356e02536cba133d17d8dc3b3b37541377d2e95d4d33433ea90e79ab0d859feS3Bucket6AE969C2": { "Type": "String", - "Description": "S3 bucket for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "S3 bucket for asset \"9356e02536cba133d17d8dc3b3b37541377d2e95d4d33433ea90e79ab0d859fe\"" }, - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD": { + "AssetParameters9356e02536cba133d17d8dc3b3b37541377d2e95d4d33433ea90e79ab0d859feS3VersionKeyC3B7FB97": { "Type": "String", - "Description": "S3 key for asset version \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "S3 key for asset version \"9356e02536cba133d17d8dc3b3b37541377d2e95d4d33433ea90e79ab0d859fe\"" }, - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204ArtifactHashB80B497F": { + "AssetParameters9356e02536cba133d17d8dc3b3b37541377d2e95d4d33433ea90e79ab0d859feArtifactHashF024A0DD": { "Type": "String", - "Description": "Artifact hash for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "Artifact hash for asset \"9356e02536cba133d17d8dc3b3b37541377d2e95d4d33433ea90e79ab0d859fe\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB": { + "AssetParameters0dbfc75e2973c6dc284120b2ea80dd6df7c704e57ed3d90672ee28131aef83aaS3Bucket8A1FC920": { "Type": "String", - "Description": "S3 bucket for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "S3 bucket for asset \"0dbfc75e2973c6dc284120b2ea80dd6df7c704e57ed3d90672ee28131aef83aa\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54": { + "AssetParameters0dbfc75e2973c6dc284120b2ea80dd6df7c704e57ed3d90672ee28131aef83aaS3VersionKeyA377CBC7": { "Type": "String", - "Description": "S3 key for asset version \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "S3 key for asset version \"0dbfc75e2973c6dc284120b2ea80dd6df7c704e57ed3d90672ee28131aef83aa\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444ArtifactHash606C8127": { + "AssetParameters0dbfc75e2973c6dc284120b2ea80dd6df7c704e57ed3d90672ee28131aef83aaArtifactHash01378DA1": { "Type": "String", - "Description": "Artifact hash for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "Artifact hash for asset \"0dbfc75e2973c6dc284120b2ea80dd6df7c704e57ed3d90672ee28131aef83aa\"" }, "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-helm.lit.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-helm.lit.expected.json index 10172624f8f2e..d4894f4049fb1 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-helm.lit.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-helm.lit.expected.json @@ -449,9 +449,8 @@ } }, { - "Transform": "AWS::Serverless-2016-10-31", "Resources": { - "cluster22ClusterRole5FC933B4": { + "cluster22Role6F752780": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -531,7 +530,7 @@ "ToPort": 443 } }, - "cluster22ResourceHandlerServiceRoleC2E4F327": { + "cluster22CreationRole8343FFAB": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -540,142 +539,101 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "lambda.amazonaws.com" + "AWS": [ + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.k8sclusterawscdkawseksClusterResourceProviderOnEventHandlerServiceRoleB0E0C79CArn" + ] + }, + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.k8sclusterawscdkawseksClusterResourceProviderIsCompleteHandlerServiceRole47C757F4Arn" + ] + } + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.k8sclusterawscdkawseksKubernetesResourceProviderHandlerServiceRole0EBEFB8CArn" + ] + } + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "awscdkawseksHelmResourceProviderNestedStackawscdkawseksHelmResourceProviderNestedStackResource5C12A9A9", + "Outputs.k8sclusterawscdkawseksHelmResourceProviderHandlerServiceRoleC287DC67Arn" + ] + } } } ], "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] + } } }, - "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC": { + "cluster22CreationRoleDefaultPolicy0015FEEF": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { "Statement": [ - { - "Action": [ - "eks:CreateCluster", - "eks:DescribeCluster", - "eks:DeleteCluster", - "eks:UpdateClusterVersion" - ], - "Effect": "Allow", - "Resource": "*" - }, { "Action": "iam:PassRole", "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "cluster22ClusterRole5FC933B4", + "cluster22Role6F752780", "Arn" ] } + }, + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion", + "eks:UpdateClusterConfig" + ], + "Effect": "Allow", + "Resource": "*" } ], "Version": "2012-10-17" }, - "PolicyName": "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", + "PolicyName": "cluster22CreationRoleDefaultPolicy0015FEEF", "Roles": [ { - "Ref": "cluster22ResourceHandlerServiceRoleC2E4F327" + "Ref": "cluster22CreationRole8343FFAB" } ] } }, - "cluster22ResourceHandler6227579A": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "cluster22ResourceHandlerServiceRoleC2E4F327", - "Arn" - ] - }, - "Runtime": "python3.7", - "Layers": [ - { - "Fn::GetAtt": [ - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", - "Outputs.LayerVersionArn" - ] - } - ], - "MemorySize": 512, - "Timeout": 900 - }, - "DependsOn": [ - "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", - "cluster22ResourceHandlerServiceRoleC2E4F327" - ] - }, "cluster227BD1CB20": { "Type": "Custom::AWSCDK-EKS-Cluster", "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "cluster22ResourceHandler6227579A", - "Arn" + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.k8sclusterawscdkawseksClusterResourceProviderframeworkonEventF31E5604Arn" ] }, "Config": { "roleArn": { "Fn::GetAtt": [ - "cluster22ClusterRole5FC933B4", + "cluster22Role6F752780", "Arn" ] }, @@ -703,90 +661,28 @@ } ] } - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "cluster22KubernetesResourceHandler599F07E6": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" - } - ] - } - ] - } - ] - ] - } }, - "Handler": "index.handler", - "Role": { + "AssumeRoleArn": { "Fn::GetAtt": [ - "cluster22ResourceHandlerServiceRoleC2E4F327", + "cluster22CreationRole8343FFAB", "Arn" ] - }, - "Runtime": "python3.7", - "Environment": { - "Variables": { - "CLUSTER_NAME": { - "Ref": "cluster227BD1CB20" - } - } - }, - "Layers": [ - { - "Fn::GetAtt": [ - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", - "Outputs.LayerVersionArn" - ] - } - ], - "MemorySize": 256, - "Timeout": 900 + } }, "DependsOn": [ - "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", - "cluster22ResourceHandlerServiceRoleC2E4F327" - ] + "cluster22CreationRoleDefaultPolicy0015FEEF", + "cluster22CreationRole8343FFAB" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "cluster22AwsAuthmanifest4685C84D": { "Type": "Custom::AWSCDK-EKS-KubernetesResource", "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "cluster22KubernetesResourceHandler599F07E6", - "Arn" + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.k8sclusterawscdkawseksKubernetesResourceProviderframeworkonEvent150B7AA1Arn" ] }, "Manifest": { @@ -810,6 +706,15 @@ "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" ] ] + }, + "ClusterName": { + "Ref": "cluster227BD1CB20" + }, + "RoleArn": { + "Fn::GetAtt": [ + "cluster22CreationRole8343FFAB", + "Arn" + ] } }, "UpdateReplacePolicy": "Delete", @@ -1106,7 +1011,16 @@ "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "cluster22HelmChartHandler0BAF302E", + "awscdkawseksHelmResourceProviderNestedStackawscdkawseksHelmResourceProviderNestedStackResource5C12A9A9", + "Outputs.k8sclusterawscdkawseksHelmResourceProviderframeworkonEvent603E80AFArn" + ] + }, + "ClusterName": { + "Ref": "cluster227BD1CB20" + }, + "RoleArn": { + "Fn::GetAtt": [ + "cluster22CreationRole8343FFAB", "Arn" ] }, @@ -1118,84 +1032,21 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "cluster22HelmChartHandler0BAF302E": { - "Type": "AWS::Lambda::Function", + "cluster22chartnginxingress90C2D506": { + "Type": "Custom::AWSCDK-EKS-HelmChart", "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653S3BucketD01BFA78" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653S3VersionKeyD67E9179" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653S3VersionKeyD67E9179" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "index.handler", - "Role": { + "ServiceToken": { "Fn::GetAtt": [ - "cluster22ResourceHandlerServiceRoleC2E4F327", - "Arn" + "awscdkawseksHelmResourceProviderNestedStackawscdkawseksHelmResourceProviderNestedStackResource5C12A9A9", + "Outputs.k8sclusterawscdkawseksHelmResourceProviderframeworkonEvent603E80AFArn" ] }, - "Runtime": "python3.7", - "Environment": { - "Variables": { - "CLUSTER_NAME": { - "Ref": "cluster227BD1CB20" - } - } + "ClusterName": { + "Ref": "cluster227BD1CB20" }, - "Layers": [ - { - "Fn::GetAtt": [ - "kubectllayer200beta1B9303363", - "Outputs.LayerVersionArn" - ] - } - ], - "MemorySize": 256, - "Timeout": 900 - }, - "DependsOn": [ - "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", - "cluster22ResourceHandlerServiceRoleC2E4F327" - ] - }, - "cluster22chartnginxingress90C2D506": { - "Type": "Custom::AWSCDK-EKS-HelmChart", - "Properties": { - "ServiceToken": { + "RoleArn": { "Fn::GetAtt": [ - "cluster22HelmChartHandler0BAF302E", + "cluster22CreationRole8343FFAB", "Arn" ] }, @@ -1207,15 +1058,64 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA": { - "Type": "AWS::Serverless::Application", + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454": { + "Type": "AWS::CloudFormation::Stack", "Properties": { - "Location": { - "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", - "SemanticVersion": "1.13.7" + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeS3BucketB74693EC" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeS3VersionKeyDD3D4940" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeS3VersionKeyDD3D4940" + } + ] + } + ] + } + ] + ] }, "Parameters": { - "LayerName": "kubectl-bedb92f2e70f45155fba70d3425dd148" + "referencetok8sclusterAssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3Bucket40DAFEBFRef": { + "Ref": "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3BucketE861A468" + }, + "referencetok8sclusterAssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyD9847EBDRef": { + "Ref": "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyB62DD0C4" + }, + "referencetok8sclusterAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3Bucket6EE66685Ref": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE" + }, + "referencetok8sclusterAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKey32B2BE5BRef": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8" + } } } }, @@ -1247,15 +1147,125 @@ } } }, - "kubectllayer200beta1B9303363": { - "Type": "AWS::Serverless::Application", + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decS3BucketF0706FEF" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decS3VersionKeyEBBC2327" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decS3VersionKeyEBBC2327" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetok8sclusterAssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3Bucket09CADC1ERef": { + "Ref": "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketB6E15294" + }, + "referencetok8sclusterAssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey34019A7CRef": { + "Ref": "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey5071432F" + }, + "referencetok8sclusterAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3Bucket6EE66685Ref": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE" + }, + "referencetok8sclusterAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKey32B2BE5BRef": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8" + } + } + } + }, + "awscdkawseksHelmResourceProviderNestedStackawscdkawseksHelmResourceProviderNestedStackResource5C12A9A9": { + "Type": "AWS::CloudFormation::Stack", "Properties": { - "Location": { - "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", - "SemanticVersion": "2.0.0-beta1" + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersa467d97d4891200274ebc7f9003508e56784ba6824583ab33ef9ff4a8916d4f1S3Bucket1573BEA4" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersa467d97d4891200274ebc7f9003508e56784ba6824583ab33ef9ff4a8916d4f1S3VersionKeyEF22A448" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersa467d97d4891200274ebc7f9003508e56784ba6824583ab33ef9ff4a8916d4f1S3VersionKeyEF22A448" + } + ] + } + ] + } + ] + ] }, "Parameters": { - "LayerName": "kubectl-aa3d1881d348da39094e6b1ce165f580" + "referencetok8sclusterAssetParameters6149251e9b15b9d8b1c4ce160e99094f79b86b323da0a96f97467aabd6e41463S3Bucket557D94B1Ref": { + "Ref": "AssetParameters6149251e9b15b9d8b1c4ce160e99094f79b86b323da0a96f97467aabd6e41463S3Bucket29EBC6FF" + }, + "referencetok8sclusterAssetParameters6149251e9b15b9d8b1c4ce160e99094f79b86b323da0a96f97467aabd6e41463S3VersionKey7262C322Ref": { + "Ref": "AssetParameters6149251e9b15b9d8b1c4ce160e99094f79b86b323da0a96f97467aabd6e41463S3VersionKeyC8E24930" + }, + "referencetok8sclusterAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3Bucket6EE66685Ref": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE" + }, + "referencetok8sclusterAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKey32B2BE5BRef": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8" + } } } } @@ -1291,41 +1301,89 @@ } }, "Parameters": { - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8": { + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3BucketE861A468": { + "Type": "String", + "Description": "S3 bucket for asset \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyB62DD0C4": { + "Type": "String", + "Description": "S3 key for asset version \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cArtifactHash1871DA56": { + "Type": "String", + "Description": "Artifact hash for asset \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE": { + "Type": "String", + "Description": "S3 bucket for asset \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8": { + "Type": "String", + "Description": "S3 key for asset version \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0ArtifactHashDD0FF81A": { + "Type": "String", + "Description": "Artifact hash for asset \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketB6E15294": { + "Type": "String", + "Description": "S3 bucket for asset \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey5071432F": { + "Type": "String", + "Description": "S3 key for asset version \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2ArtifactHash1FC307C8": { + "Type": "String", + "Description": "Artifact hash for asset \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters6149251e9b15b9d8b1c4ce160e99094f79b86b323da0a96f97467aabd6e41463S3Bucket29EBC6FF": { + "Type": "String", + "Description": "S3 bucket for asset \"6149251e9b15b9d8b1c4ce160e99094f79b86b323da0a96f97467aabd6e41463\"" + }, + "AssetParameters6149251e9b15b9d8b1c4ce160e99094f79b86b323da0a96f97467aabd6e41463S3VersionKeyC8E24930": { + "Type": "String", + "Description": "S3 key for asset version \"6149251e9b15b9d8b1c4ce160e99094f79b86b323da0a96f97467aabd6e41463\"" + }, + "AssetParameters6149251e9b15b9d8b1c4ce160e99094f79b86b323da0a96f97467aabd6e41463ArtifactHash23C682DF": { + "Type": "String", + "Description": "Artifact hash for asset \"6149251e9b15b9d8b1c4ce160e99094f79b86b323da0a96f97467aabd6e41463\"" + }, + "AssetParametersa467d97d4891200274ebc7f9003508e56784ba6824583ab33ef9ff4a8916d4f1S3Bucket1573BEA4": { "Type": "String", - "Description": "S3 bucket for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "S3 bucket for asset \"a467d97d4891200274ebc7f9003508e56784ba6824583ab33ef9ff4a8916d4f1\"" }, - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD": { + "AssetParametersa467d97d4891200274ebc7f9003508e56784ba6824583ab33ef9ff4a8916d4f1S3VersionKeyEF22A448": { "Type": "String", - "Description": "S3 key for asset version \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "S3 key for asset version \"a467d97d4891200274ebc7f9003508e56784ba6824583ab33ef9ff4a8916d4f1\"" }, - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204ArtifactHashB80B497F": { + "AssetParametersa467d97d4891200274ebc7f9003508e56784ba6824583ab33ef9ff4a8916d4f1ArtifactHashF79A0ADE": { "Type": "String", - "Description": "Artifact hash for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "Artifact hash for asset \"a467d97d4891200274ebc7f9003508e56784ba6824583ab33ef9ff4a8916d4f1\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB": { + "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decS3BucketF0706FEF": { "Type": "String", - "Description": "S3 bucket for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "S3 bucket for asset \"8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40dec\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54": { + "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decS3VersionKeyEBBC2327": { "Type": "String", - "Description": "S3 key for asset version \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "S3 key for asset version \"8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40dec\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444ArtifactHash606C8127": { + "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decArtifactHash7303DBC4": { "Type": "String", - "Description": "Artifact hash for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "Artifact hash for asset \"8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40dec\"" }, - "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653S3BucketD01BFA78": { + "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeS3BucketB74693EC": { "Type": "String", - "Description": "S3 bucket for asset \"8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653\"" + "Description": "S3 bucket for asset \"6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eae\"" }, - "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653S3VersionKeyD67E9179": { + "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeS3VersionKeyDD3D4940": { "Type": "String", - "Description": "S3 key for asset version \"8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653\"" + "Description": "S3 key for asset version \"6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eae\"" }, - "AssetParameters8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653ArtifactHash77099D9F": { + "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeArtifactHashE7AA97DD": { "Type": "String", - "Description": "Artifact hash for asset \"8e2989bd32b411eba804b201a0f3984c984893c7fe6daa0b572fdd59c63e3653\"" + "Description": "Artifact hash for asset \"6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eae\"" }, "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-kubectl.lit.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-kubectl.lit.expected.json index 720d8f76704e9..c490d41a5c4c2 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-kubectl.lit.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-kubectl.lit.expected.json @@ -41,7 +41,7 @@ { "Key": "kubernetes.io/role/elb", "Value": "1" - } + } ] } }, @@ -59,7 +59,7 @@ { "Key": "kubernetes.io/role/elb", "Value": "1" - } + } ] } }, @@ -101,7 +101,7 @@ { "Key": "kubernetes.io/role/elb", "Value": "1" - } + } ] } }, @@ -125,7 +125,7 @@ { "Key": "kubernetes.io/role/elb", "Value": "1" - } + } ] } }, @@ -154,7 +154,7 @@ { "Key": "kubernetes.io/role/elb", "Value": "1" - } + } ] } }, @@ -172,7 +172,7 @@ { "Key": "kubernetes.io/role/elb", "Value": "1" - } + } ] } }, @@ -214,7 +214,7 @@ { "Key": "kubernetes.io/role/elb", "Value": "1" - } + } ] } }, @@ -238,7 +238,7 @@ { "Key": "kubernetes.io/role/elb", "Value": "1" - } + } ] } }, @@ -449,9 +449,8 @@ } }, { - "Transform": "AWS::Serverless-2016-10-31", "Resources": { - "cluster22ClusterRole5FC933B4": { + "cluster22Role6F752780": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -531,7 +530,7 @@ "ToPort": 443 } }, - "cluster22ResourceHandlerServiceRoleC2E4F327": { + "cluster22CreationRole8343FFAB": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -540,142 +539,89 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "lambda.amazonaws.com" + "AWS": [ + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.k8sclusterawscdkawseksClusterResourceProviderOnEventHandlerServiceRoleB0E0C79CArn" + ] + }, + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.k8sclusterawscdkawseksClusterResourceProviderIsCompleteHandlerServiceRole47C757F4Arn" + ] + } + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.k8sclusterawscdkawseksKubernetesResourceProviderHandlerServiceRole0EBEFB8CArn" + ] + } } } ], "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] + } } }, - "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC": { + "cluster22CreationRoleDefaultPolicy0015FEEF": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { "Statement": [ - { - "Action": [ - "eks:CreateCluster", - "eks:DescribeCluster", - "eks:DeleteCluster", - "eks:UpdateClusterVersion" - ], - "Effect": "Allow", - "Resource": "*" - }, { "Action": "iam:PassRole", "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "cluster22ClusterRole5FC933B4", + "cluster22Role6F752780", "Arn" ] } + }, + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion", + "eks:UpdateClusterConfig" + ], + "Effect": "Allow", + "Resource": "*" } ], "Version": "2012-10-17" }, - "PolicyName": "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", + "PolicyName": "cluster22CreationRoleDefaultPolicy0015FEEF", "Roles": [ { - "Ref": "cluster22ResourceHandlerServiceRoleC2E4F327" + "Ref": "cluster22CreationRole8343FFAB" } ] } }, - "cluster22ResourceHandler6227579A": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "cluster22ResourceHandlerServiceRoleC2E4F327", - "Arn" - ] - }, - "Runtime": "python3.7", - "Layers": [ - { - "Fn::GetAtt": [ - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", - "Outputs.LayerVersionArn" - ] - } - ], - "MemorySize": 512, - "Timeout": 900 - }, - "DependsOn": [ - "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", - "cluster22ResourceHandlerServiceRoleC2E4F327" - ] - }, "cluster227BD1CB20": { "Type": "Custom::AWSCDK-EKS-Cluster", "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "cluster22ResourceHandler6227579A", - "Arn" + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.k8sclusterawscdkawseksClusterResourceProviderframeworkonEventF31E5604Arn" ] }, "Config": { "roleArn": { "Fn::GetAtt": [ - "cluster22ClusterRole5FC933B4", + "cluster22Role6F752780", "Arn" ] }, @@ -703,90 +649,28 @@ } ] } - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "cluster22KubernetesResourceHandler599F07E6": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" - } - ] - } - ] - } - ] - ] - } }, - "Handler": "index.handler", - "Role": { + "AssumeRoleArn": { "Fn::GetAtt": [ - "cluster22ResourceHandlerServiceRoleC2E4F327", + "cluster22CreationRole8343FFAB", "Arn" ] - }, - "Runtime": "python3.7", - "Environment": { - "Variables": { - "CLUSTER_NAME": { - "Ref": "cluster227BD1CB20" - } - } - }, - "Layers": [ - { - "Fn::GetAtt": [ - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", - "Outputs.LayerVersionArn" - ] - } - ], - "MemorySize": 256, - "Timeout": 900 + } }, "DependsOn": [ - "cluster22ResourceHandlerServiceRoleDefaultPolicy1D33C3AC", - "cluster22ResourceHandlerServiceRoleC2E4F327" - ] + "cluster22CreationRoleDefaultPolicy0015FEEF", + "cluster22CreationRole8343FFAB" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "cluster22AwsAuthmanifest4685C84D": { "Type": "Custom::AWSCDK-EKS-KubernetesResource", "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "cluster22KubernetesResourceHandler599F07E6", - "Arn" + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.k8sclusterawscdkawseksKubernetesResourceProviderframeworkonEvent150B7AA1Arn" ] }, "Manifest": { @@ -810,6 +694,15 @@ "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" ] ] + }, + "ClusterName": { + "Ref": "cluster227BD1CB20" + }, + "RoleArn": { + "Fn::GetAtt": [ + "cluster22CreationRole8343FFAB", + "Arn" + ] } }, "UpdateReplacePolicy": "Delete", @@ -1106,24 +999,82 @@ "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "cluster22KubernetesResourceHandler599F07E6", - "Arn" + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.k8sclusterawscdkawseksKubernetesResourceProviderframeworkonEvent150B7AA1Arn" ] }, - "Manifest": "[{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"name\":\"hello-kubernetes\"},\"spec\":{\"type\":\"LoadBalancer\",\"ports\":[{\"port\":80,\"targetPort\":8080}],\"selector\":{\"app\":\"hello-kubernetes\"}}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"hello-kubernetes\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"hello-kubernetes\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"hello-kubernetes\"}},\"spec\":{\"containers\":[{\"name\":\"hello-kubernetes\",\"image\":\"paulbouwer/hello-kubernetes:1.5\",\"ports\":[{\"containerPort\":8080}]}]}}}}]" + "Manifest": "[{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"name\":\"hello-kubernetes\"},\"spec\":{\"type\":\"LoadBalancer\",\"ports\":[{\"port\":80,\"targetPort\":8080}],\"selector\":{\"app\":\"hello-kubernetes\"}}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"hello-kubernetes\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"hello-kubernetes\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"hello-kubernetes\"}},\"spec\":{\"containers\":[{\"name\":\"hello-kubernetes\",\"image\":\"paulbouwer/hello-kubernetes:1.5\",\"ports\":[{\"containerPort\":8080}]}]}}}}]", + "ClusterName": { + "Ref": "cluster227BD1CB20" + }, + "RoleArn": { + "Fn::GetAtt": [ + "cluster22CreationRole8343FFAB", + "Arn" + ] + } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA": { - "Type": "AWS::Serverless::Application", + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454": { + "Type": "AWS::CloudFormation::Stack", "Properties": { - "Location": { - "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", - "SemanticVersion": "1.13.7" + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeS3BucketB74693EC" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeS3VersionKeyDD3D4940" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeS3VersionKeyDD3D4940" + } + ] + } + ] + } + ] + ] }, "Parameters": { - "LayerName": "kubectl-bedb92f2e70f45155fba70d3425dd148" + "referencetok8sclusterAssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3Bucket40DAFEBFRef": { + "Ref": "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3BucketE861A468" + }, + "referencetok8sclusterAssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyD9847EBDRef": { + "Ref": "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyB62DD0C4" + }, + "referencetok8sclusterAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3Bucket6EE66685Ref": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE" + }, + "referencetok8sclusterAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKey32B2BE5BRef": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8" + } } } }, @@ -1154,6 +1105,67 @@ "Version": "2012-10-17" } } + }, + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decS3BucketF0706FEF" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decS3VersionKeyEBBC2327" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decS3VersionKeyEBBC2327" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetok8sclusterAssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3Bucket09CADC1ERef": { + "Ref": "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketB6E15294" + }, + "referencetok8sclusterAssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey34019A7CRef": { + "Ref": "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey5071432F" + }, + "referencetok8sclusterAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3Bucket6EE66685Ref": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE" + }, + "referencetok8sclusterAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKey32B2BE5BRef": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8" + } + } + } } }, "Outputs": { @@ -1187,29 +1199,65 @@ } }, "Parameters": { - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8": { + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3BucketE861A468": { + "Type": "String", + "Description": "S3 bucket for asset \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyB62DD0C4": { + "Type": "String", + "Description": "S3 key for asset version \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cArtifactHash1871DA56": { + "Type": "String", + "Description": "Artifact hash for asset \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE": { + "Type": "String", + "Description": "S3 bucket for asset \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8": { + "Type": "String", + "Description": "S3 key for asset version \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0ArtifactHashDD0FF81A": { + "Type": "String", + "Description": "Artifact hash for asset \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketB6E15294": { + "Type": "String", + "Description": "S3 bucket for asset \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey5071432F": { + "Type": "String", + "Description": "S3 key for asset version \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2ArtifactHash1FC307C8": { + "Type": "String", + "Description": "Artifact hash for asset \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decS3BucketF0706FEF": { "Type": "String", - "Description": "S3 bucket for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "S3 bucket for asset \"8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40dec\"" }, - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD": { + "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decS3VersionKeyEBBC2327": { "Type": "String", - "Description": "S3 key for asset version \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "S3 key for asset version \"8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40dec\"" }, - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204ArtifactHashB80B497F": { + "AssetParameters8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40decArtifactHash7303DBC4": { "Type": "String", - "Description": "Artifact hash for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "Artifact hash for asset \"8ca585e0c579a7f28f17f90e8e3e462c4cfeac06aa452dc164cb8200e5a40dec\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB": { + "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeS3BucketB74693EC": { "Type": "String", - "Description": "S3 bucket for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "S3 bucket for asset \"6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eae\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54": { + "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeS3VersionKeyDD3D4940": { "Type": "String", - "Description": "S3 key for asset version \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "S3 key for asset version \"6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eae\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444ArtifactHash606C8127": { + "AssetParameters6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eaeArtifactHashE7AA97DD": { "Type": "String", - "Description": "Artifact hash for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "Artifact hash for asset \"6d5f4417369ef3ef78dab6f6deae64529815a2be8d25a7d6161999af0bfe9eae\"" }, "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-spot.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-spot.expected.json index 982e65e5d7d63..09dba021cfe11 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-spot.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-spot.expected.json @@ -1,5 +1,4 @@ { - "Transform": "AWS::Serverless-2016-10-31", "Resources": { "vpcA2121C38": { "Type": "AWS::EC2::VPC", @@ -404,7 +403,7 @@ } } }, - "myClusterClusterRoleF3B08D5F": { + "myClusterRole0D8296A4": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -505,7 +504,7 @@ "ToPort": 443 } }, - "myClusterResourceHandlerServiceRole95F554E2": { + "myClusterCreationRole32535C76": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -514,142 +513,89 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "lambda.amazonaws.com" + "AWS": [ + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.integeksspotawscdkawseksClusterResourceProviderOnEventHandlerServiceRoleFAE56881Arn" + ] + }, + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.integeksspotawscdkawseksClusterResourceProviderIsCompleteHandlerServiceRoleABA3B752Arn" + ] + } + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.integeksspotawscdkawseksKubernetesResourceProviderHandlerServiceRole34439311Arn" + ] + } } } ], "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] + } } }, - "myClusterResourceHandlerServiceRoleDefaultPolicyB0BF3AD3": { + "myClusterCreationRoleDefaultPolicy942AD3ED": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { "Statement": [ - { - "Action": [ - "eks:CreateCluster", - "eks:DescribeCluster", - "eks:DeleteCluster", - "eks:UpdateClusterVersion" - ], - "Effect": "Allow", - "Resource": "*" - }, { "Action": "iam:PassRole", "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "myClusterClusterRoleF3B08D5F", + "myClusterRole0D8296A4", "Arn" ] } + }, + { + "Action": [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion", + "eks:UpdateClusterConfig" + ], + "Effect": "Allow", + "Resource": "*" } ], "Version": "2012-10-17" }, - "PolicyName": "myClusterResourceHandlerServiceRoleDefaultPolicyB0BF3AD3", + "PolicyName": "myClusterCreationRoleDefaultPolicy942AD3ED", "Roles": [ { - "Ref": "myClusterResourceHandlerServiceRole95F554E2" + "Ref": "myClusterCreationRole32535C76" } ] } }, - "myClusterResourceHandler19D131C9": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "myClusterResourceHandlerServiceRole95F554E2", - "Arn" - ] - }, - "Runtime": "python3.7", - "Layers": [ - { - "Fn::GetAtt": [ - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", - "Outputs.LayerVersionArn" - ] - } - ], - "MemorySize": 512, - "Timeout": 900 - }, - "DependsOn": [ - "myClusterResourceHandlerServiceRoleDefaultPolicyB0BF3AD3", - "myClusterResourceHandlerServiceRole95F554E2" - ] - }, "myClusterE51CD07F": { "Type": "Custom::AWSCDK-EKS-Cluster", "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "myClusterResourceHandler19D131C9", - "Arn" + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.integeksspotawscdkawseksClusterResourceProviderframeworkonEvent8482DC0FArn" ] }, "Config": { "roleArn": { "Fn::GetAtt": [ - "myClusterClusterRoleF3B08D5F", + "myClusterRole0D8296A4", "Arn" ] }, @@ -677,82 +623,20 @@ } ] } - } - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - }, - "myClusterKubernetesResourceHandler50297E32": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54" - } - ] - } - ] - } - ] - ] - } }, - "Handler": "index.handler", - "Role": { + "AssumeRoleArn": { "Fn::GetAtt": [ - "myClusterResourceHandlerServiceRole95F554E2", + "myClusterCreationRole32535C76", "Arn" ] - }, - "Runtime": "python3.7", - "Environment": { - "Variables": { - "CLUSTER_NAME": { - "Ref": "myClusterE51CD07F" - } - } - }, - "Layers": [ - { - "Fn::GetAtt": [ - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA", - "Outputs.LayerVersionArn" - ] - } - ], - "MemorySize": 256, - "Timeout": 900 + } }, "DependsOn": [ - "myClusterResourceHandlerServiceRoleDefaultPolicyB0BF3AD3", - "myClusterResourceHandlerServiceRole95F554E2" - ] + "myClusterCreationRoleDefaultPolicy942AD3ED", + "myClusterCreationRole32535C76" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" }, "myClusterDefaultCapacityInstanceSecurityGroup22595F6B": { "Type": "AWS::EC2::SecurityGroup", @@ -1045,8 +929,8 @@ "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "myClusterKubernetesResourceHandler50297E32", - "Arn" + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.integeksspotawscdkawseksKubernetesResourceProviderframeworkonEventE36D0171Arn" ] }, "Manifest": { @@ -1070,6 +954,15 @@ "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" ] ] + }, + "ClusterName": { + "Ref": "myClusterE51CD07F" + }, + "RoleArn": { + "Fn::GetAtt": [ + "myClusterCreationRole32535C76", + "Arn" + ] } }, "UpdateReplacePolicy": "Delete", @@ -1366,24 +1259,143 @@ "Properties": { "ServiceToken": { "Fn::GetAtt": [ - "myClusterKubernetesResourceHandler50297E32", - "Arn" + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.integeksspotawscdkawseksKubernetesResourceProviderframeworkonEventE36D0171Arn" ] }, - "Manifest": "[{\"kind\":\"ClusterRole\",\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"metadata\":{\"name\":\"node-termination-handler\",\"namespace\":\"default\"},\"rules\":[{\"apiGroups\":[\"apps\"],\"resources\":[\"daemonsets\"],\"verbs\":[\"get\",\"delete\"]},{\"apiGroups\":[\"\"],\"resources\":[\"*\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"rbac.authorization.k8s.io\"],\"resources\":[\"*\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apiextensions.k8s.io\"],\"resources\":[\"customresourcedefinitions\"],\"verbs\":[\"get\",\"list\",\"watch\",\"create\",\"delete\"]}]},{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"name\":\"node-termination-handler\"}},{\"kind\":\"ClusterRoleBinding\",\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"metadata\":{\"name\":\"node-termination-handler\",\"namespace\":\"default\"},\"subjects\":[{\"kind\":\"ServiceAccount\",\"name\":\"node-termination-handler\",\"namespace\":\"default\"}],\"roleRef\":{\"kind\":\"ClusterRole\",\"name\":\"node-termination-handler\",\"apiGroup\":\"rbac.authorization.k8s.io\"}},{\"apiVersion\":\"apps/v1beta2\",\"kind\":\"DaemonSet\",\"metadata\":{\"name\":\"node-termination-handler\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"node-termination-handler\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"node-termination-handler\"}},\"spec\":{\"serviceAccountName\":\"node-termination-handler\",\"containers\":[{\"name\":\"node-termination-handler\",\"image\":\"amazon/aws-node-termination-handler:v1.0.0\",\"imagePullPolicy\":\"Always\",\"env\":[{\"name\":\"NODE_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"spec.nodeName\"}}},{\"name\":\"POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}},{\"name\":\"NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"SPOT_POD_IP\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"status.podIP\"}}}],\"resources\":{\"requests\":{\"memory\":\"64Mi\",\"cpu\":\"50m\"},\"limits\":{\"memory\":\"128Mi\",\"cpu\":\"100m\"}}}],\"nodeSelector\":{\"lifecycle\":\"Ec2Spot\"}}}}}]" + "Manifest": "[{\"kind\":\"ClusterRole\",\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"metadata\":{\"name\":\"node-termination-handler\",\"namespace\":\"default\"},\"rules\":[{\"apiGroups\":[\"apps\"],\"resources\":[\"daemonsets\"],\"verbs\":[\"get\",\"delete\"]},{\"apiGroups\":[\"\"],\"resources\":[\"*\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"rbac.authorization.k8s.io\"],\"resources\":[\"*\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apiextensions.k8s.io\"],\"resources\":[\"customresourcedefinitions\"],\"verbs\":[\"get\",\"list\",\"watch\",\"create\",\"delete\"]}]},{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"name\":\"node-termination-handler\"}},{\"kind\":\"ClusterRoleBinding\",\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"metadata\":{\"name\":\"node-termination-handler\",\"namespace\":\"default\"},\"subjects\":[{\"kind\":\"ServiceAccount\",\"name\":\"node-termination-handler\",\"namespace\":\"default\"}],\"roleRef\":{\"kind\":\"ClusterRole\",\"name\":\"node-termination-handler\",\"apiGroup\":\"rbac.authorization.k8s.io\"}},{\"apiVersion\":\"apps/v1beta2\",\"kind\":\"DaemonSet\",\"metadata\":{\"name\":\"node-termination-handler\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"node-termination-handler\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"node-termination-handler\"}},\"spec\":{\"serviceAccountName\":\"node-termination-handler\",\"containers\":[{\"name\":\"node-termination-handler\",\"image\":\"amazon/aws-node-termination-handler:v1.0.0\",\"imagePullPolicy\":\"Always\",\"env\":[{\"name\":\"NODE_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"spec.nodeName\"}}},{\"name\":\"POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}},{\"name\":\"NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"SPOT_POD_IP\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"status.podIP\"}}}],\"resources\":{\"requests\":{\"memory\":\"64Mi\",\"cpu\":\"50m\"},\"limits\":{\"memory\":\"128Mi\",\"cpu\":\"100m\"}}}],\"nodeSelector\":{\"lifecycle\":\"Ec2Spot\"}}}}}]", + "ClusterName": { + "Ref": "myClusterE51CD07F" + }, + "RoleArn": { + "Fn::GetAtt": [ + "myClusterCreationRole32535C76", + "Arn" + ] + } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "kubectllayer8C2542BCBF2B4DFEB765E181FD30A9A0617C4ADA": { - "Type": "AWS::Serverless::Application", + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454": { + "Type": "AWS::CloudFormation::Stack", "Properties": { - "Location": { - "ApplicationId": "arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl", - "SemanticVersion": "1.13.7" + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameters7ad0795676184a4f55098809e8bc18c4253e36cd3c181295c2e43ad3eaf89ce4S3Bucket6A27A3E1" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters7ad0795676184a4f55098809e8bc18c4253e36cd3c181295c2e43ad3eaf89ce4S3VersionKey37965366" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters7ad0795676184a4f55098809e8bc18c4253e36cd3c181295c2e43ad3eaf89ce4S3VersionKey37965366" + } + ] + } + ] + } + ] + ] }, "Parameters": { - "LayerName": "kubectl-e3c1a5897fab23abec558d991fea218c" + "referencetointegeksspotAssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3Bucket6C030246Ref": { + "Ref": "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3BucketE861A468" + }, + "referencetointegeksspotAssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyA27A439ARef": { + "Ref": "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyB62DD0C4" + }, + "referencetointegeksspotAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3Bucket44C2F8EBRef": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE" + }, + "referencetointegeksspotAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKey88B38D04Ref": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8" + } + } + } + }, + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.test-region.", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParameterse31578e5879b0e1834f6dd91c6b5fa5d2bfc07e3932f8d3ff3d7c476123ae005S3Bucket76E09CF2" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameterse31578e5879b0e1834f6dd91c6b5fa5d2bfc07e3932f8d3ff3d7c476123ae005S3VersionKeyD2AEAED3" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameterse31578e5879b0e1834f6dd91c6b5fa5d2bfc07e3932f8d3ff3d7c476123ae005S3VersionKeyD2AEAED3" + } + ] + } + ] + } + ] + ] + }, + "Parameters": { + "referencetointegeksspotAssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketDB32FE86Ref": { + "Ref": "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketB6E15294" + }, + "referencetointegeksspotAssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey94E0B1D5Ref": { + "Ref": "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey5071432F" + }, + "referencetointegeksspotAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3Bucket44C2F8EBRef": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE" + }, + "referencetointegeksspotAssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKey88B38D04Ref": { + "Ref": "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8" + } } } } @@ -1419,33 +1431,69 @@ } }, "Parameters": { - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3Bucket371D99F8": { + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3BucketE861A468": { + "Type": "String", + "Description": "S3 bucket for asset \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cS3VersionKeyB62DD0C4": { + "Type": "String", + "Description": "S3 key for asset version \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameterse3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7cArtifactHash1871DA56": { + "Type": "String", + "Description": "Artifact hash for asset \"e3e075ac49ed41df2d65a2461111e2462dae18db8ec082455daea8dab8d47b7c\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3BucketE8BB46CE": { + "Type": "String", + "Description": "S3 bucket for asset \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0S3VersionKeyDFF4B5D8": { + "Type": "String", + "Description": "S3 key for asset version \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0ArtifactHashDD0FF81A": { + "Type": "String", + "Description": "Artifact hash for asset \"3e728f777afd6d4a580bc77d99f86194358dca730432b3f4583e544f1e85d2a0\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3BucketB6E15294": { + "Type": "String", + "Description": "S3 bucket for asset \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2S3VersionKey5071432F": { + "Type": "String", + "Description": "S3 key for asset version \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameters21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2ArtifactHash1FC307C8": { + "Type": "String", + "Description": "Artifact hash for asset \"21d34b692341e38aac82036dc6f81727130d345c23a6dce90d2af2779c9adfe2\"" + }, + "AssetParameterse31578e5879b0e1834f6dd91c6b5fa5d2bfc07e3932f8d3ff3d7c476123ae005S3Bucket76E09CF2": { "Type": "String", - "Description": "S3 bucket for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "S3 bucket for asset \"e31578e5879b0e1834f6dd91c6b5fa5d2bfc07e3932f8d3ff3d7c476123ae005\"" }, - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204S3VersionKeyFDCB25DD": { + "AssetParameterse31578e5879b0e1834f6dd91c6b5fa5d2bfc07e3932f8d3ff3d7c476123ae005S3VersionKeyD2AEAED3": { "Type": "String", - "Description": "S3 key for asset version \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "S3 key for asset version \"e31578e5879b0e1834f6dd91c6b5fa5d2bfc07e3932f8d3ff3d7c476123ae005\"" }, - "AssetParametersea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204ArtifactHashB80B497F": { + "AssetParameterse31578e5879b0e1834f6dd91c6b5fa5d2bfc07e3932f8d3ff3d7c476123ae005ArtifactHash62FD8043": { "Type": "String", - "Description": "Artifact hash for asset \"ea4957b16062595851e7d293ee45835db05c5693669a729cc02944b6ad19a204\"" + "Description": "Artifact hash for asset \"e31578e5879b0e1834f6dd91c6b5fa5d2bfc07e3932f8d3ff3d7c476123ae005\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3Bucket919126CB": { + "AssetParameters7ad0795676184a4f55098809e8bc18c4253e36cd3c181295c2e43ad3eaf89ce4S3Bucket6A27A3E1": { "Type": "String", - "Description": "S3 bucket for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "S3 bucket for asset \"7ad0795676184a4f55098809e8bc18c4253e36cd3c181295c2e43ad3eaf89ce4\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444S3VersionKey529BEF54": { + "AssetParameters7ad0795676184a4f55098809e8bc18c4253e36cd3c181295c2e43ad3eaf89ce4S3VersionKey37965366": { "Type": "String", - "Description": "S3 key for asset version \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "S3 key for asset version \"7ad0795676184a4f55098809e8bc18c4253e36cd3c181295c2e43ad3eaf89ce4\"" }, - "AssetParameters640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444ArtifactHash606C8127": { + "AssetParameters7ad0795676184a4f55098809e8bc18c4253e36cd3c181295c2e43ad3eaf89ce4ArtifactHashED1E024F": { "Type": "String", - "Description": "Artifact hash for asset \"640847533c8a00b3133aeb128edcac41fb7b60349c9e18764fcf7ea4af14d444\"" + "Description": "Artifact hash for asset \"7ad0795676184a4f55098809e8bc18c4253e36cd3c181295c2e43ad3eaf89ce4\"" }, "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", "Default": "/aws/service/eks/optimized-ami/1.14/amazon-linux-2/recommended/image_id" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts b/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts new file mode 100644 index 0000000000000..a852a40a30166 --- /dev/null +++ b/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts @@ -0,0 +1,395 @@ +import { Test } from 'nodeunit'; +import { ClusterResourceHandler } from '../lib/cluster-resource-handler/handler'; +import * as mocks from './cluster-resource-handler-mocks'; + +export = { + setUp(callback: any) { + mocks.reset(); + callback(); + }, + + create: { + async 'onCreate: minimal defaults (vpc + role)'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Create', mocks.MOCK_PROPS)); + await handler.onEvent(); + + test.deepEqual(mocks.actualRequest.configureAssumeRoleRequest, { + RoleArn: mocks.MOCK_ASSUME_ROLE_ARN, + RoleSessionName: 'AWSCDK.EKSCluster.Create.fake-request-id', + }); + + test.deepEqual(mocks.actualRequest.createClusterRequest, { + roleArn: 'arn:of:role', + resourcesVpcConfig: { + subnetIds: ['subnet1', 'subnet2'], + securityGroupIds: ['sg1', 'sg2', 'sg3'] + }, + name: 'MyResourceId-fake-request-id' + }); + + test.done(); + }, + + async 'onCreate: explicit cluster name'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Create', { + ...mocks.MOCK_PROPS, + name: 'ExplicitCustomName' + })); + await handler.onEvent(); + + test.deepEqual(mocks.actualRequest.createClusterRequest!.name, 'ExplicitCustomName'); + test.done(); + }, + + async 'with no specific version'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Create', { + ...mocks.MOCK_PROPS, + version: '12.34.56', + })); + await handler.onEvent(); + + test.deepEqual(mocks.actualRequest.createClusterRequest!.version, '12.34.56'); + test.done(); + }, + + async 'isCreateComplete still not complete if cluster is not ACTIVE'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Create')); + mocks.simulateResponse.describeClusterResponseMockStatus = 'CREATING'; + const resp = await handler.isComplete(); + test.deepEqual(mocks.actualRequest.describeClusterRequest!.name, 'physical-resource-id'); + test.deepEqual(resp, { IsComplete: false }); + test.done(); + }, + + async 'isCreateComplete is complete when cluster is ACTIVE'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Create')); + mocks.simulateResponse.describeClusterResponseMockStatus = 'ACTIVE'; + const resp = await handler.isComplete(); + test.deepEqual(resp, { + IsComplete: true, + Data: { + Name: 'physical-resource-id', + Endpoint: 'http://endpoint', + Arn: 'arn:cluster-arn', + CertificateAuthorityData: 'certificateAuthority-data' + } + }); + test.done(); + }, + + }, + + delete: { + async 'returns correct physical name'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Delete')); + const resp = await handler.onEvent(); + test.deepEqual(mocks.actualRequest.deleteClusterRequest!.name, 'physical-resource-id'); + test.deepEqual(resp, { PhysicalResourceId: 'physical-resource-id' }); + test.done(); + }, + + async 'onDelete ignores ResourceNotFoundException'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Delete')); + mocks.simulateResponse.deleteClusterErrorCode = 'ResourceNotFoundException'; + await handler.onEvent(); + test.done(); + }, + + async 'isDeleteComplete returns false as long as describeCluster succeeds'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Delete')); + const resp = await handler.isComplete(); + test.deepEqual(mocks.actualRequest.describeClusterRequest!.name, 'physical-resource-id'); + test.ok(!resp.IsComplete); + test.done(); + }, + + async 'isDeleteComplete returns true when describeCluster throws a ResourceNotFound exception'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Delete')); + mocks.simulateResponse.describeClusterExceptionCode = 'ResourceNotFoundException'; + const resp = await handler.isComplete(); + test.ok(resp.IsComplete); + test.done(); + }, + + async 'isDeleteComplete propagates other errors'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Delete')); + mocks.simulateResponse.describeClusterExceptionCode = 'OtherException'; + let error; + try { + await handler.isComplete(); + } catch (e) { + error = e; + } + test.equal(error.code, 'OtherException'); + test.done(); + } + }, + + update: { + + async 'no change'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', mocks.MOCK_PROPS, mocks.MOCK_PROPS)); + const resp = await handler.onEvent(); + test.equal(resp, undefined); + test.equal(mocks.actualRequest.createClusterRequest, undefined); + test.equal(mocks.actualRequest.updateClusterConfigRequest, undefined); + test.equal(mocks.actualRequest.updateClusterVersionRequest, undefined); + test.done(); + }, + + async 'isUpdateComplete is not complete when status is not ACTIVE'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update')); + mocks.simulateResponse.describeClusterResponseMockStatus = 'UPDATING'; + const resp = await handler.isComplete(); + test.deepEqual(resp.IsComplete, false); + test.done(); + }, + + async 'isUpdateComplete waits for ACTIVE'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update')); + mocks.simulateResponse.describeClusterResponseMockStatus = 'ACTIVE'; + const resp = await handler.isComplete(); + test.deepEqual(resp.IsComplete, true); + test.done(); + }, + + 'requires replacement': { + + 'name change': { + + async 'explicit name change'(test: Test) { + // GIVEN + const req = mocks.newRequest('Update', { + ...mocks.MOCK_PROPS, + name: 'new-cluster-name-1234' + }, { + name: 'old-cluster-name' + }); + const handler = new ClusterResourceHandler(mocks.client, req); + + // WHEN + const resp = await handler.onEvent(); + + // THEN + test.deepEqual(mocks.actualRequest.createClusterRequest!, { + name: 'new-cluster-name-1234', + roleArn: 'arn:of:role', + resourcesVpcConfig: + { + subnetIds: ['subnet1', 'subnet2'], + securityGroupIds: ['sg1', 'sg2', 'sg3'] + } + }); + test.deepEqual(resp, { PhysicalResourceId: 'new-cluster-name-1234' }); + test.done(); + }, + + async 'from auto-gen name to explicit name'(test: Test) { + // GIVEN + const req = mocks.newRequest('Update', { + ...mocks.MOCK_PROPS, + name: undefined // auto-gen + }, { + name: 'explicit' // auto-gen + }); + + const handler = new ClusterResourceHandler(mocks.client, req); + + // WHEN + const resp = await handler.onEvent(); + + // THEN + test.deepEqual(mocks.actualRequest.createClusterRequest!, { + name: 'MyResourceId-fake-request-id', + roleArn: 'arn:of:role', + resourcesVpcConfig: + { + subnetIds: ['subnet1', 'subnet2'], + securityGroupIds: ['sg1', 'sg2', 'sg3'] + } + }); + test.deepEqual(resp, { PhysicalResourceId: 'MyResourceId-fake-request-id' }); + test.done(); + }, + + }, + + async 'subnets or security groups requires a replacement'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + ...mocks.MOCK_PROPS, + resourcesVpcConfig: { + subnetIds: ['subnet1', 'subnet2'], + securityGroupIds: ['sg1'] + } + }, { + ...mocks.MOCK_PROPS, + resourcesVpcConfig: { + subnetIds: ['subnet1'], + securityGroupIds: ['sg2'] + } + })); + const resp = await handler.onEvent(); + + test.deepEqual(resp, { PhysicalResourceId: 'MyResourceId-fake-request-id' }); + test.deepEqual(mocks.actualRequest.createClusterRequest, { + name: 'MyResourceId-fake-request-id', + roleArn: 'arn:of:role', + resourcesVpcConfig: + { + subnetIds: ['subnet1', 'subnet2'], + securityGroupIds: ['sg1'] + } + }); + test.done(); + }, + + async '"roleArn" requires a replcement'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + roleArn: 'new-arn' + }, { + roleArn: 'old-arn' + })); + const resp = await handler.onEvent(); + + test.deepEqual(resp, { PhysicalResourceId: 'MyResourceId-fake-request-id' }); + test.deepEqual(mocks.actualRequest.createClusterRequest, { + name: 'MyResourceId-fake-request-id', + roleArn: 'new-arn' + }); + test.done(); + }, + + async 'fails if cluster has an explicit "name" that is the same as the old "name"'(test: Test) { + // GIVEN + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + roleArn: 'new-arn', + name: 'explicit-cluster-name' + }, { + roleArn: 'old-arn', + name: 'explicit-cluster-name' + })); + + // THEN + let err; + try { + await handler.onEvent(); + } catch (e) { + err = e; + } + + test.equal(err?.message, 'Cannot replace cluster "explicit-cluster-name" since it has an explicit physical name. Either rename the cluster or remove the "name" configuration'); + test.done(); + }, + + async 'succeeds if cluster had an explicit "name" and now it does not'(test: Test) { + // GIVEN + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + roleArn: 'new-arn', + name: undefined // auto-gen + }, { + roleArn: 'old-arn', + name: 'explicit-cluster-name' + })); + + // WHEN + const resp = await handler.onEvent(); + + // THEN + test.deepEqual(resp, { PhysicalResourceId: 'MyResourceId-fake-request-id' }); + test.deepEqual(mocks.actualRequest.createClusterRequest, { + name: 'MyResourceId-fake-request-id', + roleArn: 'new-arn' + }); + test.done(); + }, + + async 'succeeds if cluster had an explicit "name" and now it has a different name'(test: Test) { + // GIVEN + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + roleArn: 'new-arn', + name: 'new-explicit-cluster-name' + }, { + roleArn: 'old-arn', + name: 'old-explicit-cluster-name' + })); + + // WHEN + const resp = await handler.onEvent(); + + // THEN + test.deepEqual(resp, { PhysicalResourceId: 'new-explicit-cluster-name' }); + test.deepEqual(mocks.actualRequest.createClusterRequest, { + name: 'new-explicit-cluster-name', + roleArn: 'new-arn' + }); + test.done(); + } + }, + + 'in-place': { + 'version change': { + async 'from undefined to a specific value'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + version: '12.34' + }, { + version: undefined + })); + const resp = await handler.onEvent(); + test.equal(resp, undefined); + test.deepEqual(mocks.actualRequest.updateClusterVersionRequest!, { + name: 'physical-resource-id', + version: '12.34' + }); + test.equal(mocks.actualRequest.createClusterRequest, undefined); + test.done(); + }, + + async 'from a specific value to another value'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + version: '2.0' + }, { + version: '1.1' + })); + const resp = await handler.onEvent(); + test.equal(resp, undefined); + test.deepEqual(mocks.actualRequest.updateClusterVersionRequest!, { + name: 'physical-resource-id', + version: '2.0' + }); + test.equal(mocks.actualRequest.createClusterRequest, undefined); + test.done(); + }, + + async 'to a new value that is already the current version'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + version: '1.0' + })); + const resp = await handler.onEvent(); + test.equal(resp, undefined); + test.deepEqual(mocks.actualRequest.describeClusterRequest, { name: 'physical-resource-id' }); + test.equal(mocks.actualRequest.updateClusterVersionRequest, undefined); + test.equal(mocks.actualRequest.createClusterRequest, undefined); + test.done(); + }, + + async 'fails from specific value to undefined'(test: Test) { + const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { + version: undefined + }, { + version: '1.2' + })); + let error; + try { + await handler.onEvent(); + } catch (e) { + error = e; + } + + test.equal(error.message, 'Cannot remove cluster version configuration. Current version is 1.2'); + test.done(); + } + } + }, + }, + +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index 63934c1319046..06a764f80d6a4 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -580,4 +580,212 @@ export = { ), 'EKS AMI with GPU should be in ssm parameters'); test.done(); }, + + 'when using custom resource a creation role & policy is defined'(test: Test) { + // GIVEN + const { stack } = testFixture(); + + // WHEN + new eks.Cluster(stack, 'MyCluster', { + clusterName: 'my-cluster-name' + }); + + // THEN + expect(stack).to(haveResource('Custom::AWSCDK-EKS-Cluster', { + Config: { + name: "my-cluster-name", + roleArn: { "Fn::GetAtt": [ "MyClusterRoleBA20FE72", "Arn" ] }, + resourcesVpcConfig: { + securityGroupIds: [ { "Fn::GetAtt": [ "MyClusterControlPlaneSecurityGroup6B658F79", "GroupId" ] } ], + subnetIds: [ + { Ref: "MyClusterDefaultVpcPublicSubnet1SubnetFAE5A9B6" }, + { Ref: "MyClusterDefaultVpcPublicSubnet2SubnetF6D028A0" }, + { Ref: "MyClusterDefaultVpcPrivateSubnet1SubnetE1D0DCDB" }, + { Ref: "MyClusterDefaultVpcPrivateSubnet2Subnet11FEA8D0" } + ] + } + } + })); + + // role can be assumed by 3 lambda handlers (2 for the cluster resource and 1 for the kubernetes resource) + expect(stack).to(haveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { + AWS: [ + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.StackawscdkawseksClusterResourceProviderOnEventHandlerServiceRole3AEE0A43Arn" + ] + }, + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.StackawscdkawseksClusterResourceProviderIsCompleteHandlerServiceRole8E7F1C11Arn" + ] + } + ] + } + }, + { + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { + AWS: { + "Fn::GetAtt": [ + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.StackawscdkawseksKubernetesResourceProviderHandlerServiceRole36007028Arn" + ] + } + } + } + ], + Version: "2012-10-17" + } + })); + + // policy allows creation role to pass the cluster role and to interact with the cluster (given we know the explicit cluster name) + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: "iam:PassRole", + Effect: "Allow", + Resource: { + "Fn::GetAtt": [ + "MyClusterRoleBA20FE72", + "Arn" + ] + } + }, + { + Action: [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion", + "eks:UpdateClusterConfig" + ], + Effect: "Allow", + Resource: { "Fn::Join": [ "", [ "arn:", { Ref: "AWS::Partition" }, ":eks:us-east-1:", { Ref: "AWS::AccountId" }, ":cluster/my-cluster-name" ] ] } + } + ], + Version: "2012-10-17" + } + })); + test.done(); + }, + + 'if an explicit cluster name is not provided, the creation role policy is wider (allows interacting with all clusters)'(test: Test) { + // GIVEN + const { stack } = testFixture(); + + // WHEN + new eks.Cluster(stack, 'MyCluster'); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: "iam:PassRole", + Effect: "Allow", + Resource: { + "Fn::GetAtt": [ + "MyClusterRoleBA20FE72", + "Arn" + ] + } + }, + { + Action: [ + "eks:CreateCluster", + "eks:DescribeCluster", + "eks:DeleteCluster", + "eks:UpdateClusterVersion", + "eks:UpdateClusterConfig" + ], + Effect: "Allow", + Resource: "*" + } + ], + Version: "2012-10-17" + } + })); + test.done(); + }, + + 'if helm charts are used, its resource provider is allowed to assume the creation role'(test: Test) { + // GIVEN + const { stack } = testFixture(); + const cluster = new eks.Cluster(stack, 'MyCluster', { + clusterName: 'my-cluster-name' + }); + + // WHEN + cluster.addChart('MyChart', { + chart: 'foo' + }); + + // THEN + + // role can be assumed by 4 principals: two for the cluster resource, one + // for kubernetes resource and one for the helm resource. + expect(stack).to(haveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { + AWS: [ + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.StackawscdkawseksClusterResourceProviderOnEventHandlerServiceRole3AEE0A43Arn" + ] + }, + { + "Fn::GetAtt": [ + "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", + "Outputs.StackawscdkawseksClusterResourceProviderIsCompleteHandlerServiceRole8E7F1C11Arn" + ] + } + ] + } + }, + { + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { + AWS: { + "Fn::GetAtt": [ + "awscdkawseksKubernetesResourceProviderNestedStackawscdkawseksKubernetesResourceProviderNestedStackResource1A5AAA66", + "Outputs.StackawscdkawseksKubernetesResourceProviderHandlerServiceRole36007028Arn" + ] + } + } + }, + { + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { + AWS: { + "Fn::GetAtt": [ + "awscdkawseksHelmResourceProviderNestedStackawscdkawseksHelmResourceProviderNestedStackResource5C12A9A9", + "Outputs.StackawscdkawseksHelmResourceProviderHandlerServiceRole83B6C3CEArn" + ] + } + } + } + ], + Version: "2012-10-17" + } + })); + test.done(); + } }; diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 943dc67daa546..0f0960f6fa4b8 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -161,7 +161,8 @@ "jsii-reflect": "^0.20.11", "jsonschema": "^1.2.5", "yaml": "1.7.2", - "yargs": "^15.0.1" + "yargs": "^15.0.1", + "@aws-cdk/aws-eks-legacy": "1.19.0" }, "devDependencies": { "@types/fs-extra": "^8.0.1",