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",