From 8e055d449808f97436b92b6d6e57f8053e289653 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Thu, 2 Jul 2020 14:48:00 +0300 Subject: [PATCH 01/28] feat(custom-resources): include handler log group in error messages (#8839) Adds the CloudWatch log group name to error messages emitted by user handlers so it's easier to go find additional information. Additionally, added descriptions to the provider framework lambda functions so they are easier to identify. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../test/integ.global.expected.json | 40 +- .../test/integ.eks-cluster.expected.json | 796 ++++++++++-------- .../lib/provider-framework/provider.ts | 1 + .../provider-framework/runtime/framework.ts | 26 +- .../integ.provider.expected.json | 42 +- .../test/provider-framework/runtime.test.ts | 6 +- 6 files changed, 526 insertions(+), 385 deletions(-) diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json b/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json index f2a65b428507b..2301663780e86 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.global.expected.json @@ -231,7 +231,7 @@ }, "/", { - "Ref": "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eS3Bucket09EBFB87" + "Ref": "AssetParameters51ac5fed1b824803906219cb11f0c17572739bd4fb019d961cdc1fbbfefc620eS3Bucket58C634A6" }, "/", { @@ -241,7 +241,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eS3VersionKey46B9F32F" + "Ref": "AssetParameters51ac5fed1b824803906219cb11f0c17572739bd4fb019d961cdc1fbbfefc620eS3VersionKeyE8ACA4C1" } ] } @@ -254,7 +254,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eS3VersionKey46B9F32F" + "Ref": "AssetParameters51ac5fed1b824803906219cb11f0c17572739bd4fb019d961cdc1fbbfefc620eS3VersionKeyE8ACA4C1" } ] } @@ -270,11 +270,11 @@ "referencetocdkdynamodbglobal20191121AssetParameters23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1S3VersionKey849A4FA5Ref": { "Ref": "AssetParameters23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1S3VersionKeyC7F72494" }, - "referencetocdkdynamodbglobal20191121AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket14A4D0F4Ref": { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" + "referencetocdkdynamodbglobal20191121AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket80086951Ref": { + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E" }, - "referencetocdkdynamodbglobal20191121AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKeyF9E42BF5Ref": { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" + "referencetocdkdynamodbglobal20191121AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKeyC3096D21Ref": { + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" } } } @@ -293,29 +293,29 @@ "Type": "String", "Description": "Artifact hash for asset \"23c5f8cc1bdef276fc20dbb166d24d1b7d8cb516a5d5822ed0d38feec9631fd1\"" }, - "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18": { + "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E": { "Type": "String", - "Description": "S3 bucket for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" + "Description": "S3 bucket for asset \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" }, - "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471": { + "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9": { "Type": "String", - "Description": "S3 key for asset version \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" + "Description": "S3 key for asset version \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" }, - "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6ArtifactHash3651BF53": { + "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3ArtifactHash2CBB11D2": { "Type": "String", - "Description": "Artifact hash for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" + "Description": "Artifact hash for asset \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" }, - "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eS3Bucket09EBFB87": { + "AssetParameters51ac5fed1b824803906219cb11f0c17572739bd4fb019d961cdc1fbbfefc620eS3Bucket58C634A6": { "Type": "String", - "Description": "S3 bucket for asset \"b83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3e\"" + "Description": "S3 bucket for asset \"51ac5fed1b824803906219cb11f0c17572739bd4fb019d961cdc1fbbfefc620e\"" }, - "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eS3VersionKey46B9F32F": { + "AssetParameters51ac5fed1b824803906219cb11f0c17572739bd4fb019d961cdc1fbbfefc620eS3VersionKeyE8ACA4C1": { "Type": "String", - "Description": "S3 key for asset version \"b83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3e\"" + "Description": "S3 key for asset version \"51ac5fed1b824803906219cb11f0c17572739bd4fb019d961cdc1fbbfefc620e\"" }, - "AssetParametersb83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3eArtifactHash55859AF2": { + "AssetParameters51ac5fed1b824803906219cb11f0c17572739bd4fb019d961cdc1fbbfefc620eArtifactHashDF827F41": { "Type": "String", - "Description": "Artifact hash for asset \"b83a25851fe809a45e4920961bf8456cf216e425943d42aaba01916e2f868a3e\"" + "Description": "Artifact hash for asset \"51ac5fed1b824803906219cb11f0c17572739bd4fb019d961cdc1fbbfefc620e\"" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index dfe7768ca82a4..b38417369d77d 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json @@ -4,24 +4,26 @@ "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Statement": [{ - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::12345678:root" + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::12345678:root" + ] ] - ] + } } } - }], + ], "Version": "2012-10-17" } } @@ -33,10 +35,12 @@ "EnableDnsHostnames": true, "EnableDnsSupport": true, "InstanceTenancy": "default", - "Tags": [{ - "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Vpc" - }] + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc" + } + ] } }, "VpcPublicSubnet1Subnet5C2D37C4": { @@ -48,7 +52,8 @@ }, "AvailabilityZone": "test-region-1a", "MapPublicIpOnLaunch": true, - "Tags": [{ + "Tags": [ + { "Key": "aws-cdk:subnet-name", "Value": "Public" }, @@ -73,7 +78,8 @@ "VpcId": { "Ref": "Vpc8378EB38" }, - "Tags": [{ + "Tags": [ + { "Key": "kubernetes.io/role/elb", "Value": "1" }, @@ -114,7 +120,8 @@ "Type": "AWS::EC2::EIP", "Properties": { "Domain": "vpc", - "Tags": [{ + "Tags": [ + { "Key": "kubernetes.io/role/elb", "Value": "1" }, @@ -137,7 +144,8 @@ "SubnetId": { "Ref": "VpcPublicSubnet1Subnet5C2D37C4" }, - "Tags": [{ + "Tags": [ + { "Key": "kubernetes.io/role/elb", "Value": "1" }, @@ -157,7 +165,8 @@ }, "AvailabilityZone": "test-region-1b", "MapPublicIpOnLaunch": true, - "Tags": [{ + "Tags": [ + { "Key": "aws-cdk:subnet-name", "Value": "Public" }, @@ -182,7 +191,8 @@ "VpcId": { "Ref": "Vpc8378EB38" }, - "Tags": [{ + "Tags": [ + { "Key": "kubernetes.io/role/elb", "Value": "1" }, @@ -228,7 +238,8 @@ }, "AvailabilityZone": "test-region-1c", "MapPublicIpOnLaunch": true, - "Tags": [{ + "Tags": [ + { "Key": "aws-cdk:subnet-name", "Value": "Public" }, @@ -253,7 +264,8 @@ "VpcId": { "Ref": "Vpc8378EB38" }, - "Tags": [{ + "Tags": [ + { "Key": "kubernetes.io/role/elb", "Value": "1" }, @@ -299,7 +311,8 @@ }, "AvailabilityZone": "test-region-1a", "MapPublicIpOnLaunch": false, - "Tags": [{ + "Tags": [ + { "Key": "aws-cdk:subnet-name", "Value": "Private" }, @@ -324,7 +337,8 @@ "VpcId": { "Ref": "Vpc8378EB38" }, - "Tags": [{ + "Tags": [ + { "Key": "kubernetes.io/role/internal-elb", "Value": "1" }, @@ -367,7 +381,8 @@ }, "AvailabilityZone": "test-region-1b", "MapPublicIpOnLaunch": false, - "Tags": [{ + "Tags": [ + { "Key": "aws-cdk:subnet-name", "Value": "Private" }, @@ -392,7 +407,8 @@ "VpcId": { "Ref": "Vpc8378EB38" }, - "Tags": [{ + "Tags": [ + { "Key": "kubernetes.io/role/internal-elb", "Value": "1" }, @@ -435,7 +451,8 @@ }, "AvailabilityZone": "test-region-1c", "MapPublicIpOnLaunch": false, - "Tags": [{ + "Tags": [ + { "Key": "aws-cdk:subnet-name", "Value": "Private" }, @@ -460,7 +477,8 @@ "VpcId": { "Ref": "Vpc8378EB38" }, - "Tags": [{ + "Tags": [ + { "Key": "kubernetes.io/role/internal-elb", "Value": "1" }, @@ -497,10 +515,12 @@ "VpcIGWD7BA715C": { "Type": "AWS::EC2::InternetGateway", "Properties": { - "Tags": [{ - "Key": "Name", - "Value": "aws-cdk-eks-cluster-test/Vpc" - }] + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-eks-cluster-test/Vpc" + } + ] } }, "VpcVPCGWBF912B6E": { @@ -518,38 +538,44 @@ "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Statement": [{ - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "eks.amazonaws.com" + "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" + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSClusterPolicy" + ] ] - ] - }] + } + ] } }, "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" - }], + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], "VpcId": { "Ref": "Vpc8378EB38" } @@ -643,11 +669,13 @@ "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Statement": [{ + "Statement": [ + { "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "AWS": [{ + "AWS": [ + { "Fn::GetAtt": [ "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454", "Outputs.awscdkeksclustertestawscdkawseksClusterResourceProviderOnEventHandlerServiceRole5B783C71Arn" @@ -683,7 +711,8 @@ "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { - "Statement": [{ + "Statement": [ + { "Action": "iam:PassRole", "Effect": "Allow", "Resource": { @@ -750,9 +779,11 @@ "Version": "2012-10-17" }, "PolicyName": "ClusterCreationRoleDefaultPolicyE8BDFC7B", - "Roles": [{ - "Ref": "ClusterCreationRole360249B6" - }] + "Roles": [ + { + "Ref": "ClusterCreationRole360249B6" + } + ] } }, "Cluster9EE0221C": { @@ -773,13 +804,16 @@ }, "version": "1.16", "resourcesVpcConfig": { - "securityGroupIds": [{ - "Fn::GetAtt": [ - "ClusterControlPlaneSecurityGroupD274242C", - "GroupId" - ] - }], - "subnetIds": [{ + "securityGroupIds": [ + { + "Fn::GetAtt": [ + "ClusterControlPlaneSecurityGroupD274242C", + "GroupId" + ] + } + ], + "subnetIds": [ + { "Ref": "VpcPublicSubnet1Subnet5C2D37C4" }, { @@ -898,26 +932,29 @@ "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Statement": [{ - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "ec2.", - { - "Ref": "AWS::URLSuffix" - } + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] ] - ] + } } } - }], + ], "Version": "2012-10-17" }, - "ManagedPolicyArns": [{ + "ManagedPolicyArns": [ + { "Fn::Join": [ "", [ @@ -968,7 +1005,8 @@ "Arn" ] }, - "Subnets": [{ + "Subnets": [ + { "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { @@ -993,27 +1031,31 @@ "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Statement": [{ - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "eks-fargate-pods.amazonaws.com" + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "eks-fargate-pods.amazonaws.com" + } } - }], + ], "Version": "2012-10-17" }, - "ManagedPolicyArns": [{ - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy" + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy" + ] ] - ] - }] + } + ] } }, "ClusterfargateprofiledefaultEFC59F14": { @@ -1041,9 +1083,11 @@ "Arn" ] }, - "selectors": [{ - "namespace": "default" - }] + "selectors": [ + { + "namespace": "default" + } + ] } }, "UpdateReplacePolicy": "Delete", @@ -1053,12 +1097,15 @@ "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "aws-cdk-eks-cluster-test/Cluster/Nodes/InstanceSecurityGroup", - "SecurityGroupEgress": [{ - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" - }], - "Tags": [{ + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -1147,26 +1194,29 @@ "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Statement": [{ - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "ec2.", - { - "Ref": "AWS::URLSuffix" - } + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] ] - ] + } } } - }], + ], "Version": "2012-10-17" }, - "ManagedPolicyArns": [{ + "ManagedPolicyArns": [ + { "Fn::Join": [ "", [ @@ -1203,7 +1253,8 @@ ] } ], - "Tags": [{ + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -1227,9 +1278,11 @@ "ClusterNodesInstanceProfileF2DD0E21": { "Type": "AWS::IAM::InstanceProfile", "Properties": { - "Roles": [{ - "Ref": "ClusterNodesInstanceRoleC3C01328" - }] + "Roles": [ + { + "Ref": "ClusterNodesInstanceRoleC3C01328" + } + ] } }, "ClusterNodesLaunchConfig7C420A27": { @@ -1242,12 +1295,14 @@ "IamInstanceProfile": { "Ref": "ClusterNodesInstanceProfileF2DD0E21" }, - "SecurityGroups": [{ - "Fn::GetAtt": [ - "ClusterNodesInstanceSecurityGroup899246BD", - "GroupId" - ] - }], + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ClusterNodesInstanceSecurityGroup899246BD", + "GroupId" + ] + } + ], "UserData": { "Fn::Base64": { "Fn::Join": [ @@ -1275,7 +1330,8 @@ "LaunchConfigurationName": { "Ref": "ClusterNodesLaunchConfig7C420A27" }, - "Tags": [{ + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -1296,7 +1352,8 @@ "Value": "aws-cdk-eks-cluster-test/Cluster/Nodes" } ], - "VPCZoneIdentifier": [{ + "VPCZoneIdentifier": [ + { "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { @@ -1328,12 +1385,15 @@ "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "aws-cdk-eks-cluster-test/Cluster/BottlerocketNodes/InstanceSecurityGroup", - "SecurityGroupEgress": [{ - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" - }], - "Tags": [{ + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -1422,26 +1482,29 @@ "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Statement": [{ - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "ec2.", - { - "Ref": "AWS::URLSuffix" - } + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] ] - ] + } } } - }], + ], "Version": "2012-10-17" }, - "ManagedPolicyArns": [{ + "ManagedPolicyArns": [ + { "Fn::Join": [ "", [ @@ -1478,7 +1541,8 @@ ] } ], - "Tags": [{ + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -1502,9 +1566,11 @@ "ClusterBottlerocketNodesInstanceProfileB6E2F25A": { "Type": "AWS::IAM::InstanceProfile", "Properties": { - "Roles": [{ - "Ref": "ClusterBottlerocketNodesInstanceRole68E4BCFB" - }] + "Roles": [ + { + "Ref": "ClusterBottlerocketNodesInstanceRole68E4BCFB" + } + ] } }, "ClusterBottlerocketNodesLaunchConfig76D7BEBE": { @@ -1517,12 +1583,14 @@ "IamInstanceProfile": { "Ref": "ClusterBottlerocketNodesInstanceProfileB6E2F25A" }, - "SecurityGroups": [{ - "Fn::GetAtt": [ - "ClusterBottlerocketNodesInstanceSecurityGroup3794A94B", - "GroupId" - ] - }], + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ClusterBottlerocketNodesInstanceSecurityGroup3794A94B", + "GroupId" + ] + } + ], "UserData": { "Fn::Base64": { "Fn::Join": [ @@ -1564,7 +1632,8 @@ "LaunchConfigurationName": { "Ref": "ClusterBottlerocketNodesLaunchConfig76D7BEBE" }, - "Tags": [{ + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -1585,7 +1654,8 @@ "Value": "aws-cdk-eks-cluster-test/Cluster/BottlerocketNodes" } ], - "VPCZoneIdentifier": [{ + "VPCZoneIdentifier": [ + { "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { @@ -1617,12 +1687,15 @@ "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "aws-cdk-eks-cluster-test/Cluster/spot/InstanceSecurityGroup", - "SecurityGroupEgress": [{ - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" - }], - "Tags": [{ + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -1711,26 +1784,29 @@ "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Statement": [{ - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "ec2.", - { - "Ref": "AWS::URLSuffix" - } + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] ] - ] + } } } - }], + ], "Version": "2012-10-17" }, - "ManagedPolicyArns": [{ + "ManagedPolicyArns": [ + { "Fn::Join": [ "", [ @@ -1767,7 +1843,8 @@ ] } ], - "Tags": [{ + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -1791,9 +1868,11 @@ "ClusterspotInstanceProfileAB88D077": { "Type": "AWS::IAM::InstanceProfile", "Properties": { - "Roles": [{ - "Ref": "ClusterspotInstanceRole39043830" - }] + "Roles": [ + { + "Ref": "ClusterspotInstanceRole39043830" + } + ] } }, "ClusterspotLaunchConfigCC19F2E6": { @@ -1806,12 +1885,14 @@ "IamInstanceProfile": { "Ref": "ClusterspotInstanceProfileAB88D077" }, - "SecurityGroups": [{ - "Fn::GetAtt": [ - "ClusterspotInstanceSecurityGroup01F7B1CE", - "GroupId" - ] - }], + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ClusterspotInstanceSecurityGroup01F7B1CE", + "GroupId" + ] + } + ], "SpotPrice": "0.1094", "UserData": { "Fn::Base64": { @@ -1840,7 +1921,8 @@ "LaunchConfigurationName": { "Ref": "ClusterspotLaunchConfigCC19F2E6" }, - "Tags": [{ + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -1861,7 +1943,8 @@ "Value": "aws-cdk-eks-cluster-test/Cluster/spot" } ], - "VPCZoneIdentifier": [{ + "VPCZoneIdentifier": [ + { "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { @@ -1922,12 +2005,15 @@ "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "aws-cdk-eks-cluster-test/Cluster/InferenceInstances/InstanceSecurityGroup", - "SecurityGroupEgress": [{ - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" - }], - "Tags": [{ + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -2016,26 +2102,29 @@ "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Statement": [{ - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "ec2.", - { - "Ref": "AWS::URLSuffix" - } + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] ] - ] + } } } - }], + ], "Version": "2012-10-17" }, - "ManagedPolicyArns": [{ + "ManagedPolicyArns": [ + { "Fn::Join": [ "", [ @@ -2072,7 +2161,8 @@ ] } ], - "Tags": [{ + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -2096,9 +2186,11 @@ "ClusterInferenceInstancesInstanceProfile5A1209B4": { "Type": "AWS::IAM::InstanceProfile", "Properties": { - "Roles": [{ - "Ref": "ClusterInferenceInstancesInstanceRole59AC6F56" - }] + "Roles": [ + { + "Ref": "ClusterInferenceInstancesInstanceRole59AC6F56" + } + ] } }, "ClusterInferenceInstancesLaunchConfig03BF48FE": { @@ -2111,12 +2203,14 @@ "IamInstanceProfile": { "Ref": "ClusterInferenceInstancesInstanceProfile5A1209B4" }, - "SecurityGroups": [{ - "Fn::GetAtt": [ - "ClusterInferenceInstancesInstanceSecurityGroupECB3FC45", - "GroupId" - ] - }], + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ClusterInferenceInstancesInstanceSecurityGroupECB3FC45", + "GroupId" + ] + } + ], "UserData": { "Fn::Base64": { "Fn::Join": [ @@ -2144,7 +2238,8 @@ "LaunchConfigurationName": { "Ref": "ClusterInferenceInstancesLaunchConfig03BF48FE" }, - "Tags": [{ + "Tags": [ + { "Key": { "Fn::Join": [ "", @@ -2165,7 +2260,8 @@ "Value": "aws-cdk-eks-cluster-test/Cluster/InferenceInstances" } ], - "VPCZoneIdentifier": [{ + "VPCZoneIdentifier": [ + { "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { @@ -2220,26 +2316,29 @@ "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Statement": [{ - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "ec2.", - { - "Ref": "AWS::URLSuffix" - } + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] ] - ] + } } } - }], + ], "Version": "2012-10-17" }, - "ManagedPolicyArns": [{ + "ManagedPolicyArns": [ + { "Fn::Join": [ "", [ @@ -2290,7 +2389,8 @@ "Arn" ] }, - "Subnets": [{ + "Subnets": [ + { "Ref": "VpcPrivateSubnet1Subnet536B997A" }, { @@ -2454,23 +2554,25 @@ "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Statement": [{ - "Action": "sts:AssumeRoleWithWebIdentity", - "Condition": { - "StringEquals": { - "Fn::GetAtt": [ - "ClusterMyServiceAccountConditionJson671C0633", - "Value" - ] - } - }, - "Effect": "Allow", - "Principal": { - "Federated": { - "Ref": "ClusterOpenIdConnectProviderE7EB0530" + "Statement": [ + { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "Fn::GetAtt": [ + "ClusterMyServiceAccountConditionJson671C0633", + "Value" + ] + } + }, + "Effect": "Allow", + "Principal": { + "Federated": { + "Ref": "ClusterOpenIdConnectProviderE7EB0530" + } } } - }], + ], "Version": "2012-10-17" } } @@ -2550,7 +2652,7 @@ }, "/", { - "Ref": "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896S3BucketA0BEF119" + "Ref": "AssetParameters5215f685494c7a295ec1b06b713a041a82e7ac216473965711a88e32405e9053S3Bucket50B33A86" }, "/", { @@ -2560,7 +2662,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896S3VersionKey639A5314" + "Ref": "AssetParameters5215f685494c7a295ec1b06b713a041a82e7ac216473965711a88e32405e9053S3VersionKey1FB82B13" } ] } @@ -2573,7 +2675,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896S3VersionKey639A5314" + "Ref": "AssetParameters5215f685494c7a295ec1b06b713a041a82e7ac216473965711a88e32405e9053S3VersionKey1FB82B13" } ] } @@ -2589,11 +2691,11 @@ "referencetoawscdkeksclustertestAssetParametersc23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03S3VersionKeyAC8DDB71Ref": { "Ref": "AssetParametersc23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03S3VersionKeyEFEE8BE5" }, - "referencetoawscdkeksclustertestAssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket8FECC379Ref": { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" + "referencetoawscdkeksclustertestAssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket8E231383Ref": { + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E" }, - "referencetoawscdkeksclustertestAssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey0555F20FRef": { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" + "referencetoawscdkeksclustertestAssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey33D81F32Ref": { + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" } } } @@ -2611,7 +2713,7 @@ }, "/", { - "Ref": "AssetParametersf6c9f832ab537bf67a2e6f11d920c2b7462186e1dc599dc92a7ada76f087b3bfS3BucketAD1A1343" + "Ref": "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762S3Bucket8B322431" }, "/", { @@ -2621,7 +2723,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf6c9f832ab537bf67a2e6f11d920c2b7462186e1dc599dc92a7ada76f087b3bfS3VersionKeyA6C46C4E" + "Ref": "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762S3VersionKey3D736637" } ] } @@ -2634,7 +2736,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf6c9f832ab537bf67a2e6f11d920c2b7462186e1dc599dc92a7ada76f087b3bfS3VersionKeyA6C46C4E" + "Ref": "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762S3VersionKey3D736637" } ] } @@ -2650,11 +2752,11 @@ "referencetoawscdkeksclustertestAssetParameters2d65340a9414c04d1844e421bd328aa3b80015d6a02e74afe9a222168b2ba050S3VersionKey4E1E47F7Ref": { "Ref": "AssetParameters2d65340a9414c04d1844e421bd328aa3b80015d6a02e74afe9a222168b2ba050S3VersionKeyF3400812" }, - "referencetoawscdkeksclustertestAssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket8FECC379Ref": { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" + "referencetoawscdkeksclustertestAssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket8E231383Ref": { + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E" }, - "referencetoawscdkeksclustertestAssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey0555F20FRef": { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" + "referencetoawscdkeksclustertestAssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey33D81F32Ref": { + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" } } } @@ -2664,17 +2766,21 @@ "Properties": { "AssumeRolePolicyDocument": { "Version": "2012-10-17", - "Statement": [{ - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } } - }] + ] }, - "ManagedPolicyArns": [{ - "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - }] + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] } }, "AWSCDKCfnUtilsProviderCustomResourceProviderHandlerCF82AA57": { @@ -2687,7 +2793,8 @@ "S3Key": { "Fn::Join": [ "", - [{ + [ + { "Fn::Select": [ 0, { @@ -2737,34 +2844,42 @@ "Properties": { "AssumeRolePolicyDocument": { "Version": "2012-10-17", - "Statement": [{ - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - }] - }, - "ManagedPolicyArns": [{ - "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - }], - "Policies": [{ - "PolicyName": "Inline", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ + "Statement": [ + { + "Action": "sts:AssumeRole", "Effect": "Allow", - "Resource": "*", - "Action": [ - "iam:CreateOpenIDConnectProvider", - "iam:DeleteOpenIDConnectProvider", - "iam:UpdateOpenIDConnectProviderThumbprint", - "iam:AddClientIDToOpenIDConnectProvider", - "iam:RemoveClientIDFromOpenIDConnectProvider" + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": "*", + "Action": [ + "iam:CreateOpenIDConnectProvider", + "iam:DeleteOpenIDConnectProvider", + "iam:UpdateOpenIDConnectProviderThumbprint", + "iam:AddClientIDToOpenIDConnectProvider", + "iam:RemoveClientIDFromOpenIDConnectProvider" + ] + } ] - }] + } } - }] + ] } }, "CustomAWSCDKOpenIdConnectProviderCustomResourceProviderHandlerF2C543E0": { @@ -2777,7 +2892,8 @@ "S3Key": { "Fn::Join": [ "", - [{ + [ + { "Fn::Select": [ 0, { @@ -2923,17 +3039,17 @@ "Type": "String", "Description": "Artifact hash for asset \"c23ce59a47ffb1e28812148fb83f7dcb0d94f1f0286e122a2f1aa189c0b35d03\"" }, - "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18": { + "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E": { "Type": "String", - "Description": "S3 bucket for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" + "Description": "S3 bucket for asset \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" }, - "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471": { + "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9": { "Type": "String", - "Description": "S3 key for asset version \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" + "Description": "S3 key for asset version \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" }, - "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6ArtifactHash3651BF53": { + "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3ArtifactHash2CBB11D2": { "Type": "String", - "Description": "Artifact hash for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" + "Description": "Artifact hash for asset \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" }, "AssetParameters2d65340a9414c04d1844e421bd328aa3b80015d6a02e74afe9a222168b2ba050S3Bucket0EAA682D": { "Type": "String", @@ -2971,29 +3087,29 @@ "Type": "String", "Description": "Artifact hash for asset \"ea46702e1c05b2735e48e826d630f7bf6acdf7e55d6fa8d9fa8df858d5542161\"" }, - "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896S3BucketA0BEF119": { + "AssetParameters5215f685494c7a295ec1b06b713a041a82e7ac216473965711a88e32405e9053S3Bucket50B33A86": { "Type": "String", - "Description": "S3 bucket for asset \"7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896\"" + "Description": "S3 bucket for asset \"5215f685494c7a295ec1b06b713a041a82e7ac216473965711a88e32405e9053\"" }, - "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896S3VersionKey639A5314": { + "AssetParameters5215f685494c7a295ec1b06b713a041a82e7ac216473965711a88e32405e9053S3VersionKey1FB82B13": { "Type": "String", - "Description": "S3 key for asset version \"7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896\"" + "Description": "S3 key for asset version \"5215f685494c7a295ec1b06b713a041a82e7ac216473965711a88e32405e9053\"" }, - "AssetParameters7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896ArtifactHash8FB2ECC5": { + "AssetParameters5215f685494c7a295ec1b06b713a041a82e7ac216473965711a88e32405e9053ArtifactHash599411DD": { "Type": "String", - "Description": "Artifact hash for asset \"7255958d303a278986617e8d7ce027c96cd00a70fb8f1ca02c1e0da94a571896\"" + "Description": "Artifact hash for asset \"5215f685494c7a295ec1b06b713a041a82e7ac216473965711a88e32405e9053\"" }, - "AssetParametersf6c9f832ab537bf67a2e6f11d920c2b7462186e1dc599dc92a7ada76f087b3bfS3BucketAD1A1343": { + "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762S3Bucket8B322431": { "Type": "String", - "Description": "S3 bucket for asset \"f6c9f832ab537bf67a2e6f11d920c2b7462186e1dc599dc92a7ada76f087b3bf\"" + "Description": "S3 bucket for asset \"159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762\"" }, - "AssetParametersf6c9f832ab537bf67a2e6f11d920c2b7462186e1dc599dc92a7ada76f087b3bfS3VersionKeyA6C46C4E": { + "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762S3VersionKey3D736637": { "Type": "String", - "Description": "S3 key for asset version \"f6c9f832ab537bf67a2e6f11d920c2b7462186e1dc599dc92a7ada76f087b3bf\"" + "Description": "S3 key for asset version \"159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762\"" }, - "AssetParametersf6c9f832ab537bf67a2e6f11d920c2b7462186e1dc599dc92a7ada76f087b3bfArtifactHash0C55941B": { + "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762ArtifactHash56F3AB07": { "Type": "String", - "Description": "Artifact hash for asset \"f6c9f832ab537bf67a2e6f11d920c2b7462186e1dc599dc92a7ada76f087b3bf\"" + "Description": "Artifact hash for asset \"159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762\"" }, "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", @@ -3008,4 +3124,4 @@ "Default": "/aws/service/eks/optimized-ami/1.16/amazon-linux-2-gpu/recommended/image_id" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts index 753a75f3a876b..e420c5c6e8c38 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts @@ -134,6 +134,7 @@ export class Provider extends Construct implements cfn.ICustomResourceProvider { private createFunction(entrypoint: string) { const fn = new lambda.Function(this, `framework-${entrypoint}`, { code: lambda.Code.fromAsset(RUNTIME_HANDLER_PATH), + description: `AWS CDK resource provider framework - ${entrypoint} (${this.node.path})`.slice(0, 256), runtime: lambda.Runtime.NODEJS_10_X, handler: `framework.${entrypoint}`, timeout: FRAMEWORK_HANDLER_TIMEOUT, diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts index 2bd4116caa283..7777bfcb2b70d 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts @@ -109,11 +109,31 @@ async function invokeUserFunction(functionArnEnv: string, payload: any) { const jsonPayload = parseJsonPayload(resp.Payload); if (resp.FunctionError) { log('user function threw an error:', resp.FunctionError); + const errorMessage = jsonPayload.errorMessage || 'error'; - const trace = jsonPayload.trace ? '\nRemote function error: ' + jsonPayload.trace.join('\n') : ''; - const e = new Error(errorMessage); - e.stack += trace; + // parse function name from arn + // arn:${Partition}:lambda:${Region}:${Account}:function:${FunctionName} + const arn = functionArn.split(':'); + const functionName = arn[arn.length - 1]; + + // append a reference to the log group. + const message = [ + errorMessage, + '', + `Logs: /aws/lambda/${functionName}`, // cloudwatch log group + '', + ].join('\n'); + + const e = new Error(message); + + // the output that goes to CFN is what's in `stack`, not the error message. + // if we have a remote trace, construct a nice message with log group information + if (jsonPayload.trace) { + // skip first trace line because it's the message + e.stack = [ message, ...jsonPayload.trace.slice(1) ].join('\n'); + } + throw e; } diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json index 38ac259c2254a..e2f123073b19e 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json @@ -200,7 +200,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E" }, "S3Key": { "Fn::Join": [ @@ -213,7 +213,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" } ] } @@ -226,7 +226,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" } ] } @@ -244,6 +244,7 @@ ] }, "Runtime": "nodejs10.x", + "Description": "AWS CDK resource provider framework - onEvent (integ-provider-framework/com.amazonaws.cdk.custom-resources.s3file-provider/s3file-provider)", "Environment": { "Variables": { "USER_ON_EVENT_FUNCTION_ARN": { @@ -579,7 +580,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E" }, "S3Key": { "Fn::Join": [ @@ -592,7 +593,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" } ] } @@ -605,7 +606,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" } ] } @@ -623,6 +624,7 @@ ] }, "Runtime": "nodejs10.x", + "Description": "AWS CDK resource provider framework - onEvent (integ-provider-framework/com.amazonaws.cdk.custom-resources.s3assert-provider/s3assert-provider)", "Environment": { "Variables": { "USER_ON_EVENT_FUNCTION_ARN": { @@ -721,7 +723,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E" }, "S3Key": { "Fn::Join": [ @@ -734,7 +736,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" } ] } @@ -747,7 +749,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" } ] } @@ -765,6 +767,7 @@ ] }, "Runtime": "nodejs10.x", + "Description": "AWS CDK resource provider framework - isComplete (integ-provider-framework/com.amazonaws.cdk.custom-resources.s3assert-provider/s3assert-provider)", "Environment": { "Variables": { "USER_ON_EVENT_FUNCTION_ARN": { @@ -860,7 +863,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E" }, "S3Key": { "Fn::Join": [ @@ -873,7 +876,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" } ] } @@ -886,7 +889,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471" + "Ref": "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9" } ] } @@ -904,6 +907,7 @@ ] }, "Runtime": "nodejs10.x", + "Description": "AWS CDK resource provider framework - onTimeout (integ-provider-framework/com.amazonaws.cdk.custom-resources.s3assert-provider/s3assert-provider)", "Environment": { "Variables": { "USER_ON_EVENT_FUNCTION_ARN": { @@ -1042,17 +1046,17 @@ "Type": "String", "Description": "Artifact hash for asset \"fd43c0b4c0b961743009872f92ea51e922be342a344c19b9a8dd693f494d8da2\"" }, - "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3Bucket03CDDE18": { + "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3Bucket0EEA1C2E": { "Type": "String", - "Description": "S3 bucket for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" + "Description": "S3 bucket for asset \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" }, - "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6S3VersionKey68B2E471": { + "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3S3VersionKey7BCE18C9": { "Type": "String", - "Description": "S3 key for asset version \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" + "Description": "S3 key for asset version \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" }, - "AssetParameters5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6ArtifactHash3651BF53": { + "AssetParameters956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3ArtifactHash2CBB11D2": { "Type": "String", - "Description": "Artifact hash for asset \"5d5280180ad87e8a1c2a08423cb5b2dae41281832799cd51db5eff913091ade6\"" + "Description": "Artifact hash for asset \"956c2f92ddbde06f551fdf914445c679dcadb21c6e8d1ee9c9632144ef5a2ad3\"" }, "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3Bucket0DB889DF": { "Type": "String", @@ -1085,4 +1089,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts index 3898246fab6c4..911d51f234666 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts @@ -86,7 +86,7 @@ test('isComplete throws in the first invocation', async () => { ResourceProperties: MOCK_PROPS, }); - expectCloudFormationFailed('Some failure'); + expectCloudFormationFailed('Some failure\n\nLogs: /aws/lambda/complete\n'); }); test('fails gracefully if "onEvent" throws an error', async () => { @@ -100,7 +100,7 @@ test('fails gracefully if "onEvent" throws an error', async () => { }); // THEN - expectCloudFormationFailed('error thrown during onEvent'); + expectCloudFormationFailed('error thrown during onEvent\n\nLogs: /aws/lambda/event\n'); expectNoWaiter(); }); @@ -254,7 +254,7 @@ describe('if CREATE fails, the subsequent DELETE will be ignored', () => { RequestType: 'Create', }); - expectCloudFormationFailed('CREATE FAILED', { + expectCloudFormationFailed('CREATE FAILED\n\nLogs: /aws/lambda/event\n', { PhysicalResourceId: cfnResponse.CREATE_FAILED_PHYSICAL_ID_MARKER, }); }); From 4fad9bcbd75702e89eea02a140aa010f8952329a Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Thu, 2 Jul 2020 15:21:10 +0300 Subject: [PATCH 02/28] fix(eks): kubectl resources fail before fargate profiles are created (#8859) When a Fargate profile is being created, the Kubernetes API server in EKS sometimes rejects requests. This means that kubectl-related resources such as KubernetesResources Helm charts may fail during deployment. To address this, we add a "barrier resource" (in the form of an SSM parameter) which waits for all fargate profiles to be created before allowing kubectl resources to continue. This is done by the barrier taking a dependency on all FargateProfile resources and all kubectl resources taking a dependency on the barrier. Fixes #8854 This commit also fixes #8574 by adding `iam:ListAttachedRolePolicies` to the cluster's creation role IAM policy. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-eks/lib/cluster-resource.ts | 2 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 88 +++++++++++++++---- packages/@aws-cdk/aws-eks/lib/helm-chart.ts | 2 +- packages/@aws-cdk/aws-eks/lib/k8s-patch.ts | 2 +- packages/@aws-cdk/aws-eks/lib/k8s-resource.ts | 2 +- .../@aws-cdk/aws-eks/lib/kubectl-provider.ts | 1 + .../test/integ.eks-cluster.expected.json | 59 ++++++++++--- .../@aws-cdk/aws-eks/test/test.cluster.ts | 83 ++++++++++++++++- .../@aws-cdk/aws-eks/test/test.fargate.ts | 2 +- 9 files changed, 206 insertions(+), 35 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts index 745dcc799b954..13b493808d0b0 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts @@ -104,7 +104,7 @@ export class ClusterResource extends Construct { })); this.creationRole.addToPolicy(new iam.PolicyStatement({ - actions: [ 'iam:GetRole' ], + actions: [ 'iam:GetRole', 'iam:listAttachedRolePolicies' ], resources: [ '*' ], })); diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 5cf344f067f75..914269ac0b393 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -2,7 +2,7 @@ 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 ssm from '@aws-cdk/aws-ssm'; -import { CfnOutput, Construct, IResource, Resource, Stack, Tag, Token } from '@aws-cdk/core'; +import { CfnOutput, CfnResource, Construct, IResource, Resource, Stack, Tag, Token } from '@aws-cdk/core'; import * as fs from 'fs'; import * as path from 'path'; import * as YAML from 'yaml'; @@ -392,6 +392,20 @@ export class Cluster extends Resource implements ICluster { private readonly version: string | undefined; + /** + * A dummy CloudFormation resource that is used as a wait barrier which + * represents that the cluster is ready to receive "kubectl" commands. + * + * Specifically, all fargate profiles are automatically added as a dependency + * of this barrier, which means that it will only be "signaled" when all + * fargate profiles have been successfully created. + * + * When kubectl resources call `_attachKubectlResourceScope()`, this resource + * is added as their dependency which implies that they can only be deployed + * after the cluster is ready. + */ + private readonly _kubectlReadyBarrier?: CfnResource; + /** * Initiates an EKS Cluster with the supplied arguments * @@ -448,6 +462,18 @@ export class Cluster extends Resource implements ICluster { if (this.kubectlEnabled) { resource = new ClusterResource(this, 'Resource', clusterProps); this._clusterResource = resource; + + // we use an SSM parameter as a barrier because it's free and fast. + this._kubectlReadyBarrier = new CfnResource(this, 'KubectlReadyBarrier', { + type: 'AWS::SSM::Parameter', + properties: { + Type: 'String', + Value: 'aws:cdk:eks:kubectl-ready', + }, + }); + + // add the cluster resource itself as a dependency of the barrier + this._kubectlReadyBarrier.node.addDependency(this._clusterResource); } else { resource = new CfnCluster(this, 'Resource', clusterProps); } @@ -502,7 +528,7 @@ export class Cluster extends Resource implements ICluster { } if (this.kubectlEnabled) { - this.defineCoreDnsComputeType(props.coreDnsComputeType || CoreDnsComputeType.EC2); + this.defineCoreDnsComputeType(props.coreDnsComputeType ?? CoreDnsComputeType.EC2); } } @@ -794,33 +820,59 @@ export class Cluster extends Resource implements ICluster { } /** - * Returns the custom resource provider for kubectl-related resources. + * Internal API used by `FargateProfile` to keep inventory of Fargate profiles associated with + * this cluster, for the sake of ensuring the profiles are created sequentially. + * + * @returns the list of FargateProfiles attached to this cluster, including the one just attached. + * @internal + */ + public _attachFargateProfile(fargateProfile: FargateProfile): FargateProfile[] { + this._fargateProfiles.push(fargateProfile); + + // add all profiles as a dependency of the "kubectl-ready" barrier because all kubectl- + // resources can only be deployed after all fargate profiles are created. + if (this._kubectlReadyBarrier) { + this._kubectlReadyBarrier.node.addDependency(fargateProfile); + } + + return this._fargateProfiles; + } + + /** + * Adds a resource scope that requires `kubectl` to this cluster and returns + * the `KubectlProvider` which is the custom resource provider that should be + * used as the resource provider. + * + * Called from `HelmResource` and `KubernetesResource` + * + * @property resourceScope the construct scope in which kubectl resources are defined. + * * @internal */ - public get _kubectlProvider(): KubectlProvider { + public _attachKubectlResourceScope(resourceScope: Construct): KubectlProvider { + const uid = '@aws-cdk/aws-eks.KubectlProvider'; + if (!this._clusterResource) { throw new Error('Unable to perform this operation since kubectl is not enabled for this cluster'); } - const uid = '@aws-cdk/aws-eks.KubectlProvider'; - const provider = this.stack.node.tryFindChild(uid) as KubectlProvider || new KubectlProvider(this.stack, uid); + // singleton + let provider = this.stack.node.tryFindChild(uid) as KubectlProvider; + if (!provider) { + // create the provider. + provider = new KubectlProvider(this.stack, uid); + } // allow the kubectl provider to assume the cluster creation role. this._clusterResource.addTrustedRole(provider.role); - return provider; - } + if (!this._kubectlReadyBarrier) { + throw new Error('unexpected: kubectl enabled clusters should have a kubectl-ready barrier resource'); + } - /** - * Internal API used by `FargateProfile` to keep inventory of Fargate profiles associated with - * this cluster, for the sake of ensuring the profiles are created sequentially. - * - * @returns the list of FargateProfiles attached to this cluster, including the one just attached. - * @internal - */ - public _attachFargateProfile(fargateProfile: FargateProfile): FargateProfile[] { - this._fargateProfiles.push(fargateProfile); - return this._fargateProfiles; + resourceScope.node.addDependency(this._kubectlReadyBarrier); + + return provider; } /** diff --git a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts index cb668784129fe..b46f0f8bbe09d 100644 --- a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts @@ -89,7 +89,7 @@ export class HelmChart extends Construct { const stack = Stack.of(this); - const provider = props.cluster._kubectlProvider; + const provider = props.cluster._attachKubectlResourceScope(this); const timeout = props.timeout?.toSeconds(); if (timeout && timeout > 900) { diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts b/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts index 1c819a3b36968..63901ee7917a0 100644 --- a/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts +++ b/packages/@aws-cdk/aws-eks/lib/k8s-patch.ts @@ -70,7 +70,7 @@ export class KubernetesPatch extends Construct { super(scope, id); const stack = Stack.of(this); - const provider = props.cluster._kubectlProvider; + const provider = props.cluster._attachKubectlResourceScope(this); new CustomResource(this, 'Resource', { serviceToken: provider.serviceToken, diff --git a/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts b/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts index 3a58cfacff102..ae0fa409c8727 100644 --- a/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/k8s-resource.ts @@ -54,7 +54,7 @@ export class KubernetesResource extends Construct { super(scope, id); const stack = Stack.of(this); - const provider = props.cluster._kubectlProvider; + const provider = props.cluster._attachKubectlResourceScope(this); new CustomResource(this, 'Resource', { serviceToken: provider.serviceToken, diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts index 5c144e42abf97..9fe12c4b6169b 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts @@ -24,6 +24,7 @@ export class KubectlProvider extends NestedStack { runtime: lambda.Runtime.PYTHON_3_7, handler: 'index.handler', timeout: Duration.minutes(15), + description: 'onEvent handler for EKS kubectl resource provider', layers: [ KubectlLayer.getOrCreate(this, { version: '2.0.0' }) ], memorySize: 256, }); diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index b38417369d77d..f957a467b48aa 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json @@ -756,7 +756,10 @@ "Resource": "*" }, { - "Action": "iam:GetRole", + "Action": [ + "iam:GetRole", + "iam:listAttachedRolePolicies" + ], "Effect": "Allow", "Resource": "*" }, @@ -849,6 +852,20 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "ClusterKubectlReadyBarrier200052AF": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "aws:cdk:eks:kubectl-ready" + }, + "DependsOn": [ + "ClusterfargateprofiledefaultPodExecutionRole09952CFF", + "ClusterfargateprofiledefaultEFC59F14", + "ClusterCreationRoleDefaultPolicyE8BDFC7B", + "ClusterCreationRole360249B6", + "Cluster9EE0221C" + ] + }, "ClusterAwsAuthmanifestFE51F8AE": { "Type": "Custom::AWSCDK-EKS-KubernetesResource", "Properties": { @@ -925,6 +942,9 @@ ] } }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, @@ -1998,6 +2018,9 @@ "Repository": "https://aws.github.io/eks-charts", "CreateNamespace": true }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, @@ -2309,6 +2332,9 @@ ] } }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, @@ -2431,6 +2457,9 @@ ] } }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, @@ -2458,6 +2487,9 @@ "Repository": "https://kubernetes.github.io/dashboard/", "CreateNamespace": true }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, @@ -2481,6 +2513,9 @@ ] } }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, @@ -2510,6 +2545,7 @@ "Repository": "https://helm.nginx.com/stable" }, "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF", "ClustermanifestnginxnamespaceA68B4CE0" ], "UpdateReplacePolicy": "Delete", @@ -2636,6 +2672,9 @@ ] } }, + "DependsOn": [ + "ClusterKubectlReadyBarrier200052AF" + ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, @@ -2713,7 +2752,7 @@ }, "/", { - "Ref": "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762S3Bucket8B322431" + "Ref": "AssetParameters2181e1ea22ea11a566260dec2f26c5f66ac77bb1b73812ba467b9c3bc564e42bS3BucketBEF9DA08" }, "/", { @@ -2723,7 +2762,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762S3VersionKey3D736637" + "Ref": "AssetParameters2181e1ea22ea11a566260dec2f26c5f66ac77bb1b73812ba467b9c3bc564e42bS3VersionKey8B401BBD" } ] } @@ -2736,7 +2775,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762S3VersionKey3D736637" + "Ref": "AssetParameters2181e1ea22ea11a566260dec2f26c5f66ac77bb1b73812ba467b9c3bc564e42bS3VersionKey8B401BBD" } ] } @@ -3099,17 +3138,17 @@ "Type": "String", "Description": "Artifact hash for asset \"5215f685494c7a295ec1b06b713a041a82e7ac216473965711a88e32405e9053\"" }, - "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762S3Bucket8B322431": { + "AssetParameters2181e1ea22ea11a566260dec2f26c5f66ac77bb1b73812ba467b9c3bc564e42bS3BucketBEF9DA08": { "Type": "String", - "Description": "S3 bucket for asset \"159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762\"" + "Description": "S3 bucket for asset \"2181e1ea22ea11a566260dec2f26c5f66ac77bb1b73812ba467b9c3bc564e42b\"" }, - "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762S3VersionKey3D736637": { + "AssetParameters2181e1ea22ea11a566260dec2f26c5f66ac77bb1b73812ba467b9c3bc564e42bS3VersionKey8B401BBD": { "Type": "String", - "Description": "S3 key for asset version \"159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762\"" + "Description": "S3 key for asset version \"2181e1ea22ea11a566260dec2f26c5f66ac77bb1b73812ba467b9c3bc564e42b\"" }, - "AssetParameters159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762ArtifactHash56F3AB07": { + "AssetParameters2181e1ea22ea11a566260dec2f26c5f66ac77bb1b73812ba467b9c3bc564e42bArtifactHash87F44C09": { "Type": "String", - "Description": "Artifact hash for asset \"159827cc934bc51d62122aee0f5681239bab34e46900fceb8557c05e8109d762\"" + "Description": "Artifact hash for asset \"2181e1ea22ea11a566260dec2f26c5f66ac77bb1b73812ba467b9c3bc564e42b\"" }, "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index 93bf628e83b2a..16790052c4c24 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -956,7 +956,7 @@ export = { }, }, { - Action: 'iam:GetRole', + Action: [ 'iam:GetRole', 'iam:listAttachedRolePolicies' ], Effect: 'Allow', Resource: '*', }, @@ -1025,7 +1025,7 @@ export = { Resource: '*', }, { - Action: 'iam:GetRole', + Action: [ 'iam:GetRole', 'iam:listAttachedRolePolicies' ], Effect: 'Allow', Resource: '*', }, @@ -1177,4 +1177,83 @@ export = { })); test.done(); }, + + 'kubectl resources are always created after all fargate profiles'(test: Test) { + // GIVEN + const { stack, app } = testFixture(); + const cluster = new eks.Cluster(stack, 'Cluster'); + + // WHEN + cluster.addFargateProfile('profile1', { selectors: [ { namespace: 'profile1' } ]}); + cluster.addResource('resource1', { foo: 123 }); + cluster.addFargateProfile('profile2', { selectors: [ { namespace: 'profile2' } ]}); + new eks.HelmChart(stack, 'chart', { cluster, chart: 'mychart' }); + cluster.addFargateProfile('profile3', { selectors: [ { namespace: 'profile3' } ]}); + new eks.KubernetesPatch(stack, 'patch1', { + cluster, + applyPatch: { foo: 123 }, + restorePatch: { bar: 123 }, + resourceName: 'foo/bar', + }); + cluster.addFargateProfile('profile4', { selectors: [ { namespace: 'profile4' } ]}); + + // THEN + const template = app.synth().getStackArtifact(stack.artifactId).template; + + const barrier = template.Resources.ClusterKubectlReadyBarrier200052AF; + + test.deepEqual(barrier.DependsOn, [ + 'Clusterfargateprofileprofile1PodExecutionRoleE85F87B5', + 'Clusterfargateprofileprofile129AEA3C6', + 'Clusterfargateprofileprofile2PodExecutionRole22670AF8', + 'Clusterfargateprofileprofile233B9A117', + 'Clusterfargateprofileprofile3PodExecutionRole475C0D8F', + 'Clusterfargateprofileprofile3D06F3076', + 'Clusterfargateprofileprofile4PodExecutionRole086057FB', + 'Clusterfargateprofileprofile4A0E3BBE8', + 'ClusterCreationRoleDefaultPolicyE8BDFC7B', + 'ClusterCreationRole360249B6', + 'Cluster9EE0221C', + ]); + + const kubectlResources = [ 'chartF2447AFC', 'patch1B964AC93', 'Clustermanifestresource10B1C9505', 'ClusterAwsAuthmanifestFE51F8AE' ]; + + // check that all kubectl resources depend on the barrier + for (const r of kubectlResources) { + test.deepEqual(template.Resources[r].DependsOn, [ 'ClusterKubectlReadyBarrier200052AF' ]); + } + + test.done(); + }, + + 'kubectl provider role is trusted to assume cluster creation role'(test: Test) { + // GIVEN + const { stack, app } = testFixture(); + const c1 = new eks.Cluster(stack, 'Cluster1'); + const c2 = new eks.Cluster(stack, 'Cluster2'); + + // WHEN + + // activate kubectl provider + c1.addResource('c1a', { foo: 123 }); + c1.addResource('c1b', { foo: 123 }); + c2.addResource('c2', { foo: 123 }); + + // THEN + const template = app.synth().getStackArtifact(stack.artifactId).template; + + const creationRoleToKubectlRole = { + Cluster1CreationRoleA231BE8D: 'Outputs.StackawscdkawseksKubectlProviderHandlerServiceRole2C52B3ECArn', + Cluster2CreationRole9254EAB6: 'Outputs.StackawscdkawseksKubectlProviderHandlerServiceRole2C52B3ECArn', + }; + + // verify that the kubectl role appears as the 2nd IAM trust policy statement + for (const [ creationRole, kubectlRole ] of Object.entries(creationRoleToKubectlRole)) { + const trustPolicy = template.Resources[creationRole].Properties.AssumeRolePolicyDocument.Statement; + test.equal(trustPolicy.length, 2, 'expecting the creation role\'s trust policy to include two statements'); + test.deepEqual(trustPolicy[1].Principal.AWS['Fn::GetAtt'][1], kubectlRole); + } + + test.done(); + }, }}; diff --git a/packages/@aws-cdk/aws-eks/test/test.fargate.ts b/packages/@aws-cdk/aws-eks/test/test.fargate.ts index 593a5eccd42ca..db454d81477de 100644 --- a/packages/@aws-cdk/aws-eks/test/test.fargate.ts +++ b/packages/@aws-cdk/aws-eks/test/test.fargate.ts @@ -392,7 +392,7 @@ export = { Resource: '*', }, { - Action: 'iam:GetRole', + Action: [ 'iam:GetRole', 'iam:listAttachedRolePolicies' ], Effect: 'Allow', Resource: '*', }, From 8d41671b1a7f28d18760c20da2e8fd16890f68c3 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 2 Jul 2020 13:58:22 +0100 Subject: [PATCH 03/28] chore(apigateway): modernize Integration.bind() (#8814) Currently, the Method construct reaches into the internals of the Integration class to construct itself, via the _props() internal method. Change to a more recent pattern where the bind() returns a result that contains all of the information that the Method class requires to finalize the bind. Motivation for the change This change - https://github.com/aws/aws-cdk/pull/8813 - requires a property returned to the Method that is not user specified. The change already introduces this pattern. This PR takes it a bit further and applies the same logic to existing properties, so that there is now only one way for the Method to get the result of the bind. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-apigateway/lib/integration.ts | 57 ++++++++++++---- .../aws-apigateway/lib/integrations/aws.ts | 5 +- .../aws-apigateway/lib/integrations/lambda.ts | 5 +- .../@aws-cdk/aws-apigateway/lib/method.ts | 27 ++------ .../aws-apigateway/test/test.integration.ts | 58 ++++++++++++++++ .../aws-apigateway/test/test.method.ts | 68 ------------------- 6 files changed, 116 insertions(+), 104 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigateway/test/test.integration.ts diff --git a/packages/@aws-cdk/aws-apigateway/lib/integration.ts b/packages/@aws-cdk/aws-apigateway/lib/integration.ts index 6f305e2932efb..5b6a3040cb946 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integration.ts @@ -135,9 +135,33 @@ export interface IntegrationProps { } /** - * Result of binding an Integration to the Method + * Result of binding an Integration to a Method. */ export interface IntegrationConfig { + /** + * Integration options. + * @default - no integration options + */ + readonly options?: IntegrationOptions; + + /** + * Specifies an API method integration type. + */ + readonly type: IntegrationType; + + /** + * The Uniform Resource Identifier (URI) for the integration. + * @see https://docs.aws.amazon.com/apigateway/api-reference/resource/integration/#uri + * @default - no URI. Usually applies to MOCK integration + */ + readonly uri?: string; + + /** + * The integration's HTTP method type. + * @default - no integration method specified. + */ + readonly integrationHttpMethod?: string; + /** * This value is included in computing the Deployment's fingerprint. When the fingerprint * changes, a new deployment is triggered. @@ -155,23 +179,32 @@ export interface IntegrationConfig { * or implement on your own by specifying the set of props. */ export class Integration { - constructor(private readonly props: IntegrationProps) { } - - /** - * Allows `Method` to access the integration props. - * - * @internal - */ - public get _props() { - return this.props; + constructor(private readonly props: IntegrationProps) { + const options = this.props.options || { }; + if (options.credentialsPassthrough !== undefined && options.credentialsRole !== undefined) { + throw new Error('\'credentialsPassthrough\' and \'credentialsRole\' are mutually exclusive'); + } + + if (options.connectionType === ConnectionType.VPC_LINK && options.vpcLink === undefined) { + throw new Error('\'connectionType\' of VPC_LINK requires \'vpcLink\' prop to be set'); + } + + if (options.connectionType === ConnectionType.INTERNET && options.vpcLink !== undefined) { + throw new Error('cannot set \'vpcLink\' where \'connectionType\' is INTERNET'); + } } /** * Can be overridden by subclasses to allow the integration to interact with the method * being integrated, access the REST API object, method ARNs, etc. */ - public bind(_method: Method): IntegrationConfig | undefined { - return; + public bind(_method: Method): IntegrationConfig { + return { + options: this.props.options, + type: this.props.type, + uri: this.props.uri, + integrationHttpMethod: this.props.integrationHttpMethod, + }; } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts index 6300c3b77379d..1f95b88233ab8 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts @@ -92,8 +92,9 @@ export class AwsIntegration extends Integration { }); } - public bind(method: Method): IntegrationConfig | undefined { + public bind(method: Method): IntegrationConfig { + const bindResult = super.bind(method); this.scope = method; - return; + return bindResult; } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts index 715c1d91237fe..7464232286786 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts @@ -52,8 +52,8 @@ export class LambdaIntegration extends AwsIntegration { this.enableTest = options.allowTestInvoke === undefined ? true : false; } - public bind(method: Method): IntegrationConfig | undefined { - super.bind(method); + public bind(method: Method): IntegrationConfig { + const bindResult = super.bind(method); const principal = new iam.ServicePrincipal('apigateway.amazonaws.com'); const desc = `${method.api.node.uniqueId}.${method.httpMethod}.${method.resource.path.replace(/\//g, '.')}`; @@ -79,6 +79,7 @@ export class LambdaIntegration extends AwsIntegration { deploymentToken = JSON.stringify({ functionName: cfnFunction.functionName }); } return { + ...bindResult, deploymentToken, }; } diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index b9a9143df6506..6e3ed26a0c6d9 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -1,7 +1,7 @@ import { Construct, Resource, Stack } from '@aws-cdk/core'; import { CfnMethod, CfnMethodProps } from './apigateway.generated'; import { Authorizer, IAuthorizer } from './authorizer'; -import { ConnectionType, Integration } from './integration'; +import { Integration, IntegrationConfig } from './integration'; import { MockIntegration } from './integrations/mock'; import { MethodResponse } from './methodresponse'; import { IModel } from './model'; @@ -204,7 +204,7 @@ export class Method extends Resource { authorizationType, authorizerId, requestParameters: options.requestParameters || defaultMethodOptions.requestParameters, - integration: this.renderIntegration(integration), + integration: this.renderIntegration(bindResult), methodResponses: this.renderMethodResponses(options.methodResponses), requestModels: this.renderRequestModels(options.requestModels), requestValidatorId: this.requestValidatorId(options), @@ -263,22 +263,9 @@ export class Method extends Resource { return this.api.arnForExecuteApi(this.httpMethod, pathForArn(this.resource.path), 'test-invoke-stage'); } - private renderIntegration(integration: Integration): CfnMethod.IntegrationProperty { - const options = integration._props.options || { }; - + private renderIntegration(bindResult: IntegrationConfig): CfnMethod.IntegrationProperty { + const options = bindResult.options ?? {}; let credentials; - if (options.credentialsPassthrough !== undefined && options.credentialsRole !== undefined) { - throw new Error('\'credentialsPassthrough\' and \'credentialsRole\' are mutually exclusive'); - } - - if (options.connectionType === ConnectionType.VPC_LINK && options.vpcLink === undefined) { - throw new Error('\'connectionType\' of VPC_LINK requires \'vpcLink\' prop to be set'); - } - - if (options.connectionType === ConnectionType.INTERNET && options.vpcLink !== undefined) { - throw new Error('cannot set \'vpcLink\' where \'connectionType\' is INTERNET'); - } - if (options.credentialsRole) { credentials = options.credentialsRole.roleArn; } else if (options.credentialsPassthrough) { @@ -288,12 +275,12 @@ export class Method extends Resource { } return { - type: integration._props.type, - uri: integration._props.uri, + type: bindResult.type, + uri: bindResult.uri, cacheKeyParameters: options.cacheKeyParameters, cacheNamespace: options.cacheNamespace, contentHandling: options.contentHandling, - integrationHttpMethod: integration._props.integrationHttpMethod, + integrationHttpMethod: bindResult.integrationHttpMethod, requestParameters: options.requestParameters, requestTemplates: options.requestTemplates, passthroughBehavior: options.passthroughBehavior, diff --git a/packages/@aws-cdk/aws-apigateway/test/test.integration.ts b/packages/@aws-cdk/aws-apigateway/test/test.integration.ts new file mode 100644 index 0000000000000..40047b53df745 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/test.integration.ts @@ -0,0 +1,58 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import * as apigw from '../lib'; + +export = { + 'integration "credentialsRole" and "credentialsPassthrough" are mutually exclusive'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); + + // THEN + test.throws(() => new apigw.Integration({ + type: apigw.IntegrationType.AWS_PROXY, + options: { + credentialsPassthrough: true, + credentialsRole: role, + }, + }), /'credentialsPassthrough' and 'credentialsRole' are mutually exclusive/); + test.done(); + }, + + 'integration connectionType VpcLink requires vpcLink to be set'(test: Test) { + test.throws(() => new apigw.Integration({ + type: apigw.IntegrationType.HTTP_PROXY, + integrationHttpMethod: 'ANY', + options: { + connectionType: apigw.ConnectionType.VPC_LINK, + }, + }), /'connectionType' of VPC_LINK requires 'vpcLink' prop to be set/); + test.done(); + }, + + 'connectionType of INTERNET and vpcLink are mutually exclusive'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { + vpc, + }); + const link = new apigw.VpcLink(stack, 'link', { + targets: [nlb], + }); + + // THEN + test.throws(() => new apigw.Integration({ + type: apigw.IntegrationType.HTTP_PROXY, + integrationHttpMethod: 'ANY', + options: { + connectionType: apigw.ConnectionType.INTERNET, + vpcLink: link, + }, + }), /cannot set 'vpcLink' where 'connectionType' is INTERNET/); + test.done(); + }, +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/test.method.ts b/packages/@aws-cdk/aws-apigateway/test/test.method.ts index ba5ab8c1c2c91..85111b4636882 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.method.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.method.ts @@ -1,6 +1,4 @@ import { ABSENT, expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; @@ -327,72 +325,6 @@ export = { test.done(); }, - 'integration "credentialsRole" and "credentialsPassthrough" are mutually exclusive'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const api = new apigw.RestApi(stack, 'test-api', { deploy: false }); - const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); - - // WHEN - const integration = new apigw.Integration({ - type: apigw.IntegrationType.AWS_PROXY, - options: { - credentialsPassthrough: true, - credentialsRole: role, - }, - }); - - // THEN - test.throws(() => api.root.addMethod('GET', integration), /'credentialsPassthrough' and 'credentialsRole' are mutually exclusive/); - test.done(); - }, - - 'integration connectionType VpcLink requires vpcLink to be set'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const api = new apigw.RestApi(stack, 'test-api', { deploy: false }); - - // WHEN - const integration = new apigw.Integration({ - type: apigw.IntegrationType.HTTP_PROXY, - integrationHttpMethod: 'ANY', - options: { - connectionType: apigw.ConnectionType.VPC_LINK, - }, - }); - - // THEN - test.throws(() => api.root.addMethod('GET', integration), /'connectionType' of VPC_LINK requires 'vpcLink' prop to be set/); - test.done(); - }, - - 'connectionType of INTERNET and vpcLink are mutually exclusive'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const api = new apigw.RestApi(stack, 'test-api', { deploy: false }); - const vpc = new ec2.Vpc(stack, 'VPC'); - const nlb = new elbv2.NetworkLoadBalancer(stack, 'NLB', { - vpc, - }); - const link = new apigw.VpcLink(stack, 'link', { - targets: [nlb], - }); - - // WHEN - const integration = new apigw.Integration({ - type: apigw.IntegrationType.HTTP_PROXY, - integrationHttpMethod: 'ANY', - options: { - connectionType: apigw.ConnectionType.INTERNET, - vpcLink: link, - }, - }); - - // THEN - test.throws(() => api.root.addMethod('GET', integration), /cannot set 'vpcLink' where 'connectionType' is INTERNET/); - test.done(); - }, - 'methodResponse set one or more method responses via options'(test: Test) { // GIVEN const stack = new cdk.Stack(); From 8420f96ffd6201656e908d6d7f61cdbbc3331cc1 Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Thu, 2 Jul 2020 19:30:38 +0300 Subject: [PATCH 04/28] fix(apigateway): Lambda integration for imported functions (#8870) Use `instanceof` to identify real functions vs imported ones and appropriately use whats needed to extract the function name. Fixes #8869 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-apigateway/lib/integrations/lambda.ts | 16 +++++++++++++--- .../aws-apigateway/test/test.lambda.ts | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts index 7464232286786..c6a8ec04863c7 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts @@ -73,10 +73,20 @@ export class LambdaIntegration extends AwsIntegration { }); } - const cfnFunction = this.handler.node.defaultChild as lambda.CfnFunction; + let functionName; + + if (this.handler instanceof lambda.Function) { + // if not imported, extract the name from the CFN layer to reach + // the literal value if it is given (rather than a token) + functionName = (this.handler.node.defaultChild as lambda.CfnFunction).functionName; + } else { + // imported, just take the function name. + functionName = this.handler.functionName; + } + let deploymentToken; - if (!Token.isUnresolved(cfnFunction.functionName)) { - deploymentToken = JSON.stringify({ functionName: cfnFunction.functionName }); + if (!Token.isUnresolved(functionName)) { + deploymentToken = JSON.stringify({ functionName }); } return { ...bindResult, diff --git a/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts b/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts index afc1a2230e848..b518db5a29bc9 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.lambda.ts @@ -259,4 +259,23 @@ export = { test.done(); }, + + 'bind works for integration with imported functions'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const restapi = new apigateway.RestApi(stack, 'RestApi'); + const method = restapi.root.addMethod('ANY'); + const handler = lambda.Function.fromFunctionArn(stack, 'MyFunc', 'arn:aws:lambda:region:account:function:myfunc'); + const integration = new apigateway.LambdaIntegration(handler); + + // WHEN + const bindResult = integration.bind(method); + + // the deployment token should be defined since the function name + // should be a literal string. + test.equal(bindResult?.deploymentToken, JSON.stringify({functionName: 'myfunc'})); + + test.done(); + }, + }; From 0b09bbb1d43192db71f682ff4f3ad125eb231d91 Mon Sep 17 00:00:00 2001 From: comcalvi <66279577+comcalvi@users.noreply.github.com> Date: Thu, 2 Jul 2020 13:10:43 -0400 Subject: [PATCH 05/28] feat(cfn-include): add support for retrieving Output objects from the template (#8821) Closes #8820 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/cloudformation-include/README.md | 20 ++++- .../cloudformation-include/lib/cfn-include.ts | 64 +++++++++++++++- .../test/invalid-templates.test.ts | 6 ++ ...put-referencing-nonexistant-condition.json | 7 ++ .../outputs-with-references.json | 45 ++++++++++++ .../test/valid-templates.test.ts | 73 ++++++++++++++++++- packages/@aws-cdk/core/lib/cfn-output.ts | 72 ++++++++++++++++-- 7 files changed, 275 insertions(+), 12 deletions(-) create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/output-referencing-nonexistant-condition.json create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json diff --git a/packages/@aws-cdk/cloudformation-include/README.md b/packages/@aws-cdk/cloudformation-include/README.md index 68f7b0e2a8b12..01be06067dde4 100644 --- a/packages/@aws-cdk/cloudformation-include/README.md +++ b/packages/@aws-cdk/cloudformation-include/README.md @@ -124,6 +124,24 @@ and any changes you make to it will be reflected in the resulting template: condition.expression = core.Fn.conditionEquals(1, 2); ``` +## Outputs + +If your template uses [CloudFormation Outputs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html), +you can retrieve them from your template: + +```typescript +import * as core from '@aws-cdk/core'; + +const output: core.CfnOutput = cfnTemplate.getOutput('MyOutput'); +``` + +The `CfnOutput` object is mutable, +and any changes you make to it will be reflected in the resulting template: + +```typescript +output.value = cfnBucket.attrArn; +``` + ## Known limitations This module is still in its early, experimental stage, @@ -135,7 +153,7 @@ All items unchecked below are currently not supported. - [x] Resources - [x] Parameters - [x] Conditions -- [ ] Outputs +- [x] Outputs ### [Resource attributes](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-product-attribute-reference.html): diff --git a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts index 49c5a769de73e..035631fd282d5 100644 --- a/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts +++ b/packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts @@ -24,6 +24,7 @@ export class CfnInclude extends core.CfnElement { private readonly conditions: { [conditionName: string]: core.CfnCondition } = {}; private readonly resources: { [logicalId: string]: core.CfnResource } = {}; private readonly parameters: { [logicalId: string]: core.CfnParameter } = {}; + private readonly outputs: { [logicalId: string]: core.CfnOutput } = {}; private readonly template: any; private readonly preserveLogicalIds: boolean; @@ -50,6 +51,12 @@ export class CfnInclude extends core.CfnElement { for (const logicalId of Object.keys(this.template.Resources || {})) { this.getOrCreateResource(logicalId); } + + const outputScope = new core.Construct(this, '$Ouputs'); + + for (const logicalId of Object.keys(this.template.Outputs || {})) { + this.createOutput(logicalId, outputScope); + } } /** @@ -112,14 +119,32 @@ export class CfnInclude extends core.CfnElement { return ret; } + /** + * Returns the CfnOutput object from the 'Outputs' + * section of the included template + * Any modifications performed on that object will be reflected in the resulting CDK template. + * + * If an Output with the given name is not present in the template, + * throws an exception. + * + * @param logicalId the name of the output to retrieve + */ + public getOutput(logicalId: string): core.CfnOutput { + const ret = this.outputs[logicalId]; + if (!ret) { + throw new Error(`Output with logical ID '${logicalId}' was not found in the template`); + } + return ret; + } + /** @internal */ public _toCloudFormation(): object { const ret: { [section: string]: any } = {}; for (const section of Object.keys(this.template)) { // render all sections of the template unchanged, - // except Conditions, Resources, and Parameters, which will be taken care of by the created L1s - if (section !== 'Conditions' && section !== 'Resources' && section !== 'Parameters') { + // except Conditions, Resources, Parameters, and Outputs which will be taken care of by the created L1s + if (section !== 'Conditions' && section !== 'Resources' && section !== 'Parameters' && section !== 'Outputs') { ret[section] = this.template[section]; } } @@ -152,6 +177,41 @@ export class CfnInclude extends core.CfnElement { this.parameters[logicalId] = cfnParameter; } + private createOutput(logicalId: string, scope: core.Construct): void { + const self = this; + const outputAttributes = new cfn_parse.CfnParser({ + finder: { + findResource(lId): core.CfnResource | undefined { + return self.resources[lId]; + }, + findRefTarget(elementName: string): core.CfnElement | undefined { + return self.resources[elementName] ?? self.parameters[elementName]; + }, + findCondition(): undefined { + return undefined; + }, + }, + }).parseValue(this.template.Outputs[logicalId]); + const cfnOutput = new core.CfnOutput(scope, logicalId, { + value: outputAttributes.Value, + description: outputAttributes.Description, + exportName: outputAttributes.Export ? outputAttributes.Export.Name : undefined, + condition: (() => { + if (!outputAttributes.Condition) { + return undefined; + } else if (this.conditions[outputAttributes.Condition]) { + return self.getCondition(outputAttributes.Condition); + } + + throw new Error(`Output with name '${logicalId}' refers to a Condition with name\ + '${outputAttributes.Condition}' which was not found in this template`); + })(), + }); + + cfnOutput.overrideLogicalId(logicalId); + this.outputs[logicalId] = cfnOutput; + } + private createCondition(conditionName: string): void { // ToDo condition expressions can refer to other conditions - // will be important when implementing preserveLogicalIds=false diff --git a/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts index eb04ef059e15e..0d1ba70795e5d 100644 --- a/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/invalid-templates.test.ts @@ -76,6 +76,12 @@ describe('CDK Include', () => { includeTestTemplate(stack, 'getting-attribute-of-a-non-existent-resource.json'); }).toThrow(/Resource used in GetAtt expression with logical ID: 'DoesNotExist' not found/); }); + + test("throws a validation exception when an output references a condition that doesn't exist", () => { + expect(() => { + includeTestTemplate(stack, 'output-referencing-nonexistant-condition.json'); + }).toThrow(/Output with name 'SomeOutput' refers to a Condition with name 'NonexistantCondition' which was not found in this template/); + }); }); function includeTestTemplate(scope: core.Construct, testTemplate: string): inc.CfnInclude { diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/output-referencing-nonexistant-condition.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/output-referencing-nonexistant-condition.json new file mode 100644 index 0000000000000..fbb06694b8ae6 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/invalid/output-referencing-nonexistant-condition.json @@ -0,0 +1,7 @@ +{ + "Outputs": { + "SomeOutput": { + "Condition": "NonexistantCondition" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json new file mode 100644 index 0000000000000..206b1d1d15009 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/outputs-with-references.json @@ -0,0 +1,45 @@ +{ + "Conditions": { + "AlwaysFalseCond": { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "completely-made-up-region" + ] + } + }, + "Parameters": { + "MyParam": { + "Type": "String" + } + }, + "Resources": { + "Bucket": { + "Type": "AWS::S3::Bucket" + }, + "Output1": { + "Type": "AWS::S3::Bucket" + } + }, + "Outputs": { + "Output1": { + "Value": { + "Fn::Join": [ "", [ + { "Ref": "MyParam" }, + { "Fn::GetAtt": [ "Bucket", "Arn" ] } ] + ] + }, + "Description": { + "Ref": "Bucket" + }, + "Condition": "AlwaysFalseCond", + "Export": { + "Name": "Bucket" + } + }, + "OutputWithNoCondition": { + "Value": "some-value" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts index 6994a77f6bff1..57e6d0470dcb9 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -251,7 +251,7 @@ describe('CDK Include', () => { ); }); - test("correctly parses templates with parameters", () => { + test('correctly parses templates with parameters', () => { const cfnTemplate = includeTestTemplate(stack, 'bucket-with-parameters.json'); const param = cfnTemplate.getParameter('BucketName'); new s3.CfnBucket(stack, 'NewBucket', { @@ -396,8 +396,8 @@ describe('CDK Include', () => { ], }, }, - "Metadata" : { - "Object1" : "Location1", + "Metadata": { + "Object1": "Location1", "KeyRef": { "Ref": "TotallyDifferentKey" }, "KeyArn": { "Fn::GetAtt": ["TotallyDifferentKey", "Arn"] }, }, @@ -414,6 +414,73 @@ describe('CDK Include', () => { includeTestTemplate(stack, 'non-existent-resource-type.json'); }).toThrow(/Unrecognized CloudFormation resource type: 'AWS::FakeService::DoesNotExist'/); }); + + test('can ingest a template that contains outputs and modify them', () => { + const cfnTemplate = includeTestTemplate(stack, 'outputs-with-references.json'); + + const output = cfnTemplate.getOutput('Output1'); + output.value = 'a mutated value'; + output.description = undefined; + output.exportName = 'an export'; + output.condition = new core.CfnCondition(stack, 'MyCondition', { + expression: core.Fn.conditionIf('AlwaysFalseCond', core.Aws.NO_VALUE, true), + }); + + const originalTemplate = loadTestFileToJsObject('outputs-with-references.json'); + + expect(stack).toMatchTemplate({ + "Conditions": { + ...originalTemplate.Conditions, + "MyCondition": { + "Fn::If": [ + "AlwaysFalseCond", + { "Ref": "AWS::NoValue" }, + true, + ], + }, + }, + "Parameters": { + ...originalTemplate.Parameters, + }, + "Resources": { + ...originalTemplate.Resources, + }, + "Outputs": { + "Output1": { + "Value": "a mutated value", + "Export": { + "Name": "an export", + }, + "Condition": "MyCondition", + }, + "OutputWithNoCondition": { + "Value": "some-value", + }, + }, + }); + }); + + test('can ingest a template that contains outputs and get those outputs', () => { + const cfnTemplate = includeTestTemplate(stack, 'outputs-with-references.json'); + const output = cfnTemplate.getOutput('Output1'); + + expect(output.condition).toBe(cfnTemplate.getCondition('AlwaysFalseCond')); + expect(output.description).toBeDefined(); + expect(output.value).toBeDefined(); + expect(output.exportName).toBeDefined(); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('outputs-with-references.json'), + ); + }); + + test("throws an exception when attempting to retrieve an Output that doesn't exist", () => { + const cfnTemplate = includeTestTemplate(stack, 'outputs-with-references.json'); + + expect(() => { + cfnTemplate.getOutput('FakeOutput'); + }).toThrow(/Output with logical ID 'FakeOutput' was not found in the template/); + }); }); interface IncludeTestTemplateProps { diff --git a/packages/@aws-cdk/core/lib/cfn-output.ts b/packages/@aws-cdk/core/lib/cfn-output.ts index d99622badb1ef..387c5216ead91 100644 --- a/packages/@aws-cdk/core/lib/cfn-output.ts +++ b/packages/@aws-cdk/core/lib/cfn-output.ts @@ -36,10 +36,10 @@ export interface CfnOutputProps { } export class CfnOutput extends CfnElement { - private readonly _description?: string; - private readonly _condition?: CfnCondition; - private readonly _value?: any; - private readonly _export?: string; + private _description?: string; + private _condition?: CfnCondition; + private _value?: any; + private _exportName?: string; /** * Creates an CfnOutput value for this stack. @@ -56,7 +56,67 @@ export class CfnOutput extends CfnElement { this._description = props.description; this._value = props.value; this._condition = props.condition; - this._export = props.exportName; + this._exportName = props.exportName; + } + + /** + * Returns the description of this Output + */ + public get description() { + return this._description; + } + + /** + * Sets this output's description to the parameter + * @param description the description to update this Output's description to + */ + public set description(description: string | undefined) { + this._description = description; + } + + /** + * Returns the value of this Output + */ + public get value() { + return this._value; + } + + /** + * Sets this output's value to the parameter + * @param value the value to update this Output's value to + */ + public set value(value: any) { + this._value = value; + } + + /** + * Returns the condition of this Output + */ + public get condition() { + return this._condition; + } + + /** + * Sets this output's condition to the parameter + * @param condition the condition to update this Output's condition to + */ + public set condition(condition: CfnCondition | undefined) { + this._condition = condition; + } + + /** + * Returns the export of this Output + */ + public get exportName() { + return this._exportName; + } + + /** + * Sets this output's export to the parameter + * @param exportName the export to update this Output's export to + */ + public set exportName(exportName: string | undefined) { + this._exportName = exportName; } /** @@ -68,7 +128,7 @@ export class CfnOutput extends CfnElement { [this.logicalId]: { Description: this._description, Value: this._value, - Export: this._export != null ? { Name: this._export } : undefined, + Export: this._exportName != null ? { Name: this._exportName } : undefined, Condition: this._condition ? this._condition.logicalId : undefined, }, }, From c49c39c4e6fc1b1340b0f7a2eeaec1337cfced82 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Thu, 2 Jul 2020 20:30:06 +0300 Subject: [PATCH 06/28] chore: cdk-integ --dry-run (#8861) Add a switch to `cdk-integ` which will only update the snapshot without actually deployment. Intentionally kept undocumented because we don't want to encourage this with contributors. Tested manually. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- tools/cdk-integ-tools/bin/cdk-integ.ts | 35 +++++++++++++++++--------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/tools/cdk-integ-tools/bin/cdk-integ.ts b/tools/cdk-integ-tools/bin/cdk-integ.ts index e39d738c55c53..5003d1fc79eea 100644 --- a/tools/cdk-integ-tools/bin/cdk-integ.ts +++ b/tools/cdk-integ-tools/bin/cdk-integ.ts @@ -11,6 +11,7 @@ async function main() { .option('list', { type: 'boolean', default: false, desc: 'List tests instead of running them' }) .option('clean', { type: 'boolean', default: true, desc: 'Skips stack clean up after test is completed (use --no-clean to negate)' }) .option('verbose', { type: 'boolean', default: false, alias: 'v', desc: 'Verbose logs' }) + .option('dry-run', { type: 'boolean', default: false, desc: 'do not actually deploy the stack. just update the snapshot (not recommended!)' }) .argv; const tests = await new IntegrationTests('test').fromCliArgs(argv._); @@ -21,7 +22,7 @@ async function main() { } for (const test of tests) { - console.error(`Trying to deploy ${test.name}`); + console.error(`Synthesizing ${test.name}.`); const stackToDeploy = await test.determineTestStack(); console.error(`Selected stack: ${stackToDeploy}`); @@ -33,26 +34,36 @@ async function main() { args.push('--verbose'); } + const dryRun = argv['dry-run'] ?? false; + try { - // tslint:disable-next-line:max-line-length - await test.invokeCli([ ...args, 'deploy', '--require-approval', 'never', ...stackToDeploy ], { - verbose: argv.verbose, - // Note: no "context" and "env", so use default user settings! - }); - console.error('Success! Writing out reference synth.'); + if (dryRun) { + console.error('Skipping deployment (--dry-run), updating snapshot.'); + } else { + console.error(`Deploying ${test.name}...`); + await test.invokeCli([ ...args, 'deploy', '--require-approval', 'never', ...stackToDeploy ], { + verbose: argv.verbose, + // Note: no "context" and "env", so use default user settings! + }); + console.error('Deployment succeeded, updating snapshot.'); + } // If this all worked, write the new expectation file const actual = await test.cdkSynthFast(DEFAULT_SYNTH_OPTIONS); await test.writeExpected(actual); } finally { - if (argv.clean) { - console.error('Cleaning up.'); - await test.invokeCli(['destroy', '--force', ...stackToDeploy ]); - } else { - console.error('Skipping clean up (--no-clean).'); + + if (!dryRun) { + if (argv.clean) { + console.error('Cleaning up.'); + await test.invokeCli(['destroy', '--force', ...stackToDeploy ]); + } else { + console.error('Skipping clean up (--no-clean).'); + } } + } } } From b5acfaac89351ff6285acfdb36145bccca4b6b65 Mon Sep 17 00:00:00 2001 From: Meng Xin Zhu Date: Fri, 3 Jul 2020 02:39:46 +0800 Subject: [PATCH 07/28] feat(eks): document how to add a manifest from url (#8802) Add new example of deploying resources from remote url Resolves #8340 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 69aae3320d059..2d0a34fba136d 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -336,6 +336,19 @@ new KubernetesResource(this, 'hello-kub', { cluster.addResource('hello-kub', service, deployment); ``` +#### Adding resources from a URL + +The following example will deploy the resource manifest hosting on remote server: + +```ts +import * as yaml from 'js-yaml'; +import * as request from 'sync-request'; + +const manifestUrl = 'https://url/of/manifest.yaml'; +const manifest = yaml.safeLoadAll(request('GET', manifestUrl).getBody()); +cluster.addResource('my-resource', ...manifest); +``` + 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 From a732d149ff33f6958b83d539ba3429a025dcd631 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Sat, 4 Jul 2020 02:07:59 +0800 Subject: [PATCH 08/28] feat(eks): support cluster version pinning (#8889) feat(eks): support cluster version pinning Support cluster version pinning with the mandatory `version` property in the `Cluster` construct. Fixes: #7762 BREAKING CHANGE: `version` is now a mandatory property ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/README.md | 28 ++- packages/@aws-cdk/aws-eks/lib/cluster.ts | 43 +++- .../@aws-cdk/aws-eks/lib/fargate-cluster.ts | 3 +- .../test/example.ssh-into-nodes.lit.ts | 1 + ...eks-cluster.kubectl-disabled.expected.json | 9 +- .../integ.eks-cluster.kubectl-disabled.ts | 1 + .../aws-eks/test/integ.eks-cluster.ts | 2 +- .../@aws-cdk/aws-eks/test/test.awsauth.ts | 10 +- .../@aws-cdk/aws-eks/test/test.cluster.ts | 215 ++++++++++++------ .../@aws-cdk/aws-eks/test/test.fargate.ts | 26 ++- .../@aws-cdk/aws-eks/test/test.k8s-patch.ts | 10 +- .../@aws-cdk/aws-eks/test/test.manifest.ts | 8 +- .../@aws-cdk/aws-eks/test/test.nodegroup.ts | 71 +++++- packages/@aws-cdk/aws-eks/test/util.ts | 6 +- 14 files changed, 303 insertions(+), 130 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 2d0a34fba136d..ee73d0bedcba5 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -25,7 +25,9 @@ This example defines an Amazon EKS cluster with the following configuration: - 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'); +const cluster = new eks.Cluster(this, 'hello-eks', { + version: eks.KubernetesVersion.V1_16, +}); cluster.addResource('mypod', { apiVersion: 'v1', @@ -45,17 +47,20 @@ cluster.addResource('mypod', { ### Capacity -By default, `eks.Cluster` is created with a managed nodegroup with x2 `m5.large` instances. +By default, `eks.Cluster` is created with a managed nodegroup with x2 `m5.large` instances. You must specify the kubernetes version for the cluster with the `version` property. ```ts -new eks.Cluster(this, 'cluster-two-m5-large'); +new eks.Cluster(this, 'cluster-two-m5-large', { + version: eks.KubernetesVersion.V1_16, +}); ``` To use the traditional self-managed Amazon EC2 instances instead, set `defaultCapacityType` to `DefaultCapacityType.EC2` ```ts const cluster = new eks.Cluster(this, 'cluster-self-managed-ec2', { - defaultCapacityType: eks.DefaultCapacityType.EC2 + defaultCapacityType: eks.DefaultCapacityType.EC2, + version: eks.KubernetesVersion.V1_16, }); ``` @@ -65,14 +70,18 @@ the `defaultCapacity` and `defaultCapacityInstance` props: ```ts new eks.Cluster(this, 'cluster', { defaultCapacity: 10, - defaultCapacityInstance: new ec2.InstanceType('m2.xlarge') + defaultCapacityInstance: new ec2.InstanceType('m2.xlarge'), + version: eks.KubernetesVersion.V1_16, }); ``` To disable the default capacity, simply set `defaultCapacity` to `0`: ```ts -new eks.Cluster(this, 'cluster-with-no-capacity', { defaultCapacity: 0 }); +new eks.Cluster(this, 'cluster-with-no-capacity', { + defaultCapacity: 0, + version: eks.KubernetesVersion.V1_16, +}); ``` The `cluster.defaultCapacity` property will reference the `AutoScalingGroup` @@ -145,7 +154,9 @@ The following code defines an Amazon EKS cluster without EC2 capacity and a defa Fargate Profile that matches all pods from the "kube-system" and "default" namespaces. It is also configured to [run CoreDNS on Fargate](https://docs.aws.amazon.com/eks/latest/userguide/fargate-getting-started.html#fargate-gs-coredns) through the `coreDnsComputeType` cluster option. ```ts -const cluster = new eks.FargateCluster(this, 'MyCluster'); +const cluster = new eks.FargateCluster(this, 'MyCluster', { + version: eks.KubernetesVersion.V1_16, +}); // apply k8s resources on this cluster cluster.addResource(...); @@ -219,7 +230,8 @@ const clusterAdmin = new iam.Role(this, 'AdminRole', { // now define the cluster and map role to "masters" RBAC group new eks.Cluster(this, 'Cluster', { - mastersRole: clusterAdmin + mastersRole: clusterAdmin, + version: eks.KubernetesVersion.V1_16, }); ``` diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 914269ac0b393..6a245c3e3e2ef 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -166,10 +166,8 @@ export interface ClusterOptions { /** * The Kubernetes version to run in the cluster - * - * @default - If not supplied, will use Amazon default version */ - readonly version?: string; + readonly version: KubernetesVersion; /** * An IAM role that will be added to the `system:masters` Kubernetes RBAC @@ -275,6 +273,37 @@ export interface ClusterProps extends ClusterOptions { readonly defaultCapacityType?: DefaultCapacityType; } +/** + * Kubernetes cluster version + */ +export class KubernetesVersion { + /** + * Kubernetes version 1.14 + */ + public static readonly V1_14 = KubernetesVersion.of('1.14'); + + /** + * Kubernetes version 1.15 + */ + public static readonly V1_15 = KubernetesVersion.of('1.15'); + + /** + * Kubernetes version 1.16 + */ + public static readonly V1_16 = KubernetesVersion.of('1.16'); + + /** + * Custom cluster version + * @param version custom version number + */ + public static of(version: string) { return new KubernetesVersion(version); } + /** + * + * @param version cluster version number + */ + private constructor(public readonly version: string) { } +} + /** * A Cluster represents a managed Kubernetes Service (EKS) * @@ -390,7 +419,7 @@ export class Cluster extends Resource implements ICluster { private _neuronDevicePlugin?: KubernetesResource; - private readonly version: string | undefined; + private readonly version: KubernetesVersion; /** * A dummy CloudFormation resource that is used as a wait barrier which @@ -413,7 +442,7 @@ export class Cluster extends Resource implements ICluster { * @param name the name of the Construct to create * @param props properties in the IClusterProps interface */ - constructor(scope: Construct, id: string, props: ClusterProps = { }) { + constructor(scope: Construct, id: string, props: ClusterProps) { super(scope, id, { physicalName: props.clusterName, }); @@ -450,7 +479,7 @@ export class Cluster extends Resource implements ICluster { const clusterProps: CfnClusterProps = { name: this.physicalName, roleArn: this.role.roleArn, - version: props.version, + version: props.version.version, resourcesVpcConfig: { securityGroupIds: [securityGroup.securityGroupId], subnetIds, @@ -555,7 +584,7 @@ export class Cluster extends Resource implements ICluster { new BottleRocketImage() : new EksOptimizedImage({ nodeType: nodeTypeForInstanceType(options.instanceType), - kubernetesVersion: this.version, + kubernetesVersion: this.version.version, }), updateType: options.updateType || autoscaling.UpdateType.ROLLING_UPDATE, instanceType: options.instanceType, diff --git a/packages/@aws-cdk/aws-eks/lib/fargate-cluster.ts b/packages/@aws-cdk/aws-eks/lib/fargate-cluster.ts index 6c26506464356..a6fd1d3c2c8e5 100644 --- a/packages/@aws-cdk/aws-eks/lib/fargate-cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/fargate-cluster.ts @@ -23,12 +23,13 @@ export interface FargateClusterProps extends ClusterOptions { * `addFargateProfile`. */ export class FargateCluster extends Cluster { - constructor(scope: Construct, id: string, props: FargateClusterProps = { }) { + constructor(scope: Construct, id: string, props: FargateClusterProps) { super(scope, id, { ...props, defaultCapacity: 0, kubectlEnabled: true, coreDnsComputeType: props.coreDnsComputeType ?? CoreDnsComputeType.FARGATE, + version: props.version, }); this.addFargateProfile( diff --git a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts index d26f7d38a7af0..3e33c697c1c45 100644 --- a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts +++ b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts @@ -10,6 +10,7 @@ class EksClusterStack extends cdk.Stack { const cluster = new eks.Cluster(this, 'EKSCluster', { vpc, + version: eks.KubernetesVersion.V1_16, }); /// !show 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 b390fb3c551cd..8b055a913524d 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 @@ -692,7 +692,8 @@ "EKSClusterRoleC0AEAC3D", "Arn" ] - } + }, + "Version": "1.16" } }, "EKSClusterNodesInstanceSecurityGroup460A275E": { @@ -891,7 +892,7 @@ "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "ImageId": { - "Ref": "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + "Ref": "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" }, "InstanceType": "t2.medium", "IamInstanceProfile": { @@ -1023,9 +1024,9 @@ } }, "Parameters": { - "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { "Type": "AWS::SSM::Parameter::Value", - "Default": "/aws/service/eks/optimized-ami/1.14/amazon-linux-2/recommended/image_id" + "Default": "/aws/service/eks/optimized-ami/1.16/amazon-linux-2/recommended/image_id" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts index e1a25dd64185a..2282772d42a7c 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.kubectl-disabled.ts @@ -13,6 +13,7 @@ class EksClusterStack extends TestStack { vpc, kubectlEnabled: false, defaultCapacity: 0, + version: eks.KubernetesVersion.V1_16, }); cluster.addCapacity('Nodes', { diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts index b2e1e1242cad7..f346b24dee180 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts @@ -22,7 +22,7 @@ class EksClusterStack extends TestStack { vpc, mastersRole, defaultCapacity: 2, - version: '1.16', + version: eks.KubernetesVersion.V1_16, }); // fargate profile for resources in the "default" namespace diff --git a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts index 471f70ceda822..cf3ddc99e09e8 100644 --- a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts +++ b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts @@ -1,17 +1,19 @@ 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 { Cluster, KubernetesResource, KubernetesVersion } from '../lib'; import { AwsAuth } from '../lib/aws-auth'; import { testFixtureNoVpc } from './util'; // tslint:disable:max-line-length +const CLUSTER_VERSION = KubernetesVersion.V1_16; + export = { 'empty aws-auth'(test: Test) { // GIVEN const { stack } = testFixtureNoVpc(); - const cluster = new Cluster(stack, 'cluster'); + const cluster = new Cluster(stack, 'cluster', { version: CLUSTER_VERSION }); // WHEN new AwsAuth(stack, 'AwsAuth', { cluster }); @@ -31,7 +33,7 @@ export = { '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 cluster = new Cluster(stack, 'Cluster', { version: CLUSTER_VERSION }); const role = new iam.Role(stack, 'role', { assumedBy: new iam.AnyPrincipal() }); const user = new iam.User(stack, 'user'); @@ -104,7 +106,7 @@ export = { 'imported users and roles can be also be used'(test: Test) { // GIVEN const { stack } = testFixtureNoVpc(); - const cluster = new Cluster(stack, 'Cluster'); + const cluster = new Cluster(stack, 'Cluster', { version: CLUSTER_VERSION }); const role = iam.Role.fromRoleArn(stack, 'imported-role', 'arn:aws:iam::123456789012:role/S3Access'); const user = iam.User.fromUserName(stack, 'import-user', 'MyUserName'); diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index 16790052c4c24..1acc770b47129 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -12,13 +12,15 @@ import { testFixture, testFixtureNoVpc } from './util'; // tslint:disable:max-line-length +const CLUSTER_VERSION = eks.KubernetesVersion.V1_16; + 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 }); + new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0, version: CLUSTER_VERSION }); // THEN expect(stack).to(haveResourceLike('AWS::EKS::Cluster', { @@ -42,7 +44,7 @@ export = { // WHEN const vpc = new ec2.Vpc(stack, 'VPC'); - new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); + new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0, version: CLUSTER_VERSION }); const layer = KubectlLayer.getOrCreate(stack, {}); // THEN @@ -63,14 +65,14 @@ export = { // WHEN const vpc = new ec2.Vpc(stack, 'VPC'); - new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); + new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0, version: CLUSTER_VERSION }); new KubectlLayer(stack, 'NewLayer'); const layer = KubectlLayer.getOrCreate(stack); // THEN expect(stack).to(haveResource('Custom::AWSCDK-EKS-Cluster')); expect(stack).to(haveResourceLike('AWS::Serverless::Application', { - Location: { + Location: { ApplicationId: 'arn:aws-cn:serverlessrepo:cn-north-1:487369736442:applications/lambda-layer-kubectl', }, })); @@ -83,7 +85,7 @@ export = { const { stack } = testFixtureNoVpc(); // WHEN - new eks.Cluster(stack, 'cluster'); + new eks.Cluster(stack, 'cluster', { version: CLUSTER_VERSION }) ; // THEN expect(stack).to(haveResource('AWS::EC2::VPC')); @@ -97,7 +99,7 @@ export = { const { stack } = testFixtureNoVpc(); // WHEN - const cluster = new eks.Cluster(stack, 'cluster'); + const cluster = new eks.Cluster(stack, 'cluster', { version: CLUSTER_VERSION }); // THEN test.ok(cluster.defaultNodegroup); @@ -122,6 +124,7 @@ export = { const cluster = new eks.Cluster(stack, 'cluster', { defaultCapacity: 10, defaultCapacityInstance: new ec2.InstanceType('m2.xlarge'), + version: CLUSTER_VERSION, }); // THEN @@ -142,7 +145,7 @@ export = { const { stack } = testFixtureNoVpc(); // WHEN - const cluster = new eks.Cluster(stack, 'cluster', { defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // THEN test.ok(!cluster.defaultCapacity); @@ -157,7 +160,7 @@ export = { const { stack, vpc } = testFixture(); // WHEN - new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); + new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0, version: CLUSTER_VERSION }); // THEN expect(stack).to(haveResource('AWS::EC2::Subnet', { @@ -177,7 +180,7 @@ export = { const { stack, vpc } = testFixture(); // WHEN - new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); + new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0, version: CLUSTER_VERSION }); // THEN expect(stack).to(haveResource('AWS::EC2::Subnet', { @@ -196,7 +199,11 @@ export = { '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 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, kubectlEnabled: false, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // WHEN cluster.addCapacity('Default', { @@ -207,7 +214,7 @@ export = { expect(stack).to(haveResource('AWS::AutoScaling::AutoScalingGroup', { Tags: [ { - Key: { 'Fn::Join': [ '', [ 'kubernetes.io/cluster/', { Ref: 'ClusterEB0386A7' } ] ] }, + Key: { 'Fn::Join': ['', ['kubernetes.io/cluster/', { Ref: 'ClusterEB0386A7' }]] }, PropagateAtLaunch: true, Value: 'owned', }, @@ -230,6 +237,7 @@ export = { const cluster = new eks.Cluster(stack, 'cluster', { defaultCapacity: 10, defaultCapacityInstance: new ec2.InstanceType('m2.xlarge'), + version: CLUSTER_VERSION, }); const existingRole = new iam.Role(stack, 'ExistingRole', { @@ -256,7 +264,12 @@ export = { 'adding bottlerocket 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 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: false, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // WHEN cluster.addCapacity('Bottlerocket', { @@ -285,7 +298,12 @@ export = { 'adding bottlerocket capacity with bootstrapOptions throws error'(test: Test) { // GIVEN const { stack, vpc } = testFixture(); - const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: false, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); test.throws(() => cluster.addCapacity('Bottlerocket', { instanceType: new ec2.InstanceType('t2.medium'), @@ -299,7 +317,12 @@ export = { // 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 }); + const cluster = new eks.Cluster(stack1, 'Cluster', { + vpc, + kubectlEnabled: false, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // WHEN const imported = eks.Cluster.fromClusterAttributes(stack2, 'Imported', { @@ -332,7 +355,12 @@ export = { '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 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: false, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); test.throws(() => cluster.awsAuth, /Cannot define aws-auth mappings if kubectl is disabled/); test.throws(() => cluster.addResource('foo', {}), /Unable to perform this operation since kubectl is not enabled for this cluster/); @@ -349,7 +377,12 @@ export = { const role = new iam.Role(stack, 'role', { assumedBy: new iam.AnyPrincipal() }); // WHEN - new eks.Cluster(stack, 'Cluster', { vpc, mastersRole: role, defaultCapacity: 0 }); + new eks.Cluster(stack, 'Cluster', { + vpc, + mastersRole: role, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // THEN expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { @@ -383,11 +416,15 @@ export = { '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 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // WHEN cluster.addResource('manifest1', { foo: 123 }); - cluster.addResource('manifest2', { bar: 123 }, { boor: [ 1, 2, 3 ] }); + cluster.addResource('manifest2', { bar: 123 }, { boor: [1, 2, 3] }); // THEN expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { @@ -404,13 +441,13 @@ export = { 'kubectl resources can be created in a separate stack'(test: Test) { // GIVEN const { stack, app } = testFixture(); - const cluster = new eks.Cluster(stack, 'cluster'); // cluster is under stack2 + const cluster = new eks.Cluster(stack, 'cluster', { version: CLUSTER_VERSION }); // cluster is under stack2 // WHEN resource is under stack2 const stack2 = new cdk.Stack(app, 'stack2', { env: { account: stack.account, region: stack.region } }); new eks.KubernetesResource(stack2, 'myresource', { cluster, - manifest: [ { foo: 'bar' } ], + manifest: [{ foo: 'bar' }], }); // THEN @@ -441,7 +478,11 @@ export = { '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 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // WHEN cluster.addCapacity('default', { @@ -473,7 +514,11 @@ export = { '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 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // WHEN cluster.addCapacity('default', { @@ -489,7 +534,12 @@ export = { '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 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: false, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // WHEN cluster.addCapacity('default', { @@ -507,14 +557,14 @@ export = { const { app, stack } = testFixtureNoVpc(); // WHEN - new eks.Cluster(stack, 'Cluster'); + new eks.Cluster(stack, 'Cluster', { version: CLUSTER_VERSION }); // 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' ] ] } }, + 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(); }, @@ -525,14 +575,17 @@ export = { // WHEN const mastersRole = new iam.Role(stack, 'masters', { assumedBy: new iam.AccountRootPrincipal() }); - new eks.Cluster(stack, 'Cluster', { mastersRole }); + new eks.Cluster(stack, 'Cluster', { + mastersRole, + version: CLUSTER_VERSION, + }); // 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' ] } ] ] } }, + 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(); }, @@ -546,6 +599,7 @@ export = { new eks.Cluster(stack, 'Cluster', { mastersRole, outputConfigCommand: false, + version: CLUSTER_VERSION, }); // THEN @@ -563,6 +617,7 @@ export = { new eks.Cluster(stack, 'Cluster', { outputConfigCommand: false, outputClusterName: true, + version: CLUSTER_VERSION, }); // THEN @@ -583,13 +638,14 @@ export = { outputConfigCommand: false, outputMastersRoleArn: true, mastersRole: new iam.Role(stack, 'masters', { assumedBy: new iam.AccountRootPrincipal() }), + version: CLUSTER_VERSION, }); // THEN const assembly = app.synth(); const template = assembly.getStackByName(stack.stackName).template; test.deepEqual(template.Outputs, { - ClusterMastersRoleArnB15964B1: { Value: { 'Fn::GetAtt': [ 'masters0D04F23D', 'Arn' ] } }, + ClusterMastersRoleArnB15964B1: { Value: { 'Fn::GetAtt': ['masters0D04F23D', 'Arn'] } }, }); test.done(); }, @@ -597,9 +653,9 @@ export = { 'boostrap user-data': { 'rendered by default for ASGs'(test: Test) { - // GIVEN + // GIVEN const { app, stack } = testFixtureNoVpc(); - const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN cluster.addCapacity('MyCapcity', { instanceType: new ec2.InstanceType('m3.xlargs') }); @@ -607,14 +663,14 @@ export = { // 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.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 + // GIVEN const { app, stack } = testFixtureNoVpc(); - const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN cluster.addCapacity('MyCapcity', { @@ -631,9 +687,9 @@ export = { // cursory test for options: see test.user-data.ts for full suite 'bootstrap options'(test: Test) { - // GIVEN + // GIVEN const { app, stack } = testFixtureNoVpc(); - const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN cluster.addCapacity('MyCapcity', { @@ -646,16 +702,16 @@ export = { // 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.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 + // GIVEN const { app, stack } = testFixtureNoVpc(); - const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN cluster.addCapacity('MyCapcity', { @@ -666,14 +722,14 @@ export = { // 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.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 }); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN cluster.addCapacity('MyCapcity', { @@ -695,7 +751,7 @@ export = { 'its possible to add two capacities with spot instances and only one stop handler will be installed'(test: Test) { // GIVEN const { stack } = testFixtureNoVpc(); - const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN cluster.addCapacity('Spot1', { @@ -714,9 +770,13 @@ export = { }, 'if kubectl is disabled, interrupt handler is not added'(test: Test) { - // GIVEN + // GIVEN const { stack } = testFixtureNoVpc(); - const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, kubectlEnabled: false }); + const cluster = new eks.Cluster(stack, 'Cluster', { + defaultCapacity: 0, + kubectlEnabled: false, + version: CLUSTER_VERSION, + }); // WHEN cluster.addCapacity('MyCapcity', { @@ -736,7 +796,7 @@ export = { 'if bootstrap is disabled cannot specify options'(test: Test) { // GIVEN const { stack } = testFixtureNoVpc(); - const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // THEN test.throws(() => cluster.addCapacity('MyCapcity', { @@ -760,7 +820,7 @@ export = { 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-linux-2/'), + (v as any).Default.includes('/amazon-linux-2/'), ), 'EKS STANDARD AMI should be in ssm parameters'); test.ok(Object.entries(parameters).some( ([k, v]) => k.startsWith('SsmParameterValueawsserviceeksoptimizedami') && @@ -791,12 +851,13 @@ export = { }, 'EKS-Optimized AMI with GPU support when addCapacity'(test: Test) { - // GIVEN + // GIVEN const { app, stack } = testFixtureNoVpc(); // WHEN new eks.Cluster(stack, 'cluster', { defaultCapacity: 0, + version: CLUSTER_VERSION, }).addCapacity('GPUCapacity', { instanceType: new ec2.InstanceType('g4dn.xlarge'), }); @@ -811,21 +872,23 @@ export = { }, 'when using custom resource a creation role & policy is defined'(test: Test) { - // GIVEN + // GIVEN const { stack } = testFixture(); // WHEN new eks.Cluster(stack, 'MyCluster', { clusterName: 'my-cluster-name', + version: CLUSTER_VERSION, }); // THEN expect(stack).to(haveResource('Custom::AWSCDK-EKS-Cluster', { Config: { name: 'my-cluster-name', - roleArn: { 'Fn::GetAtt': [ 'MyClusterRoleBA20FE72', 'Arn' ] }, + roleArn: { 'Fn::GetAtt': ['MyClusterRoleBA20FE72', 'Arn'] }, + version: '1.16', resourcesVpcConfig: { - securityGroupIds: [ { 'Fn::GetAtt': [ 'MyClusterControlPlaneSecurityGroup6B658F79', 'GroupId' ] } ], + securityGroupIds: [{ 'Fn::GetAtt': ['MyClusterControlPlaneSecurityGroup6B658F79', 'GroupId'] }], subnetIds: [ { Ref: 'MyClusterDefaultVpcPublicSubnet1SubnetFAE5A9B6' }, { Ref: 'MyClusterDefaultVpcPublicSubnet2SubnetF6D028A0' }, @@ -900,7 +963,7 @@ export = { 'eks:UntagResource', ], Effect: 'Allow', - Resource: [ { + Resource: [{ 'Fn::Join': [ '', [ @@ -930,7 +993,7 @@ export = { ':cluster/my-cluster-name/*', ], ], - } ], + }], }, { Action: [ @@ -956,7 +1019,7 @@ export = { }, }, { - Action: [ 'iam:GetRole', 'iam:listAttachedRolePolicies' ], + Action: ['iam:GetRole', 'iam:listAttachedRolePolicies'], Effect: 'Allow', Resource: '*', }, @@ -973,11 +1036,11 @@ export = { }, 'if an explicit cluster name is not provided, the creation role policy is wider (allows interacting with all clusters)'(test: Test) { - // GIVEN + // GIVEN const { stack } = testFixture(); // WHEN - new eks.Cluster(stack, 'MyCluster'); + new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); // THEN expect(stack).to(haveResource('AWS::IAM::Policy', { @@ -1014,7 +1077,7 @@ export = { 'eks:UntagResource', ], Effect: 'Allow', - Resource: [ '*' ], + Resource: ['*'], }, { Action: [ @@ -1025,7 +1088,7 @@ export = { Resource: '*', }, { - Action: [ 'iam:GetRole', 'iam:listAttachedRolePolicies' ], + Action: ['iam:GetRole', 'iam:listAttachedRolePolicies'], Effect: 'Allow', Resource: '*', }, @@ -1042,10 +1105,11 @@ export = { }, 'if helm charts are used, its resource provider is allowed to assume the creation role'(test: Test) { - // GIVEN + // GIVEN const { stack } = testFixture(); const cluster = new eks.Cluster(stack, 'MyCluster', { clusterName: 'my-cluster-name', + version: CLUSTER_VERSION, }); // WHEN @@ -1100,12 +1164,13 @@ export = { }, 'coreDnsComputeType will patch the coreDNS configuration to use a "fargate" compute type and restore to "ec2" upon removal'(test: Test) { - // GIVEN + // GIVEN const stack = new cdk.Stack(); // WHEN new eks.Cluster(stack, 'MyCluster', { coreDnsComputeType: eks.CoreDnsComputeType.FARGATE, + version: CLUSTER_VERSION, }); // THEN @@ -1127,9 +1192,9 @@ export = { test.done(); }, 'if openIDConnectProvider a new OpenIDConnectProvider resource is created and exposed'(test: Test) { - // GIVEN + // GIVEN const { stack } = testFixtureNoVpc(); - const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN const provider = cluster.openIdConnectProvider; @@ -1161,7 +1226,7 @@ export = { 'inference instances are supported'(test: Test) { // GIVEN const { stack } = testFixtureNoVpc(); - const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { defaultCapacity: 0, version: CLUSTER_VERSION }); // WHEN cluster.addCapacity('InferenceInstances', { @@ -1181,21 +1246,21 @@ export = { 'kubectl resources are always created after all fargate profiles'(test: Test) { // GIVEN const { stack, app } = testFixture(); - const cluster = new eks.Cluster(stack, 'Cluster'); + const cluster = new eks.Cluster(stack, 'Cluster', { version: CLUSTER_VERSION }); // WHEN - cluster.addFargateProfile('profile1', { selectors: [ { namespace: 'profile1' } ]}); + cluster.addFargateProfile('profile1', { selectors: [{ namespace: 'profile1' }] }); cluster.addResource('resource1', { foo: 123 }); - cluster.addFargateProfile('profile2', { selectors: [ { namespace: 'profile2' } ]}); + cluster.addFargateProfile('profile2', { selectors: [{ namespace: 'profile2' }] }); new eks.HelmChart(stack, 'chart', { cluster, chart: 'mychart' }); - cluster.addFargateProfile('profile3', { selectors: [ { namespace: 'profile3' } ]}); + cluster.addFargateProfile('profile3', { selectors: [{ namespace: 'profile3' }] }); new eks.KubernetesPatch(stack, 'patch1', { cluster, applyPatch: { foo: 123 }, restorePatch: { bar: 123 }, resourceName: 'foo/bar', }); - cluster.addFargateProfile('profile4', { selectors: [ { namespace: 'profile4' } ]}); + cluster.addFargateProfile('profile4', { selectors: [{ namespace: 'profile4' }] }); // THEN const template = app.synth().getStackArtifact(stack.artifactId).template; @@ -1216,11 +1281,11 @@ export = { 'Cluster9EE0221C', ]); - const kubectlResources = [ 'chartF2447AFC', 'patch1B964AC93', 'Clustermanifestresource10B1C9505', 'ClusterAwsAuthmanifestFE51F8AE' ]; + const kubectlResources = ['chartF2447AFC', 'patch1B964AC93', 'Clustermanifestresource10B1C9505', 'ClusterAwsAuthmanifestFE51F8AE']; // check that all kubectl resources depend on the barrier for (const r of kubectlResources) { - test.deepEqual(template.Resources[r].DependsOn, [ 'ClusterKubectlReadyBarrier200052AF' ]); + test.deepEqual(template.Resources[r].DependsOn, ['ClusterKubectlReadyBarrier200052AF']); } test.done(); @@ -1229,8 +1294,8 @@ export = { 'kubectl provider role is trusted to assume cluster creation role'(test: Test) { // GIVEN const { stack, app } = testFixture(); - const c1 = new eks.Cluster(stack, 'Cluster1'); - const c2 = new eks.Cluster(stack, 'Cluster2'); + const c1 = new eks.Cluster(stack, 'Cluster1', { version: CLUSTER_VERSION }); + const c2 = new eks.Cluster(stack, 'Cluster2', { version: CLUSTER_VERSION }); // WHEN @@ -1248,12 +1313,12 @@ export = { }; // verify that the kubectl role appears as the 2nd IAM trust policy statement - for (const [ creationRole, kubectlRole ] of Object.entries(creationRoleToKubectlRole)) { + for (const [creationRole, kubectlRole] of Object.entries(creationRoleToKubectlRole)) { const trustPolicy = template.Resources[creationRole].Properties.AssumeRolePolicyDocument.Statement; test.equal(trustPolicy.length, 2, 'expecting the creation role\'s trust policy to include two statements'); test.deepEqual(trustPolicy[1].Principal.AWS['Fn::GetAtt'][1], kubectlRole); } - test.done(); }, - }}; + }, +}; diff --git a/packages/@aws-cdk/aws-eks/test/test.fargate.ts b/packages/@aws-cdk/aws-eks/test/test.fargate.ts index db454d81477de..870a871f266fa 100644 --- a/packages/@aws-cdk/aws-eks/test/test.fargate.ts +++ b/packages/@aws-cdk/aws-eks/test/test.fargate.ts @@ -5,11 +5,13 @@ import { Stack, Tag } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as eks from '../lib'; +const CLUSTER_VERSION = eks.KubernetesVersion.V1_16; + export = { 'can be added to a cluster'(test: Test) { // GIVEN const stack = new Stack(); - const cluster = new eks.Cluster(stack, 'MyCluster'); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); // WHEN cluster.addFargateProfile('MyProfile', { @@ -30,7 +32,7 @@ export = { 'supports specifying a profile name'(test: Test) { // GIVEN const stack = new Stack(); - const cluster = new eks.Cluster(stack, 'MyCluster'); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); // WHEN cluster.addFargateProfile('MyProfile', { @@ -53,7 +55,7 @@ export = { 'supports custom execution role'(test: Test) { // GIVEN const stack = new Stack(); - const cluster = new eks.Cluster(stack, 'MyCluster'); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); const myRole = new iam.Role(stack, 'MyRole', { assumedBy: new iam.AnyPrincipal() }); // WHEN @@ -76,7 +78,7 @@ export = { 'supports tags through aspects'(test: Test) { // GIVEN const stack = new Stack(); - const cluster = new eks.Cluster(stack, 'MyCluster'); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); // WHEN cluster.addFargateProfile('MyProfile', { @@ -104,7 +106,7 @@ export = { 'supports specifying vpc'(test: Test) { // GIVEN const stack = new Stack(); - const cluster = new eks.Cluster(stack, 'MyCluster'); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); const vpc = ec2.Vpc.fromVpcAttributes(stack, 'MyVpc', { vpcId: 'vpc123', availabilityZones: [ 'az1' ], @@ -132,7 +134,7 @@ export = { 'fails if there are no selectors or if there are more than 5'(test: Test) { // GIVEN const stack = new Stack(); - const cluster = new eks.Cluster(stack, 'MyCluster'); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); // THEN test.throws(() => cluster.addFargateProfile('MyProfile', { selectors: [ ] })); @@ -154,7 +156,7 @@ export = { const stack = new Stack(); // WHEN - new eks.FargateCluster(stack, 'FargateCluster'); + new eks.FargateCluster(stack, 'FargateCluster', { version: CLUSTER_VERSION }); // THEN expect(stack).to(haveResource('Custom::AWSCDK-EKS-KubernetesPatch', { @@ -196,6 +198,7 @@ export = { defaultProfile: { fargateProfileName: 'my-app', selectors: [{namespace: 'foo'}, {namespace: 'bar'}], }, + version: CLUSTER_VERSION, }); // THEN @@ -229,6 +232,7 @@ export = { defaultProfile: { selectors: [{namespace: 'foo'}, {namespace: 'bar'}], }, + version: CLUSTER_VERSION, }); // THEN @@ -255,7 +259,7 @@ export = { 'multiple Fargate profiles added to a cluster are processed sequentially'(test: Test) { // GIVEN const stack = new Stack(); - const cluster = new eks.Cluster(stack, 'MyCluster'); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); // WHEN cluster.addFargateProfile('MyProfile1', { @@ -300,7 +304,7 @@ export = { const stack = new Stack(); // WHEN - new eks.FargateCluster(stack, 'FargateCluster'); + new eks.FargateCluster(stack, 'FargateCluster', { version: CLUSTER_VERSION }); // THEN expect(stack).to(haveResource('Custom::AWSCDK-EKS-KubernetesResource', { @@ -326,7 +330,7 @@ export = { 'cannot be added to a cluster without kubectl enabled'(test: Test) { // GIVEN const stack = new Stack(); - const cluster = new eks.Cluster(stack, 'MyCluster', { kubectlEnabled: false }); + const cluster = new eks.Cluster(stack, 'MyCluster', { kubectlEnabled: false, version: CLUSTER_VERSION }); // WHEN test.throws(() => new eks.FargateProfile(stack, 'MyFargateProfile', { @@ -342,7 +346,7 @@ export = { const stack = new Stack(); // WHEN - new eks.FargateCluster(stack, 'FargateCluster'); + new eks.FargateCluster(stack, 'FargateCluster', { version: CLUSTER_VERSION }); // THEN expect(stack).to(haveResourceLike('AWS::IAM::Policy', { diff --git a/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts b/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts index 8c21b200a9936..b8963aa3a3989 100644 --- a/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts +++ b/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts @@ -4,11 +4,13 @@ import { Test } from 'nodeunit'; import * as eks from '../lib'; import { KubernetesPatch, PatchType } from '../lib/k8s-patch'; +const CLUSTER_VERSION = eks.KubernetesVersion.V1_16; + export = { 'applies a patch to k8s'(test: Test) { // GIVEN const stack = new Stack(); - const cluster = new eks.Cluster(stack, 'MyCluster'); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); // WHEN new KubernetesPatch(stack, 'MyPatch', { @@ -45,7 +47,7 @@ export = { 'defaults to "strategic" patch type if no patchType is specified'(test: Test) { // GIVEN const stack = new Stack(); - const cluster = new eks.Cluster(stack, 'MyCluster'); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); // WHEN new KubernetesPatch(stack, 'MyPatch', { @@ -62,7 +64,7 @@ export = { 'uses specified to patch type if specified'(test: Test) { // GIVEN const stack = new Stack(); - const cluster = new eks.Cluster(stack, 'MyCluster'); + const cluster = new eks.Cluster(stack, 'MyCluster', { version: CLUSTER_VERSION }); // WHEN new KubernetesPatch(stack, 'jsonPatch', { @@ -101,4 +103,4 @@ export = { })); test.done(); }, -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/aws-eks/test/test.manifest.ts b/packages/@aws-cdk/aws-eks/test/test.manifest.ts index 21459b633f6b0..565cf28aaffbf 100644 --- a/packages/@aws-cdk/aws-eks/test/test.manifest.ts +++ b/packages/@aws-cdk/aws-eks/test/test.manifest.ts @@ -1,15 +1,17 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { Test } from 'nodeunit'; -import { Cluster, KubernetesResource } from '../lib'; +import { Cluster, KubernetesResource, KubernetesVersion } from '../lib'; import { testFixtureNoVpc } from './util'; // tslint:disable:max-line-length +const CLUSTER_VERSION = KubernetesVersion.V1_16; + export = { 'basic usage'(test: Test) { // GIVEN const { stack } = testFixtureNoVpc(); - const cluster = new Cluster(stack, 'cluster'); + const cluster = new Cluster(stack, 'cluster', { version: CLUSTER_VERSION }); const manifest = [ { @@ -74,4 +76,4 @@ export = { })); test.done(); }, -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts index acd5871c6cb6b..160259706d727 100644 --- a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts @@ -7,13 +7,20 @@ import { testFixture } from './util'; // tslint:disable:max-line-length +const CLUSTER_VERSION = eks.KubernetesVersion.V1_16; + export = { 'create nodegroup correctly'(test: Test) { // GIVEN const { stack, vpc } = testFixture(); // WHEN - const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: true, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); new eks.Nodegroup(stack, 'Nodegroup', { cluster }); // THEN @@ -50,7 +57,12 @@ export = { const { stack, vpc } = testFixture(); // WHEN - const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: true, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); new eks.Nodegroup(stack, 'Nodegroup', { cluster, remoteAccess: { @@ -80,7 +92,12 @@ export = { const { stack, vpc } = testFixture(); // WHEN - const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: true, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); new eks.Nodegroup(stack, 'Nodegroup', { cluster, forceUpdate: false }); // THEN @@ -95,7 +112,12 @@ export = { const { stack, vpc } = testFixture(); // WHEN - const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 2 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: false, + defaultCapacity: 2, + version: CLUSTER_VERSION, + }); // add a extra nodegroup cluster.addNodegroup('extra-ng'); // THEN @@ -107,7 +129,12 @@ export = { const { stack, vpc } = testFixture(); // WHEN - const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: true, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); new eks.Nodegroup(stack, 'Nodegroup', { cluster, instanceType: new ec2.InstanceType('m5.large'), @@ -127,7 +154,11 @@ export = { const { stack, vpc } = testFixture(); // WHEN - const cluster = new eks.Cluster(stack, 'Cluster', { vpc, defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); new eks.Nodegroup(stack, 'Nodegroup', { cluster, remoteAccess: { @@ -149,7 +180,12 @@ export = { // 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 }); + const cluster = new eks.Cluster(stack1, 'Cluster', { + vpc, + kubectlEnabled: false, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // WHEN // const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); @@ -172,7 +208,12 @@ export = { 'addNodegroup correctly'(test: Test) { // GIVEN const { stack, vpc } = testFixture(); - const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: true, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // WHEN cluster.addNodegroup('ng'); @@ -209,7 +250,12 @@ export = { 'throws when desiredSize > maxSize'(test: Test) { // GIVEN const { stack, vpc } = testFixture(); - const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: true, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // THEN test.throws(() => cluster.addNodegroup('ng', { desiredSize: 3, maxSize: 2 }), /Desired capacity 3 can't be greater than max size 2/); test.done(); @@ -217,7 +263,12 @@ export = { 'throws when desiredSize < minSize'(test: Test) { // GIVEN const { stack, vpc } = testFixture(); - const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: true, defaultCapacity: 0 }); + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: true, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); // THEN test.throws(() => cluster.addNodegroup('ng', { desiredSize: 2, minSize: 3 }), /Minimum capacity 3 can't be greater than desired size 2/); test.done(); diff --git a/packages/@aws-cdk/aws-eks/test/util.ts b/packages/@aws-cdk/aws-eks/test/util.ts index 7112aa374ec91..28751aa5a96b0 100644 --- a/packages/@aws-cdk/aws-eks/test/util.ts +++ b/packages/@aws-cdk/aws-eks/test/util.ts @@ -1,6 +1,8 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import { App, Construct, Stack } from '@aws-cdk/core'; -import { Cluster } from '../lib'; +import { Cluster, KubernetesVersion } from '../lib'; + +const CLUSTER_VERSION = KubernetesVersion.V1_16; export function testFixture() { const { stack, app } = testFixtureNoVpc(); @@ -17,7 +19,7 @@ export function testFixtureNoVpc() { export function testFixtureCluster() { const { stack, app } = testFixtureNoVpc(); - const cluster = new Cluster(stack, 'Cluster'); + const cluster = new Cluster(stack, 'Cluster', { version: CLUSTER_VERSION }); return { stack, app, cluster }; } From ea0552a4378d61cffd14483896abadad7afa5a0a Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Sun, 5 Jul 2020 13:56:55 +0800 Subject: [PATCH 09/28] fix(eks): incorrect enableDockerBridge value when enabled (#8895) When `enableDockerBridge` is enabled in `BootstrapOptions`, pass the value correctly to user-data. Fixes: #5786 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/lib/user-data.ts | 4 ++-- packages/@aws-cdk/aws-eks/test/test.user-data.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/lib/user-data.ts b/packages/@aws-cdk/aws-eks/lib/user-data.ts index d8ad0905e9c8a..b613663b42a67 100644 --- a/packages/@aws-cdk/aws-eks/lib/user-data.ts +++ b/packages/@aws-cdk/aws-eks/lib/user-data.ts @@ -20,7 +20,7 @@ export function renderAmazonLinuxUserData(clusterName: string, autoScalingGroup: } if (options.enableDockerBridge) { - extraArgs.push('--enable-docker-bridge'); + extraArgs.push('--enable-docker-bridge true'); } if (options.dockerConfigJson) { @@ -67,4 +67,4 @@ export enum LifecycleLabel { * spot instances */ SPOT = 'Ec2Spot' -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-eks/test/test.user-data.ts b/packages/@aws-cdk/aws-eks/test/test.user-data.ts index a984f18df0c31..021cad8f16d3b 100644 --- a/packages/@aws-cdk/aws-eks/test/test.user-data.ts +++ b/packages/@aws-cdk/aws-eks/test/test.user-data.ts @@ -90,7 +90,7 @@ export = { })); // 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.deepEqual(userData[1], '/etc/eks/bootstrap.sh my-cluster-name --kubelet-extra-args "--node-labels lifecycle=OnDemand" --use-max-pods true --enable-docker-bridge true'); test.done(); }, From 57914c7f430b69ae54c9d2d9fac4da1092b45b42 Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Sun, 5 Jul 2020 20:51:20 +0300 Subject: [PATCH 10/28] feat(s3-deployment): prune - keep missing files on destination bucket (#8263) Introduce a `prune` property that eventually controls the `--delete` flag when invoking the `aws s3 sync` command. Resolves #953 In addition, migrate the module from `nodeunit` to `jest`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/assert/jest.ts | 11 + .../assert/lib/assertions/count-resources.ts | 10 +- .../@aws-cdk/aws-s3-deployment/.gitignore | 2 + .../@aws-cdk/aws-s3-deployment/.npmignore | 3 +- packages/@aws-cdk/aws-s3-deployment/README.md | 42 +- .../@aws-cdk/aws-s3-deployment/jest.config.js | 2 + .../aws-s3-deployment/lambda/src/index.py | 16 +- .../aws-s3-deployment/lambda/test/aws | 2 +- .../aws-s3-deployment/lambda/test/test.py | 30 +- .../lib/bucket-deployment.ts | 12 + .../@aws-cdk/aws-s3-deployment/package.json | 7 +- .../test/bucket-deployment.test.ts | 651 +++++++++++++++++ ...bucket-deployment-cloudfront.expected.json | 19 +- .../integ.bucket-deployment.expected.json | 115 +-- .../test/integ.bucket-deployment.ts | 10 +- .../test/test.bucket-deployment.ts | 661 ------------------ tools/cdk-build-tools/bin/cdk-test.ts | 2 +- yarn.lock | 372 ++++++++-- 18 files changed, 1165 insertions(+), 802 deletions(-) create mode 100644 packages/@aws-cdk/aws-s3-deployment/jest.config.js create mode 100644 packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts delete mode 100644 packages/@aws-cdk/aws-s3-deployment/test/test.bucket-deployment.ts diff --git a/packages/@aws-cdk/assert/jest.ts b/packages/@aws-cdk/assert/jest.ts index 8523318a11376..5c6db5727ed8d 100644 --- a/packages/@aws-cdk/assert/jest.ts +++ b/packages/@aws-cdk/assert/jest.ts @@ -1,5 +1,6 @@ import * as core from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; +import { countResources } from './lib'; import { JestFriendlyAssertion } from './lib/assertion'; import { haveOutput, HaveOutputProperties } from './lib/assertions/have-output'; import { HaveResourceAssertion, ResourcePart } from './lib/assertions/have-resource'; @@ -25,6 +26,8 @@ declare global { comparison?: ResourcePart): R; toHaveOutput(props: HaveOutputProperties): R; + + toCountResources(resourceType: string, count: number): R; } } } @@ -77,6 +80,14 @@ expect.extend({ return applyAssertion(haveOutput(props), actual); }, + + toCountResources( + actual: cxapi.CloudFormationStackArtifact | core.Stack, + resourceType: string, + count = 1) { + + return applyAssertion(countResources(resourceType, count), actual); + }, }); function applyAssertion(assertion: JestFriendlyAssertion, actual: cxapi.CloudFormationStackArtifact | core.Stack) { diff --git a/packages/@aws-cdk/assert/lib/assertions/count-resources.ts b/packages/@aws-cdk/assert/lib/assertions/count-resources.ts index ba9405fd45820..0827ba1f18306 100644 --- a/packages/@aws-cdk/assert/lib/assertions/count-resources.ts +++ b/packages/@aws-cdk/assert/lib/assertions/count-resources.ts @@ -1,11 +1,11 @@ -import { Assertion } from '../assertion'; +import { Assertion, JestFriendlyAssertion } from '../assertion'; import { StackInspector } from '../inspector'; import { isSuperObject } from './have-resource'; /** * An assertion to check whether a resource of a given type and with the given properties exists, disregarding properties */ -export function countResources(resourceType: string, count = 1): Assertion { +export function countResources(resourceType: string, count = 1): JestFriendlyAssertion { return new CountResourcesAssertion(resourceType, count); } @@ -16,7 +16,7 @@ export function countResourcesLike(resourceType: string, count = 1, props: any): return new CountResourcesAssertion(resourceType, count, props); } -class CountResourcesAssertion extends Assertion { +class CountResourcesAssertion extends JestFriendlyAssertion { private inspected: number = 0; private readonly props: any; @@ -48,6 +48,10 @@ class CountResourcesAssertion extends Assertion { return counted === this.count; } + public generateErrorMessage(): string { + return this.description; + } + public get description(): string { return `stack only has ${this.inspected} resource of type ${this.resourceType}${this.props ? ' with specified properties' : ''} but we expected to find ${this.count}`; } diff --git a/packages/@aws-cdk/aws-s3-deployment/.gitignore b/packages/@aws-cdk/aws-s3-deployment/.gitignore index e99004d67b1a1..48b0b20af63bf 100644 --- a/packages/@aws-cdk/aws-s3-deployment/.gitignore +++ b/packages/@aws-cdk/aws-s3-deployment/.gitignore @@ -18,3 +18,5 @@ nyc.config.js *.snk !.eslintrc.js + +!jest.config.js \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3-deployment/.npmignore b/packages/@aws-cdk/aws-s3-deployment/.npmignore index d1bc4d39e6428..5ad383d334409 100644 --- a/packages/@aws-cdk/aws-s3-deployment/.npmignore +++ b/packages/@aws-cdk/aws-s3-deployment/.npmignore @@ -22,5 +22,6 @@ lambda/*.sh tsconfig.json .eslintrc.js +jest.config.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out diff --git a/packages/@aws-cdk/aws-s3-deployment/README.md b/packages/@aws-cdk/aws-s3-deployment/README.md index 80bd7a4e9e515..a90ea32f1ae20 100644 --- a/packages/@aws-cdk/aws-s3-deployment/README.md +++ b/packages/@aws-cdk/aws-s3-deployment/README.md @@ -41,7 +41,7 @@ This is what happens under the hood: is set to point to the assets bucket. 3. The custom resource downloads the .zip archive, extracts it and issues `aws s3 sync --delete` against the destination bucket (in this case - `websiteBucket`). If there is more than one source, the sources will be + `websiteBucket`). If there is more than one source, the sources will be downloaded and merged pre-deployment at this step. ## Supported sources @@ -59,10 +59,44 @@ all but a single file: ## Retain on Delete -By default, the contents of the destination bucket will be deleted when the +By default, the contents of the destination bucket will **not** be deleted when the `BucketDeployment` resource is removed from the stack or when the destination is -changed. You can use the option `retainOnDelete: true` to disable this behavior, -in which case the contents will be retained. +changed. You can use the option `retainOnDelete: false` to disable this behavior, +in which case the contents will be deleted. + +## Prune + +By default, files in the destination bucket that don't exist in the source will be deleted +when the `BucketDeployment` resource is created or updated. You can use the option `prune: false` to disable +this behavior, in which case the files will not be deleted. + +```typescript +new s3deploy.BucketDeployment(this, 'DeployMeWithoutDeletingFilesOnDestination', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket, + prune: false, +}); +``` + +This option also enables you to specify multiple bucket deployments for the same destination bucket & prefix, +each with its own characteristics. For example, you can set different cache-control headers +based on file extensions: + +```typescript +new BucketDeployment(this, 'BucketDeployment', { + sources: [Source.asset('./website', { exclude: ['index.html' })], + destinationBucket: bucket, + cacheControl: [CacheControl.fromString('max-age=31536000,public,immutable')], + prune: false, +}); + +new BucketDeployment(this, 'HTMLBucketDeployment', { + sources: [Source.asset('./website', { exclude: ['!index.html'] })], + destinationBucket: bucket, + cacheControl: [CacheControl.fromString('max-age=0,no-cache,no-store,must-revalidate')], + prune: false, +}); +``` ## Objects metadata diff --git a/packages/@aws-cdk/aws-s3-deployment/jest.config.js b/packages/@aws-cdk/aws-s3-deployment/jest.config.js new file mode 100644 index 0000000000000..cd664e1d069e5 --- /dev/null +++ b/packages/@aws-cdk/aws-s3-deployment/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3-deployment/lambda/src/index.py b/packages/@aws-cdk/aws-s3-deployment/lambda/src/index.py index 4daff5f3bce5d..474eb6d69226c 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lambda/src/index.py +++ b/packages/@aws-cdk/aws-s3-deployment/lambda/src/index.py @@ -47,6 +47,7 @@ def cfn_error(message=None): distribution_id = props.get('DistributionId', '') user_metadata = props.get('UserMetadata', {}) system_metadata = props.get('SystemMetadata', {}) + prune = props.get('Prune', 'true').lower() == 'true' default_distribution_path = dest_bucket_prefix if not default_distribution_path.endswith("/"): @@ -98,7 +99,7 @@ def cfn_error(message=None): aws_command("s3", "rm", old_s3_dest, "--recursive") if request_type == "Update" or request_type == "Create": - s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata) + s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune) if distribution_id: cloudfront_invalidate(distribution_id, distribution_paths) @@ -112,7 +113,7 @@ def cfn_error(message=None): #--------------------------------------------------------------------------------------------------- # populate all files from s3_source_zips to a destination bucket -def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata): +def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune): # create a temporary working directory workdir=tempfile.mkdtemp() logger.info("| workdir: %s" % workdir) @@ -131,7 +132,16 @@ def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata): zip.extractall(contents_dir) # sync from "contents" to destination - aws_command("s3", "sync", "--delete", contents_dir, s3_dest, *create_metadata_args(user_metadata, system_metadata)) + + s3_command = ["s3", "sync"] + + if prune: + s3_command.append("--delete") + + s3_command.extend([contents_dir, s3_dest]) + s3_command.extend(create_metadata_args(user_metadata, system_metadata)) + aws_command(*s3_command) + shutil.rmtree(workdir) #--------------------------------------------------------------------------------------------------- diff --git a/packages/@aws-cdk/aws-s3-deployment/lambda/test/aws b/packages/@aws-cdk/aws-s3-deployment/lambda/test/aws index 1796d021f650b..969bb982cd08c 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lambda/test/aws +++ b/packages/@aws-cdk/aws-s3-deployment/lambda/test/aws @@ -21,7 +21,7 @@ if sys.argv[2] == "cp" and not sys.argv[4].startswith("s3://"): sys.argv[4] = "archive.zip" if sys.argv[2] == "sync": - sys.argv[4] = "contents.zip" + sys.argv[4 if '--delete' in sys.argv else 3] = "contents.zip" with open("./aws.out", "a") as myfile: myfile.write(json.dumps(sys.argv[1:]) + "\n") diff --git a/packages/@aws-cdk/aws-s3-deployment/lambda/test/test.py b/packages/@aws-cdk/aws-s3-deployment/lambda/test/test.py index 30ac3ba26ada7..24552c98d45ea 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lambda/test/test.py +++ b/packages/@aws-cdk/aws-s3-deployment/lambda/test/test.py @@ -16,7 +16,7 @@ class TestHandler(unittest.TestCase): def setUp(self): logger = logging.getLogger() - + # clean up old aws.out file (from previous runs) try: os.remove("aws.out") except OSError: pass @@ -37,6 +37,34 @@ def test_create_update(self): ["s3", "sync", "--delete", "contents.zip", "s3:///"] ) + def test_create_no_delete(self): + invoke_handler("Create", { + "SourceBucketNames": [""], + "SourceObjectKeys": [""], + "DestinationBucketName": "", + "Prune": "false" + }) + + self.assertAwsCommands( + ["s3", "cp", "s3:///", "archive.zip"], + ["s3", "sync", "contents.zip", "s3:///"] + ) + + def test_update_no_delete(self): + invoke_handler("Update", { + "SourceBucketNames": [""], + "SourceObjectKeys": [""], + "DestinationBucketName": "", + "Prune": "false" + }, old_resource_props={ + "DestinationBucketName": "", + }, physical_id="") + + self.assertAwsCommands( + ["s3", "cp", "s3:///", "archive.zip"], + ["s3", "sync", "contents.zip", "s3:///"] + ) + def test_create_update_multiple_sources(self): invoke_handler("Create", { "SourceBucketNames": ["", ""], diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts index b46e239a75a83..c564b2da9d6c4 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts @@ -30,6 +30,16 @@ export interface BucketDeploymentProps { */ readonly destinationKeyPrefix?: string; + /** + * If this is set to false, files in the destination bucket that + * do not exist in the asset, will NOT be deleted during deployment (create/update). + * + * @see https://docs.aws.amazon.com/cli/latest/reference/s3/sync.html + * + * @default true + */ + readonly prune?: boolean + /** * If this is set to "false", the destination files will be deleted when the * resource is deleted or the destination is updated. @@ -197,12 +207,14 @@ export class BucketDeployment extends cdk.Construct { DestinationBucketName: props.destinationBucket.bucketName, DestinationBucketKeyPrefix: props.destinationKeyPrefix, RetainOnDelete: props.retainOnDelete, + Prune: props.prune ?? true, UserMetadata: props.metadata ? mapUserMetadata(props.metadata) : undefined, SystemMetadata: mapSystemMetadata(props), DistributionId: props.distribution ? props.distribution.distributionId : undefined, DistributionPaths: props.distributionPaths, }, }); + } private renderSingletonUuid(memoryLimit?: number) { diff --git a/packages/@aws-cdk/aws-s3-deployment/package.json b/packages/@aws-cdk/aws-s3-deployment/package.json index 9291eb8292125..cde79ab1cfc0f 100644 --- a/packages/@aws-cdk/aws-s3-deployment/package.json +++ b/packages/@aws-cdk/aws-s3-deployment/package.json @@ -51,7 +51,8 @@ ], "test": [ "/bin/bash lambda/test.sh" - ] + ], + "jest": true }, "keywords": [ "aws", @@ -77,10 +78,10 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/nodeunit": "^0.0.31", + "@types/jest": "^25.2.3", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "nodeunit": "^0.11.3", + "jest": "^25.5.4", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts new file mode 100644 index 0000000000000..ce64692d9a9ef --- /dev/null +++ b/packages/@aws-cdk/aws-s3-deployment/test/bucket-deployment.test.ts @@ -0,0 +1,651 @@ +import { arrayWith } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as path from 'path'; +import * as s3deploy from '../lib'; + +// tslint:disable:max-line-length +// tslint:disable:object-literal-key-quotes + +test('deploy from local directory asset', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket: bucket, + }); + + // THEN + expect(stack).toHaveResource('Custom::CDKBucketDeployment', { + 'ServiceToken': { + 'Fn::GetAtt': [ + 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536', + 'Arn', + ], + }, + 'SourceBucketNames': [{ + 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A', + }], + 'SourceObjectKeys': [{ + 'Fn::Join': [ + '', + [ + { + 'Fn::Select': [ + 0, + { + 'Fn::Split': [ + '||', + { + 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', + }, + ], + }, + ], + }, + { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '||', + { + 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', + }, + ], + }, + ], + }, + ], + ], + }], + 'DestinationBucketName': { + 'Ref': 'DestC383B82A', + }, + }); +}); + +test('deploy from local directory assets', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [ + s3deploy.Source.asset(path.join(__dirname, 'my-website')), + s3deploy.Source.asset(path.join(__dirname, 'my-website-second')), + ], + destinationBucket: bucket, + }); + + // THEN + expect(stack).toHaveResource('Custom::CDKBucketDeployment', { + 'ServiceToken': { + 'Fn::GetAtt': [ + 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536', + 'Arn', + ], + }, + 'SourceBucketNames': [ + { + 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A', + }, + { + 'Ref': 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3Bucket99793559', + }, + ], + 'SourceObjectKeys': [ + { + 'Fn::Join': [ + '', + [ + { + 'Fn::Select': [ + 0, + { + 'Fn::Split': [ + '||', + { + 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', + }, + ], + }, + ], + }, + { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '||', + { + 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', + }, + ], + }, + ], + }, + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + { + 'Fn::Select': [ + 0, + { + 'Fn::Split': [ + '||', + { + 'Ref': 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3VersionKeyD9ACE665', + }, + ], + }, + ], + }, + { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '||', + { + 'Ref': 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3VersionKeyD9ACE665', + }, + ], + }, + ], + }, + ], + ], + }, + ], + 'DestinationBucketName': { + 'Ref': 'DestC383B82A', + }, + }); +}); + +test('fails if local asset is a non-zip file', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // THEN + expect(() => new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website', 'index.html'))], + destinationBucket: bucket, + })).toThrow(/Asset path must be either a \.zip file or a directory/); +}); + +test('deploy from a local .zip file', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], + destinationBucket: bucket, + }); + +}); + +test('honors passed asset options', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'), { + exclude: ['*', '!index.html'], + })], + destinationBucket: bucket, + }); + + // THEN + expect(stack).toHaveResource('Custom::CDKBucketDeployment', { + 'ServiceToken': { + 'Fn::GetAtt': [ + 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536', + 'Arn', + ], + }, + 'SourceBucketNames': [{ + 'Ref': 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3BucketB41AE64D', + }], + 'SourceObjectKeys': [{ + 'Fn::Join': [ + '', + [ + { + 'Fn::Select': [ + 0, + { + 'Fn::Split': [ + '||', + { + 'Ref': 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3VersionKeyF3CBA38F', + }, + ], + }, + ], + }, + { + 'Fn::Select': [ + 1, + { + 'Fn::Split': [ + '||', + { + 'Ref': 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3VersionKeyF3CBA38F', + }, + ], + }, + ], + }, + ], + ], + }], + 'DestinationBucketName': { + 'Ref': 'DestC383B82A', + }, + }); +}); + +test('retainOnDelete can be used to retain files when resource is deleted', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], + destinationBucket: bucket, + retainOnDelete: true, + }); + + // THEN + expect(stack).toHaveResource('Custom::CDKBucketDeployment', { + RetainOnDelete: true, + }); +}); + +test('user metadata is correctly transformed', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], + destinationBucket: bucket, + metadata: { + A: '1', + B: '2', + }, + }); + + // THEN + expect(stack).toHaveResource('Custom::CDKBucketDeployment', { + UserMetadata: { 'x-amzn-meta-a': '1', 'x-amzn-meta-b': '2' }, + }); +}); + +test('system metadata is correctly transformed', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], + destinationBucket: bucket, + contentType: 'text/html', + contentLanguage: 'en', + storageClass: s3deploy.StorageClass.INTELLIGENT_TIERING, + contentDisposition: 'inline', + serverSideEncryption: s3deploy.ServerSideEncryption.AWS_KMS, + serverSideEncryptionAwsKmsKeyId: 'mykey', + serverSideEncryptionCustomerAlgorithm: 'rot13', + websiteRedirectLocation: 'example', + cacheControl: [s3deploy.CacheControl.setPublic(), s3deploy.CacheControl.maxAge(cdk.Duration.hours(1))], + expires: s3deploy.Expires.after(cdk.Duration.hours(12)), + }); + + // THEN + expect(stack).toHaveResource('Custom::CDKBucketDeployment', { + SystemMetadata: { + 'content-type': 'text/html', + 'content-language': 'en', + 'content-disposition': 'inline', + 'storage-class': 'INTELLIGENT_TIERING', + 'sse': 'aws:kms', + 'sse-kms-key-id': 'mykey', + 'cache-control': 'public, max-age=3600', + 'expires': s3deploy.Expires.after(cdk.Duration.hours(12)).value, + 'sse-c-copy-source': 'rot13', + 'website-redirect': 'example', + }, + }); +}); + +test('expires type has correct values', () => { + expect(s3deploy.Expires.atDate(new Date('Sun, 26 Jan 2020 00:53:20 GMT')).value).toEqual('Sun, 26 Jan 2020 00:53:20 GMT'); + expect(s3deploy.Expires.atTimestamp(1580000000000).value).toEqual('Sun, 26 Jan 2020 00:53:20 GMT'); + expect(Math.abs(new Date(s3deploy.Expires.after(cdk.Duration.minutes(10)).value).getTime() - (Date.now() + 600000)) < 15000).toBeTruthy(); + expect(s3deploy.Expires.fromString('Tue, 04 Feb 2020 08:45:33 GMT').value).toEqual('Tue, 04 Feb 2020 08:45:33 GMT'); + +}); + +test('cache control type has correct values', () => { + expect(s3deploy.CacheControl.mustRevalidate().value).toEqual('must-revalidate'); + expect(s3deploy.CacheControl.noCache().value).toEqual('no-cache'); + expect(s3deploy.CacheControl.noTransform().value).toEqual('no-transform'); + expect(s3deploy.CacheControl.setPublic().value).toEqual('public'); + expect(s3deploy.CacheControl.setPrivate().value).toEqual('private'); + expect(s3deploy.CacheControl.proxyRevalidate().value).toEqual('proxy-revalidate'); + expect(s3deploy.CacheControl.maxAge(cdk.Duration.minutes(1)).value).toEqual('max-age=60'); + expect(s3deploy.CacheControl.sMaxAge(cdk.Duration.minutes(1)).value).toEqual('s-maxage=60'); + expect(s3deploy.CacheControl.fromString('only-if-cached').value).toEqual('only-if-cached'); +}); + +test('storage class type has correct values', () => { + expect(s3deploy.StorageClass.STANDARD).toEqual('STANDARD'); + expect(s3deploy.StorageClass.REDUCED_REDUNDANCY).toEqual('REDUCED_REDUNDANCY'); + expect(s3deploy.StorageClass.STANDARD_IA).toEqual('STANDARD_IA'); + expect(s3deploy.StorageClass.ONEZONE_IA).toEqual('ONEZONE_IA'); + expect(s3deploy.StorageClass.INTELLIGENT_TIERING).toEqual('INTELLIGENT_TIERING'); + expect(s3deploy.StorageClass.GLACIER).toEqual('GLACIER'); + expect(s3deploy.StorageClass.DEEP_ARCHIVE).toEqual('DEEP_ARCHIVE'); +}); + +test('server side encryption type has correct values', () => { + expect(s3deploy.ServerSideEncryption.AES_256).toEqual('AES256'); + expect(s3deploy.ServerSideEncryption.AWS_KMS).toEqual('aws:kms'); +}); + +test('distribution can be used to provide a CloudFront distribution for invalidation', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + const distribution = new cloudfront.CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: bucket, + }, + behaviors: [{ isDefaultBehavior: true }], + }, + ], + }); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], + destinationBucket: bucket, + distribution, + distributionPaths: ['/images/*'], + }); + + expect(stack).toHaveResource('Custom::CDKBucketDeployment', { + DistributionId: { + 'Ref': 'DistributionCFDistribution882A7313', + }, + DistributionPaths: ['/images/*'], + }); +}); + +test('invalidation can happen without distributionPaths provided', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + const distribution = new cloudfront.CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: bucket, + }, + behaviors: [{ isDefaultBehavior: true }], + }, + ], + }); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], + destinationBucket: bucket, + distribution, + }); + + expect(stack).toHaveResource('Custom::CDKBucketDeployment', { + DistributionId: { + 'Ref': 'DistributionCFDistribution882A7313', + }, + }); + +}); + +test('fails if distribution paths provided but not distribution ID', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // THEN + expect(() => new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website', 'index.html'))], + destinationBucket: bucket, + distributionPaths: ['/images/*'], + })).toThrow(/Distribution must be specified if distribution paths are specified/); + +}); + +test('lambda execution role gets permissions to read from the source bucket and read/write in destination', () => { + // GIVEN + const stack = new cdk.Stack(); + const source = new s3.Bucket(stack, 'Source'); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.bucket(source, 'file.zip')], + destinationBucket: bucket, + }); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': [ + 's3:GetObject*', + 's3:GetBucket*', + 's3:List*', + ], + 'Effect': 'Allow', + 'Resource': [ + { + 'Fn::GetAtt': [ + 'Source71E471F1', + 'Arn', + ], + }, + { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + 'Source71E471F1', + 'Arn', + ], + }, + '/*', + ], + ], + }, + ], + }, + { + 'Action': [ + 's3:GetObject*', + 's3:GetBucket*', + 's3:List*', + 's3:DeleteObject*', + 's3:PutObject*', + 's3:Abort*', + ], + 'Effect': 'Allow', + 'Resource': [ + { + 'Fn::GetAtt': [ + 'DestC383B82A', + 'Arn', + ], + }, + { + 'Fn::Join': [ + '', + [ + { + 'Fn::GetAtt': [ + 'DestC383B82A', + 'Arn', + ], + }, + '/*', + ], + ], + }, + ], + }, + ], + 'Version': '2012-10-17', + }, + 'PolicyName': 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF', + 'Roles': [ + { + 'Ref': 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265', + }, + ], + }); +}); + +test('memoryLimit can be used to specify the memory limit for the deployment resource handler', () => { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + + // we define 3 deployments with 2 different memory configurations + + new s3deploy.BucketDeployment(stack, 'Deploy256-1', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket: bucket, + memoryLimit: 256, + }); + + new s3deploy.BucketDeployment(stack, 'Deploy256-2', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket: bucket, + memoryLimit: 256, + }); + + new s3deploy.BucketDeployment(stack, 'Deploy1024', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket: bucket, + memoryLimit: 1024, + }); + + // THEN + + // we expect to find only two handlers, one for each configuration + + expect(stack).toCountResources('AWS::Lambda::Function', 2); + expect(stack).toHaveResource('AWS::Lambda::Function', { MemorySize: 256 }); + expect(stack).toHaveResource('AWS::Lambda::Function', { MemorySize: 1024 }); +}); + +test('deployment allows custom role to be supplied', () => { + + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + const existingRole = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('lambda.amazon.com'), + }); + + // WHEN + new s3deploy.BucketDeployment(stack, 'DeployWithRole', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket: bucket, + role: existingRole, + }); + + // THEN + expect(stack).toCountResources('AWS::IAM::Role', 1); + expect(stack).toCountResources('AWS::Lambda::Function', 1); + expect(stack).toHaveResource('AWS::Lambda::Function', { + 'Role': { + 'Fn::GetAtt': [ + 'Role1ABCC5F0', + 'Arn', + ], + }, + }); +}); + +test('deploy without deleting missing files from destination', () => { + + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket: bucket, + prune: false, + }); + + expect(stack).toHaveResourceLike('Custom::CDKBucketDeployment', { + 'Prune': false, + }); +}); + +test('Deployment role gets KMS permissions when using assets from new style synthesizer', () => { + const stack = new cdk.Stack(undefined, undefined, { + synthesizer: new cdk.DefaultStackSynthesizer(), + }); + const bucket = new s3.Bucket(stack, 'Dest'); + + // WHEN + new s3deploy.BucketDeployment(stack, 'Deploy', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket: bucket, + }); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: arrayWith({ + Action: ['kms:Decrypt', 'kms:DescribeKey'], + Effect: 'Allow', + Resource: { 'Fn::ImportValue': 'CdkBootstrap-hnb659fds-FileAssetKeyArn' }, + }), + }, + }); + +}); diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json index d0e61e14a23ae..e5e0dfe714f00 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment-cloudfront.expected.json @@ -104,6 +104,7 @@ "Ref": "Destination3E3DC043D" }, "RetainOnDelete": false, + "Prune": true, "DistributionId": { "Ref": "DistributionCFDistribution882A7313" }, @@ -248,7 +249,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3Bucket88A20322" + "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3BucketB6159468" }, "S3Key": { "Fn::Join": [ @@ -261,7 +262,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8" + "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0" } ] } @@ -274,7 +275,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8" + "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0" } ] } @@ -301,17 +302,17 @@ } }, "Parameters": { - "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3Bucket88A20322": { + "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3BucketB6159468": { "Type": "String", - "Description": "S3 bucket for asset \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" + "Description": "S3 bucket for asset \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" }, - "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8": { + "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0": { "Type": "String", - "Description": "S3 key for asset version \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" + "Description": "S3 key for asset version \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" }, - "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0ArtifactHash877EFA91": { + "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50ArtifactHash846130E4": { "Type": "String", - "Description": "Artifact hash for asset \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" + "Description": "Artifact hash for asset \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" }, "AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A": { "Type": "String", diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json index e3308f63a431d..a3b8a6d1c81c9 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.expected.json @@ -10,38 +10,6 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, - "DestinationPolicy7982387E": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "Destination920A3C57" - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:GetObject", - "Effect": "Allow", - "Principal": "*", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "Destination920A3C57", - "Arn" - ] - }, - "/*" - ] - ] - } - } - ], - "Version": "2012-10-17" - } - } - }, "DeployMeCustomResource4455EE35": { "Type": "Custom::CDKBucketDeployment", "Properties": { @@ -94,7 +62,8 @@ "DestinationBucketName": { "Ref": "Destination920A3C57" }, - "RetainOnDelete": false + "RetainOnDelete": false, + "Prune": true }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" @@ -291,7 +260,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3Bucket88A20322" + "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3BucketB6159468" }, "S3Key": { "Fn::Join": [ @@ -304,7 +273,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8" + "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0" } ] } @@ -317,7 +286,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8" + "Ref": "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0" } ] } @@ -400,7 +369,8 @@ "Ref": "Destination281A09BDF" }, "DestinationBucketKeyPrefix": "deploy/here/", - "RetainOnDelete": false + "RetainOnDelete": false, + "Prune": true }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" @@ -463,6 +433,7 @@ "Ref": "Destination3E3DC043D" }, "RetainOnDelete": false, + "Prune": true, "UserMetadata": { "x-amzn-meta-a": "aaa", "x-amzn-meta-b": "bbb", @@ -475,20 +446,78 @@ }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" + }, + "DeployMeWithoutDeletingFilesOnDestinationCustomResourceA390B02B": { + "Type": "Custom::CDKBucketDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", + "Arn" + ] + }, + "SourceBucketNames": [ + { + "Ref": "AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A" + } + ], + "SourceObjectKeys": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C" + } + ] + } + ] + } + ] + ] + } + ], + "DestinationBucketName": { + "Ref": "Destination920A3C57" + }, + "RetainOnDelete": false, + "Prune": false + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" } }, "Parameters": { - "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3Bucket88A20322": { + "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3BucketB6159468": { "Type": "String", - "Description": "S3 bucket for asset \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" + "Description": "S3 bucket for asset \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" }, - "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0S3VersionKey5726B1E8": { + "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50S3VersionKey2060CAC0": { "Type": "String", - "Description": "S3 key for asset version \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" + "Description": "S3 key for asset version \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" }, - "AssetParameters85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0ArtifactHash877EFA91": { + "AssetParameters4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50ArtifactHash846130E4": { "Type": "String", - "Description": "Artifact hash for asset \"85263806834b4abe18b7438876d0e408b131a41c86272285f069bb9fa96666f0\"" + "Description": "Artifact hash for asset \"4184245adc1f2ed71e1f0ae5719f8fd7f34324b750f1bf06b2fb5cf1f4014f50\"" }, "AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A": { "Type": "String", diff --git a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts index 41460bb1af538..ee9918709b0b5 100644 --- a/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/test/integ.bucket-deployment.ts @@ -9,7 +9,7 @@ class TestBucketDeployment extends cdk.Stack { const destinationBucket = new s3.Bucket(this, 'Destination', { websiteIndexDocument: 'index.html', - publicReadAccess: true, + publicReadAccess: false, removalPolicy: cdk.RemovalPolicy.DESTROY, }); @@ -38,6 +38,14 @@ class TestBucketDeployment extends cdk.Stack { contentType: 'text/html', metadata: { A: 'aaa', B: 'bbb', C: 'ccc' }, }); + + new s3deploy.BucketDeployment(this, 'DeployMeWithoutDeletingFilesOnDestination', { + sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], + destinationBucket, + prune: false, + retainOnDelete: false, + }); + } } diff --git a/packages/@aws-cdk/aws-s3-deployment/test/test.bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/test/test.bucket-deployment.ts deleted file mode 100644 index fa4fad93d681e..0000000000000 --- a/packages/@aws-cdk/aws-s3-deployment/test/test.bucket-deployment.ts +++ /dev/null @@ -1,661 +0,0 @@ -import { arrayWith, countResources, expect, haveResource } from '@aws-cdk/assert'; -import * as cloudfront from '@aws-cdk/aws-cloudfront'; -import * as iam from '@aws-cdk/aws-iam'; -import * as s3 from '@aws-cdk/aws-s3'; -import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; -import * as path from 'path'; -import * as s3deploy from '../lib'; - -// tslint:disable:max-line-length -// tslint:disable:object-literal-key-quotes - -export = { - 'deploy from local directory asset'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - - // WHEN - new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], - destinationBucket: bucket, - }); - - // THEN - expect(stack).to(haveResource('Custom::CDKBucketDeployment', { - 'ServiceToken': { - 'Fn::GetAtt': [ - 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536', - 'Arn', - ], - }, - 'SourceBucketNames': [{ - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A', - }], - 'SourceObjectKeys': [{ - 'Fn::Join': [ - '', - [ - { - 'Fn::Select': [ - 0, - { - 'Fn::Split': [ - '||', - { - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', - }, - ], - }, - ], - }, - { - 'Fn::Select': [ - 1, - { - 'Fn::Split': [ - '||', - { - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', - }, - ], - }, - ], - }, - ], - ], - }], - 'DestinationBucketName': { - 'Ref': 'DestC383B82A', - }, - })); - test.done(); - }, - - 'deploy from local directory assets'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - - // WHEN - new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [ - s3deploy.Source.asset(path.join(__dirname, 'my-website')), - s3deploy.Source.asset(path.join(__dirname, 'my-website-second')), - ], - destinationBucket: bucket, - }); - - // THEN - expect(stack).to(haveResource('Custom::CDKBucketDeployment', { - 'ServiceToken': { - 'Fn::GetAtt': [ - 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536', - 'Arn', - ], - }, - 'SourceBucketNames': [ - { - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3Bucket9CD8B20A', - }, - { - 'Ref': 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3Bucket99793559', - }, - ], - 'SourceObjectKeys': [ - { - 'Fn::Join': [ - '', - [ - { - 'Fn::Select': [ - 0, - { - 'Fn::Split': [ - '||', - { - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', - }, - ], - }, - ], - }, - { - 'Fn::Select': [ - 1, - { - 'Fn::Split': [ - '||', - { - 'Ref': 'AssetParametersfc4481abf279255619ff7418faa5d24456fef3432ea0da59c95542578ff0222eS3VersionKeyA58D380C', - }, - ], - }, - ], - }, - ], - ], - }, - { - 'Fn::Join': [ - '', - [ - { - 'Fn::Select': [ - 0, - { - 'Fn::Split': [ - '||', - { - 'Ref': 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3VersionKeyD9ACE665', - }, - ], - }, - ], - }, - { - 'Fn::Select': [ - 1, - { - 'Fn::Split': [ - '||', - { - 'Ref': 'AssetParametersa94977ede0211fd3b45efa33d6d8d1d7bbe0c5a96d977139d8b16abfa96fe9cbS3VersionKeyD9ACE665', - }, - ], - }, - ], - }, - ], - ], - }, - ], - 'DestinationBucketName': { - 'Ref': 'DestC383B82A', - }, - })); - test.done(); - }, - - 'fails if local asset is a non-zip file'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - - // THEN - test.throws(() => new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website', 'index.html'))], - destinationBucket: bucket, - }), /Asset path must be either a \.zip file or a directory/); - - test.done(); - }, - - 'deploy from a local .zip file'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - - // WHEN - new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], - destinationBucket: bucket, - }); - - test.done(); - }, - - 'honors passed asset options'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - - // WHEN - new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'), { - exclude: ['*', '!index.html'], - })], - destinationBucket: bucket, - }); - - // THEN - expect(stack).to(haveResource('Custom::CDKBucketDeployment', { - 'ServiceToken': { - 'Fn::GetAtt': [ - 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536', - 'Arn', - ], - }, - 'SourceBucketNames': [{ - 'Ref': 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3BucketB41AE64D', - }], - 'SourceObjectKeys': [{ - 'Fn::Join': [ - '', - [ - { - 'Fn::Select': [ - 0, - { - 'Fn::Split': [ - '||', - { - 'Ref': 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3VersionKeyF3CBA38F', - }, - ], - }, - ], - }, - { - 'Fn::Select': [ - 1, - { - 'Fn::Split': [ - '||', - { - 'Ref': 'AssetParameters86f8bca4f28a0bcafef0a98fe4cea25c0071aca27401e35cfaecd06313373bcaS3VersionKeyF3CBA38F', - }, - ], - }, - ], - }, - ], - ], - }], - 'DestinationBucketName': { - 'Ref': 'DestC383B82A', - }, - })); - test.done(); - }, - 'retainOnDelete can be used to retain files when resource is deleted'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - - // WHEN - new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], - destinationBucket: bucket, - retainOnDelete: true, - }); - - // THEN - expect(stack).to(haveResource('Custom::CDKBucketDeployment', { - RetainOnDelete: true, - })); - - test.done(); - }, - - 'user metadata is correctly transformed'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - - // WHEN - new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], - destinationBucket: bucket, - metadata: { - A: '1', - B: '2', - }, - }); - - // THEN - expect(stack).to(haveResource('Custom::CDKBucketDeployment', { - UserMetadata: { 'x-amzn-meta-a': '1', 'x-amzn-meta-b': '2' }, - })); - - test.done(); - }, - - 'system metadata is correctly transformed'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - - // WHEN - new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], - destinationBucket: bucket, - contentType: 'text/html', - contentLanguage: 'en', - storageClass: s3deploy.StorageClass.INTELLIGENT_TIERING, - contentDisposition: 'inline', - serverSideEncryption: s3deploy.ServerSideEncryption.AWS_KMS, - serverSideEncryptionAwsKmsKeyId: 'mykey', - serverSideEncryptionCustomerAlgorithm: 'rot13', - websiteRedirectLocation: 'example', - cacheControl: [s3deploy.CacheControl.setPublic(), s3deploy.CacheControl.maxAge(cdk.Duration.hours(1))], - expires: s3deploy.Expires.after(cdk.Duration.hours(12)), - }); - - // THEN - expect(stack).to(haveResource('Custom::CDKBucketDeployment', { - SystemMetadata: { - 'content-type': 'text/html', - 'content-language': 'en', - 'content-disposition': 'inline', - 'storage-class': 'INTELLIGENT_TIERING', - 'sse': 'aws:kms', - 'sse-kms-key-id': 'mykey', - 'cache-control': 'public, max-age=3600', - 'expires': s3deploy.Expires.after(cdk.Duration.hours(12)).value, - 'sse-c-copy-source': 'rot13', - 'website-redirect': 'example', - }, - })); - - test.done(); - }, - - 'expires type has correct values'(test: Test) { - test.equal(s3deploy.Expires.atDate(new Date('Sun, 26 Jan 2020 00:53:20 GMT')).value, 'Sun, 26 Jan 2020 00:53:20 GMT'); - test.equal(s3deploy.Expires.atTimestamp(1580000000000).value, 'Sun, 26 Jan 2020 00:53:20 GMT'); - test.ok(Math.abs(new Date(s3deploy.Expires.after(cdk.Duration.minutes(10)).value).getTime() - (Date.now() + 600000)) < 15000, 'Expires.after accurate to within 15 seconds'); - test.equal(s3deploy.Expires.fromString('Tue, 04 Feb 2020 08:45:33 GMT').value, 'Tue, 04 Feb 2020 08:45:33 GMT'); - - test.done(); - }, - - 'cache control type has correct values'(test: Test) { - test.equal(s3deploy.CacheControl.mustRevalidate().value, 'must-revalidate'); - test.equal(s3deploy.CacheControl.noCache().value, 'no-cache'); - test.equal(s3deploy.CacheControl.noTransform().value, 'no-transform'); - test.equal(s3deploy.CacheControl.setPublic().value, 'public'); - test.equal(s3deploy.CacheControl.setPrivate().value, 'private'); - test.equal(s3deploy.CacheControl.proxyRevalidate().value, 'proxy-revalidate'); - test.equal(s3deploy.CacheControl.maxAge(cdk.Duration.minutes(1)).value, 'max-age=60'); - test.equal(s3deploy.CacheControl.sMaxAge(cdk.Duration.minutes(1)).value, 's-maxage=60'); - test.equal(s3deploy.CacheControl.fromString('only-if-cached').value, 'only-if-cached'); - - test.done(); - }, - - 'storage class type has correct values'(test: Test) { - test.equal(s3deploy.StorageClass.STANDARD, 'STANDARD'); - test.equal(s3deploy.StorageClass.REDUCED_REDUNDANCY, 'REDUCED_REDUNDANCY'); - test.equal(s3deploy.StorageClass.STANDARD_IA, 'STANDARD_IA'); - test.equal(s3deploy.StorageClass.ONEZONE_IA, 'ONEZONE_IA'); - test.equal(s3deploy.StorageClass.INTELLIGENT_TIERING, 'INTELLIGENT_TIERING'); - test.equal(s3deploy.StorageClass.GLACIER, 'GLACIER'); - test.equal(s3deploy.StorageClass.DEEP_ARCHIVE, 'DEEP_ARCHIVE'); - - test.done(); - }, - - 'server side encryption type has correct values'(test: Test) { - test.equal(s3deploy.ServerSideEncryption.AES_256, 'AES256'); - test.equal(s3deploy.ServerSideEncryption.AWS_KMS, 'aws:kms'); - - test.done(); - }, - - 'distribution can be used to provide a CloudFront distribution for invalidation'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - const distribution = new cloudfront.CloudFrontWebDistribution(stack, 'Distribution', { - originConfigs: [ - { - s3OriginSource: { - s3BucketSource: bucket, - }, - behaviors: [{ isDefaultBehavior: true }], - }, - ], - }); - - // WHEN - new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], - destinationBucket: bucket, - distribution, - distributionPaths: ['/images/*'], - }); - - expect(stack).to(haveResource('Custom::CDKBucketDeployment', { - DistributionId: { - 'Ref': 'DistributionCFDistribution882A7313', - }, - DistributionPaths: ['/images/*'], - })); - - test.done(); - }, - - 'invalidation can happen without distributionPaths provided'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - const distribution = new cloudfront.CloudFrontWebDistribution(stack, 'Distribution', { - originConfigs: [ - { - s3OriginSource: { - s3BucketSource: bucket, - }, - behaviors: [{ isDefaultBehavior: true }], - }, - ], - }); - - // WHEN - new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website.zip'))], - destinationBucket: bucket, - distribution, - }); - - expect(stack).to(haveResource('Custom::CDKBucketDeployment', { - DistributionId: { - 'Ref': 'DistributionCFDistribution882A7313', - }, - })); - - test.done(); - }, - - 'fails if distribution paths provided but not distribution ID'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - - // THEN - test.throws(() => new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website', 'index.html'))], - destinationBucket: bucket, - distributionPaths: ['/images/*'], - }), /Distribution must be specified if distribution paths are specified/); - - test.done(); - }, - - 'lambda execution role gets permissions to read from the source bucket and read/write in destination'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const source = new s3.Bucket(stack, 'Source'); - const bucket = new s3.Bucket(stack, 'Dest'); - - // WHEN - new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.bucket(source, 'file.zip')], - destinationBucket: bucket, - }); - - // THEN - expect(stack).to(haveResource('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': [ - { - 'Action': [ - 's3:GetObject*', - 's3:GetBucket*', - 's3:List*', - ], - 'Effect': 'Allow', - 'Resource': [ - { - 'Fn::GetAtt': [ - 'Source71E471F1', - 'Arn', - ], - }, - { - 'Fn::Join': [ - '', - [ - { - 'Fn::GetAtt': [ - 'Source71E471F1', - 'Arn', - ], - }, - '/*', - ], - ], - }, - ], - }, - { - 'Action': [ - 's3:GetObject*', - 's3:GetBucket*', - 's3:List*', - 's3:DeleteObject*', - 's3:PutObject*', - 's3:Abort*', - ], - 'Effect': 'Allow', - 'Resource': [ - { - 'Fn::GetAtt': [ - 'DestC383B82A', - 'Arn', - ], - }, - { - 'Fn::Join': [ - '', - [ - { - 'Fn::GetAtt': [ - 'DestC383B82A', - 'Arn', - ], - }, - '/*', - ], - ], - }, - ], - }, - ], - 'Version': '2012-10-17', - }, - 'PolicyName': 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF', - 'Roles': [ - { - 'Ref': 'CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265', - }, - ], - })); - test.done(); - }, - - 'Deployment role gets KMS permissions when using assets from new style synthesizer'(test: Test) { - const stack = new cdk.Stack(undefined, undefined, { - synthesizer: new cdk.DefaultStackSynthesizer(), - }); - const bucket = new s3.Bucket(stack, 'Dest'); - - // WHEN - new s3deploy.BucketDeployment(stack, 'Deploy', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], - destinationBucket: bucket, - }); - - // THEN - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: arrayWith({ - Action: ['kms:Decrypt', 'kms:DescribeKey'], - Effect: 'Allow', - Resource: { 'Fn::ImportValue': 'CdkBootstrap-hnb659fds-FileAssetKeyArn' }, - }), - }, - })); - - test.done(); - }, - - 'memoryLimit can be used to specify the memory limit for the deployment resource handler'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - - // WHEN - - // we define 3 deployments with 2 different memory configurations - - new s3deploy.BucketDeployment(stack, 'Deploy256-1', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], - destinationBucket: bucket, - memoryLimit: 256, - }); - - new s3deploy.BucketDeployment(stack, 'Deploy256-2', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], - destinationBucket: bucket, - memoryLimit: 256, - }); - - new s3deploy.BucketDeployment(stack, 'Deploy1024', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], - destinationBucket: bucket, - memoryLimit: 1024, - }); - - // THEN - - // we expect to find only two handlers, one for each configuration - - expect(stack).to(countResources('AWS::Lambda::Function', 2)); - expect(stack).to(haveResource('AWS::Lambda::Function', { MemorySize: 256 })); - expect(stack).to(haveResource('AWS::Lambda::Function', { MemorySize: 1024 })); - test.done(); - }, - - 'deployment allows custom role to be supplied'(test: Test) { - - // GIVEN - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Dest'); - const existingRole = new iam.Role(stack, 'Role', { - assumedBy: new iam.ServicePrincipal('lambda.amazon.com'), - }); - - // WHEN - new s3deploy.BucketDeployment(stack, 'DeployWithRole', { - sources: [s3deploy.Source.asset(path.join(__dirname, 'my-website'))], - destinationBucket: bucket, - role: existingRole, - }); - - // THEN - expect(stack).to(countResources('AWS::IAM::Role', 1)); - expect(stack).to(countResources('AWS::Lambda::Function', 1)); - expect(stack).to(haveResource('AWS::Lambda::Function', { - 'Role': { - 'Fn::GetAtt': [ - 'Role1ABCC5F0', - 'Arn', - ], - }, - })); - test.done(); - }, -}; diff --git a/tools/cdk-build-tools/bin/cdk-test.ts b/tools/cdk-build-tools/bin/cdk-test.ts index 4e89f9a562570..5a9ad332568ef 100644 --- a/tools/cdk-build-tools/bin/cdk-test.ts +++ b/tools/cdk-build-tools/bin/cdk-test.ts @@ -46,7 +46,7 @@ async function main() { if (useJest) { if (testFiles.length > 0) { - throw new Error(`Jest is enabled, but ${testFiles.length} nodeunit tests were found!`); + throw new Error(`Jest is enabled, but ${testFiles.length} nodeunit tests were found!: ${testFiles.map(f => f.filename)}`); } await shell([args.jest], defaultShellOptions); } else if (testFiles.length > 0) { diff --git a/yarn.lock b/yarn.lock index 982df84b248d3..4a0c668c6b587 100644 --- a/yarn.lock +++ b/yarn.lock @@ -529,10 +529,10 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jsii/spec@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.7.0.tgz#2a70ee5753aab1711a5e65161a1988845eb91043" - integrity sha512-gvj0vEvKWSo89ywclzb0OfFDSOqwTpvk0VQp2F3UEHewvR+hjJMgLjo7+ycpQF2bTLLni99KLmapMg/huxfshA== +"@jsii/spec@^1.7.0", "@jsii/spec@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.8.0.tgz#6860d3ca3461bfc6a568a7418b11d8fa52b98888" + integrity sha512-c9pLt+vYCwHP/lXoVK10nE572ekNo1e1U9osMOlXODW188Ca/ytP12VLCReypt3H8OMqebyK7ea7QdcszR+19w== dependencies: jsonschema "^1.2.6" @@ -1427,6 +1427,11 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== +"@types/events@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + "@types/fs-extra@^8.1.0": version "8.1.0" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.0.tgz#1114834b53c3914806cd03b3304b37b3bd221a4d" @@ -1441,7 +1446,16 @@ dependencies: "@types/node" "*" -"@types/glob@*", "@types/glob@^7.1.1", "@types/glob@^7.1.2": +"@types/glob@*", "@types/glob@^7.1.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" + integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== + dependencies: + "@types/events" "*" + "@types/minimatch" "*" + "@types/node" "*" + +"@types/glob@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987" integrity sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA== @@ -1476,6 +1490,14 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/jest@^25.2.3": + version "25.2.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" + integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw== + dependencies: + jest-diff "^25.2.1" + pretty-format "^25.2.1" + "@types/jest@^26.0.3": version "26.0.3" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.3.tgz#79534e0e94857171c0edc596db0ebe7cb7863251" @@ -1901,6 +1923,11 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" +app-root-path@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" + integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== + append-transform@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" @@ -2019,7 +2046,7 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= -array-includes@^3.1.1: +array-includes@^3.0.3, array-includes@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== @@ -2045,7 +2072,7 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.flat@^1.2.3: +array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== @@ -2129,7 +2156,7 @@ available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: dependencies: array-filter "^1.0.0" -aws-sdk-mock@^5.1.0: +aws-sdk-mock@^5.0.0, aws-sdk-mock@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/aws-sdk-mock/-/aws-sdk-mock-5.1.0.tgz#6f2c0bd670d7f378c906a8dd806f812124db71aa" integrity sha512-Wa5eCSo8HX0Snqb7FdBylaXMmfrAWoWZ+d7MFhiYsgHPvNvMEGjV945FF2qqE1U0Tolr1ALzik1fcwgaOhqUWQ== @@ -2138,7 +2165,37 @@ aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.637.0, aws-sdk@^2.709.0: +aws-sdk@^2.596.0: + version "2.685.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.685.0.tgz#ba5add21e98cc785b3c05ceb9f3fcb8ab046aa8a" + integrity sha512-mAOj7b4PuXRxIZkNdSkBWZ28lS2wYUY7O9u33nH9a7BawlttMNbxOgE/wDCPMrTLfj+RLQx0jvoIYj8BKCTRFw== + dependencies: + buffer "4.9.1" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + +aws-sdk@^2.637.0: + version "2.681.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.681.0.tgz#09eeedb5ca49813dfc637908abe408ae114a6824" + integrity sha512-/p8CDJ7LZvB1i4WrJrb32FUbbPdiZFZSN6FI2lv7s/scKypmuv/iJ9kpx6QWSWQZ72kJ3Njk/0o7GuVlw0jHXw== + dependencies: + buffer "4.9.1" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + +aws-sdk@^2.709.0: version "2.709.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.709.0.tgz#33b0c0fe8b9420c65610545394be047ac2d93c8f" integrity sha512-F3sKXsCiutj9RglVXdqb/XJ3Ko3G+pX081Nf1YjVJpLydwE2v16FGxrLqE5pqyWMDeUf5nZHnBoMuOYD8ip+Kw== @@ -2354,6 +2411,15 @@ buffer-from@1.x, buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + buffer@4.9.2: version "4.9.2" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" @@ -2693,13 +2759,13 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -codemaker@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.7.0.tgz#bcad7226740319dcdc99c23ba908a06c0e05ea35" - integrity sha512-TypUtZ56Au+BEvf0lSs37S/H5p2tpYfzF1FwE7hPNNasBffrAkqfryz0GFyOKPK7Svla5h5qTxRXFQWJ+g9Ciw== +codemaker@^1.7.0, codemaker@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.8.0.tgz#84e1fca0526cce078f04f2aa05ac4751a8ea4543" + integrity sha512-6ND1K9SIBgKVL2qXSZy0HdKKqvmpBQ71U5fwuHasmdFfzyylb8mx/hhVBkzBYpBH3EVecSqh3LUyZu1YZuGNwA== dependencies: camelcase "^6.0.0" - decamelize "^1.2.0" + decamelize "^4.0.0" fs-extra "^9.0.1" collect-v8-coverage@^1.0.0: @@ -3660,6 +3726,16 @@ dot-prop@^4.2.0: dependencies: is-obj "^1.0.0" +dotenv-json@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dotenv-json/-/dotenv-json-1.0.0.tgz#fc7f672aafea04bed33818733b9f94662332815c" + integrity sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ== + +dotenv@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + dotgitignore@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/dotgitignore/-/dotgitignore-2.1.0.tgz#a4b15a4e4ef3cf383598aaf1dfa4a04bcc089b7b" @@ -3842,7 +3918,20 @@ escodegen@1.x.x, escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" -eslint-import-resolver-node@^0.3.3, eslint-import-resolver-node@^0.3.4: +eslint-config-standard@^14.1.0: + version "14.1.1" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" + integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== + +eslint-import-resolver-node@^0.3.2, eslint-import-resolver-node@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" + integrity sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + +eslint-import-resolver-node@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== @@ -3861,7 +3950,7 @@ eslint-import-resolver-typescript@^2.0.0: tiny-glob "^0.2.6" tsconfig-paths "^3.9.0" -eslint-module-utils@^2.6.0: +eslint-module-utils@^2.4.1, eslint-module-utils@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== @@ -3869,6 +3958,32 @@ eslint-module-utils@^2.6.0: debug "^2.6.9" pkg-dir "^2.0.0" +eslint-plugin-es@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-2.0.0.tgz#0f5f5da5f18aa21989feebe8a73eadefb3432976" + integrity sha512-f6fceVtg27BR02EYnBhgWLFQfK6bN4Ll0nQFrBHOlCsAyxeZkn0NHns5O0YZOPrV1B3ramd6cgFwaoFLcSkwEQ== + dependencies: + eslint-utils "^1.4.2" + regexpp "^3.0.0" + +eslint-plugin-import@^2.19.1: + version "2.20.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" + integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== + dependencies: + array-includes "^3.0.3" + array.prototype.flat "^1.2.1" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.2" + eslint-module-utils "^2.4.1" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.0" + read-pkg-up "^2.0.0" + resolve "^1.12.0" + eslint-plugin-import@^2.22.0: version "2.22.0" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" @@ -3888,6 +4003,28 @@ eslint-plugin-import@^2.22.0: resolve "^1.17.0" tsconfig-paths "^3.9.0" +eslint-plugin-node@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz#fd1adbc7a300cf7eb6ac55cf4b0b6fc6e577f5a6" + integrity sha512-1CSyM/QCjs6PXaT18+zuAXsjXGIGo5Rw630rSKwokSs2jrYURQc4R5JZpoanNCqwNmepg+0eZ9L7YiRUJb8jiQ== + dependencies: + eslint-plugin-es "^2.0.0" + eslint-utils "^1.4.2" + ignore "^5.1.1" + minimatch "^3.0.4" + resolve "^1.10.1" + semver "^6.1.0" + +eslint-plugin-promise@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" + integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== + +eslint-plugin-standard@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4" + integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ== + eslint-scope@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" @@ -3896,7 +4033,7 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.3: +eslint-utils@^1.4.2, eslint-utils@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== @@ -4156,7 +4293,12 @@ fast-deep-equal@^2.0.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== @@ -4729,7 +4871,12 @@ globrex@^0.1.1: resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -4954,6 +5101,11 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.1.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.6.tgz#643194ad4bf2712f37852e386b6998eff0db2106" + integrity sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -5916,7 +6068,7 @@ jest-worker@^25.5.0: merge-stream "^2.0.0" supports-color "^7.0.0" -jest@^25.4.0, jest@^25.5.2, jest@^25.5.3, jest@^25.5.4: +jest@^25.4.0, jest@^25.5.0, jest@^25.5.2, jest@^25.5.3, jest@^25.5.4: version "25.5.4" resolved "https://registry.yarnpkg.com/jest/-/jest-25.5.4.tgz#f21107b6489cfe32b076ce2adcadee3587acb9db" integrity sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ== @@ -5941,9 +6093,9 @@ js-tokens@^4.0.0: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1, js-yaml@^3.2.7: - version "3.14.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" - integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -5991,64 +6143,64 @@ jsesc@^2.5.1: integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== jsii-diff@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.7.0.tgz#f63a36ebcb0c948ef2480015a5cc6137de6d451d" - integrity sha512-EqZWSnHL6Qe7p3rfMIIV2h4a2bdx8Q1mB48uHa9ORZPEPiXwKjmrmY5TKk7XRcR9GSamEoKEcxILmC1ld4CfdQ== + version "1.8.0" + resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.8.0.tgz#d913a845b5b80d0a134f664e10a99069542c9ccf" + integrity sha512-yucKS75pidO3hRMK1ZzUZ1lBrzdI+GI6ij5BilrkT/fmmQXlIi+OQrHu4TUkMTjl6S+s8W/cxnNC+cUOqzoIAw== dependencies: - "@jsii/spec" "^1.7.0" + "@jsii/spec" "^1.8.0" fs-extra "^9.0.1" - jsii-reflect "^1.7.0" + jsii-reflect "^1.8.0" log4js "^6.3.0" - typescript "~3.9.5" + typescript "~3.9.6" yargs "^15.3.1" jsii-pacmak@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.7.0.tgz#4e9da8cf33916a149d7e3c90088df9f9c9fd228d" - integrity sha512-HFAR4/ySCqUAkDu++tEjIK5c5eiRoqUa43KahAy0nBqcyg3TrPqqKiK2m6nUbB+A6tQ0n4zO1EWm8yE0WlvY0w== + version "1.8.0" + resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.8.0.tgz#de463913ac694298aa81ad08ce2e4061b85cbd4b" + integrity sha512-vR6A5ulEjvWLmY4Fuvrw978Yv+STeTFKH8yU6OLO4q05BC9dRXo32l0h30GGJ+Uyi0YaDYtm5BnFk4p4vOVhWQ== dependencies: - "@jsii/spec" "^1.7.0" + "@jsii/spec" "^1.8.0" clone "^2.1.2" - codemaker "^1.7.0" + codemaker "^1.8.0" commonmark "^0.29.1" escape-string-regexp "^4.0.0" fs-extra "^9.0.1" - jsii-reflect "^1.7.0" - jsii-rosetta "^1.7.0" + jsii-reflect "^1.8.0" + jsii-rosetta "^1.8.0" semver "^7.3.2" spdx-license-list "^6.2.0" xmlbuilder "^15.1.1" yargs "^15.3.1" -jsii-reflect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.7.0.tgz#4e01f803d80babb4a5125a72793e4dc48316bbd4" - integrity sha512-2r/GC+Ka0rghcCDGjlcg2RUjLTtYvT5z5GpyewCRP2Ss/5wwHyCo8xc/MjpDulzFzFozVa2xBtrckcux1seSKA== +jsii-reflect@^1.7.0, jsii-reflect@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.8.0.tgz#edea43fa83903b46c65c9f2d22b6246610460568" + integrity sha512-M+ai0nQNE0I/osOW6n1UywRFMJWMv5NI49XZUfUknBoO6c6tlDTiKLRmqrxzpJx9f7yoaV2tbf0kAMYNRedwGw== dependencies: - "@jsii/spec" "^1.7.0" + "@jsii/spec" "^1.8.0" colors "^1.4.0" fs-extra "^9.0.1" - oo-ascii-tree "^1.7.0" + oo-ascii-tree "^1.8.0" yargs "^15.3.1" -jsii-rosetta@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.7.0.tgz#eabcb159a2230a1e69cc1503c2afae18316513e8" - integrity sha512-DFoWSaVYtHJuxUmBohabsBtmdVPyRAaHkKRXOerRLEwdnS/WIix+FgbtxeVTeWMOHuw7C928DjW6Gwbo+lJu8w== +jsii-rosetta@^1.7.0, jsii-rosetta@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.8.0.tgz#0dd8e4fc7a862c6655c99a7c1f6392e20f256ed6" + integrity sha512-04DCDDO3jD4/xBDYsUlAh4x+BlYehtkyvhtvDUKZvGVjKvpU0iK7P/6258/8C19mpRYhBeFO6Ot1UW6NGxVYBA== dependencies: - "@jsii/spec" "^1.7.0" + "@jsii/spec" "^1.8.0" commonmark "^0.29.1" fs-extra "^9.0.1" - typescript "~3.9.5" + typescript "~3.9.6" xmldom "^0.3.0" yargs "^15.3.1" jsii@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.7.0.tgz#a9e8267fcb7f964f60ed400851a78d47543eee5e" - integrity sha512-isvI0v39OGzPYK3TYA0tg6H8FOKoq7GS+bYYWrhI3YWXNrVl9ZZNQW0hz6sUWN1cla3W9KqMT7HFcRf3ttt0Sg== + version "1.8.0" + resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.8.0.tgz#fc12a7a727879132b5eac1459c69f565c4ac4b1e" + integrity sha512-kvd0Afm50QnKOlGNeNnTHsKWXTeX+QTqHjRM2GkQYzKK7U/vVKUAU/eahbYUWyEHIC8VJd8rnUa0JLfOL/1jCA== dependencies: - "@jsii/spec" "^1.7.0" + "@jsii/spec" "^1.8.0" case "^1.6.3" colors "^1.4.0" deep-equal "^2.0.3" @@ -6058,7 +6210,7 @@ jsii@^1.7.0: semver-intersect "^1.4.0" sort-json "^2.0.0" spdx-license-list "^6.2.0" - typescript "~3.9.5" + typescript "~3.9.6" yargs "^15.3.1" json-diff@^0.5.4: @@ -6157,7 +6309,17 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@*, jszip@^3.5.0: +jszip@*: + version "3.4.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.4.0.tgz#1a69421fa5f0bb9bc222a46bca88182fba075350" + integrity sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" + +jszip@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== @@ -6201,6 +6363,24 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +lambda-leak@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lambda-leak/-/lambda-leak-2.0.0.tgz#771985d3628487f6e885afae2b54510dcfb2cd7e" + integrity sha1-dxmF02KEh/boha+uK1RRDc+yzX4= + +lambda-tester@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/lambda-tester/-/lambda-tester-3.6.0.tgz#ceb7d4f4f0da768487a05cff37dcd088508b5247" + integrity sha512-F2ZTGWCLyIR95o/jWK46V/WnOCFAEUG/m/V7/CLhPJ7PCM+pror1rZ6ujP3TkItSGxUfpJi0kqwidw+M/nEqWw== + dependencies: + app-root-path "^2.2.1" + dotenv "^8.0.0" + dotenv-json "^1.0.0" + lambda-leak "^2.0.0" + semver "^6.1.1" + uuid "^3.3.2" + vandium-utils "^1.1.1" + lazystream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" @@ -6806,7 +6986,7 @@ mkdirp@*, mkdirp@1.x: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -6937,6 +7117,17 @@ nise@^4.0.1: just-extend "^4.0.2" path-to-regexp "^1.7.0" +nock@^11.7.0: + version "11.9.1" + resolved "https://registry.yarnpkg.com/nock/-/nock-11.9.1.tgz#2b026c5beb6d0dbcb41e7e4cefa671bc36db9c61" + integrity sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + lodash "^4.17.13" + mkdirp "^0.5.0" + propagate "^2.0.0" + nock@^13.0.2: version "13.0.2" resolved "https://registry.yarnpkg.com/nock/-/nock-13.0.2.tgz#3e50f88348edbb90cce1bbbf0a3ea6a068993983" @@ -7275,7 +7466,7 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.1.1: +object.values@^1.1.0, object.values@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== @@ -7311,10 +7502,10 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -oo-ascii-tree@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.7.0.tgz#6d804ffd0971105900379e6a1091c0fa58a545ae" - integrity sha512-Kfz5r6vEtUTZV1J8jIQVOIsfNujk/Rk2ngUgHKDwDOliycLytI9Bg55iCUxUoeiuy9NCefx7ZaLAbzM0CkjaOA== +oo-ascii-tree@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.8.0.tgz#47c040a99045bb162281c7603dea0c3fe22591fd" + integrity sha512-vo/cRakWcK/UeGGYQ7ByvgVWzCTchYkogldL3i2ZiHZt1etw/yh1YwimCfUM9rjf/pgBoy1Xe0pJuB+WQ3Ojcw== opener@^1.5.1: version "1.5.1" @@ -8307,13 +8498,20 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2: +resolve@^1.1.6, resolve@^1.10.1, resolve@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" +resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2: + version "1.16.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.16.1.tgz#49fac5d8bacf1fd53f200fa51247ae736175832c" + integrity sha512-rmAglCSqWWMrrBv/XM6sW0NuRFiKViw/W4d9EbC4pt+49H8JwHy+mcGmALTEg504AUDcLTvb1T2q3E9AnmY+ig== + dependencies: + path-parse "^1.0.6" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -8458,6 +8656,11 @@ semver-intersect@^1.4.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +semver@6.x, semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + semver@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667" @@ -8468,11 +8671,6 @@ semver@7.x, semver@^7.2.2, semver@^7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== -semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -9446,6 +9644,22 @@ trivial-deferred@^1.0.1: resolved "https://registry.yarnpkg.com/trivial-deferred/-/trivial-deferred-1.0.1.tgz#376d4d29d951d6368a6f7a0ae85c2f4d5e0658f3" integrity sha1-N21NKdlR1jaKb3oK6FwvTV4GWPM= +ts-jest@^25.3.1: + version "25.5.1" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.5.1.tgz#2913afd08f28385d54f2f4e828be4d261f4337c7" + integrity sha512-kHEUlZMK8fn8vkxDjwbHlxXRB9dHYpyzqKIGDNxbzs+Rz+ssNDSDNusEK8Fk/sDd4xE6iKoQLfFkFVaskmTJyw== + dependencies: + bs-logger "0.x" + buffer-from "1.x" + fast-json-stable-stringify "2.x" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + micromatch "4.x" + mkdirp "0.x" + semver "6.x" + yargs-parser "18.x" + ts-jest@^26.1.1: version "26.1.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.1.1.tgz#b98569b8a4d4025d966b3d40c81986dd1c510f8d" @@ -9467,7 +9681,18 @@ ts-mock-imports@^1.2.6, ts-mock-imports@^1.3.0: resolved "https://registry.yarnpkg.com/ts-mock-imports/-/ts-mock-imports-1.3.0.tgz#ed9b743349f3c27346afe5b7454ffd2bcaa2302d" integrity sha512-cCrVcRYsp84eDvPict0ZZD/D7ppQ0/JSx4ve6aEU8DjlsaWRJWV6ADMovp2sCuh6pZcduLFoIYhKTDU2LARo7Q== -ts-node@^8.0.2, ts-node@^8.10.2: +ts-node@^8.0.2: + version "8.8.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.2.tgz#0b39e690bee39ea5111513a9d2bcdc0bc121755f" + integrity sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.6" + yn "3.1.1" + +ts-node@^8.10.2: version "8.10.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== @@ -9613,10 +9838,10 @@ typescript@^3.3.3, typescript@^3.5.3, typescript@~3.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== -typescript@~3.9.5: - version "3.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" - integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== +typescript@~3.9.5, typescript@~3.9.6: + version "3.9.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" + integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== uglify-js@^3.1.4: version "3.9.1" @@ -9795,6 +10020,11 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" +vandium-utils@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/vandium-utils/-/vandium-utils-1.2.0.tgz#44735de4b7641a05de59ebe945f174e582db4f59" + integrity sha1-RHNd5LdkGgXeWevpRfF05YLbT1k= + verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" From a9c66f780b233ce3c25e12f39e3b1122636411b3 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Mon, 6 Jul 2020 13:47:41 +0800 Subject: [PATCH 11/28] fix(eks): missing nodegroup identity in aws-auth after awsAuth.addMasterRole (#8901) fix(eks): missing nodegroup identity in aws-auth after awsAuth.addMasterRole This PR adds the state tracking by `awsAuth.addRoleMapping` for the managed nodegroups Fixed: #7595 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-eks/lib/managed-nodegroup.ts | 14 ++++- .../test/integ.eks-cluster.expected.json | 14 +++++ .../@aws-cdk/aws-eks/test/test.awsauth.ts | 63 ++++++++++++++++++- .../@aws-cdk/aws-eks/test/test.cluster.ts | 12 ++++ .../@aws-cdk/aws-eks/test/test.nodegroup.ts | 43 +++++++++++++ 5 files changed, 143 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts b/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts index c7640916d70c1..3ab5287b9f782 100644 --- a/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts @@ -263,8 +263,18 @@ export class Nodegroup extends Resource implements INodegroup { tags: props.tags, }); - // As managed nodegroup will auto map the instance role to RBAC behind the scene and users don't have to manually - // do it anymore. We don't need to print out the instance role arn now. + // managed nodegroups update the `aws-auth` on creation, but we still need to track + // its state for consistency. + if (this.cluster.kubectlEnabled) { + // see https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html + this.cluster.awsAuth.addRoleMapping(this.role, { + username: 'system:node:{{EC2PrivateDNSName}}', + groups: [ + 'system:bootstrappers', + 'system:nodes', + ], + }); + } this.nodegroupArn = this.getResourceArnAttribute(resource.attrArn, { service: 'eks', diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index f957a467b48aa..afec1aeda1a31 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json @@ -894,6 +894,13 @@ ] }, "\\\",\\\"groups\\\":[\\\"system:masters\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterNodegroupDefaultCapacityNodeGroupRole55953B04", + "Arn" + ] + }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]},{\\\"rolearn\\\":\\\"", { "Fn::GetAtt": [ "ClusterfargateprofiledefaultPodExecutionRole09952CFF", @@ -928,6 +935,13 @@ "Arn" ] }, + "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]},{\\\"rolearn\\\":\\\"", + { + "Fn::GetAtt": [ + "ClusterNodegroupextrangNodeGroupRole23AE23D0", + "Arn" + ] + }, "\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]" ] ] diff --git a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts index cf3ddc99e09e8..ca13492487027 100644 --- a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts +++ b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts @@ -53,6 +53,13 @@ export = { '', [ '[{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"aws-auth","namespace":"kube-system"},"data":{"mapRoles":"[{\\"rolearn\\":\\"', + { + 'Fn::GetAtt': [ + 'ClusterNodegroupDefaultCapacityNodeGroupRole55953B04', + 'Arn', + ], + }, + '\\",\\"username\\":\\"system:node:{{EC2PrivateDNSName}}\\",\\"groups\\":[\\"system:bootstrappers\\",\\"system:nodes\\"]},{\\"rolearn\\":\\"', { 'Fn::GetAtt': [ 'roleC7B7E775', @@ -120,7 +127,14 @@ export = { 'Fn::Join': [ '', [ - '[{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"aws-auth","namespace":"kube-system"},"data":{"mapRoles":"[{\\"rolearn\\":\\"arn:aws:iam::123456789012:role/S3Access\\",\\"username\\":\\"arn:aws:iam::123456789012:role/S3Access\\",\\"groups\\":[\\"group1\\"]}]","mapUsers":"[{\\"userarn\\":\\"arn:', + '[{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"aws-auth","namespace":"kube-system"},"data":{"mapRoles":"[{\\"rolearn\\":\\"', + { + 'Fn::GetAtt': [ + 'ClusterNodegroupDefaultCapacityNodeGroupRole55953B04', + 'Arn', + ], + }, + '\\",\\"username\\":\\"system:node:{{EC2PrivateDNSName}}\\",\\"groups\\":[\\"system:bootstrappers\\",\\"system:nodes\\"]},{\\"rolearn\\":\\"arn:aws:iam::123456789012:role/S3Access\\",\\"username\\":\\"arn:aws:iam::123456789012:role/S3Access\\",\\"groups\\":[\\"group1\\"]}]\",\"mapUsers\":\"[{\\"userarn\\":\\"arn:', { Ref: 'AWS::Partition', }, @@ -142,6 +156,53 @@ export = { }, })); + test.done(); + }, + 'addMastersRole after addNodegroup correctly'(test: Test) { + // GIVEN + const { stack } = testFixtureNoVpc(); + const cluster = new Cluster(stack, 'Cluster', { version: CLUSTER_VERSION }); + cluster.addNodegroup('NG'); + const role = iam.Role.fromRoleArn(stack, 'imported-role', 'arn:aws:iam::123456789012:role/S3Access'); + + // WHEN + cluster.awsAuth.addMastersRole(role); + + // 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': [ + 'ClusterNodegroupDefaultCapacityNodeGroupRole55953B04', + 'Arn', + ], + }, + '\\",\\"username\\":\\"system:node:{{EC2PrivateDNSName}}\\",\\"groups\\":[\\"system:bootstrappers\\",\\"system:nodes\\"]},{\\"rolearn\\":\\"', + { + 'Fn::GetAtt': [ + 'ClusterNodegroupNGNodeGroupRole7C078920', + 'Arn', + ], + }, + '\\",\\"username\\":\\"system:node:{{EC2PrivateDNSName}}\\",\\"groups\\":[\\"system:bootstrappers\\",\\"system:nodes\\"]},{\\"rolearn\\":\\"arn:aws:iam::123456789012:role/S3Access\\",\\"username\\":\\"arn:aws:iam::123456789012:role/S3Access\\",\\"groups\\":[\\"system:masters\\"]}]","mapUsers":"[]","mapAccounts":"[]"}}]', + ], + ], + }, + ClusterName: { + Ref: 'Cluster9EE0221C', + }, + RoleArn: { + 'Fn::GetAtt': [ + 'ClusterCreationRole360249B6', + 'Arn', + ], + }, + })); + test.done(); }, }; diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index 1acc770b47129..4101348a06774 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -923,6 +923,18 @@ export = { ], }, }, + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::GetAtt': [ + 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', + 'Outputs.StackawscdkawseksKubectlProviderHandlerServiceRole2C52B3ECArn', + ], + }, + }, + }, ], Version: '2012-10-17', }, diff --git a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts index 160259706d727..226fd09bc30e9 100644 --- a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts @@ -52,6 +52,49 @@ export = { )); test.done(); }, + 'aws-auth will be updated'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + const cluster = new eks.Cluster(stack, 'Cluster', { + vpc, + kubectlEnabled: true, + defaultCapacity: 0, + version: CLUSTER_VERSION, + }); + new eks.Nodegroup(stack, 'Nodegroup', { cluster }); + + // THEN + // 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': [ + 'NodegroupNodeGroupRole038A128B', + 'Arn', + ], + }, + '\\",\\"username\\":\\"system:node:{{EC2PrivateDNSName}}\\",\\"groups\\":[\\"system:bootstrappers\\",\\"system:nodes\\"]}]","mapUsers":"[]","mapAccounts":"[]"}}]', + ], + ], + }, + ClusterName: { + Ref: 'Cluster9EE0221C', + }, + RoleArn: { + 'Fn::GetAtt': [ + 'ClusterCreationRole360249B6', + 'Arn', + ], + }, + })); + test.done(); + }, 'create nodegroup correctly with security groups provided'(test: Test) { // GIVEN const { stack, vpc } = testFixture(); From a03578479a9e5252e2b348bb35121104d739cbe6 Mon Sep 17 00:00:00 2001 From: Stephen Rodriguez Date: Mon, 6 Jul 2020 11:07:03 +0200 Subject: [PATCH 12/28] docs(batch): update README (#8214) This should be the final docs update based on the recent changes to: - Introduce Batch support - Fix issues with managed/unmanaged compute envs ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-batch/README.md | 220 +++++++++++++++++++++++--- 1 file changed, 195 insertions(+), 25 deletions(-) diff --git a/packages/@aws-cdk/aws-batch/README.md b/packages/@aws-cdk/aws-batch/README.md index a1ad987fd09bd..83589b65a12f8 100644 --- a/packages/@aws-cdk/aws-batch/README.md +++ b/packages/@aws-cdk/aws-batch/README.md @@ -1,4 +1,5 @@ ## AWS Batch Construct Library + --- @@ -15,37 +16,206 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. -## Launch template support +AWS Batch is a batch processing tool for efficiently running hundreds of thousands computing jobs in AWS. Batch can dynamically provision different types of compute resources based on the resource requirements of submitted jobs. + +AWS Batch simplifies the planning, scheduling, and executions of your batch workloads across a full range of compute services like [Amazon EC2](https://aws.amazon.com/ec2/) and [Spot Resources](https://aws.amazon.com/ec2/spot/). + +Batch achieves this by utilizing queue processing of batch job requests. To successfully submit a job for execution, you need the following resources: + +1. [Job Definition](#job-definition) - *Group various job properties (container image, resource requirements, env variables...) into a single definition. These definitions are used at job submission time.* +2. [Compute Environment](#compute-environment) - *the execution runtime of submitted batch jobs* +3. [Job Queue](#job-queue) - *the queue where batch jobs can be submitted to via AWS SDK/CLI* + +For more information on **AWS Batch** visit the [AWS Docs for Batch](https://docs.aws.amazon.com/batch/index.html). + +## Compute Environment + +At the core of AWS Batch is the compute environment. All batch jobs are processed within a compute environment, which uses resource like OnDemand or Spot EC2 instances. + +In **MANAGED** mode, AWS will handle the provisioning of compute resources to accommodate the demand. Otherwise, in **UNMANAGED** mode, you will need to manage the provisioning of those resources. + +Below is an example of each available type of compute environment: + +```ts +const defaultVpc = new ec2.Vpc(this, 'VPC'); + +// default is managed +const awsManagedEnvironment = new batch.ComputeEnvironment(stack, 'AWS-Managed-Compute-Env', { + computeResources: { + vpc + } +}); + +const customerManagedEnvironment = new batch.ComputeEnvironment(stack, 'Customer-Managed-Compute-Env', { + managed: false // unmanaged environment +}); +``` + +### Spot-Based Compute Environment + +It is possible to have AWS Batch submit spotfleet requests for obtaining compute resources. Below is an example of how this can be done: + +```ts +const vpc = new ec2.Vpc(this, 'VPC'); + +const spotEnvironment = new batch.ComputeEnvironment(stack, 'MySpotEnvironment', { + computeResources: { + type: batch.ComputeResourceType.SPOT, + bidPercentage: 75, // Bids for resources at 75% of the on-demand price + vpc, + }, +}); +``` + +### Understanding Progressive Allocation Strategies + +AWS Batch uses an [allocation strategy](https://docs.aws.amazon.com/batch/latest/userguide/allocation-strategies.html) to determine what compute resource will efficiently handle incoming job requests. By default, **BEST_FIT** will pick an available compute instance based on vCPU requirements. If none exist, the job will wait until resources become available. However, with this strategy, you may have jobs waiting in the queue unnecessarily despite having more powerful instances available. Below is an example of how that situation might look like: + +``` +Compute Environment: + +1. m5.xlarge => 4 vCPU +2. m5.2xlarge => 8 vCPU +``` + +``` +Job Queue: +--------- +| A | B | +--------- + +Job Requirements: +A => 4 vCPU - ALLOCATED TO m5.xlarge +B => 2 vCPU - WAITING +``` + +In this situation, Batch will allocate **Job A** to compute resource #1 because it is the most cost efficient resource that matches the vCPU requirement. However, with this `BEST_FIT` strategy, **Job B** will not be allocated to our other available compute resource even though it is strong enough to handle it. Instead, it will wait until the first job is finished processing or wait a similar `m5.xlarge` resource to be provisioned. + +The alternative would be to use the `BEST_FIT_PROGRESSIVE` strategy in order for the remaining job to be handled in larger containers regardless of vCPU requirement and costs. + +### Launch template support -### Usage Simply define your Launch Template: ```typescript - const myLaunchTemplate = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', { - launchTemplateName: 'extra-storage-template', - launchTemplateData: { - blockDeviceMappings: [ - { - deviceName: '/dev/xvdcz', - ebs: { - encrypted: true, - volumeSize: 100, - volumeType: 'gp2' - } - } - ] +const myLaunchTemplate = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', { + launchTemplateName: 'extra-storage-template', + launchTemplateData: { + blockDeviceMappings: [ + { + deviceName: '/dev/xvdcz', + ebs: { + encrypted: true, + volumeSize: 100, + volumeType: 'gp2' + } } - }); + ] + } +}); ``` + and use it: ```typescript - const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { - computeResources: { - launchTemplate: { - launchTemplateName: myLaunchTemplate.launchTemplateName as string, //or simply use an existing template name - }, - vpc, - }, - computeEnvironmentName: 'MyStorageCapableComputeEnvironment', - }); +const myComputeEnv = new batch.ComputeEnvironment(this, 'ComputeEnv', { + computeResources: { + launchTemplate: { + launchTemplateName: myLaunchTemplate.launchTemplateName as string, //or simply use an existing template name + }, + vpc, + }, + computeEnvironmentName: 'MyStorageCapableComputeEnvironment', +}); +``` + +### Importing an existing Compute Environment + +To import an existing batch compute environment, call `ComputeEnvironment.fromComputeEnvironmentArn()`. + +Below is an example: + +```ts +const computeEnv = batch.ComputeEnvironment.fromComputeEnvironmentArn(this, 'imported-compute-env', 'arn:aws:batch:us-east-1:555555555555:compute-environment/My-Compute-Env'); +``` + +## Job Queue + +Jobs are always submitted to a specific queue. This means that you have to create a queue before you can start submitting jobs. Each queue is mapped to at least one (and no more than three) compute environment. When the job is scheduled for execution, AWS Batch will select the compute environment based on ordinal priority and available capacity in each environment. + +```ts +const jobQueue = new batch.JobQueue(stack, 'JobQueue', { + computeEnvironments: [ + { + // Defines a collection of compute resources to handle assigned batch jobs + computeEnvironment, + // Order determines the allocation order for jobs (i.e. Lower means higher preferance for job assignment) + order: 1, + }, + ], +}); +``` + +### Priorty-Based Queue Example + +Sometimes you might have jobs that are more important than others, and when submitted, should take precedence over the existing jobs. To achieve this, you can create a priority based execution strategy, by assigning each queue its own priority: + +```ts +const highPrioQueue = new batch.JobQueue(stack, 'JobQueue', { + computeEnvironments: sharedComputeEnvs, + priority: 2, +}); + +const lowPrioQueue = new batch.JobQueue(stack, 'JobQueue', { + computeEnvironments: sharedComputeEnvs, + priority: 1, +}); +``` + +By making sure to use the same compute environments between both job queues, we will give precedence to the `highPrioQueue` for the assigning of jobs to available compute environments. + +### Importing an existing Job Queue + +To import an existing batch job queue, call `JobQueue.fromJobQueueArn()`. + +Below is an example: + +```ts +const jobQueue = batch.JobQueue.fromJobQueueArn(this, 'imported-job-queue', 'arn:aws:batch:us-east-1:555555555555:job-queue/High-Prio-Queue'); +``` + +## Job Definition + +A Batch Job definition helps AWS Batch understand important details about how to run your application in the scope of a Batch Job. This involves key information like resource requirements, what containers to run, how the compute environment should be prepared, and more. Below is a simple example of how to create a job definition: + +```ts +const repo = ecr.Repository.fromRepositoryName(stack, 'batch-job-repo', 'todo-list'); + +new batch.JobDefinition(stack, 'batch-job-def-from-ecr', { + container: { + image: new ecs.EcrImage(repo, 'latest'), + }, +}); +``` + +### Using a local Docker project + +Below is an example of how you can create a Batch Job Definition from a local Docker application. + +```ts +new batch.JobDefinition(stack, 'batch-job-def-from-local', { + container: { + // todo-list is a directory containing a Dockerfile to build the application + image: ecs.ContainerImage.fromAsset('../todo-list'), + }, +}); +``` + +### Importing an existing Job Definition + +To import an existing batch job definition, call `JobDefinition.fromJobDefinitionArn()`. + +Below is an example: + +```ts +const job = batch.JobDefinition.fromJobDefinitionArn(this, 'imported-job-definition', 'arn:aws:batch:us-east-1:555555555555:job-definition/my-job-definition'); ``` From 5e43348ecdb6a8da865bb0db22c4782b6fa4bc96 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Mon, 6 Jul 2020 18:48:38 +0800 Subject: [PATCH 13/28] feat(apigatewayv2): http api - custom domain & stage mapping (#8027) - [x] implementation - [x] README - [x] integ test - [x] 100% unit test coverage ### Commit Message feat(apigatewayv2): http api - custom domain & stage mapping (#8027) - Add new `DomainName` and `HttpApiMapping` construct classes and `addDomainName()` method for `HttpApi` resource. - Add `defaultDomainMapping` construct property for `HttpApi` - Add `domainMapping` attribute for `addStage` Closes #7847 ### End Commit Message ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-apigatewayv2/.npmignore | 1 + packages/@aws-cdk/aws-apigatewayv2/README.md | 66 ++++ .../lib/common/api-mapping.ts | 13 + .../lib/common/domain-name.ts | 117 ++++++ .../aws-apigatewayv2/lib/common/index.ts | 4 +- .../aws-apigatewayv2/lib/http/api-mapping.ts | 78 ++++ .../@aws-cdk/aws-apigatewayv2/lib/http/api.ts | 20 +- .../aws-apigatewayv2/lib/http/index.ts | 3 +- .../aws-apigatewayv2/lib/http/stage.ts | 57 ++- .../@aws-cdk/aws-apigatewayv2/package.json | 4 + .../test/http/api-mapping.test.ts | 89 +++++ .../aws-apigatewayv2/test/http/api.test.ts | 11 +- .../test/http/domain-name.test.ts | 154 ++++++++ .../http/integ.custom-domain.expected.json | 348 ++++++++++++++++++ .../test/http/integ.custom-domain.ts | 57 +++ .../test/http/integrations/http.test.ts | 36 +- .../aws-apigatewayv2/test/http/route.test.ts | 23 ++ .../aws-apigatewayv2/test/http/stage.test.ts | 15 + 18 files changed, 1089 insertions(+), 7 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/common/api-mapping.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/api-mapping.test.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/integ.custom-domain.expected.json create mode 100644 packages/@aws-cdk/aws-apigatewayv2/test/http/integ.custom-domain.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2/.npmignore b/packages/@aws-cdk/aws-apigatewayv2/.npmignore index 683e3e0847e1f..4bed40b6573da 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/.npmignore +++ b/packages/@aws-cdk/aws-apigatewayv2/.npmignore @@ -4,6 +4,7 @@ *.snk !*.d.ts !*.js +**/cdk.out # Coverage coverage diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index a885fa9979695..cbf0958608abd 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -21,6 +21,7 @@ - [Defining HTTP APIs](#defining-http-apis) - [Cross Origin Resource Sharing (CORS)](#cross-origin-resource-sharing-cors) - [Publishing HTTP APIs](#publishing-http-apis) + - [Custom Domain](#custom-domain) ## Introduction @@ -134,3 +135,68 @@ If you omit the `stageName` will create a `$default` stage. A `$default` stage i the API's URL - `https://{api_id}.execute-api.{region}.amazonaws.com/`. Note that, `HttpApi` will always creates a `$default` stage, unless the `createDefaultStage` property is unset. + + + +### Custom Domain + +Custom domain names are simpler and more intuitive URLs that you can provide to your API users. Custom domain name are associated to API stages. + +The code snippet below creates a custom domain and configures a default domain mapping for your API that maps the +custom domain to the `$default` stage of the API. + +```ts +const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate'; +const domainName = 'example.com'; + +const dn = new DomainName(stack, 'DN', { + domainName, + certificate: acm.Certificate.fromCertificateArn(stack, 'cert', certArn), +}); + +const api = new HttpApi(stack, 'HttpProxyProdApi', { + defaultIntegration: new LambdaProxyIntegration({ handler }), + // https://${dn.domainName} goes to prodApi $default stage + defaultDomainMapping: { + domainName: dn, + mappingKey: '/', + }, +}); +``` + +To associate a specifc `Stage` to a custom domain mapping - + +```ts +api.addStage('beta', { + stageName: 'beta', + autoDeploy: true, + // https://${dn.domainName}/beta goes to the beta stage + domainMapping: { + domainName: dn, + mappingKey: 'beta', + }, +}); +``` + +The same domain name can be associated with stages across different `HttpApi` as so - + +```ts +const apiDemo = new HttpApi(stack, 'DemoApi', { + defaultIntegration: new LambdaProxyIntegration({ handler }), + // https://${dn.domainName}/demo goes to apiDemo $default stage + defaultDomainMapping: { + domainName: dn, + mappingKey: 'demo', + }, +}); +``` + +The `mappingKey` determines the `path` of the URL with the custom domain. Each custom domain is only allowed +to have one API mapping with the root(/) `mappingKey`. In the sample above, the custom domain is associated +with 3 API mapping resources across different APIs and Stages. + +| API | Stage | URL | +| :------------: | :---------: | :----: | +| api | $default | `https://${domainName}` | +| api | beta | `https://${domainName}/beta` | +| apiDemo | $default | `https://${domainName}/demo` | diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/api-mapping.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/api-mapping.ts new file mode 100644 index 0000000000000..d843b51f8b315 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/api-mapping.ts @@ -0,0 +1,13 @@ +import { IResource } from '@aws-cdk/core'; + +/** + * Represents an ApiGatewayV2 ApiMapping resource + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-apimapping.html + */ +export interface IApiMapping extends IResource { + /** + * ID of the api mapping + * @attribute + */ + readonly apiMappingId: string; +} diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts new file mode 100644 index 0000000000000..93234807bbf09 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/domain-name.ts @@ -0,0 +1,117 @@ +import { ICertificate } from '@aws-cdk/aws-certificatemanager'; +import { Construct, IResource, Resource, Token } from '@aws-cdk/core'; +import { CfnDomainName, CfnDomainNameProps } from '../apigatewayv2.generated'; + +/** + * Represents an APIGatewayV2 DomainName + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-domainname.html + */ +export interface IDomainName extends IResource { + /** + * The custom domain name + * + * @attribute + * + */ + readonly domainName: string; + + /** + * The domain name associated with the regional endpoint for this custom domain name. + * + * @attribute + */ + readonly regionalDomainName: string; + + /** + * The region-specific Amazon Route 53 Hosted Zone ID of the regional endpoint. + * + * @attribute + */ + readonly regionalHostedZoneId: string; +} + +/** + * custom domain name attributes + */ +export interface DomainNameAttributes { + /** + * domain name string + */ + readonly domainName: string; + + /** + * The domain name associated with the regional endpoint for this custom domain name. + */ + readonly regionalDomainName: string; + + /** + * The region-specific Amazon Route 53 Hosted Zone ID of the regional endpoint. + */ + readonly regionalHostedZoneId: string; +} + +/** + * properties used for creating the DomainName + */ +export interface DomainNameProps { + /** + * The custom domain name + */ + readonly domainName: string; + /** + * The ACM certificate for this domain name + */ + readonly certificate: ICertificate; +} + +/** + * Custom domain resource for the API + */ +export class DomainName extends Resource implements IDomainName { + /** + * import from attributes + */ + public static fromDomainNameAttributes(scope: Construct, id: string, attrs: DomainNameAttributes): IDomainName { + class Import extends Resource implements IDomainName { + public readonly regionalDomainName = attrs.regionalDomainName; + public readonly regionalHostedZoneId = attrs.regionalHostedZoneId; + public readonly domainName = attrs.domainName; + } + return new Import(scope, id); + } + + /** + * The custom domain name for your API in Amazon API Gateway. + * + * @attribute + */ + public readonly domainName: string; + + /** + * The domain name associated with the regional endpoint for this custom domain name. + */ + public readonly regionalDomainName: string; + + /** + * The region-specific Amazon Route 53 Hosted Zone ID of the regional endpoint. + */ + public readonly regionalHostedZoneId: string; + + constructor(scope: Construct, id: string, props: DomainNameProps) { + super(scope, id); + + const domainNameProps: CfnDomainNameProps = { + domainName: props.domainName, + domainNameConfigurations: [ + { + certificateArn: props.certificate.certificateArn, + endpointType: 'REGIONAL', + }, + ], + }; + const resource = new CfnDomainName(this, 'Resource', domainNameProps); + this.domainName = props.domainName ?? resource.ref; + this.regionalDomainName = Token.asString(resource.getAtt('RegionalDomainName')); + this.regionalHostedZoneId = Token.asString(resource.getAtt('RegionalHostedZoneId')); + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/common/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/common/index.ts index 5995c40125978..d727436b86c99 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/common/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/common/index.ts @@ -1,3 +1,5 @@ export * from './integration'; export * from './route'; -export * from './stage'; \ No newline at end of file +export * from './stage'; +export * from './domain-name'; +export * from './api-mapping'; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts new file mode 100644 index 0000000000000..855c6a1f30638 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api-mapping.ts @@ -0,0 +1,78 @@ +import { Construct, Resource } from '@aws-cdk/core'; +import { CfnApiMapping, CfnApiMappingProps } from '../apigatewayv2.generated'; +import { IApiMapping, IDomainName } from '../common'; +import { IHttpApi } from '../http/api'; +import { IHttpStage } from './stage'; + +/** + * Properties used to create the HttpApiMapping resource + */ +export interface HttpApiMappingProps { + /** + * Api mapping key. The path where this stage should be mapped to on the domain + * @default '/' + */ + readonly apiMappingKey?: string; + + /** + * The HttpApi to which this mapping is applied + */ + readonly api: IHttpApi; + + /** + * custom domain name of the mapping target + */ + readonly domainName: IDomainName; + + /** + * stage for the HttpApiMapping resource + * + * @default - the $default stage + */ + readonly stage?: IHttpStage; +} + +/** + * The attributes used to import existing HttpApiMapping + */ +export interface HttpApiMappingAttributes { + /** + * The API mapping ID + */ + readonly apiMappingId: string; +} + +/** + * Create a new API mapping for API Gateway HTTP API endpoint. + * @resource AWS::ApiGatewayV2::ApiMapping + */ +export class HttpApiMapping extends Resource implements IApiMapping { + /** + * import from API ID + */ + public static fromHttpApiMappingAttributes(scope: Construct, id: string, attrs: HttpApiMappingAttributes): IApiMapping { + class Import extends Resource implements IApiMapping { + public readonly apiMappingId = attrs.apiMappingId; + } + return new Import(scope, id); + } + /** + * ID of the API Mapping + */ + public readonly apiMappingId: string; + + constructor(scope: Construct, id: string, props: HttpApiMappingProps) { + super(scope, id); + + const apiMappingProps: CfnApiMappingProps = { + apiId: props.api.httpApiId, + domainName: props.domainName.domainName, + stage: props.stage?.stageName ?? '$default', + apiMappingKey: props.apiMappingKey, + }; + + const resource = new CfnApiMapping(this, 'Resource', apiMappingProps); + this.apiMappingId = resource.ref; + } + +} diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index 7856ff5f87bf7..c8a12d35ed354 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -1,5 +1,6 @@ import { Construct, Duration, IResource, Resource } from '@aws-cdk/core'; import { CfnApi, CfnApiProps } from '../apigatewayv2.generated'; +import { DefaultDomainMappingOptions } from '../http/stage'; import { IHttpRouteIntegration } from './integration'; import { BatchHttpRouteOptions, HttpMethod, HttpRoute, HttpRouteKey } from './route'; import { HttpStage, HttpStageOptions } from './stage'; @@ -43,6 +44,13 @@ export interface HttpApiProps { * @default - CORS disabled. */ readonly corsPreflight?: CorsPreflightOptions; + + /** + * Configure a custom domain with the API mapping resource to the HTTP API + * + * @default - no default domain mapping configured. meaningless if `createDefaultStage` is `false`. + */ + readonly defaultDomainMapping?: DefaultDomainMappingOptions; } /** @@ -118,6 +126,9 @@ export class HttpApi extends Resource implements IHttpApi { } public readonly httpApiId: string; + /** + * default stage of the api resource + */ private readonly defaultStage: HttpStage | undefined; constructor(scope: Construct, id: string, props?: HttpApiProps) { @@ -166,8 +177,14 @@ export class HttpApi extends Resource implements IHttpApi { this.defaultStage = new HttpStage(this, 'DefaultStage', { httpApi: this, autoDeploy: true, + domainMapping: props?.defaultDomainMapping, }); } + + if (props?.createDefaultStage === false && props.defaultDomainMapping) { + throw new Error('defaultDomainMapping not supported with createDefaultStage disabled', + ); + } } /** @@ -182,10 +199,11 @@ export class HttpApi extends Resource implements IHttpApi { * Add a new stage. */ public addStage(id: string, options: HttpStageOptions): HttpStage { - return new HttpStage(this, id, { + const stage = new HttpStage(this, id, { httpApi: this, ...options, }); + return stage; } /** diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts index 4fbb1c4e76f6a..c42e089aa1d08 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts @@ -2,4 +2,5 @@ export * from './api'; export * from './route'; export * from './integration'; export * from './integrations'; -export * from './stage'; \ No newline at end of file +export * from './stage'; +export * from './api-mapping'; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts index 1335cbf839984..8fc3e605d87b4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts @@ -1,14 +1,27 @@ import { Construct, Resource, Stack } from '@aws-cdk/core'; import { CfnStage } from '../apigatewayv2.generated'; -import { CommonStageOptions, IStage } from '../common'; +import { CommonStageOptions, IDomainName, IStage } from '../common'; import { IHttpApi } from './api'; +import { HttpApiMapping } from './api-mapping'; const DEFAULT_STAGE_NAME = '$default'; +/** + * Represents the HttpStage + */ +export interface IHttpStage extends IStage { +} + /** * Options to create a new stage for an HTTP API. */ export interface HttpStageOptions extends CommonStageOptions { + /** + * The options for custom domain and api mapping + * + * @default - no custom domain and api mapping configuration + */ + readonly domainMapping?: DomainMappingOptions; } /** @@ -21,6 +34,36 @@ export interface HttpStageProps extends HttpStageOptions { readonly httpApi: IHttpApi; } +/** + * Options for defaultDomainMapping + */ +export interface DefaultDomainMappingOptions { + /** + * The domain name for the mapping + * + */ + readonly domainName: IDomainName; + + /** + * The API mapping key. Specify '/' for the root path mapping. + * + */ + readonly mappingKey: string; + +} + +/** + * Options for DomainMapping + */ +export interface DomainMappingOptions extends DefaultDomainMappingOptions { + /** + * The API Stage + * + * @default - the $default stage + */ + readonly stage?: IStage; +} + /** * Represents a stage where an instance of the API is deployed. * @resource AWS::ApiGatewayV2::Stage @@ -52,6 +95,16 @@ export class HttpStage extends Resource implements IStage { this.stageName = this.physicalName; this.httpApi = props.httpApi; + + if (props.domainMapping) { + new HttpApiMapping(this, `${props.domainMapping.domainName}${props.domainMapping.mappingKey}`, { + api: props.httpApi, + domainName: props.domainMapping.domainName, + stage: this, + apiMappingKey: props.domainMapping.mappingKey, + }); + } + } /** @@ -62,4 +115,4 @@ export class HttpStage extends Resource implements IStage { const urlPath = this.stageName === DEFAULT_STAGE_NAME ? '' : this.stageName; return `https://${this.httpApi.httpApiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath}`; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index 0920b42061902..bc22bfc9ed2f7 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -71,6 +71,7 @@ "pkglint": "0.0.0" }, "dependencies": { + "@aws-cdk/aws-certificatemanager": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/core": "0.0.0", @@ -79,6 +80,7 @@ "peerDependencies": { "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", + "@aws-cdk/aws-certificatemanager": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.0.2" }, @@ -89,7 +91,9 @@ "exclude": [ "from-method:@aws-cdk/aws-apigatewayv2.HttpIntegration", "from-method:@aws-cdk/aws-apigatewayv2.HttpRoute", + "from-method:@aws-cdk/aws-apigatewayv2.HttpStage", "props-physical-name-type:@aws-cdk/aws-apigatewayv2.HttpStageProps.stageName", + "props-physical-name:@aws-cdk/aws-apigatewayv2.HttpApiMappingProps", "props-physical-name:@aws-cdk/aws-apigatewayv2.HttpIntegrationProps", "props-physical-name:@aws-cdk/aws-apigatewayv2.HttpRouteProps" ] diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/api-mapping.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/api-mapping.test.ts new file mode 100644 index 0000000000000..d2a536ae52862 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/api-mapping.test.ts @@ -0,0 +1,89 @@ +import '@aws-cdk/assert/jest'; +import { Certificate } from '@aws-cdk/aws-certificatemanager'; +import { Stack } from '@aws-cdk/core'; +import { DomainName, HttpApi, HttpApiMapping } from '../../lib'; + +const domainName = 'example.com'; +const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate'; + +describe('ApiMapping', () => { + test('default stage', () => { + + const stack = new Stack(); + const api = new HttpApi(stack, 'Api'); + + const dn = new DomainName(stack, 'DomainName', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + }); + + new HttpApiMapping(stack, 'Mapping', { + api, + domainName: dn, + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::ApiMapping', { + ApiId: { + Ref: 'ApiF70053CD', + }, + DomainName: 'example.com', + Stage: '$default', + }); + }); + + test('beta stage mapping', () => { + + const stack = new Stack(); + const api = new HttpApi(stack, 'Api', { + createDefaultStage: false, + }); + + const beta = api.addStage('beta', { + stageName: 'beta', + }); + + const dn = new DomainName(stack, 'DomainName', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + }); + + new HttpApiMapping(stack, 'Mapping', { + api, + domainName: dn, + stage: beta, + apiMappingKey: 'beta', + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::ApiMapping', { + ApiId: { + Ref: 'ApiF70053CD', + }, + DomainName: 'example.com', + Stage: 'beta', + ApiMappingKey: 'beta', + }); + }); + + test('import mapping', () => { + + const stack = new Stack(); + const api = new HttpApi(stack, 'Api'); + + const dn = new DomainName(stack, 'DomainName', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + }); + + const mapping = new HttpApiMapping(stack, 'Mapping', { + api, + domainName: dn, + apiMappingKey: '/', + }); + + const imported = HttpApiMapping.fromHttpApiMappingAttributes(stack, 'ImportedMapping', { + apiMappingId: mapping.apiMappingId, + } ); + + expect(imported.apiMappingId).toEqual(mapping.apiMappingId); + }); +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts index 3f89ff312b14f..5e88dbfe3cbf5 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts @@ -25,6 +25,15 @@ describe('HttpApi', () => { expect(api.url).toBeDefined(); }); + test('import', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'api', { apiName: 'customName' }); + const imported = HttpApi.fromApiId(stack, 'imported', api.httpApiId ); + + expect(imported.httpApiId).toEqual(api.httpApiId); + + }); + test('unsetting createDefaultStage', () => { const stack = new Stack(); const api = new HttpApi(stack, 'api', { @@ -104,4 +113,4 @@ describe('HttpApi', () => { RouteKey: 'ANY /pets', }); }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts new file mode 100644 index 0000000000000..12d3327300df3 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/domain-name.test.ts @@ -0,0 +1,154 @@ +import '@aws-cdk/assert/jest'; +// import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import { Certificate } from '@aws-cdk/aws-certificatemanager'; +import { Stack } from '@aws-cdk/core'; +import { DomainName, HttpApi } from '../../lib'; + +const domainName = 'example.com'; +const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate'; + +describe('DomainName', () => { + test('create domain name correctly', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new DomainName(stack, 'DomainName', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::DomainName', { + DomainName: 'example.com', + DomainNameConfigurations: [ + { + CertificateArn: 'arn:aws:acm:us-east-1:111111111111:certificate', + EndpointType: 'REGIONAL', + }, + ], + }); + }); + + test('import domain name correctly', () => { + // GIVEN + const stack = new Stack(); + + const dn = new DomainName(stack, 'DomainName', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + }); + + // WHEN + const imported = DomainName.fromDomainNameAttributes(stack, 'dn', { + domainName: dn.domainName, + regionalDomainName: dn.regionalDomainName, + regionalHostedZoneId: dn.regionalHostedZoneId, + }); + + // THEN; + expect(imported.domainName).toEqual(dn.domainName); + expect(imported.regionalDomainName).toEqual(dn.regionalDomainName); + expect(imported.regionalHostedZoneId).toEqual(dn.regionalHostedZoneId); + }); + + test('addStage with domainNameMapping', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'Api', { + createDefaultStage: true, + }); + + // WHEN + const dn = new DomainName(stack, 'DN', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + }); + + api.addStage('beta', { + stageName: 'beta', + autoDeploy: true, + domainMapping: { + domainName: dn, + mappingKey: 'beta', + }, + }); + + expect(stack).toHaveResourceLike('AWS::ApiGatewayV2::DomainName', { + DomainName: 'example.com', + DomainNameConfigurations: [ + { + CertificateArn: 'arn:aws:acm:us-east-1:111111111111:certificate', + EndpointType: 'REGIONAL', + }, + ], + }); + expect(stack).toHaveResourceLike('AWS::ApiGatewayV2::ApiMapping', { + ApiId: { + Ref: 'ApiF70053CD', + }, + DomainName: 'example.com', + Stage: 'beta', + ApiMappingKey: 'beta', + }); + }); + + test('api with defaultDomainMapping', () => { + // GIVEN + const stack = new Stack(); + const dn = new DomainName(stack, 'DN', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + }); + + // WHEN + new HttpApi(stack, 'Api', { + createDefaultStage: true, + defaultDomainMapping: { + domainName: dn, + mappingKey: '/', + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::ApiGatewayV2::DomainName', { + DomainName: 'example.com', + DomainNameConfigurations: [ + { + CertificateArn: 'arn:aws:acm:us-east-1:111111111111:certificate', + EndpointType: 'REGIONAL', + }, + ], + }); + + expect(stack).toHaveResourceLike('AWS::ApiGatewayV2::ApiMapping', { + ApiId: { + Ref: 'ApiF70053CD', + }, + DomainName: 'example.com', + Stage: '$default', + }); + }); + + test('throws when defaultDomainMapping enabled with createDefaultStage disabled', () => { + // GIVEN + const stack = new Stack(); + const dn = new DomainName(stack, 'DN', { + domainName, + certificate: Certificate.fromCertificateArn(stack, 'cert', certArn), + }); + const t = () => { + new HttpApi(stack, 'Api', { + createDefaultStage: false, + defaultDomainMapping: { + domainName: dn, + mappingKey: '/', + }, + }); + }; + + // WHEN/THEN + expect(t).toThrow('defaultDomainMapping not supported with createDefaultStage disabled'); + + }); +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/integ.custom-domain.expected.json b/packages/@aws-cdk/aws-apigatewayv2/test/http/integ.custom-domain.expected.json new file mode 100644 index 0000000000000..2fb796e79265c --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/integ.custom-domain.expected.json @@ -0,0 +1,348 @@ +{ + "Resources": { + "echohandlerServiceRole833A8F7A": { + "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" + ] + ] + }] + } + }, + "echohandler8F648AB2": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function(event, context) { return { statusCode: 200, headers: { \"content-type\": \"application/json\" }, body: JSON.stringify(event) }; };" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "echohandlerServiceRole833A8F7A", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + }, + "DependsOn": [ + "echohandlerServiceRole833A8F7A" + ] + }, + "echohandlerinteghttpproxyHttpProxyProdApiDefaultRoute20082F68PermissionBE86C6B3": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "echohandler8F648AB2", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "HttpProxyProdApi368B6161" + }, + "/*/*" + ] + ] + } + } + }, + "echohandlerinteghttpproxyHttpProxyBetaApiDefaultRouteC328B302Permission40FB964B": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "echohandler8F648AB2", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "HttpProxyBetaApiBFB4DA5E" + }, + "/*/*" + ] + ] + } + } + }, + "DNFDC76583": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": "apigv2.demo.com", + "DomainNameConfigurations": [{ + "CertificateArn": "arn:aws:acm:us-east-1:111111111111:certificate", + "EndpointType": "REGIONAL" + }] + } + }, + "HttpProxyProdApi368B6161": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "HttpProxyProdApi", + "ProtocolType": "HTTP" + } + }, + "HttpProxyProdApiDefaultRouteDefaultRouteIntegration702F0DF7": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "HttpProxyProdApi368B6161" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationUri": { + "Fn::GetAtt": [ + "echohandler8F648AB2", + "Arn" + ] + }, + "PayloadFormatVersion": "2.0" + } + }, + "HttpProxyProdApiDefaultRoute40EFC108": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "HttpProxyProdApi368B6161" + }, + "RouteKey": "$default", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "HttpProxyProdApiDefaultRouteDefaultRouteIntegration702F0DF7" + } + ] + ] + } + } + }, + "HttpProxyProdApiDefaultStage0038B180": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "HttpProxyProdApi368B6161" + }, + "StageName": "$default", + "AutoDeploy": true + } + }, + "HttpProxyProdApiDefaultStageinteghttpproxyDN4CD83A2F": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "HttpProxyProdApi368B6161" + }, + "ApiMappingKey": "/", + "DomainName": "apigv2.demo.com", + "Stage": "$default" + } + }, + "HttpProxyProdApitesting225373A0": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "HttpProxyProdApi368B6161" + }, + "StageName": "testing", + "AutoDeploy": true + } + }, + "HttpProxyProdApitestinginteghttpproxyDNtestingBEBAEF7B": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "HttpProxyProdApi368B6161" + }, + "DomainName": "apigv2.demo.com", + "Stage": "testing", + "ApiMappingKey": "testing" + } + }, + "HttpProxyBetaApiBFB4DA5E": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "HttpProxyBetaApi", + "ProtocolType": "HTTP" + } + }, + "HttpProxyBetaApiDefaultRouteDefaultRouteIntegration24A25241": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "HttpProxyBetaApiBFB4DA5E" + }, + "IntegrationType": "AWS_PROXY", + "IntegrationUri": { + "Fn::GetAtt": [ + "echohandler8F648AB2", + "Arn" + ] + }, + "PayloadFormatVersion": "2.0" + } + }, + "HttpProxyBetaApiDefaultRoute12DC547F": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "HttpProxyBetaApiBFB4DA5E" + }, + "RouteKey": "$default", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "HttpProxyBetaApiDefaultRouteDefaultRouteIntegration24A25241" + } + ] + ] + } + } + }, + "HttpProxyBetaApiDefaultStage4890F8A1": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "HttpProxyBetaApiBFB4DA5E" + }, + "StageName": "$default", + "AutoDeploy": true + } + }, + "HttpProxyBetaApiDefaultStageinteghttpproxyDNbeta0904192E": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "HttpProxyBetaApiBFB4DA5E" + }, + "ApiMappingKey": "beta", + "DomainName": "apigv2.demo.com", + "Stage": "$default" + } + } + }, + "Outputs": { + "RegionalDomainName": { + "Value": { + "Fn::GetAtt": [ + "DNFDC76583", + "RegionalDomainName" + ] + } + }, + "DomainName": { + "Value": "apigv2.demo.com" + }, + "CustomUDomainURL": { + "Value": "https://apigv2.demo.com" + }, + "ProdApiEndpoint": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "HttpProxyProdApi368B6161" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/" + ] + ] + } + }, + "BetaApiEndpoint": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "HttpProxyBetaApiBFB4DA5E" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/" + ] + ] + } + }, + "Region": { + "Value": { + "Ref": "AWS::Region" + } + } + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/integ.custom-domain.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/integ.custom-domain.ts new file mode 100644 index 0000000000000..4064a256d961f --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/integ.custom-domain.ts @@ -0,0 +1,57 @@ +import * as acm from '@aws-cdk/aws-certificatemanager'; +import * as lambda from '@aws-cdk/aws-lambda'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { DomainName, HttpApi, LambdaProxyIntegration } from '../../lib'; + +const app = new App(); + +const stack = new Stack(app, 'integ-http-proxy'); + +const handler = new lambda.Function(stack, 'echohandler', { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: new lambda.InlineCode('exports.handler = async function(event, context) { return { statusCode: 200, headers: { "content-type": "application/json" }, body: JSON.stringify(event) }; };'), +}); + +const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate'; +const domainName = 'apigv2.demo.com'; + +const dn = new DomainName(stack, 'DN', { + domainName, + certificate: acm.Certificate.fromCertificateArn(stack, 'cert', certArn), +}); + +const prodApi = new HttpApi(stack, 'HttpProxyProdApi', { + defaultIntegration: new LambdaProxyIntegration({ handler }), + // https://${dn.domainName} goes to prodApi $default stage + defaultDomainMapping: { + domainName: dn, + mappingKey: '/', + }, +}); + +const betaApi = new HttpApi(stack, 'HttpProxyBetaApi', { + defaultIntegration: new LambdaProxyIntegration({ handler }), + // https://${dn.domainName}/beta goes to betaApi $default stage + defaultDomainMapping: { + domainName: dn, + mappingKey: 'beta', + }, +}); + +prodApi.addStage('testing', { + stageName: 'testing', + autoDeploy: true, + // https://${dn.domainName}/testing goes to prodApi testing stage + domainMapping: { + domainName: dn, + mappingKey: 'testing', + }, +} ); + +new CfnOutput(stack, 'RegionalDomainName', { value: dn.regionalDomainName }); +new CfnOutput(stack, 'DomainName', { value: dn.domainName }); +new CfnOutput(stack, 'CustomUDomainURL', { value: `https://${dn.domainName}` }); +new CfnOutput(stack, 'ProdApiEndpoint', { value: prodApi.url! }); +new CfnOutput(stack, 'BetaApiEndpoint', { value: betaApi.url! }); +new CfnOutput(stack, 'Region', { value: Stack.of(stack).region}); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/http.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/http.test.ts index cc5ed64df20eb..8f187d0ab9c78 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/http.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/integrations/http.test.ts @@ -1,7 +1,7 @@ import { ABSENT } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import { Duration, Stack } from '@aws-cdk/core'; -import { HttpApi, HttpMethod, HttpProxyIntegration, HttpRoute, HttpRouteKey } from '../../../lib'; +import { HttpApi, HttpIntegration, HttpIntegrationType, HttpMethod, HttpProxyIntegration, HttpRoute, HttpRouteKey, PayloadFormatVersion } from '../../../lib'; describe('HttpProxyIntegration', () => { test('default', () => { @@ -40,6 +40,40 @@ describe('HttpProxyIntegration', () => { }); }); + test('custom payload format version is allowed', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + new HttpIntegration(stack, 'HttpInteg', { + payloadFormatVersion: PayloadFormatVersion.custom('99.99'), + httpApi: api, + integrationType: HttpIntegrationType.HTTP_PROXY, + integrationUri: 'some-target-url', + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + IntegrationUri: 'some-target-url', + PayloadFormatVersion: '99.99', + }); + }); + + test('HttpIntegration without payloadFormatVersion is allowed', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + new HttpIntegration(stack, 'HttpInteg', { + httpApi: api, + integrationType: HttpIntegrationType.HTTP_PROXY, + integrationUri: 'some-target-url', + }); + + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + IntegrationUri: 'some-target-url', + }); + }); +}); + +describe('CORS', () => { test('CORS Configuration is correctly configured.', () => { const stack = new Stack(); new HttpApi(stack, 'HttpApi', { diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts index 25df12528e63e..f129db5186486 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts @@ -52,6 +52,29 @@ describe('HttpRoute', () => { IntegrationUri: 'some-uri', }); }); + + test('throws when path not start with /', () => { + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'HttpApi'); + + expect(() => new HttpRoute(stack, 'HttpRoute', { + httpApi, + integration: new DummyIntegration(), + routeKey: HttpRouteKey.with('books', HttpMethod.GET), + })).toThrowError(/path must always start with a "\/" and not end with a "\/"/); + }); + + test('throws when path ends with /', () => { + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'HttpApi'); + + expect(() => new HttpRoute(stack, 'HttpRoute', { + httpApi, + integration: new DummyIntegration(), + routeKey: HttpRouteKey.with('/books/', HttpMethod.GET), + })).toThrowError(/path must always start with a "\/" and not end with a "\/"/); + }); + }); class DummyIntegration implements IHttpRouteIntegration { diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts index 336d3a74852a4..06e7a9efbc4ce 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts @@ -19,6 +19,21 @@ describe('HttpStage', () => { }); }); + test('import', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'Api', { + createDefaultStage: false, + }); + + const stage = new HttpStage(stack, 'Stage', { + httpApi: api, + }); + + const imported = HttpStage.fromStageName(stack, 'Import', stage.stageName ); + + expect(imported.stageName).toEqual(stage.stageName); + }); + test('url returns the correct path', () => { const stack = new Stack(); const api = new HttpApi(stack, 'Api', { From 4ba20fd2f1579034483683995fac1e18e97a1b12 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 6 Jul 2020 15:05:22 +0200 Subject: [PATCH 14/28] feat(lambda-nodejs): allow jsx and tsx entry files (#8892) Support JSX adn TSX for entry files ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda-nodejs/README.md | 2 +- .../@aws-cdk/aws-lambda-nodejs/lib/function.ts | 2 +- .../aws-lambda-nodejs/test/function.test.ts | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index b4e4579991f9e..20b79f7878cdc 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -34,7 +34,7 @@ automatically transpiled and bundled whether it's written in JavaScript or TypeS Alternatively, an entry file and handler can be specified: ```ts new lambda.NodejsFunction(this, 'MyFunction', { - entry: '/path/to/my/file.ts', + entry: '/path/to/my/file.ts', // accepts .js, .jsx, .ts and .tsx files handler: 'myExportedFunc' }); ``` diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts index 1138807443fec..3f623aa08e94f 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts @@ -84,7 +84,7 @@ export class NodejsFunction extends lambda.Function { */ function findEntry(id: string, entry?: string): string { if (entry) { - if (!/\.(js|ts)$/.test(entry)) { + if (!/\.(jsx?|tsx?)$/.test(entry)) { throw new Error('Only JavaScript or TypeScript entry files are supported.'); } if (!fs.existsSync(entry)) { diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts index 200200fdeb1ce..fca00ea0f9562 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts @@ -1,6 +1,8 @@ import '@aws-cdk/assert/jest'; import { Runtime } from '@aws-cdk/aws-lambda'; import { Stack } from '@aws-cdk/core'; +import * as fs from 'fs'; +import * as path from 'path'; import { NodejsFunction } from '../lib'; import { Bundling } from '../lib/bundling'; @@ -67,6 +69,18 @@ test('throws when entry is not js/ts', () => { })).toThrow(/Only JavaScript or TypeScript entry files are supported/); }); +test('accepts tsx', () => { + const entry = path.join(__dirname, 'handler.tsx'); + + fs.symlinkSync(path.join(__dirname, 'function.test.handler1.ts'), entry); + + expect(() => new NodejsFunction(stack, 'Fn', { + entry, + })).not.toThrow(); + + fs.unlinkSync(entry); +}); + test('throws when entry does not exist', () => { expect(() => new NodejsFunction(stack, 'Fn', { entry: 'notfound.ts', From 021533caa8f4e515299d1f0cdaadd9f625d6f64d Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Mon, 6 Jul 2020 07:42:14 -0700 Subject: [PATCH 15/28] feat(stepfunctions-tasks): task for invoking a Step Functions activity worker (#8840) Replacement class for `InvokeActivity` which currently uses the embedded task. The notable changes are: * This change merges task and service integration level properties by extending `TaskStateBase`, similar to all the other task states. * `activity` is now a property in the new class and not specified in the constructor I've left the current tests intact for fidelity and updated the `README` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-stepfunctions-tasks/README.md | 31 +++++++ .../aws-stepfunctions-tasks/lib/index.ts | 1 + .../lib/invoke-activity.ts | 2 + .../lib/stepfunctions/invoke-activity.ts | 44 ++++++++++ .../integ.invoke-activity.expected.json | 85 +++++++++++++++++++ .../stepfunctions/integ.invoke-activity.ts | 69 +++++++++++++++ .../test/stepfunctions/invoke-activity.ts | 64 ++++++++++++++ 7 files changed, 296 insertions(+) create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/invoke-activity.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.invoke-activity.expected.json create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.invoke-activity.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.ts diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index d5142e2159d2b..eb391390b2056 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -54,6 +54,8 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw - [Create Transform Job](#create-transform-job) - [SNS](#sns) - [Step Functions](#step-functions) + - [Start Execution](#start-execution) + - [Invoke Activity Worker](#invoke-activity) - [SQS](#sqs) ## Task @@ -750,6 +752,8 @@ const task2 = new tasks.SnsPublish(this, 'Publish2', { ## Step Functions +### Start Execution + You can manage [AWS Step Functions](https://docs.aws.amazon.com/step-functions/latest/dg/connect-stepfunctions.html) executions. AWS Step Functions supports it's own [`StartExecution`](https://docs.aws.amazon.com/step-functions/latest/apireference/API_StartExecution.html) API as a service integration. @@ -777,6 +781,33 @@ new sfn.StateMachine(stack, 'ParentStateMachine', { }); ``` +### Invoke Activity + +You can invoke a [Step Functions Activity](https://docs.aws.amazon.com/step-functions/latest/dg/concepts-activities.html) which enables you to have +a task in your state machine where the work is performed by a *worker* that can +be hosted on Amazon EC2, Amazon ECS, AWS Lambda, basically anywhere. Activities +are a way to associate code running somewhere (known as an activity worker) with +a specific task in a state machine. + +When Step Functions reaches an activity task state, the workflow waits for an +activity worker to poll for a task. An activity worker polls Step Functions by +using GetActivityTask, and sending the ARN for the related activity. + +After the activity worker completes its work, it can provide a report of its +success or failure by using `SendTaskSuccess` or `SendTaskFailure`. These two +calls use the taskToken provided by GetActivityTask to associate the result +with that task. + +The following example creates an activity and creates a task that invokes the activity. + +```ts +const submitJobActivity = new sfn.Activity(this, 'SubmitJob'); + +new tasks.StepFunctionsInvokeActivity(this, 'Submit Job', { + activity: submitJobActivity, +}); +``` + ## SQS Step Functions supports [Amazon SQS](https://docs.aws.amazon.com/step-functions/latest/dg/connect-sqs.html) diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts index 68d8c30b93997..ee034e389ad96 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts @@ -16,6 +16,7 @@ export * from './sagemaker/create-training-job'; export * from './sagemaker/create-transform-job'; export * from './start-execution'; export * from './stepfunctions/start-execution'; +export * from './stepfunctions/invoke-activity'; export * from './evaluate-expression'; export * from './emr/emr-create-cluster'; export * from './emr/emr-set-cluster-termination-protection'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts index e9d6342fefcc4..a0c78b5cf073c 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts @@ -19,6 +19,8 @@ export interface InvokeActivityProps { * A Step Functions Task to invoke an Activity worker. * * An Activity can be used directly as a Resource. + * + * @deprecated - use `StepFunctionsInvokeActivity` */ export class InvokeActivity implements sfn.IStepFunctionsTask { constructor(private readonly activity: sfn.IActivity, private readonly props: InvokeActivityProps = {}) { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/invoke-activity.ts new file mode 100644 index 0000000000000..829a35eb03658 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/stepfunctions/invoke-activity.ts @@ -0,0 +1,44 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; + +/** + * Properties for invoking an Activity worker + */ +export interface StepFunctionsInvokeActivityProps extends sfn.TaskStateBaseProps { + + /** + * Step Functions Activity to invoke + */ + readonly activity: sfn.IActivity +} + +/** + * A Step Functions Task to invoke an Activity worker. + * + * An Activity can be used directly as a Resource. + */ +export class StepFunctionsInvokeActivity extends sfn.TaskStateBase { + protected readonly taskMetrics?: sfn.TaskMetricsConfig; + // No IAM permissions necessary, execution role implicitly has Activity permissions. + protected readonly taskPolicies?: iam.PolicyStatement[]; + + constructor(scope: cdk.Construct, id: string, private readonly props: StepFunctionsInvokeActivityProps) { + super(scope, id, props); + + this.taskMetrics = { + metricDimensions: { ActivityArn: this.props.activity.activityArn }, + metricPrefixSingular: 'Activity', + metricPrefixPlural: 'Activities', + }; + } + + /** + * @internal + */ + protected _renderTask(): any { + return { + Resource: this.props.activity.activityArn, + }; + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.invoke-activity.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.invoke-activity.expected.json new file mode 100644 index 0000000000000..e58efc93007de --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.invoke-activity.expected.json @@ -0,0 +1,85 @@ +{ + "Resources": { + "SubmitJobFB773A16": { + "Type": "AWS::StepFunctions::Activity", + "Properties": { + "Name": "awsstepfunctionsintegSubmitJobA2508960" + } + }, + "CheckJob5FFC1D6F": { + "Type": "AWS::StepFunctions::Activity", + "Properties": { + "Name": "awsstepfunctionsintegCheckJobC4AC762D" + } + }, + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Submit Job\",\"States\":{\"Submit Job\":{\"Next\":\"Wait X Seconds\",\"Type\":\"Task\",\"ResultPath\":\"$.guid\",\"Resource\":\"", + { + "Ref": "SubmitJobFB773A16" + }, + "\"},\"Wait X Seconds\":{\"Type\":\"Wait\",\"SecondsPath\":\"$.wait_time\",\"Next\":\"Get Job Status\"},\"Get Job Status\":{\"Next\":\"Job Complete?\",\"Type\":\"Task\",\"InputPath\":\"$.guid\",\"ResultPath\":\"$.status\",\"Resource\":\"", + { + "Ref": "CheckJob5FFC1D6F" + }, + "\"},\"Job Complete?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Get Final Job Status\"}],\"Default\":\"Wait X Seconds\"},\"Job Failed\":{\"Type\":\"Fail\",\"Error\":\"DescribeJob returned FAILED\",\"Cause\":\"AWS Batch Job Failed\"},\"Get Final Job Status\":{\"End\":true,\"Type\":\"Task\",\"InputPath\":\"$.guid\",\"Resource\":\"", + { + "Ref": "CheckJob5FFC1D6F" + }, + "\"}},\"TimeoutSeconds\":300}" + ] + ] + } + }, + "DependsOn": [ + "StateMachineRoleB840431D" + ] + } + }, + "Outputs": { + "stateMachineArn": { + "Value": { + "Ref": "StateMachine2E01A3A5" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.invoke-activity.ts new file mode 100644 index 0000000000000..918c31d809aaf --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/integ.invoke-activity.ts @@ -0,0 +1,69 @@ +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import * as tasks from '../../lib'; + +/* + * Creates a state machine with a job poller sample project + * https://docs.aws.amazon.com/step-functions/latest/dg/sample-project-job-poller.html + * + * Stack verification steps: + * The generated State Machine can be executed from the CLI (or Step Functions console) + * and runs with an execution status of `Running`. + * + * An external process can call the state machine to send a heartbeat or response before it times out. + * + * -- aws stepfunctions start-execution --state-machine-arn provides execution arn + * -- aws stepfunctions describe-execution --execution-arn returns a status of `Running` + * + * CHANGEME: extend this test to create the external resources to report heartbeats + */ +class InvokeActivityStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props: cdk.StackProps = {}) { + super(scope, id, props); + + const submitJobActivity = new sfn.Activity(this, 'SubmitJob'); + const checkJobActivity = new sfn.Activity(this, 'CheckJob'); + + const submitJob = new tasks.StepFunctionsInvokeActivity(this, 'Submit Job', { + activity: submitJobActivity, + resultPath: '$.guid', + }); + const waitX = new sfn.Wait(this, 'Wait X Seconds', { time: sfn.WaitTime.secondsPath('$.wait_time') }); + const getStatus = new tasks.StepFunctionsInvokeActivity(this, 'Get Job Status', { + activity: checkJobActivity, + inputPath: '$.guid', + resultPath: '$.status', + }); + const isComplete = new sfn.Choice(this, 'Job Complete?'); + const jobFailed = new sfn.Fail(this, 'Job Failed', { + cause: 'AWS Batch Job Failed', + error: 'DescribeJob returned FAILED', + }); + const finalStatus = new tasks.StepFunctionsInvokeActivity(this, 'Get Final Job Status', { + activity: checkJobActivity, + inputPath: '$.guid', + }); + + const chain = sfn.Chain + .start(submitJob) + .next(waitX) + .next(getStatus) + .next(isComplete + .when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed) + .when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus) + .otherwise(waitX)); + + const sm = new sfn.StateMachine(this, 'StateMachine', { + definition: chain, + timeout: cdk.Duration.seconds(300), + }); + + new cdk.CfnOutput(this, 'stateMachineArn', { + value: sm.stateMachineArn, + }); + } +} + +const app = new cdk.App(); +new InvokeActivityStack(app, 'aws-stepfunctions-integ'); +app.synth(); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.ts new file mode 100644 index 0000000000000..940517e6bdbf7 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/stepfunctions/invoke-activity.ts @@ -0,0 +1,64 @@ +import '@aws-cdk/assert/jest'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { Stack } from '@aws-cdk/core'; +import { StepFunctionsInvokeActivity } from '../../lib/stepfunctions/invoke-activity'; + +test('Activity can be used in a Task', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const activity = new sfn.Activity(stack, 'Activity'); + const task = new StepFunctionsInvokeActivity(stack, 'Task', { activity }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); + + // THEN + expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + DefinitionString: { + 'Fn::Join': ['', [ + '{"StartAt":"Task","States":{"Task":{"End":true,"Type":"Task","Resource":"', + { Ref: 'Activity04690B0A' }, + '"}}}', + ]], + }, + }); +}); + +test('Activity Task metrics and Activity metrics are the same', () => { + // GIVEN + const stack = new Stack(); + const activity = new sfn.Activity(stack, 'Activity'); + const task = new StepFunctionsInvokeActivity(stack, 'Invoke', {activity }); + + // WHEN + const activityMetrics = [ + activity.metricFailed(), + activity.metricHeartbeatTimedOut(), + activity.metricRunTime(), + activity.metricScheduled(), + activity.metricScheduleTime(), + activity.metricStarted(), + activity.metricSucceeded(), + activity.metricTime(), + activity.metricTimedOut(), + ]; + + const taskMetrics = [ + task.metricFailed(), + task.metricHeartbeatTimedOut(), + task.metricRunTime(), + task.metricScheduled(), + task.metricScheduleTime(), + task.metricStarted(), + task.metricSucceeded(), + task.metricTime(), + task.metricTimedOut(), + ]; + + // THEN + for (let i = 0; i < activityMetrics.length; i++) { + expect(activityMetrics[i]).toEqual(taskMetrics[i]); + } +}); From 33263d7bce032307eb7779de85e07c681d245e67 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 6 Jul 2020 17:02:54 +0200 Subject: [PATCH 16/28] chore(lambda-nodejs): make entry file path absolute (#8874) Although I haven't seen issues with a relative entry file path, it is safer to use a absolute one. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts | 4 ++-- .../@aws-cdk/aws-lambda-nodejs/test/function.test.ts | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts index 3f623aa08e94f..1215330212436 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/function.ts @@ -47,7 +47,7 @@ export class NodejsFunction extends lambda.Function { } // Entry and defaults - const entry = findEntry(id, props.entry); + const entry = path.resolve(findEntry(id, props.entry)); const handler = props.handler ?? 'handler'; const defaultRunTime = nodeMajorVersion() >= 12 ? lambda.Runtime.NODEJS_12_X @@ -63,9 +63,9 @@ export class NodejsFunction extends lambda.Function { ...props, runtime, code: Bundling.parcel({ + ...props, entry, runtime, - ...props, }), handler: `index.${handler}`, }); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts index fca00ea0f9562..e8f559fdfbec5 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/function.test.ts @@ -96,3 +96,14 @@ test('throws with the wrong runtime family', () => { runtime: Runtime.PYTHON_3_8, })).toThrow(/Only `NODEJS` runtimes are supported/); }); + +test('resolves entry to an absolute path', () => { + // WHEN + new NodejsFunction(stack, 'fn', { + entry: 'lib/index.ts', + }); + + expect(Bundling.parcel).toHaveBeenCalledWith(expect.objectContaining({ + entry: expect.stringMatching(/@aws-cdk\/aws-lambda-nodejs\/lib\/index.ts$/), + })); +}); From c585e1873e437341ac1b90afbe85a9cb9e6dc2d6 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 6 Jul 2020 17:23:05 +0200 Subject: [PATCH 17/28] fix(lambda-nodejs): maximum call stack size exceeded with relative entry file path (#8907) Use absolute paths in `findUp()` to avoid this. Fixes #8902 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts | 14 +++++++++----- .../@aws-cdk/aws-lambda-nodejs/test/util.test.ts | 6 ++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts index 6b3f7f8173a02..9bdab8776c86c 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/util.ts @@ -54,12 +54,16 @@ export function nodeMajorVersion(): number { * Find a file by walking up parent directories */ export function findUp(name: string, directory: string = process.cwd()): string | undefined { - const { root } = path.parse(directory); - if (directory === root && !fs.existsSync(path.join(directory, name))) { - return undefined; - } + const absoluteDirectory = path.resolve(directory); + if (fs.existsSync(path.join(directory, name))) { return directory; } - return findUp(name, path.dirname(directory)); + + const { root } = path.parse(absoluteDirectory); + if (absoluteDirectory === root) { + return undefined; + } + + return findUp(name, path.dirname(absoluteDirectory)); } diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts index 026f2bcb519e3..a85b0064cef85 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/util.test.ts @@ -10,4 +10,10 @@ test('findUp', () => { // Starting at a specific path expect(findUp('util.test.ts', path.join(__dirname, 'integ-handlers'))).toMatch(/aws-lambda-nodejs\/test$/); + + // Non existing file starting at a non existing relative path + expect(findUp('not-to-be-found.txt', 'non-existing/relative/path')).toBe(undefined); + + // Starting at a relative path + expect(findUp('util.test.ts', 'test/integ-handlers')).toMatch(/aws-lambda-nodejs\/test$/); }); From 755648a7d3e318dc63fafed2c75e720f1e9a56ed Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Mon, 6 Jul 2020 08:42:48 -0700 Subject: [PATCH 18/28] chore(stepfunctions): mark the task class as deprecated (#8829) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index 1d275d3fb25f8..29a1d6845aeb9 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -11,6 +11,8 @@ import { renderJsonPath, State } from './state'; /** * Props that are common to all tasks + * + * @deprecated - replaced by service integration specific classes (i.e. LambdaInvoke, SnsPublish) */ export interface TaskProps { /** @@ -98,6 +100,8 @@ export interface TaskProps { * * For some resource types, more specific subclasses of Task may be available * which are more convenient to use. + * + * @deprecated - replaced by service integration specific classes (i.e. LambdaInvoke, SnsPublish) */ export class Task extends State implements INextable { public readonly endStates: INextable[]; From c1d4e0fecbdf716eb55578ad5721a0ead4b306e2 Mon Sep 17 00:00:00 2001 From: Matt Morgan Date: Mon, 6 Jul 2020 13:15:48 -0400 Subject: [PATCH 19/28] fix(core): asset bundling fails with BuildKit (#8911) Docker buildkit has different output from the legacy builder unless you pass the -q flag in which case the output is always just the image tag. This PR works for both build versions. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/core/lib/bundling.ts | 4 ++-- packages/@aws-cdk/core/test/test.bundling.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index e078153386c6e..6ec85c43af22e 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -78,14 +78,14 @@ export class BundlingDockerImage { const buildArgs = options.buildArgs || {}; const dockerArgs: string[] = [ - 'build', + 'build', '-q', ...flatten(Object.entries(buildArgs).map(([k, v]) => ['--build-arg', `${k}=${v}`])), path, ]; const docker = dockerExec(dockerArgs); - const match = docker.stdout.toString().match(/Successfully built ([a-z0-9]+)/); + const match = docker.stdout.toString().match(/sha256:([a-z0-9]+)/); if (!match) { throw new Error('Failed to extract image ID from Docker build output'); diff --git a/packages/@aws-cdk/core/test/test.bundling.ts b/packages/@aws-cdk/core/test/test.bundling.ts index 558765010ccfe..500401101d22d 100644 --- a/packages/@aws-cdk/core/test/test.bundling.ts +++ b/packages/@aws-cdk/core/test/test.bundling.ts @@ -49,7 +49,7 @@ export = { const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({ status: 0, stderr: Buffer.from('stderr'), - stdout: Buffer.from(`Successfully built ${imageId}`), + stdout: Buffer.from(`sha256:${imageId}`), pid: 123, output: ['stdout', 'stderr'], signal: null, @@ -63,7 +63,7 @@ export = { image._run(); test.ok(spawnSyncStub.firstCall.calledWith('docker', [ - 'build', + 'build', '-q', '--build-arg', 'TEST_ARG=cdk-test', 'docker-path', ])); From 841060d6adde4ea6d58e008f85cc155b8c3a3768 Mon Sep 17 00:00:00 2001 From: Romain Marcadier Date: Mon, 6 Jul 2020 19:51:32 +0200 Subject: [PATCH 20/28] fix(config): cannot scope a custom rule without configurationChanges on (#8738) While CloudFormation allows to specify the scope for a custom ConfigRule without necessarily specifying `configurationChanges: true`, this was not allowed by the corresponding construct. This removed the offending guard, and replaced the test that verified the throwing behavior with a regression test that validates this configuration is allowed. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-config/lib/rule.ts | 20 +--- .../test/integ.scoped-rule.expected.json | 109 ++++++++++++++++++ .../aws-config/test/integ.scoped-rule.ts | 22 ++++ .../@aws-cdk/aws-config/test/test.rule.ts | 4 +- 4 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 packages/@aws-cdk/aws-config/test/integ.scoped-rule.expected.json create mode 100644 packages/@aws-cdk/aws-config/test/integ.scoped-rule.ts diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 0659fefa8089b..12dd86b8ab1bd 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -122,10 +122,10 @@ abstract class RuleNew extends RuleBase { * @param identifier the resource identifier */ public scopeToResource(type: string, identifier?: string) { - this.scopeTo({ + this.scope = { complianceResourceId: identifier, complianceResourceTypes: [type], - }); + }; } /** @@ -136,9 +136,9 @@ abstract class RuleNew extends RuleBase { * @param types resource types */ public scopeToResources(...types: string[]) { - this.scopeTo({ + this.scope = { complianceResourceTypes: types, - }); + }; } /** @@ -148,18 +148,10 @@ abstract class RuleNew extends RuleBase { * @param value the tag value */ public scopeToTag(key: string, value?: string) { - this.scopeTo({ + this.scope = { tagKey: key, tagValue: value, - }); - } - - private scopeTo(scope: CfnConfigRule.ScopeProperty) { - if (!this.isManaged && !this.isCustomWithChanges) { - throw new Error('Cannot scope rule when `configurationChanges` is set to false.'); - } - - this.scope = scope; + }; } } diff --git a/packages/@aws-cdk/aws-config/test/integ.scoped-rule.expected.json b/packages/@aws-cdk/aws-config/test/integ.scoped-rule.expected.json new file mode 100644 index 0000000000000..99d314d0c45af --- /dev/null +++ b/packages/@aws-cdk/aws-config/test/integ.scoped-rule.expected.json @@ -0,0 +1,109 @@ +{ + "Resources": { + "CustomFunctionServiceRoleD3F73B79": { + "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" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSConfigRulesExecutionRole" + ] + ] + } + ] + } + }, + "CustomFunctionBADD59E7": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = (event) => console.log(event);" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "CustomFunctionServiceRoleD3F73B79", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "CustomFunctionServiceRoleD3F73B79" + ] + }, + "CustomFunctionPermission41887A5E": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "CustomFunctionBADD59E7", + "Arn" + ] + }, + "Principal": "config.amazonaws.com" + } + }, + "Custom8166710A": { + "Type": "AWS::Config::ConfigRule", + "Properties": { + "Source": { + "Owner": "CUSTOM_LAMBDA", + "SourceDetails": [ + { + "EventSource": "aws.config", + "MessageType": "ScheduledNotification" + } + ], + "SourceIdentifier": { + "Fn::GetAtt": [ + "CustomFunctionBADD59E7", + "Arn" + ] + } + }, + "Scope": { + "ComplianceResourceTypes": [ + "AWS::EC2::Instance" + ] + } + }, + "DependsOn": [ + "CustomFunctionPermission41887A5E", + "CustomFunctionBADD59E7", + "CustomFunctionServiceRoleD3F73B79" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-config/test/integ.scoped-rule.ts b/packages/@aws-cdk/aws-config/test/integ.scoped-rule.ts new file mode 100644 index 0000000000000..aee8392f402f0 --- /dev/null +++ b/packages/@aws-cdk/aws-config/test/integ.scoped-rule.ts @@ -0,0 +1,22 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as config from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-config-rule-scoped-integ'); + +const fn = new lambda.Function(stack, 'CustomFunction', { + code: lambda.AssetCode.fromInline('exports.handler = (event) => console.log(event);'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, +}); + +const customRule = new config.CustomRule(stack, 'Custom', { + lambdaFunction: fn, + periodic: true, +}); + +customRule.scopeToResource('AWS::EC2::Instance'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-config/test/test.rule.ts b/packages/@aws-cdk/aws-config/test/test.rule.ts index 0f19a826de6d2..c13dacf2c8a3f 100644 --- a/packages/@aws-cdk/aws-config/test/test.rule.ts +++ b/packages/@aws-cdk/aws-config/test/test.rule.ts @@ -204,7 +204,7 @@ export = { test.done(); }, - 'throws when scoping a custom rule without configuration changes'(test: Test) { + 'allows scoping a custom rule without configurationChanges enabled'(test: Test) { // GIVEN const stack = new cdk.Stack(); const fn = new lambda.Function(stack, 'Function', { @@ -220,7 +220,7 @@ export = { }); // THEN - test.throws(() => rule.scopeToResource('resource'), /`configurationChanges`/); + test.doesNotThrow(() => rule.scopeToResource('resource')); test.done(); }, From 7d47cfb39ba40a223ccc511e5706f471b9225c52 Mon Sep 17 00:00:00 2001 From: Hyeonsoo David Lee Date: Tue, 7 Jul 2020 03:28:07 +0900 Subject: [PATCH 21/28] fix(rds): proxy for db cluster fails with model validation error (#8896) fixes #8885 follows #8476 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-rds/lib/proxy.ts | 22 +++++++--- .../aws-rds/test/integ.proxy.expected.json | 3 +- packages/@aws-cdk/aws-rds/test/test.proxy.ts | 44 +++++++++++++++---- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/lib/proxy.ts b/packages/@aws-cdk/aws-rds/lib/proxy.ts index 4e8112ed9ffbc..9733056d8ed3b 100644 --- a/packages/@aws-cdk/aws-rds/lib/proxy.ts +++ b/packages/@aws-cdk/aws-rds/lib/proxy.ts @@ -408,20 +408,30 @@ export class DatabaseProxy extends cdk.Resource this.endpoint = this.resource.attrEndpoint; let dbInstanceIdentifiers: string[] | undefined; - if (bindResult.dbClusters) { - // support for only instances of a single cluster - dbInstanceIdentifiers = bindResult.dbClusters[0].instanceIdentifiers; - } else if (bindResult.dbInstances) { + if (bindResult.dbInstances) { // support for only single instance dbInstanceIdentifiers = [ bindResult.dbInstances[0].instanceIdentifier ]; } - new CfnDBProxyTargetGroup(this, 'ProxyTargetGroup', { + let dbClusterIdentifiers: string[] | undefined; + if (bindResult.dbClusters) { + dbClusterIdentifiers = bindResult.dbClusters.map((c) => c.clusterIdentifier); + } + + if (!!dbInstanceIdentifiers && !!dbClusterIdentifiers) { + throw new Error('Cannot specify both dbInstanceIdentifiers and dbClusterIdentifiers'); + } + + const proxyTargetGroup = new CfnDBProxyTargetGroup(this, 'ProxyTargetGroup', { dbProxyName: this.dbProxyName, dbInstanceIdentifiers, - dbClusterIdentifiers: bindResult.dbClusters?.map((c) => c.clusterIdentifier), + dbClusterIdentifiers, connectionPoolConfigurationInfo: toConnectionPoolConfigurationInfo(props), }); + + // Currently(2020-07-04), this property must be set to default. + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxytargetgroup.html#TargetGroupName-fn::getatt + proxyTargetGroup.addOverride('Properties.TargetGroupName', 'default'); } /** diff --git a/packages/@aws-cdk/aws-rds/test/integ.proxy.expected.json b/packages/@aws-cdk/aws-rds/test/integ.proxy.expected.json index d02477bf1a86a..10e7b2afb02f8 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.proxy.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.proxy.expected.json @@ -554,7 +554,8 @@ { "Ref": "dbInstance4076B1EC" } - ] + ], + "TargetGroupName": "default" } } } diff --git a/packages/@aws-cdk/aws-rds/test/test.proxy.ts b/packages/@aws-cdk/aws-rds/test/test.proxy.ts index f0b6bfbf0c91f..2ec6d93f6eb0a 100644 --- a/packages/@aws-cdk/aws-rds/test/test.proxy.ts +++ b/packages/@aws-cdk/aws-rds/test/test.proxy.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import { ABSENT, expect, haveResource, ResourcePart } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; @@ -67,6 +67,7 @@ export = { Ref: 'InstanceC1063A87', }, ], + TargetGroupName: 'default', }, }, ResourcePart.CompleteDefinition)); @@ -140,17 +141,42 @@ export = { Ref: 'DatabaseB269D8BB', }, ], - DBInstanceIdentifiers: [ - { - Ref: 'DatabaseInstance1844F58FD', - }, - { - Ref: 'DatabaseInstance2AA380DEE', - }, - ], + TargetGroupName: 'default', }, }, ResourcePart.CompleteDefinition)); test.done(); }, + + 'Cannot specify both dbInstanceIdentifiers and dbClusterIdentifiers'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new rds.DatabaseCluster(stack, 'Database', { + engine: rds.DatabaseClusterEngine.AURORA_POSTGRESQL, + engineVersion: '10.7', + masterUser: { + username: 'admin', + }, + instanceProps: { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, + }, + }); + + // WHEN + test.doesNotThrow(() => { + new rds.DatabaseProxy(stack, 'Proxy', { + proxyTarget: rds.ProxyTarget.fromCluster(cluster), + secret: cluster.secret!, + vpc, + }); + }, /Cannot specify both dbInstanceIdentifiers and dbClusterIdentifiers/); + + expect(stack).to(haveResource('AWS::RDS::DBProxyTargetGroup', { + DBInstanceIdentifiers: ABSENT, + }, ResourcePart.Properties)); + + test.done(); + }, }; From d76077b7e57a2af1a40d5ed87c9ba8150ebc744d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2020 20:55:12 +0000 Subject: [PATCH 22/28] chore(deps): bump aws-sdk from 2.709.0 to 2.710.0 (#8915) Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.709.0 to 2.710.0. - [Release notes](https://github.com/aws/aws-sdk-js/releases) - [Changelog](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js/compare/v2.709.0...v2.710.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- packages/@aws-cdk/aws-cloudfront/package.json | 2 +- packages/@aws-cdk/aws-cloudtrail/package.json | 2 +- packages/@aws-cdk/aws-codebuild/package.json | 2 +- packages/@aws-cdk/aws-codecommit/package.json | 2 +- packages/@aws-cdk/aws-dynamodb/package.json | 2 +- packages/@aws-cdk/aws-eks/package.json | 2 +- .../@aws-cdk/aws-events-targets/package.json | 2 +- packages/@aws-cdk/aws-lambda/package.json | 2 +- packages/@aws-cdk/aws-route53/package.json | 2 +- packages/@aws-cdk/aws-sqs/package.json | 2 +- .../@aws-cdk/custom-resources/package.json | 2 +- packages/aws-cdk/package.json | 2 +- packages/cdk-assets/package.json | 2 +- yarn.lock | 200 ++---------------- 14 files changed, 32 insertions(+), 194 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 8c2f655f0ad81..27f6835b0f021 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index 0582cc87ac301..f024b97b7b532 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -64,7 +64,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index fe25059df3db7..4c67386790f41 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -70,7 +70,7 @@ "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 89a7688dd7b4e..ecc3e43d5f6a6 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -70,7 +70,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index 7be8d55f282bb..14ed1894e0ad4 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -65,7 +65,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/jest": "^26.0.3", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 17233a7c7689b..af3f49fd3627c 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -65,7 +65,7 @@ "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", "@types/yaml": "1.2.0", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 2463cf78299c4..d22709f9f8f16 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -68,7 +68,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 73416597604ba..4748593961596 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -71,7 +71,7 @@ "@types/lodash": "^4.14.157", "@types/nodeunit": "^0.0.31", "@types/sinon": "^9.0.4", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index c7a580349cace..40f53d8bbee5d 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 0fbba9faa25f9..d5b79ede55998 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -65,7 +65,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index b02ea6ad17d04..fb368e9fe72c1 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -73,7 +73,7 @@ "@types/aws-lambda": "^8.10.39", "@types/fs-extra": "^8.1.0", "@types/sinon": "^9.0.4", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 01bf65407bc26..bca680bdc7e8b 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -71,7 +71,7 @@ "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/region-info": "0.0.0", "archiver": "^4.0.1", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "camelcase": "^6.0.0", "cdk-assets": "0.0.0", "colors": "^1.4.0", diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index e3b70a62290a1..01283e8eb5778 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -47,7 +47,7 @@ "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "archiver": "^4.0.1", - "aws-sdk": "^2.709.0", + "aws-sdk": "^2.710.0", "glob": "^7.1.6", "yargs": "^15.3.1" }, diff --git a/yarn.lock b/yarn.lock index 4a0c668c6b587..86c3bebad3024 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1923,11 +1923,6 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" -app-root-path@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" - integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== - append-transform@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" @@ -2046,7 +2041,7 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= -array-includes@^3.0.3, array-includes@^3.1.1: +array-includes@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== @@ -2072,7 +2067,7 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3: +array.prototype.flat@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== @@ -2156,7 +2151,7 @@ available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: dependencies: array-filter "^1.0.0" -aws-sdk-mock@^5.0.0, aws-sdk-mock@^5.1.0: +aws-sdk-mock@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/aws-sdk-mock/-/aws-sdk-mock-5.1.0.tgz#6f2c0bd670d7f378c906a8dd806f812124db71aa" integrity sha512-Wa5eCSo8HX0Snqb7FdBylaXMmfrAWoWZ+d7MFhiYsgHPvNvMEGjV945FF2qqE1U0Tolr1ALzik1fcwgaOhqUWQ== @@ -2165,40 +2160,10 @@ aws-sdk-mock@^5.0.0, aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.596.0: - version "2.685.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.685.0.tgz#ba5add21e98cc785b3c05ceb9f3fcb8ab046aa8a" - integrity sha512-mAOj7b4PuXRxIZkNdSkBWZ28lS2wYUY7O9u33nH9a7BawlttMNbxOgE/wDCPMrTLfj+RLQx0jvoIYj8BKCTRFw== - dependencies: - buffer "4.9.1" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.15.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.3.2" - xml2js "0.4.19" - -aws-sdk@^2.637.0: - version "2.681.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.681.0.tgz#09eeedb5ca49813dfc637908abe408ae114a6824" - integrity sha512-/p8CDJ7LZvB1i4WrJrb32FUbbPdiZFZSN6FI2lv7s/scKypmuv/iJ9kpx6QWSWQZ72kJ3Njk/0o7GuVlw0jHXw== - dependencies: - buffer "4.9.1" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.15.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.3.2" - xml2js "0.4.19" - -aws-sdk@^2.709.0: - version "2.709.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.709.0.tgz#33b0c0fe8b9420c65610545394be047ac2d93c8f" - integrity sha512-F3sKXsCiutj9RglVXdqb/XJ3Ko3G+pX081Nf1YjVJpLydwE2v16FGxrLqE5pqyWMDeUf5nZHnBoMuOYD8ip+Kw== +aws-sdk@^2.637.0, aws-sdk@^2.710.0: + version "2.710.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.710.0.tgz#82c086587679382f80dfad743da7f0582fcd381b" + integrity sha512-GQTPH0DzJMpvvtZ3VO+grkKVdL/nqjWsIfcVf1c3oedvEjW24wSXQEs6KWAGbpG2jbHsYKH7kZ4XXuq428LVAw== dependencies: buffer "4.9.2" events "1.1.1" @@ -2411,15 +2376,6 @@ buffer-from@1.x, buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -buffer@4.9.1: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" - integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - buffer@4.9.2: version "4.9.2" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" @@ -3726,16 +3682,6 @@ dot-prop@^4.2.0: dependencies: is-obj "^1.0.0" -dotenv-json@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dotenv-json/-/dotenv-json-1.0.0.tgz#fc7f672aafea04bed33818733b9f94662332815c" - integrity sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ== - -dotenv@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" - integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== - dotgitignore@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/dotgitignore/-/dotgitignore-2.1.0.tgz#a4b15a4e4ef3cf383598aaf1dfa4a04bcc089b7b" @@ -3918,12 +3864,7 @@ escodegen@1.x.x, escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" -eslint-config-standard@^14.1.0: - version "14.1.1" - resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" - integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== - -eslint-import-resolver-node@^0.3.2, eslint-import-resolver-node@^0.3.3: +eslint-import-resolver-node@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" integrity sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg== @@ -3950,7 +3891,7 @@ eslint-import-resolver-typescript@^2.0.0: tiny-glob "^0.2.6" tsconfig-paths "^3.9.0" -eslint-module-utils@^2.4.1, eslint-module-utils@^2.6.0: +eslint-module-utils@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== @@ -3958,32 +3899,6 @@ eslint-module-utils@^2.4.1, eslint-module-utils@^2.6.0: debug "^2.6.9" pkg-dir "^2.0.0" -eslint-plugin-es@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-2.0.0.tgz#0f5f5da5f18aa21989feebe8a73eadefb3432976" - integrity sha512-f6fceVtg27BR02EYnBhgWLFQfK6bN4Ll0nQFrBHOlCsAyxeZkn0NHns5O0YZOPrV1B3ramd6cgFwaoFLcSkwEQ== - dependencies: - eslint-utils "^1.4.2" - regexpp "^3.0.0" - -eslint-plugin-import@^2.19.1: - version "2.20.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" - integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== - dependencies: - array-includes "^3.0.3" - array.prototype.flat "^1.2.1" - contains-path "^0.1.0" - debug "^2.6.9" - doctrine "1.5.0" - eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.1" - has "^1.0.3" - minimatch "^3.0.4" - object.values "^1.1.0" - read-pkg-up "^2.0.0" - resolve "^1.12.0" - eslint-plugin-import@^2.22.0: version "2.22.0" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" @@ -4003,28 +3918,6 @@ eslint-plugin-import@^2.22.0: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-node@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz#fd1adbc7a300cf7eb6ac55cf4b0b6fc6e577f5a6" - integrity sha512-1CSyM/QCjs6PXaT18+zuAXsjXGIGo5Rw630rSKwokSs2jrYURQc4R5JZpoanNCqwNmepg+0eZ9L7YiRUJb8jiQ== - dependencies: - eslint-plugin-es "^2.0.0" - eslint-utils "^1.4.2" - ignore "^5.1.1" - minimatch "^3.0.4" - resolve "^1.10.1" - semver "^6.1.0" - -eslint-plugin-promise@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" - integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== - -eslint-plugin-standard@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4" - integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ== - eslint-scope@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" @@ -4033,7 +3926,7 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.2, eslint-utils@^1.4.3: +eslint-utils@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== @@ -5101,11 +4994,6 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.6.tgz#643194ad4bf2712f37852e386b6998eff0db2106" - integrity sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA== - immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -6068,7 +5956,7 @@ jest-worker@^25.5.0: merge-stream "^2.0.0" supports-color "^7.0.0" -jest@^25.4.0, jest@^25.5.0, jest@^25.5.2, jest@^25.5.3, jest@^25.5.4: +jest@^25.4.0, jest@^25.5.2, jest@^25.5.3, jest@^25.5.4: version "25.5.4" resolved "https://registry.yarnpkg.com/jest/-/jest-25.5.4.tgz#f21107b6489cfe32b076ce2adcadee3587acb9db" integrity sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ== @@ -6363,24 +6251,6 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -lambda-leak@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lambda-leak/-/lambda-leak-2.0.0.tgz#771985d3628487f6e885afae2b54510dcfb2cd7e" - integrity sha1-dxmF02KEh/boha+uK1RRDc+yzX4= - -lambda-tester@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/lambda-tester/-/lambda-tester-3.6.0.tgz#ceb7d4f4f0da768487a05cff37dcd088508b5247" - integrity sha512-F2ZTGWCLyIR95o/jWK46V/WnOCFAEUG/m/V7/CLhPJ7PCM+pror1rZ6ujP3TkItSGxUfpJi0kqwidw+M/nEqWw== - dependencies: - app-root-path "^2.2.1" - dotenv "^8.0.0" - dotenv-json "^1.0.0" - lambda-leak "^2.0.0" - semver "^6.1.1" - uuid "^3.3.2" - vandium-utils "^1.1.1" - lazystream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" @@ -6986,7 +6856,7 @@ mkdirp@*, mkdirp@1.x: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -7117,17 +6987,6 @@ nise@^4.0.1: just-extend "^4.0.2" path-to-regexp "^1.7.0" -nock@^11.7.0: - version "11.9.1" - resolved "https://registry.yarnpkg.com/nock/-/nock-11.9.1.tgz#2b026c5beb6d0dbcb41e7e4cefa671bc36db9c61" - integrity sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA== - dependencies: - debug "^4.1.0" - json-stringify-safe "^5.0.1" - lodash "^4.17.13" - mkdirp "^0.5.0" - propagate "^2.0.0" - nock@^13.0.2: version "13.0.2" resolved "https://registry.yarnpkg.com/nock/-/nock-13.0.2.tgz#3e50f88348edbb90cce1bbbf0a3ea6a068993983" @@ -7466,7 +7325,7 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.1.0, object.values@^1.1.1: +object.values@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== @@ -8498,7 +8357,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.1.6, resolve@^1.10.1, resolve@^1.17.0: +resolve@^1.1.6, resolve@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -8656,11 +8515,6 @@ semver-intersect@^1.4.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@6.x, semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - semver@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667" @@ -8671,6 +8525,11 @@ semver@7.x, semver@^7.2.2, semver@^7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -9644,22 +9503,6 @@ trivial-deferred@^1.0.1: resolved "https://registry.yarnpkg.com/trivial-deferred/-/trivial-deferred-1.0.1.tgz#376d4d29d951d6368a6f7a0ae85c2f4d5e0658f3" integrity sha1-N21NKdlR1jaKb3oK6FwvTV4GWPM= -ts-jest@^25.3.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.5.1.tgz#2913afd08f28385d54f2f4e828be4d261f4337c7" - integrity sha512-kHEUlZMK8fn8vkxDjwbHlxXRB9dHYpyzqKIGDNxbzs+Rz+ssNDSDNusEK8Fk/sDd4xE6iKoQLfFkFVaskmTJyw== - dependencies: - bs-logger "0.x" - buffer-from "1.x" - fast-json-stable-stringify "2.x" - json5 "2.x" - lodash.memoize "4.x" - make-error "1.x" - micromatch "4.x" - mkdirp "0.x" - semver "6.x" - yargs-parser "18.x" - ts-jest@^26.1.1: version "26.1.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.1.1.tgz#b98569b8a4d4025d966b3d40c81986dd1c510f8d" @@ -10020,11 +9863,6 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -vandium-utils@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vandium-utils/-/vandium-utils-1.2.0.tgz#44735de4b7641a05de59ebe945f174e582db4f59" - integrity sha1-RHNd5LdkGgXeWevpRfF05YLbT1k= - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" From 8529387cb901fd1fea9e0ee1af1284de3ad98ce7 Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Tue, 7 Jul 2020 05:14:18 +0800 Subject: [PATCH 23/28] feat(lambda): efs filesystems (#8602) feat(lambda): Add EFS Filesystem support This PR adds `filesystemConfigs` construct property for `lambda.Function` and allows lambda functions to mount Amazon EFS Filesystems with the Amazon EFS Access Points. Close #8595 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-efs/lib/access-point.ts | 6 + .../@aws-cdk/aws-efs/lib/efs-file-system.ts | 18 +- packages/@aws-cdk/aws-lambda/README.md | 40 + .../@aws-cdk/aws-lambda/lib/filesystem.ts | 85 ++ packages/@aws-cdk/aws-lambda/lib/function.ts | 44 +- packages/@aws-cdk/aws-lambda/lib/index.ts | 1 + packages/@aws-cdk/aws-lambda/package.json | 2 + .../integ.lambda.filesystem.expected.json | 808 ++++++++++++++++++ .../test/integ.lambda.filesystem.ts | 68 ++ .../@aws-cdk/aws-lambda/test/test.function.ts | 59 +- 10 files changed, 1126 insertions(+), 5 deletions(-) create mode 100644 packages/@aws-cdk/aws-lambda/lib/filesystem.ts create mode 100644 packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.expected.json create mode 100644 packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.ts diff --git a/packages/@aws-cdk/aws-efs/lib/access-point.ts b/packages/@aws-cdk/aws-efs/lib/access-point.ts index 6fd4b6e927bce..04399991cddb6 100644 --- a/packages/@aws-cdk/aws-efs/lib/access-point.ts +++ b/packages/@aws-cdk/aws-efs/lib/access-point.ts @@ -139,6 +139,11 @@ export class AccessPoint extends Resource implements IAccessPoint { */ public readonly accessPointId: string; + /** + * The filesystem of the access point + */ + public readonly fileSystem: IFileSystem; + constructor(scope: Construct, id: string, props: AccessPointProps) { super(scope, id); @@ -165,5 +170,6 @@ export class AccessPoint extends Resource implements IAccessPoint { resource: 'access-point', resourceName: this.accessPointId, }); + this.fileSystem = props.fileSystem; } } diff --git a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts index 35c6ef381a8d5..942be37ca9a3f 100644 --- a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts +++ b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; -import { Construct, IResource, RemovalPolicy, Resource, Size, Tag } from '@aws-cdk/core'; +import { ConcreteDependable, Construct, IDependable, IResource, RemovalPolicy, Resource, Size, Tag } from '@aws-cdk/core'; import { AccessPoint, AccessPointOptions } from './access-point'; import { CfnFileSystem, CfnMountTarget } from './efs.generated'; @@ -83,6 +83,12 @@ export interface IFileSystem extends ec2.IConnectable, IResource { * @attribute */ readonly fileSystemId: string; + + /** + * Dependable that can be depended upon to ensure the mount targets of the filesystem are ready + */ + readonly mountTargetsAvailable: IDependable; + } /** @@ -205,6 +211,7 @@ export class FileSystem extends Resource implements IFileSystem { securityGroups: [attrs.securityGroup], defaultPort: ec2.Port.tcp(FileSystem.DEFAULT_PORT), }); + public readonly mountTargetsAvailable = new ConcreteDependable(); } return new Import(scope, id); @@ -225,6 +232,10 @@ export class FileSystem extends Resource implements IFileSystem { */ public readonly fileSystemId: string; + public readonly mountTargetsAvailable: IDependable; + + private readonly _mountTargetsAvailable = new ConcreteDependable(); + /** * Constructor for creating a new EFS FileSystem. */ @@ -263,15 +274,18 @@ export class FileSystem extends Resource implements IFileSystem { // We now have to create the mount target for each of the mentioned subnet let mountTargetCount = 0; + this.mountTargetsAvailable = []; subnets.subnetIds.forEach((subnetId: string) => { - new CfnMountTarget(this, + const mountTarget = new CfnMountTarget(this, 'EfsMountTarget' + (++mountTargetCount), { fileSystemId: this.fileSystemId, securityGroups: Array.of(securityGroup.securityGroupId), subnetId, }); + this._mountTargetsAvailable.add(mountTarget); }); + this.mountTargetsAvailable = this._mountTargetsAvailable; } /** diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index fb7283b2643d8..55ada8d6da307 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -277,6 +277,46 @@ correct log retention period (never expire, by default). *Further note* that, if the log group already exists and the `logRetention` is not set, the custom resource will reset the log retention to never expire even if it was configured with a different value. +### FileSystem Access + +You can configure a function to mount an Amazon Elastic File System (Amazon EFS) to a +directory in your runtime environment with the `filesystem` property. To access Amaozn EFS +from lambda function, the Amazon EFS access point will be required. + +The following sample allows the lambda function to mount the Amazon EFS access point to `/mnt/msg` in the runtime environment and access the filesystem with the POSIX identity defined in `posixUser`. + +```ts +// create a new Amaozn EFS filesystem +const fileSystem = new efs.FileSystem(stack, 'Efs', { vpc }); + +// create a new access point from the filesystem +const accessPoint = fileSystem.addAccessPoint('AccessPoint', { + // set /export/lambda as the root of the access point + path: '/export/lambda', + // as /export/lambda does not exist in a new efs filesystem, the efs will create the directory with the following createAcl + createAcl: { + ownerUid: '1001', + ownerGid: '1001', + permissions: '750', + }, + // enforce the POSIX identity so lambda function will access with this identity + posixUser: { + uid: '1001', + gid: '1001', + }, +}); + +const fn = new lambda.Function(stack, 'MyLambda', { + code, + handler, + runtime, + vpc, + // mount the access point to /mnt/msg in the lambda runtime enironment + filesystem: lambda.FileSystem.fromEfsAccessPoint(accessPoint, '/mnt/msg'), +}); +``` + + ### Singleton Function The `SingletonFunction` construct is a way to guarantee that a lambda function will be guaranteed to be part of the stack, diff --git a/packages/@aws-cdk/aws-lambda/lib/filesystem.ts b/packages/@aws-cdk/aws-lambda/lib/filesystem.ts new file mode 100644 index 0000000000000..108c7ea4116f7 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/lib/filesystem.ts @@ -0,0 +1,85 @@ +import { Connections } from '@aws-cdk/aws-ec2'; +import * as efs from '@aws-cdk/aws-efs'; +import * as iam from '@aws-cdk/aws-iam'; +import { IDependable, Stack } from '@aws-cdk/core'; + +/** + * FileSystem configurations for the Lambda function + * @experimental + */ +export interface FileSystemConfig { + /** + * mount path in the lambda runtime environment + */ + readonly localMountPath: string; + + /** + * ARN of the access point + */ + readonly arn: string; + + /** + * array of IDependable that lambda function depends on + * + * @default - no dependency + */ + readonly dependency?: IDependable[] + + /** + * connections object used to allow ingress traffic from lambda function + * + * @default - no connections required to add extra ingress rules for Lambda function + */ + readonly connections?: Connections; + + /** + * additional IAM policies required for the lambda function + * + * @default - no additional policies required + */ + readonly policies?: iam.PolicyStatement[]; +} + +/** + * Represents the filesystem for the Lambda function + * @experimental + */ +export class FileSystem { + /** + * mount the filesystem from Amazon EFS + * @param ap the Amazon EFS access point + * @param mountPath the target path in the lambda runtime environment + */ + public static fromEfsAccessPoint(ap: efs.AccessPoint, mountPath: string): FileSystem { + return new FileSystem({ + localMountPath: mountPath, + arn: ap.accessPointArn, + dependency: [ ap.fileSystem.mountTargetsAvailable ], + connections: ap.fileSystem.connections, + policies: [ + new iam.PolicyStatement({ + actions: [ 'elasticfilesystem:ClientMount' ], + resources: [ '*' ], + conditions: { + StringEquals: { + 'elasticfilesystem:AccessPointArn': ap.accessPointArn, + }, + }, + }), + new iam.PolicyStatement({ + actions: ['elasticfilesystem:ClientWrite'], + resources: [ Stack.of(ap).formatArn({ + service: 'elasticfilesystem', + resource: 'file-system', + resourceName: ap.fileSystem.fileSystemId, + }) ], + }), + ], + }); + } + + /** + * @param config the FileSystem configurations for the Lambda function + */ + protected constructor(public readonly config: FileSystemConfig) { } +} diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index d99d1b1ce8377..dbd77a2a9d793 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -7,6 +7,7 @@ import { CfnResource, Construct, Duration, Fn, Lazy, Stack } from '@aws-cdk/core import { Code, CodeConfig } from './code'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { IEventSource } from './event-source'; +import { FileSystem } from './filesystem'; import { FunctionAttributes, FunctionBase, IFunction } from './function-base'; import { calculateFunctionHash, trimFromStart } from './function-hash'; import { Version, VersionOptions } from './lambda-version'; @@ -274,6 +275,13 @@ export interface FunctionProps extends FunctionOptions { * the handler. */ readonly handler: string; + + /** + * The filesystem configuration for the lambda function + * + * @default - will not mount any filesystem + */ + readonly filesystem?: FileSystem; } /** @@ -495,6 +503,16 @@ export class Function extends FunctionBase { }); this.grantPrincipal = this.role; + // add additonal managed policies when necessary + if (props.filesystem) { + const config = props.filesystem.config; + if (config.policies) { + config.policies.forEach(p => { + this.role?.addToPolicy(p); + }); + } + } + for (const statement of (props.initialPolicy || [])) { this.role.addToPolicy(statement); } @@ -570,6 +588,22 @@ export class Function extends FunctionBase { } this.currentVersionOptions = props.currentVersionOptions; + + if (props.filesystem) { + const config = props.filesystem.config; + if (config.dependency) { + this.node.addDependency(...config.dependency); + } + + resource.addPropertyOverride('FileSystemConfigs', + [ + { + LocalMountPath: config.localMountPath, + Arn: config.arn, + }, + ], + ); + } } /** @@ -701,7 +735,7 @@ export class Function extends FunctionBase { // sort environment so the hash of the function used to create // `currentVersion` is not affected by key order (this is how lambda does // it). - const variables: { [key: string]: string } = { }; + const variables: { [key: string]: string } = {}; for (const key of Object.keys(this.environment).sort()) { variables[key] = this.environment[key]; } @@ -745,6 +779,12 @@ export class Function extends FunctionBase { this._connections = new ec2.Connections({ securityGroups }); + if (props.filesystem) { + if (props.filesystem.config.connections) { + props.filesystem.config.connections.allowDefaultPortFrom(this); + } + } + // Pick subnets, make sure they're not Public. Routing through an IGW // won't work because the ENIs don't get a Public IP. // Why are we not simply forcing vpcSubnets? Because you might still be choosing @@ -841,4 +881,4 @@ export function verifyCodeConfig(code: CodeConfig, runtime: Runtime) { if (code.inlineCode && !runtime.supportsInlineCode) { throw new Error(`Inline source not allowed for ${runtime.name}`); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda/lib/index.ts b/packages/@aws-cdk/aws-lambda/lib/index.ts index b494e924c604a..b1d676e234a9b 100644 --- a/packages/@aws-cdk/aws-lambda/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda/lib/index.ts @@ -6,6 +6,7 @@ export * from './layers'; export * from './permission'; export * from './runtime'; export * from './code'; +export * from './filesystem'; export * from './lambda-version'; export * from './singleton-lambda'; export * from './event-source'; diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 4748593961596..115b656e62759 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -85,6 +85,7 @@ "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-efs": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", @@ -99,6 +100,7 @@ "peerDependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-efs": "0.0.0", "@aws-cdk/aws-events": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.expected.json new file mode 100644 index 0000000000000..ff7a04f8b7fd5 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.expected.json @@ -0,0 +1,808 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet3SubnetBE12F0B6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTable93458DBB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTableAssociation1F1EDF02": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + } + } + }, + "VpcPublicSubnet3DefaultRoute4697774F": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet3SubnetF258B56E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableD98824C7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableAssociation16BDDC43": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + } + }, + "VpcPrivateSubnet3DefaultRoute94B74F0D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "Efs9E8BF36B": { + "Type": "AWS::EFS::FileSystem", + "Properties": { + "FileSystemTags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Efs" + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "EfsEfsSecurityGroup6F40EA3B": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-lambda-1/Efs/EfsSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-lambda-1/Efs" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "EfsEfsSecurityGroupfromawscdklambda1MyLambdaSecurityGroup86B085EE20490D9864A8": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from awscdklambda1MyLambdaSecurityGroup86B085EE:2049", + "FromPort": 2049, + "GroupId": { + "Fn::GetAtt": [ + "EfsEfsSecurityGroup6F40EA3B", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "MyLambdaSecurityGroup1E71A818", + "GroupId" + ] + }, + "ToPort": 2049 + } + }, + "EfsEfsMountTarget195B2DD2E": { + "Type": "AWS::EFS::MountTarget", + "Properties": { + "FileSystemId": { + "Ref": "Efs9E8BF36B" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "EfsEfsSecurityGroup6F40EA3B", + "GroupId" + ] + } + ], + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "EfsEfsMountTarget2315C927F": { + "Type": "AWS::EFS::MountTarget", + "Properties": { + "FileSystemId": { + "Ref": "Efs9E8BF36B" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "EfsEfsSecurityGroup6F40EA3B", + "GroupId" + ] + } + ], + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "EfsEfsMountTarget36646B9A0": { + "Type": "AWS::EFS::MountTarget", + "Properties": { + "FileSystemId": { + "Ref": "Efs9E8BF36B" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "EfsEfsSecurityGroup6F40EA3B", + "GroupId" + ] + } + ], + "SubnetId": { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + } + }, + "EfsAccessPointE419FED9": { + "Type": "AWS::EFS::AccessPoint", + "Properties": { + "FileSystemId": { + "Ref": "Efs9E8BF36B" + }, + "PosixUser": { + "Gid": "1001", + "Uid": "1001" + }, + "RootDirectory": { + "CreationInfo": { + "OwnerGid": "1001", + "OwnerUid": "1001", + "Permissions": "750" + }, + "Path": "/export/lambda" + } + } + }, + "MyLambdaServiceRole4539ECB6": { + "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" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" + ] + ] + } + ] + }, + "DependsOn": [ + "EfsEfsMountTarget195B2DD2E", + "EfsEfsMountTarget2315C927F", + "EfsEfsMountTarget36646B9A0" + ] + }, + "MyLambdaServiceRoleDefaultPolicy5BBC6F68": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "elasticfilesystem:ClientMount", + "Condition": { + "StringEquals": { + "elasticfilesystem:AccessPointArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":elasticfilesystem:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":access-point/", + { + "Ref": "EfsAccessPointE419FED9" + } + ] + ] + } + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "elasticfilesystem:ClientWrite", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":elasticfilesystem:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":file-system/", + { + "Ref": "Efs9E8BF36B" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyLambdaServiceRoleDefaultPolicy5BBC6F68", + "Roles": [ + { + "Ref": "MyLambdaServiceRole4539ECB6" + } + ] + }, + "DependsOn": [ + "EfsEfsMountTarget195B2DD2E", + "EfsEfsMountTarget2315C927F", + "EfsEfsMountTarget36646B9A0" + ] + }, + "MyLambdaSecurityGroup1E71A818": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatic security group for Lambda Function awscdklambda1MyLambda82056696", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "DependsOn": [ + "EfsEfsMountTarget195B2DD2E", + "EfsEfsMountTarget2315C927F", + "EfsEfsMountTarget36646B9A0" + ] + }, + "MyLambdaCCE802FB": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "\nimport json\nimport os\nimport string\nimport random\nimport datetime\n\nMSG_FILE_PATH = '/mnt/msg/content'\n\ndef randomString(stringLength=10):\n letters = string.ascii_lowercase\n return ''.join(random.choice(letters) for i in range(stringLength))\n\ndef lambda_handler(event, context):\n with open(MSG_FILE_PATH, 'a') as f:\n f.write(f\"{datetime.datetime.utcnow():%Y-%m-%d-%H:%M:%S} \" + randomString(5) + ' ')\n\n file = open(MSG_FILE_PATH, \"r\")\n file_content = file.read()\n file.close()\n\n return {\n 'statusCode': 200,\n 'body': str(file_content)\n }\n " + }, + "Handler": "index.lambda_handler", + "Role": { + "Fn::GetAtt": [ + "MyLambdaServiceRole4539ECB6", + "Arn" + ] + }, + "Runtime": "python3.7", + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "MyLambdaSecurityGroup1E71A818", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ] + }, + "FileSystemConfigs": [ + { + "LocalMountPath": "/mnt/msg", + "Arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":elasticfilesystem:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":access-point/", + { + "Ref": "EfsAccessPointE419FED9" + } + ] + ] + } + } + ] + }, + "DependsOn": [ + "EfsEfsMountTarget195B2DD2E", + "EfsEfsMountTarget2315C927F", + "EfsEfsMountTarget36646B9A0", + "MyLambdaServiceRoleDefaultPolicy5BBC6F68", + "MyLambdaServiceRole4539ECB6" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.ts new file mode 100644 index 0000000000000..da6515233e770 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.filesystem.ts @@ -0,0 +1,68 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as efs from '@aws-cdk/aws-efs'; +import * as cdk from '@aws-cdk/core'; +import * as lambda from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-lambda-1'); + +const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 3, + natGateways: 1, +}); + +const fileSystem = new efs.FileSystem(stack, 'Efs', { + vpc, + removalPolicy: cdk.RemovalPolicy.DESTROY, +}); + +// create an access point and expose the root of the filesystem +const accessPoint = fileSystem.addAccessPoint('AccessPoint', { + createAcl: { + ownerGid: '1001', + ownerUid: '1001', + permissions: '750', + }, + path: '/export/lambda', + posixUser: { + gid: '1001', + uid: '1001', + }, +}); + +// this function will mount the access point to '/mnt/msg' and write content onto /mnt/msg/content +new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode(` +import json +import os +import string +import random +import datetime + +MSG_FILE_PATH = '/mnt/msg/content' + +def randomString(stringLength=10): + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(stringLength)) + +def lambda_handler(event, context): + with open(MSG_FILE_PATH, 'a') as f: + f.write(f"{datetime.datetime.utcnow():%Y-%m-%d-%H:%M:%S} " + randomString(5) + ' ') + + file = open(MSG_FILE_PATH, "r") + file_content = file.read() + file.close() + + return { + 'statusCode': 200, + 'body': str(file_content) + } + `), + handler: 'index.lambda_handler', + runtime: lambda.Runtime.PYTHON_3_7, + vpc, + filesystem: lambda.FileSystem.fromEfsAccessPoint(accessPoint, '/mnt/msg'), +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-lambda/test/test.function.ts b/packages/@aws-cdk/aws-lambda/test/test.function.ts index ccdc4fd8ec250..ee4a003b7ad5b 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.function.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.function.ts @@ -1,4 +1,6 @@ -import { expect, haveOutput } from '@aws-cdk/assert'; +import { expect, haveOutput, haveResource } from '@aws-cdk/assert'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as efs from '@aws-cdk/aws-efs'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as sqs from '@aws-cdk/aws-sqs'; @@ -231,4 +233,59 @@ export = testCase({ }, }, + 'filesystem': { + + 'mount efs filesystem'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 3, + natGateways: 1, + }); + + const fs = new efs.FileSystem(stack, 'Efs', { + vpc, + }); + const accessPoint = fs.addAccessPoint('AccessPoint'); + // WHEN + new lambda.Function(stack, 'MyFunction', { + handler: 'foo', + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset(path.join(__dirname, 'handler.zip')), + filesystem: lambda.FileSystem.fromEfsAccessPoint(accessPoint, '/mnt/msg'), + }); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::Function', { + FileSystemConfigs: [ + { + Arn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':elasticfilesystem:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':access-point/', + { + Ref: 'EfsAccessPointE419FED9', + }, + ], + ], + }, + LocalMountPath: '/mnt/msg', + }], + })); + test.done(); + }, + }, }); From 2961ef9afc8a5d1e399d9ec2df7fd58d8c896ff3 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 6 Jul 2020 16:20:58 -0700 Subject: [PATCH 24/28] docs(route53): hostedZone import functions pre-requisites and usage causing confusion (#8830) Wrote up some documentation stuff but happy to change it to be more clear on the differences between `HostedZone.fromLookup`, `HostedZone.fromHostedZoneAttributes`, and `HostedZone.fromHostedZoneId`. Fixes #5547 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-route53/README.md | 34 +++++++++++++++---- .../@aws-cdk/aws-route53/lib/hosted-zone.ts | 13 +++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-route53/README.md b/packages/@aws-cdk/aws-route53/README.md index a3eec5f9f4977..cb88a5a8f16d8 100644 --- a/packages/@aws-cdk/aws-route53/README.md +++ b/packages/@aws-cdk/aws-route53/README.md @@ -83,7 +83,29 @@ Constructs are available for A, AAAA, CAA, CNAME, MX, NS, SRV and TXT records. Use the `CaaAmazonRecord` construct to easily restrict certificate authorities allowed to issue certificates for a domain to Amazon only. -### Adding records to existing hosted zones +### Imports + +If you don't know the ID of the Hosted Zone to import, you can use the +`HostedZone.fromLookup`: + +```ts +HostedZone.fromLookup(this, 'MyZone', { + domainName: 'example.com' +}); +``` + +`HostedZone.fromLookup` requires an environment to be configured. Check +out the [documentation](https://docs.aws.amazon.com/cdk/latest/guide/environments.html) for more documentation and examples. CDK +automatically looks into your `~/.aws/config` file for the `[default]` profile. +If you want to specify a different account run `cdk deploy --profile [profile]`. + +```ts +new MyDevStack(app, 'dev', { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION +}}); +``` If you know the ID and Name of a Hosted Zone, you can import it directly: @@ -94,11 +116,11 @@ const zone = HostedZone.fromHostedZoneAttributes(this, 'MyZone', { }); ``` -If you don't know the ID of a Hosted Zone, you can use the `HostedZone.fromLookup` -to discover and import it: +Alternatively, use the `HostedZone.fromHostedZoneId` to import hosted zones if +you know the ID and the retrieval for the `zoneName` is undesirable. ```ts -HostedZone.fromLookup(this, 'MyZone', { - domainName: 'example.com' +const zone = HostedZone.fromHostedZoneId(this, 'MyZone', { + hostedZoneId: 'ZOJJZC49E0EPZ', }); -``` +``` \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts index f7cb345f8cc73..17dc716ff0f7b 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts @@ -59,6 +59,8 @@ export class HostedZone extends Resource implements IHostedZone { /** * Import a Route 53 hosted zone defined either outside the CDK, or in a different CDK stack * + * Use when hosted zone ID is known. Hosted zone name becomes unavailable through this query. + * * @param scope the parent Construct for this Construct * @param id the logical name of this Construct * @param hostedZoneId the ID of the hosted zone to import @@ -79,6 +81,12 @@ export class HostedZone extends Resource implements IHostedZone { /** * Imports a hosted zone from another stack. + * + * Use when both hosted zone ID and hosted zone name are known. + * + * @param scope the parent Construct for this Construct + * @param id the logical name of this Construct + * @param attrs the HostedZoneAttributes (hosted zone ID and hosted zone name) */ public static fromHostedZoneAttributes(scope: Construct, id: string, attrs: HostedZoneAttributes): IHostedZone { class Import extends Resource implements IHostedZone { @@ -94,6 +102,11 @@ export class HostedZone extends Resource implements IHostedZone { /** * Lookup a hosted zone in the current account/region based on query parameters. + * Requires environment, you must specify env for the stack. + * + * Use to easily query hosted zones. + * + * @see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ public static fromLookup(scope: Construct, id: string, query: HostedZoneProviderProps): IHostedZone { const DEFAULT_HOSTED_ZONE: HostedZoneContextResponse = { From 38d84149bae213d0e285d5192265043a8c0de1aa Mon Sep 17 00:00:00 2001 From: Neta Nir Date: Mon, 6 Jul 2020 23:15:11 -0700 Subject: [PATCH 25/28] feat(autoscaling): allow setting autoscaling group name (#8853) * feat(autoscaling): allow setting autoscaling group name Co-authored-by: Neta Nir --- packages/@aws-cdk/aws-autoscaling/.gitignore | 2 ++ packages/@aws-cdk/aws-autoscaling/.npmignore | 3 +- .../@aws-cdk/aws-autoscaling/jest.config.js | 2 ++ .../aws-autoscaling/lib/auto-scaling-group.ts | 15 ++++++++-- .../@aws-cdk/aws-autoscaling/package.json | 6 ++-- ...ng-group.ts => auto-scaling-group.test.ts} | 29 +++++++++++++++---- .../aws-autoscaling/test/cron.test.ts | 9 ++++++ ...fecyclehooks.ts => lifecyclehooks.test.ts} | 6 ++-- .../test/{test.scaling.ts => scaling.test.ts} | 6 ++-- ...led-action.ts => scheduled-action.test.ts} | 6 ++-- .../aws-autoscaling/test/test.cron.ts | 14 --------- 11 files changed, 64 insertions(+), 34 deletions(-) create mode 100644 packages/@aws-cdk/aws-autoscaling/jest.config.js rename packages/@aws-cdk/aws-autoscaling/test/{test.auto-scaling-group.ts => auto-scaling-group.test.ts} (97%) create mode 100644 packages/@aws-cdk/aws-autoscaling/test/cron.test.ts rename packages/@aws-cdk/aws-autoscaling/test/{test.lifecyclehooks.ts => lifecyclehooks.test.ts} (97%) rename packages/@aws-cdk/aws-autoscaling/test/{test.scaling.ts => scaling.test.ts} (98%) rename packages/@aws-cdk/aws-autoscaling/test/{test.scheduled-action.ts => scheduled-action.test.ts} (97%) delete mode 100644 packages/@aws-cdk/aws-autoscaling/test/test.cron.ts diff --git a/packages/@aws-cdk/aws-autoscaling/.gitignore b/packages/@aws-cdk/aws-autoscaling/.gitignore index d7a42f6f3de38..bc3bea4d08402 100644 --- a/packages/@aws-cdk/aws-autoscaling/.gitignore +++ b/packages/@aws-cdk/aws-autoscaling/.gitignore @@ -14,3 +14,5 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js + +!jest.config.js \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/.npmignore b/packages/@aws-cdk/aws-autoscaling/.npmignore index fe4df9a06d9a9..fba0982f5ee45 100644 --- a/packages/@aws-cdk/aws-autoscaling/.npmignore +++ b/packages/@aws-cdk/aws-autoscaling/.npmignore @@ -21,4 +21,5 @@ tsconfig.json .eslintrc.js # exclude cdk artifacts -**/cdk.out \ No newline at end of file +**/cdk.out +jest.config.js \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/jest.config.js b/packages/@aws-cdk/aws-autoscaling/jest.config.js new file mode 100644 index 0000000000000..cd664e1d069e5 --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index 4007b60c7ab3a..04c63308cc2aa 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -234,6 +234,14 @@ export interface CommonAutoScalingGroupProps { * @default - Monitoring.DETAILED */ readonly instanceMonitoring?: Monitoring; + + /** + * The name of the Auto Scaling group. This name must be unique per Region per account. + * + * @default - Auto generated by CloudFormation + */ + readonly autoScalingGroupName?: string; + } /** @@ -480,7 +488,9 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements private readonly notifications: NotificationConfiguration[] = []; constructor(scope: Construct, id: string, props: AutoScalingGroupProps) { - super(scope, id); + super(scope, id, { + physicalName: props.autoScalingGroupName, + }); this.securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'InstanceSecurityGroup', { vpc: props.vpc, @@ -576,6 +586,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements const { subnetIds, hasPublic } = props.vpc.selectSubnets(props.vpcSubnets); const asgProps: CfnAutoScalingGroupProps = { + autoScalingGroupName: this.physicalName, cooldown: props.cooldown !== undefined ? props.cooldown.toSeconds().toString() : undefined, minSize: Tokenization.stringifyNumber(minCapacity), maxSize: Tokenization.stringifyNumber(maxCapacity), @@ -596,7 +607,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements this.autoScalingGroup = new CfnAutoScalingGroup(this, 'ASG', asgProps); this.osType = imageConfig.osType; - this.autoScalingGroupName = this.autoScalingGroup.ref; + this.autoScalingGroupName = this.getResourceNameAttribute(this.autoScalingGroup.ref), this.autoScalingGroupArn = Stack.of(this).formatArn({ service: 'autoscaling', resource: 'autoScalingGroup:*:autoScalingGroupName', diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index a7090a8e2284f..42362006391c9 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/package.json @@ -47,7 +47,8 @@ "compat": "cdk-compat" }, "cdk-build": { - "cloudformation": "AWS::AutoScaling" + "cloudformation": "AWS::AutoScaling", + "jest": true }, "keywords": [ "aws", @@ -64,11 +65,10 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/cx-api": "0.0.0", - "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "nodeunit": "^0.11.3", + "nodeunit-shim": "0.0.0", "pkglint": "0.0.0", "@aws-cdk/cloud-assembly-schema": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts similarity index 97% rename from packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts rename to packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts index 05ac3029b6e4c..2b0b906f4b892 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts @@ -5,12 +5,12 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import * as autoscaling from '../lib'; // tslint:disable:object-literal-key-quotes -export = { +nodeunitShim({ 'default fleet'(test: Test) { const stack = getTestStack(); const vpc = mockVpc(stack); @@ -695,7 +695,7 @@ export = { }); // THEN - test.same(asg.role, importedRole); + test.equal(asg.role, importedRole); expect(stack).to(haveResource('AWS::IAM::InstanceProfile', { 'Roles': ['HelloDude'], })); @@ -1105,7 +1105,7 @@ export = { topic, }], }); - }, 'Can not set notificationsTopic and notifications, notificationsTopic is deprected use notifications instead'); + }, 'Cannot set \'notificationsTopic\' and \'notifications\', \'notificationsTopic\' is deprecated use \'notifications\' instead'); test.done(); }, @@ -1226,7 +1226,7 @@ export = { test.deepEqual(Object.values(autoscaling.ScalingEvent).length - 1, autoscaling.ScalingEvents.ALL._types.length); test.done(); }, -}; +}); function mockVpc(stack: cdk.Stack) { return ec2.Vpc.fromVpcAttributes(stack, 'MyVpc', { @@ -1238,6 +1238,25 @@ function mockVpc(stack: cdk.Stack) { }); } +test('Can set autoScalingGroupName', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + + // WHEN + new autoscaling.AutoScalingGroup(stack, 'MyASG', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage(), + vpc, + autoScalingGroupName: 'MyAsg', + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AutoScaling::AutoScalingGroup', { + AutoScalingGroupName: 'MyAsg', + })); +}); + function mockSecurityGroup(stack: cdk.Stack) { return ec2.SecurityGroup.fromSecurityGroupId(stack, 'MySG', 'most-secure'); } diff --git a/packages/@aws-cdk/aws-autoscaling/test/cron.test.ts b/packages/@aws-cdk/aws-autoscaling/test/cron.test.ts new file mode 100644 index 0000000000000..ab47a9b60a56a --- /dev/null +++ b/packages/@aws-cdk/aws-autoscaling/test/cron.test.ts @@ -0,0 +1,9 @@ +import * as autoscaling from '../lib'; + +test('test utc cron, hour only', () => { + expect(autoscaling.Schedule.cron({ hour: '18', minute: '0' }).expressionString).toEqual('0 18 * * *'); +}); + +test('test utc cron, hour and minute', () => { + expect(autoscaling.Schedule.cron({ hour: '18', minute: '24' }).expressionString).toEqual('24 18 * * *'); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.lifecyclehooks.ts b/packages/@aws-cdk/aws-autoscaling/test/lifecyclehooks.test.ts similarity index 97% rename from packages/@aws-cdk/aws-autoscaling/test/test.lifecyclehooks.ts rename to packages/@aws-cdk/aws-autoscaling/test/lifecyclehooks.test.ts index 5b002ad102c2b..afea5c3fb1b44 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.lifecyclehooks.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/lifecyclehooks.test.ts @@ -2,10 +2,10 @@ import { expect, haveResource, ResourcePart } 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 { nodeunitShim, Test } from 'nodeunit-shim'; import * as autoscaling from '../lib'; -export = { +nodeunitShim({ 'we can add a lifecycle hook to an ASG'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -68,7 +68,7 @@ export = { test.done(); }, -}; +}); class FakeNotificationTarget implements autoscaling.ILifecycleHookTarget { public bind(_scope: cdk.Construct, lifecycleHook: autoscaling.ILifecycleHook): autoscaling.LifecycleHookTargetConfig { diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.scaling.ts b/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts similarity index 98% rename from packages/@aws-cdk/aws-autoscaling/test/test.scaling.ts rename to packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts index 6168e215cfd43..a7386aa7afdb5 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.scaling.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/scaling.test.ts @@ -3,10 +3,10 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import * as autoscaling from '../lib'; -export = { +nodeunitShim({ 'target tracking policies': { 'cpu utilization'(test: Test) { // GIVEN @@ -221,7 +221,7 @@ export = { test.done(); }, -}; +}); class ASGFixture extends cdk.Construct { public readonly vpc: ec2.Vpc; diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.scheduled-action.ts b/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts similarity index 97% rename from packages/@aws-cdk/aws-autoscaling/test/test.scheduled-action.ts rename to packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts index 05dd627b6302b..73684d88a5320 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.scheduled-action.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/scheduled-action.test.ts @@ -1,10 +1,10 @@ import { expect, haveResource, MatchStyle } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import * as autoscaling from '../lib'; -export = { +nodeunitShim({ 'can schedule an action'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -105,7 +105,7 @@ export = { test.done(); }, -}; +}); function makeAutoScalingGroup(scope: cdk.Construct) { const vpc = new ec2.Vpc(scope, 'VPC'); diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.cron.ts b/packages/@aws-cdk/aws-autoscaling/test/test.cron.ts deleted file mode 100644 index cfba6b3f8977f..0000000000000 --- a/packages/@aws-cdk/aws-autoscaling/test/test.cron.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Test } from 'nodeunit'; -import * as autoscaling from '../lib'; - -export = { - 'test utc cron, hour only'(test: Test) { - test.equals(autoscaling.Schedule.cron({ hour: '18', minute: '0' }).expressionString, '0 18 * * *'); - test.done(); - }, - - 'test utc cron, hour and minute'(test: Test) { - test.equals(autoscaling.Schedule.cron({ hour: '18', minute: '24' }).expressionString, '24 18 * * *'); - test.done(); - }, -}; From 4221874c415f748c4674d5b9ea06a50e773a8030 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2020 06:39:58 +0000 Subject: [PATCH 26/28] chore(deps): bump @typescript-eslint/eslint-plugin from 3.5.0 to 3.6.0 (#8917) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 3.5.0 to 3.6.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v3.6.0/packages/eslint-plugin) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- tools/cdk-build-tools/package.json | 2 +- yarn.lock | 50 +++++++++++++++--------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 68e05b186710f..6f57ca81f4a64 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -39,7 +39,7 @@ "pkglint": "0.0.0" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^3.5.0", + "@typescript-eslint/eslint-plugin": "^3.6.0", "@typescript-eslint/parser": "^2.19.2", "awslint": "0.0.0", "colors": "^1.4.0", diff --git a/yarn.lock b/yarn.lock index 86c3bebad3024..7ec153df3ffd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1669,12 +1669,12 @@ resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.3.tgz#38fb31d82ed07dea87df6bd565721d11979fd761" integrity sha512-mhdQq10tYpiNncMkg1vovCud5jQm+rWeRVz6fxjCJlY6uhDlAn9GnMSmBa2DQwqPf/jS5YR0K/xChDEh1jdOQg== -"@typescript-eslint/eslint-plugin@^3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.5.0.tgz#e7736e0808b5fb947a5f9dd949ae6736a7226b84" - integrity sha512-m4erZ8AkSjoIUOf8s4k2V1xdL2c1Vy0D3dN6/jC9d7+nEqjY3gxXCkgi3gW/GAxPaA4hV8biaCoTVdQmfAeTCQ== +"@typescript-eslint/eslint-plugin@^3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.6.0.tgz#ba2b6cae478b8fca3f2e58ff1313e4198eea2d8a" + integrity sha512-ubHlHVt1lsPQB/CZdEov9XuOFhNG9YRC//kuiS1cMQI6Bs1SsqKrEmZnpgRwthGR09/kEDtr9MywlqXyyYd8GA== dependencies: - "@typescript-eslint/experimental-utils" "3.5.0" + "@typescript-eslint/experimental-utils" "3.6.0" debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" @@ -1691,14 +1691,14 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/experimental-utils@3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.5.0.tgz#d09f9ffb890d1b15a7ffa9975fae92eee05597c4" - integrity sha512-zGNOrVi5Wz0jcjUnFZ6QUD0MCox5hBuVwemGCew2qJzUX5xPoyR+0EzS5qD5qQXL/vnQ8Eu+nv03tpeFRwLrDg== +"@typescript-eslint/experimental-utils@3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.6.0.tgz#0138152d66e3e53a6340f606793fb257bf2d76a1" + integrity sha512-4Vdf2hvYMUnTdkCNZu+yYlFtL2v+N2R7JOynIOkFbPjf9o9wQvRwRkzUdWlFd2YiiUwJLbuuLnl5civNg5ykOQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/types" "3.5.0" - "@typescript-eslint/typescript-estree" "3.5.0" + "@typescript-eslint/types" "3.6.0" + "@typescript-eslint/typescript-estree" "3.6.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -1712,10 +1712,10 @@ "@typescript-eslint/typescript-estree" "2.28.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/types@3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.5.0.tgz#4e3d2a2272268d8ec3e3e4a37152a64956682639" - integrity sha512-Dreqb5idi66VVs1QkbAwVeDmdJG+sDtofJtKwKCZXIaBsINuCN7Jv5eDIHrS0hFMMiOvPH9UuOs4splW0iZe4Q== +"@typescript-eslint/types@3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.6.0.tgz#4bd6eee55d2f9d35a4b36c4804be1880bf68f7bc" + integrity sha512-JwVj74ohUSt0ZPG+LZ7hb95fW8DFOqBuR6gE7qzq55KDI3BepqsCtHfBIoa0+Xi1AI7fq5nCu2VQL8z4eYftqg== "@typescript-eslint/typescript-estree@2.28.0": version "2.28.0" @@ -1730,13 +1730,13 @@ semver "^6.3.0" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.5.0.tgz#dfc895db21a381b84f24c2a719f5bf9c600dcfdc" - integrity sha512-Na71ezI6QP5WVR4EHxwcBJgYiD+Sre9BZO5iJK2QhrmRPo/42+b0no/HZIrdD1sjghzlYv7t+7Jis05M1uMxQg== +"@typescript-eslint/typescript-estree@3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.6.0.tgz#9b4cab43f1192b64ff51530815b8919f166ce177" + integrity sha512-G57NDSABHjvob7zVV09ehWyD1K6/YUKjz5+AufObFyjNO4DVmKejj47MHjVHHlZZKgmpJD2yyH9lfCXHrPITFg== dependencies: - "@typescript-eslint/types" "3.5.0" - "@typescript-eslint/visitor-keys" "3.5.0" + "@typescript-eslint/types" "3.6.0" + "@typescript-eslint/visitor-keys" "3.6.0" debug "^4.1.1" glob "^7.1.6" is-glob "^4.0.1" @@ -1744,10 +1744,10 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/visitor-keys@3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.5.0.tgz#73c1ea2582f814735e4afdc1cf6f5e3af78db60a" - integrity sha512-7cTp9rcX2sz9Z+zua9MCOX4cqp5rYyFD5o8LlbSpXrMTXoRdngTtotRZEkm8+FNMHPWYFhitFK+qt/brK8BVJQ== +"@typescript-eslint/visitor-keys@3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.6.0.tgz#44185eb0cc47651034faa95c5e2e8b314ecebb26" + integrity sha512-p1izllL2Ubwunite0ITjubuMQRBGgjdVYwyG7lXPX8GbrA6qF0uwSRz9MnXZaHMxID4948gX0Ez8v9tUDi/KfQ== dependencies: eslint-visitor-keys "^1.1.0" From ae2378cc2a537277025c9104bc43a4cc68318650 Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Tue, 7 Jul 2020 00:01:22 -0700 Subject: [PATCH 27/28] feat(stepfunctions): stepfunctions and stepfunctions-tasks modules are now stable! (#8912) Closes #6489 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-stepfunctions-tasks/README.md | 4 +--- packages/@aws-cdk/aws-stepfunctions-tasks/package.json | 7 ++----- packages/@aws-cdk/aws-stepfunctions/README.md | 6 +----- packages/@aws-cdk/aws-stepfunctions/package.json | 4 ++-- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index eb391390b2056..305530f60d0fe 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -2,9 +2,7 @@ --- -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) - -> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) --- diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index f2dcbba5acc77..6196dd98e9a72 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -111,11 +111,8 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", - "awslint": { - "exclude": [] - }, + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index d072abfc66063..7a74e563b4cff 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -4,11 +4,7 @@ ![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) -> All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. - -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) - -> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) --- diff --git a/packages/@aws-cdk/aws-stepfunctions/package.json b/packages/@aws-cdk/aws-stepfunctions/package.json index bae715668bf4b..ca5cb32a388c7 100644 --- a/packages/@aws-cdk/aws-stepfunctions/package.json +++ b/packages/@aws-cdk/aws-stepfunctions/package.json @@ -95,8 +95,8 @@ "no-unused-type:@aws-cdk/aws-stepfunctions.ServiceIntegrationPattern" ] }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "awscdkio": { "announce": false } From 25048f68856c5edbd92a0db7bc21d7c8a6ec797d Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Tue, 7 Jul 2020 11:17:27 +0000 Subject: [PATCH 28/28] chore(release): 1.50.0 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ lerna.json | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d95ed89c4daa..ce0459cfaf7c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,39 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.50.0](https://github.com/aws/aws-cdk/compare/v1.49.1...v1.50.0) (2020-07-07) + + +### ⚠ BREAKING CHANGES + +* **eks:** `version` is now a mandatory property + +### Features + +* **apigatewayv2:** http api - custom domain & stage mapping ([#8027](https://github.com/aws/aws-cdk/issues/8027)) ([5e43348](https://github.com/aws/aws-cdk/commit/5e43348ecdb6a8da865bb0db22c4782b6fa4bc96)), closes [#7847](https://github.com/aws/aws-cdk/issues/7847) +* **autoscaling:** allow setting autoscaling group name ([#8853](https://github.com/aws/aws-cdk/issues/8853)) ([38d8414](https://github.com/aws/aws-cdk/commit/38d84149bae213d0e285d5192265043a8c0de1aa)) +* **cfn-include:** add support for retrieving Output objects from the template ([#8821](https://github.com/aws/aws-cdk/issues/8821)) ([0b09bbb](https://github.com/aws/aws-cdk/commit/0b09bbb1d43192db71f682ff4f3ad125eb231d91)), closes [#8820](https://github.com/aws/aws-cdk/issues/8820) +* **custom-resources:** include handler log group in error messages ([#8839](https://github.com/aws/aws-cdk/issues/8839)) ([8e055d4](https://github.com/aws/aws-cdk/commit/8e055d449808f97436b92b6d6e57f8053e289653)) +* **eks:** document how to add a manifest from url ([#8802](https://github.com/aws/aws-cdk/issues/8802)) ([b5acfaa](https://github.com/aws/aws-cdk/commit/b5acfaac89351ff6285acfdb36145bccca4b6b65)), closes [#8340](https://github.com/aws/aws-cdk/issues/8340) +* **eks:** support cluster version pinning ([#8889](https://github.com/aws/aws-cdk/issues/8889)) ([a732d14](https://github.com/aws/aws-cdk/commit/a732d149ff33f6958b83d539ba3429a025dcd631)), closes [#7762](https://github.com/aws/aws-cdk/issues/7762) +* **lambda:** efs filesystems ([#8602](https://github.com/aws/aws-cdk/issues/8602)) ([8529387](https://github.com/aws/aws-cdk/commit/8529387cb901fd1fea9e0ee1af1284de3ad98ce7)), closes [#8595](https://github.com/aws/aws-cdk/issues/8595) +* **lambda-nodejs:** allow jsx and tsx entry files ([#8892](https://github.com/aws/aws-cdk/issues/8892)) ([4ba20fd](https://github.com/aws/aws-cdk/commit/4ba20fd2f1579034483683995fac1e18e97a1b12)) +* **s3-deployment:** prune - keep missing files on destination bucket ([#8263](https://github.com/aws/aws-cdk/issues/8263)) ([57914c7](https://github.com/aws/aws-cdk/commit/57914c7f430b69ae54c9d2d9fac4da1092b45b42)), closes [#953](https://github.com/aws/aws-cdk/issues/953) +* **stepfunctions:** stepfunctions and stepfunctions-tasks modules are now stable! ([#8912](https://github.com/aws/aws-cdk/issues/8912)) ([ae2378c](https://github.com/aws/aws-cdk/commit/ae2378cc2a537277025c9104bc43a4cc68318650)), closes [#6489](https://github.com/aws/aws-cdk/issues/6489) +* **stepfunctions-tasks:** task for invoking a Step Functions activity worker ([#8840](https://github.com/aws/aws-cdk/issues/8840)) ([021533c](https://github.com/aws/aws-cdk/commit/021533caa8f4e515299d1f0cdaadd9f625d6f64d)) + + +### Bug Fixes + +* **apigateway:** Lambda integration for imported functions ([#8870](https://github.com/aws/aws-cdk/issues/8870)) ([8420f96](https://github.com/aws/aws-cdk/commit/8420f96ffd6201656e908d6d7f61cdbbc3331cc1)), closes [#8869](https://github.com/aws/aws-cdk/issues/8869) +* **config:** cannot scope a custom rule without configurationChanges on ([#8738](https://github.com/aws/aws-cdk/issues/8738)) ([841060d](https://github.com/aws/aws-cdk/commit/841060d6adde4ea6d58e008f85cc155b8c3a3768)) +* **core:** asset bundling fails with BuildKit ([#8911](https://github.com/aws/aws-cdk/issues/8911)) ([c1d4e0f](https://github.com/aws/aws-cdk/commit/c1d4e0fecbdf716eb55578ad5721a0ead4b306e2)) +* **eks:** incorrect enableDockerBridge value when enabled ([#8895](https://github.com/aws/aws-cdk/issues/8895)) ([ea0552a](https://github.com/aws/aws-cdk/commit/ea0552a4378d61cffd14483896abadad7afa5a0a)), closes [#5786](https://github.com/aws/aws-cdk/issues/5786) +* **eks:** kubectl resources fail before fargate profiles are created ([#8859](https://github.com/aws/aws-cdk/issues/8859)) ([4fad9bc](https://github.com/aws/aws-cdk/commit/4fad9bcbd75702e89eea02a140aa010f8952329a)), closes [#8854](https://github.com/aws/aws-cdk/issues/8854) [#8574](https://github.com/aws/aws-cdk/issues/8574) +* **eks:** missing nodegroup identity in aws-auth after awsAuth.addMasterRole ([#8901](https://github.com/aws/aws-cdk/issues/8901)) ([a9c66f7](https://github.com/aws/aws-cdk/commit/a9c66f780b233ce3c25e12f39e3b1122636411b3)), closes [#7595](https://github.com/aws/aws-cdk/issues/7595) +* **lambda-nodejs:** maximum call stack size exceeded with relative entry file path ([#8907](https://github.com/aws/aws-cdk/issues/8907)) ([c585e18](https://github.com/aws/aws-cdk/commit/c585e1873e437341ac1b90afbe85a9cb9e6dc2d6)), closes [#8902](https://github.com/aws/aws-cdk/issues/8902) +* **rds:** proxy for db cluster fails with model validation error ([#8896](https://github.com/aws/aws-cdk/issues/8896)) ([7d47cfb](https://github.com/aws/aws-cdk/commit/7d47cfb39ba40a223ccc511e5706f471b9225c52)), closes [#8885](https://github.com/aws/aws-cdk/issues/8885) [#8476](https://github.com/aws/aws-cdk/issues/8476) + ## [1.49.1](https://github.com/aws/aws-cdk/compare/v1.49.0...v1.49.1) (2020-07-02) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 2babe6847f5c3..57f402c732b90 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.49.1" + "version": "1.50.0" }