diff --git a/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts b/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts index d0dad353a8727..533ccbd5d78b9 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts @@ -85,9 +85,13 @@ export interface CachePolicyProps { * A Cache Policy configuration. * * @resource AWS::CloudFront::CachePolicy + * @link https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html */ export class CachePolicy extends Resource implements ICachePolicy { - + /** + * This policy is designed for use with an origin that is an AWS Amplify web app. + */ + public static readonly AMPLIFY = CachePolicy.fromManagedCachePolicy('2e54312d-136d-493c-8eb9-b001f22f67d2'); /** * Optimize cache efficiency by minimizing the values that CloudFront includes in the cache key. * Query strings and cookies are not included in the cache key, and only the normalized 'Accept-Encoding' header is included. diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index eecc6f3857da4..f450e29dfcf20 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -744,7 +744,7 @@ By default, a new security group is created and logging is enabled. Moreover, a authorize all users to the VPC CIDR is created. To customize authorization rules, set the `authorizeAllUsersToVpcCidr` prop to `false` -and use `addaddAuthorizationRule()`: +and use `addAuthorizationRule()`: ```ts fixture=client-vpn const endpoint = vpc.addClientVpnEndpoint('Endpoint', { @@ -1110,6 +1110,7 @@ const instance = new ec2.Instance(this, 'Instance', { const localPath = instance.userData.addS3DownloadCommand({ bucket:asset.bucket, bucketKey:asset.s3ObjectKey, + region: 'us-east-1', // Optional }); instance.userData.addExecuteFileCommand({ filePath:localPath, diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index 1b778fee66e6e..97d3d03e55219 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -372,6 +372,20 @@ export enum InstanceClass { */ X1E = 'x1e', + /** + * Memory-intensive instances, 2nd generation with Graviton2 processors + * + * This instance type can be used only in RDS. It is not supported in EC2. + */ + MEMORY_INTENSIVE_2_GRAVITON2 = 'x2g', + + /** + * Memory-intensive instances, 2nd generation with Graviton2 processors + * + * This instance type can be used only in RDS. It is not supported in EC2. + */ + X2G = 'x2g', + /** * Memory-intensive instances, 2nd generation with Graviton2 processors and local NVME drive */ diff --git a/packages/@aws-cdk/aws-ec2/lib/user-data.ts b/packages/@aws-cdk/aws-ec2/lib/user-data.ts index 9b835744b3a6e..14b94d2d2ad4a 100644 --- a/packages/@aws-cdk/aws-ec2/lib/user-data.ts +++ b/packages/@aws-cdk/aws-ec2/lib/user-data.ts @@ -37,6 +37,12 @@ export interface S3DownloadOptions { */ readonly localFile?: string; + /** + * The region of the S3 Bucket (needed for access via VPC Gateway) + * @default none + */ + readonly region?: string + } /** @@ -156,7 +162,7 @@ class LinuxUserData extends UserData { const localPath = ( params.localFile && params.localFile.length !== 0 ) ? params.localFile : `/tmp/${ params.bucketKey }`; this.addCommands( `mkdir -p $(dirname '${localPath}')`, - `aws s3 cp '${s3Path}' '${localPath}'`, + `aws s3 cp '${s3Path}' '${localPath}'` + (params.region !== undefined ? ` --region ${params.region}` : ''), ); return localPath; @@ -215,7 +221,7 @@ class WindowsUserData extends UserData { const localPath = ( params.localFile && params.localFile.length !== 0 ) ? params.localFile : `C:/temp/${ params.bucketKey }`; this.addCommands( `mkdir (Split-Path -Path '${localPath}' ) -ea 0`, - `Read-S3Object -BucketName '${params.bucket.bucketName}' -key '${params.bucketKey}' -file '${localPath}' -ErrorAction Stop`, + `Read-S3Object -BucketName '${params.bucket.bucketName}' -key '${params.bucketKey}' -file '${localPath}' -ErrorAction Stop` + (params.region !== undefined ? ` -Region ${params.region}` : ''), ); return localPath; } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-lookup.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-lookup.ts index 3f9f49dba2540..8db845763dd0e 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-lookup.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-lookup.ts @@ -48,4 +48,11 @@ export interface VpcLookupOptions { * @default aws-cdk:subnet-name */ readonly subnetGroupNameTag?: string; + + /** + * Optional to override inferred region + * + * @default Current stack's environment region + */ + readonly region?: string; } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index b8194eb161c1d..cd70f71582718 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1,7 +1,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Annotations, ConcreteDependable, ContextProvider, DependableTrait, IConstruct, - IDependable, IResource, Lazy, Resource, Stack, Token, Tags, Names, + IDependable, IResource, Lazy, Resource, Stack, Token, Tags, Names, Arn, } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct, Node } from 'constructs'; @@ -78,6 +78,12 @@ export interface IVpc extends IResource { */ readonly vpcId: string; + /** + * ARN for this VPC + * @attribute + */ + readonly vpcArn: string; + /** * CIDR range for this VPC * @@ -357,6 +363,11 @@ abstract class VpcBase extends Resource implements IVpc { */ public abstract readonly vpcId: string; + /** + * Arn of this VPC + */ + public abstract readonly vpcArn: string; + /** * CIDR range for this VPC */ @@ -1118,9 +1129,15 @@ export class Vpc extends VpcBase { filter.isDefault = options.isDefault ? 'true' : 'false'; } + const overrides: {[key: string]: string} = {}; + if (options.region) { + overrides.region = options.region; + } + const attributes: cxapi.VpcContextResponse = ContextProvider.getValue(scope, { provider: cxschema.ContextProvider.VPC_PROVIDER, props: { + ...overrides, filter, returnAsymmetricSubnets: true, subnetGroupNameTag: options.subnetGroupNameTag, @@ -1147,6 +1164,11 @@ export class Vpc extends VpcBase { */ public readonly vpcId: string; + /** + * @attribute + */ + public readonly vpcArn: string; + /** * @attribute */ @@ -1277,6 +1299,11 @@ export class Vpc extends VpcBase { this.availabilityZones = this.availabilityZones.slice(0, maxAZs); this.vpcId = this.resource.ref; + this.vpcArn = Arn.format({ + service: 'ec2', + resource: 'vpc', + resourceName: this.vpcId, + }, stack); const defaultSubnet = props.natGateways === 0 ? Vpc.DEFAULT_SUBNETS_NO_NAT : Vpc.DEFAULT_SUBNETS; this.subnetConfiguration = ifUndefined(props.subnetConfiguration, defaultSubnet); @@ -1853,6 +1880,7 @@ function ifUndefined(value: T | undefined, defaultValue: T): T { class ImportedVpc extends VpcBase { public readonly vpcId: string; + public readonly vpcArn: string; public readonly publicSubnets: ISubnet[]; public readonly privateSubnets: ISubnet[]; public readonly isolatedSubnets: ISubnet[]; @@ -1864,6 +1892,11 @@ class ImportedVpc extends VpcBase { super(scope, id); this.vpcId = props.vpcId; + this.vpcArn = Arn.format({ + service: 'ec2', + resource: 'vpc', + resourceName: this.vpcId, + }, Stack.of(this)); this.cidr = props.vpcCidrBlock; this.availabilityZones = props.availabilityZones; this._vpnGatewayId = props.vpnGatewayId; @@ -1897,6 +1930,7 @@ class ImportedVpc extends VpcBase { class LookedUpVpc extends VpcBase { public readonly vpcId: string; + public readonly vpcArn: string; public readonly internetConnectivityEstablished: IDependable = new ConcreteDependable(); public readonly availabilityZones: string[]; public readonly publicSubnets: ISubnet[]; @@ -1908,6 +1942,11 @@ class LookedUpVpc extends VpcBase { super(scope, id); this.vpcId = props.vpcId; + this.vpcArn = Arn.format({ + service: 'ec2', + resource: 'vpc', + resourceName: this.vpcId, + }, Stack.of(this)); this.cidr = props.vpcCidrBlock; this._vpnGatewayId = props.vpnGatewayId; this.incompleteSubnetDefinition = isIncomplete; diff --git a/packages/@aws-cdk/aws-ec2/test/userdata.test.ts b/packages/@aws-cdk/aws-ec2/test/userdata.test.ts index 272ad60a84b00..e2596d8699abd 100644 --- a/packages/@aws-cdk/aws-ec2/test/userdata.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/userdata.test.ts @@ -85,6 +85,35 @@ describe('user data', () => { 'Read-S3Object -BucketName \'test2\' -key \'filename2.bat\' -file \'c:\\test\\location\\otherScript.bat\' -ErrorAction Stop', ); + }); + test('can windows userdata download S3 files with given region', () => { + // GIVEN + const stack = new Stack(); + const userData = ec2.UserData.forWindows(); + const bucket = Bucket.fromBucketName( stack, 'testBucket', 'test' ); + const bucket2 = Bucket.fromBucketName( stack, 'testBucket2', 'test2' ); + + // WHEN + userData.addS3DownloadCommand({ + bucket, + bucketKey: 'filename.bat', + region: 'us-east-1', + } ); + userData.addS3DownloadCommand({ + bucket: bucket2, + bucketKey: 'filename2.bat', + localFile: 'c:\\test\\location\\otherScript.bat', + region: 'us-east-1', + } ); + + // THEN + const rendered = userData.render(); + expect(rendered).toEqual('mkdir (Split-Path -Path \'C:/temp/filename.bat\' ) -ea 0\n' + + 'Read-S3Object -BucketName \'test\' -key \'filename.bat\' -file \'C:/temp/filename.bat\' -ErrorAction Stop -Region us-east-1\n' + + 'mkdir (Split-Path -Path \'c:\\test\\location\\otherScript.bat\' ) -ea 0\n' + + 'Read-S3Object -BucketName \'test2\' -key \'filename2.bat\' -file \'c:\\test\\location\\otherScript.bat\' -ErrorAction Stop -Region us-east-1', + ); + }); test('can windows userdata execute files', () => { // GIVEN @@ -189,6 +218,36 @@ describe('user data', () => { 'aws s3 cp \'s3://test2/filename2.sh\' \'c:\\test\\location\\otherScript.sh\'', ); + }); + test('can linux userdata download S3 files from specific region', () => { + // GIVEN + const stack = new Stack(); + const userData = ec2.UserData.forLinux(); + const bucket = Bucket.fromBucketName( stack, 'testBucket', 'test' ); + const bucket2 = Bucket.fromBucketName( stack, 'testBucket2', 'test2' ); + + // WHEN + userData.addS3DownloadCommand({ + bucket, + bucketKey: 'filename.sh', + region: 'us-east-1', + } ); + userData.addS3DownloadCommand({ + bucket: bucket2, + bucketKey: 'filename2.sh', + localFile: 'c:\\test\\location\\otherScript.sh', + region: 'us-east-1', + } ); + + // THEN + const rendered = userData.render(); + expect(rendered).toEqual('#!/bin/bash\n' + + 'mkdir -p $(dirname \'/tmp/filename.sh\')\n' + + 'aws s3 cp \'s3://test/filename.sh\' \'/tmp/filename.sh\' --region us-east-1\n' + + 'mkdir -p $(dirname \'c:\\test\\location\\otherScript.sh\')\n' + + 'aws s3 cp \'s3://test2/filename2.sh\' \'c:\\test\\location\\otherScript.sh\' --region us-east-1', + ); + }); test('can linux userdata execute files', () => { // GIVEN diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.from-lookup.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.from-lookup.test.ts index 4ad08203a525e..30c0ab1cf2a80 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.from-lookup.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.from-lookup.test.ts @@ -250,6 +250,24 @@ describe('vpc from lookup', () => { restoreContextProvider(previous); }); + test('passes account and region', () => { + const previous = mockVpcContextProviderWith({ + vpcId: 'vpc-1234', + subnetGroups: [], + }, options => { + expect(options.region).toEqual('region-1234'); + }); + + const stack = new Stack(); + const vpc = Vpc.fromLookup(stack, 'Vpc', { + vpcId: 'vpc-1234', + region: 'region-1234', + }); + + expect(vpc.vpcId).toEqual('vpc-1234'); + + restoreContextProvider(previous); + }); }); }); diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts index 90942056865c7..eedc950ab584b 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts @@ -36,7 +36,12 @@ describe('vpc', () => { const stack = getTestStack(); const vpc = new Vpc(stack, 'TheVPC'); expect(stack.resolve(vpc.vpcId)).toEqual({ Ref: 'TheVPC92636AB0' }); + }); + test('vpc.vpcArn returns a token to the VPC ID', () => { + const stack = getTestStack(); + const vpc = new Vpc(stack, 'TheVPC'); + expect(stack.resolve(vpc.vpcArn)).toEqual({ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':ec2:us-east-1:123456789012:vpc/', { Ref: 'TheVPC92636AB0' }]] }); }); test('it uses the correct network range', () => { @@ -1786,4 +1791,4 @@ function hasTags(expectedTags: Array<{Key: string, Value: string}>): (props: any throw e; } }; -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index ad5a2201c759c..15f9e60215ebc 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -327,7 +327,7 @@ abstract class DatabaseClusterNew extends DatabaseClusterBase { }), ]; - let { s3ImportRole, s3ExportRole } = setupS3ImportExport(this, props); + let { s3ImportRole, s3ExportRole } = setupS3ImportExport(this, props, /* combineRoles */ false); // bind the engine to the Cluster const clusterEngineBindConfig = props.engine.bindToCluster(this, { s3ImportRole, diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 8d285b7375006..3349d15fb5c97 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -576,7 +576,6 @@ export interface DatabaseInstanceNewProps { /** * Role that will be associated with this DB instance to enable S3 export. - * This feature is only supported by the Microsoft SQL Server and Oracle engines. * * This property must not be used if `s3ExportBuckets` is used. * @@ -591,7 +590,6 @@ export interface DatabaseInstanceNewProps { /** * S3 buckets that you want to load data into. - * This feature is only supported by the Microsoft SQL Server and Oracle engines. * * This property must not be used if `s3ExportRole` is used. * @@ -847,7 +845,10 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa this.multiUserRotationApplication = props.engine.multiUserRotationApplication; this.engine = props.engine; - let { s3ImportRole, s3ExportRole } = setupS3ImportExport(this, props, true); + const engineType = props.engine.engineType; + // only Oracle and SQL Server require the import and export Roles to be the same + const combineRoles = engineType.startsWith('oracle-') || engineType.startsWith('sqlserver-'); + let { s3ImportRole, s3ExportRole } = setupS3ImportExport(this, props, combineRoles); const engineConfig = props.engine.bindToInstance(this, { ...props, s3ImportRole, @@ -866,8 +867,8 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa if (!engineFeatures?.s3Export) { throw new Error(`Engine '${engineDescription(props.engine)}' does not support S3 export`); } - // Only add the export role and feature if they are different from the import role & feature. - if (s3ImportRole !== s3ExportRole || engineFeatures.s3Import !== engineFeatures?.s3Export) { + // only add the export feature if it's different from the import feature + if (engineFeatures.s3Import !== engineFeatures?.s3Export) { instanceAssociatedRoles.push({ roleArn: s3ExportRole.roleArn, featureName: engineFeatures?.s3Export }); } } @@ -883,7 +884,7 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa allowMajorVersionUpgrade: props.allowMajorVersionUpgrade, dbName: props.databaseName, dbParameterGroupName: instanceParameterGroupConfig?.parameterGroupName, - engine: props.engine.engineType, + engine: engineType, engineVersion: props.engine.engineVersion?.fullVersion, licenseModel: props.licenseModel, timezone: props.timezone, diff --git a/packages/@aws-cdk/aws-rds/lib/private/util.ts b/packages/@aws-cdk/aws-rds/lib/private/util.ts index c142a903bad7a..1664647c92bd8 100644 --- a/packages/@aws-cdk/aws-rds/lib/private/util.ts +++ b/packages/@aws-cdk/aws-rds/lib/private/util.ts @@ -31,14 +31,14 @@ export interface DatabaseS3ImportExportProps { * Validates the S3 import/export props and returns the import/export roles, if any. * If `combineRoles` is true, will reuse the import role for export (or vice versa) if possible. * - * Notably, `combineRoles` is (by default) set to true for instances, but false for clusters. + * Notably, `combineRoles` is set to true for instances, but false for clusters. * This is because the `combineRoles` functionality is most applicable to instances and didn't exist * for the initial clusters implementation. To maintain backwards compatibility, it is set to false for clusters. */ export function setupS3ImportExport( scope: Construct, props: DatabaseS3ImportExportProps, - combineRoles?: boolean): { s3ImportRole?: iam.IRole, s3ExportRole?: iam.IRole } { + combineRoles: boolean): { s3ImportRole?: iam.IRole, s3ExportRole?: iam.IRole } { let s3ImportRole = props.s3ImportRole; let s3ExportRole = props.s3ExportRole; diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.expected.json b/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.expected.json new file mode 100644 index 0000000000000..2a55df00bab2c --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.expected.json @@ -0,0 +1,601 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-rds-instance-s3-postgres-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-rds-instance-s3-postgres-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-rds-instance-s3-postgres-integ/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "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-rds-instance-s3-postgres-integ/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-instance-s3-postgres-integ/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "ImportBucketBAF3A8E9": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ExportBucket4E99310E": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "InstanceSubnetGroupF2CBA54F": { + "Type": "AWS::RDS::DBSubnetGroup", + "Properties": { + "DBSubnetGroupDescription": "Subnet group for Instance database", + "SubnetIds": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ] + } + }, + "InstanceSecurityGroupB4E5FA83": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Security group for Instance database", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "InstanceS3ImportRole30959D06": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "rds.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "InstanceS3ImportRoleDefaultPolicy297F292A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "ImportBucketBAF3A8E9", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ImportBucketBAF3A8E9", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "InstanceS3ImportRoleDefaultPolicy297F292A", + "Roles": [ + { + "Ref": "InstanceS3ImportRole30959D06" + } + ] + } + }, + "InstanceS3ExportRole9891C2F7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "rds.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "InstanceS3ExportRoleDefaultPolicy62C930BC": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "ExportBucket4E99310E", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ExportBucket4E99310E", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "InstanceS3ExportRoleDefaultPolicy62C930BC", + "Roles": [ + { + "Ref": "InstanceS3ExportRole9891C2F7" + } + ] + } + }, + "InstanceSecret478E0A47": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "Description": { + "Fn::Join": [ + "", + [ + "Generated by the CDK for stack: ", + { + "Ref": "AWS::StackName" + } + ] + ] + }, + "GenerateSecretString": { + "ExcludeCharacters": " %+~`#$&*()|[]{}:;<>?!'/@\"\\", + "GenerateStringKey": "password", + "PasswordLength": 30, + "SecretStringTemplate": "{\"username\":\"postgres\"}" + } + } + }, + "InstanceSecretAttachment83BEE581": { + "Type": "AWS::SecretsManager::SecretTargetAttachment", + "Properties": { + "SecretId": { + "Ref": "InstanceSecret478E0A47" + }, + "TargetId": { + "Ref": "InstanceC1063A87" + }, + "TargetType": "AWS::RDS::DBInstance" + } + }, + "InstanceC1063A87": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "DBInstanceClass": "db.m5.large", + "AllocatedStorage": "100", + "AssociatedRoles": [ + { + "FeatureName": "s3Import", + "RoleArn": { + "Fn::GetAtt": [ + "InstanceS3ImportRole30959D06", + "Arn" + ] + } + }, + { + "FeatureName": "s3Export", + "RoleArn": { + "Fn::GetAtt": [ + "InstanceS3ExportRole9891C2F7", + "Arn" + ] + } + } + ], + "CopyTagsToSnapshot": true, + "DBSubnetGroupName": { + "Ref": "InstanceSubnetGroupF2CBA54F" + }, + "EnableIAMDatabaseAuthentication": true, + "Engine": "postgres", + "EngineVersion": "11.12", + "MasterUsername": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "InstanceSecret478E0A47" + }, + ":SecretString:username::}}" + ] + ] + }, + "MasterUserPassword": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "InstanceSecret478E0A47" + }, + ":SecretString:password::}}" + ] + ] + }, + "MultiAZ": false, + "PubliclyAccessible": true, + "StorageType": "gp2", + "VPCSecurityGroups": [ + { + "Fn::GetAtt": [ + "InstanceSecurityGroupB4E5FA83", + "GroupId" + ] + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.ts b/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.ts new file mode 100644 index 0000000000000..e07501039549b --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/integ.instance-s3-postgres.ts @@ -0,0 +1,20 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as rds from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-cdk-rds-instance-s3-postgres-integ'); + +new rds.DatabaseInstance(stack, 'Instance', { + engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_11_12 }), + vpc: new ec2.Vpc(stack, 'VPC', { maxAzs: 2, natGateways: 1 }), + multiAz: false, + publiclyAccessible: true, + iamAuthentication: true, + s3ImportBuckets: [new s3.Bucket(stack, 'ImportBucket', { removalPolicy: cdk.RemovalPolicy.DESTROY })], + s3ExportBuckets: [new s3.Bucket(stack, 'ExportBucket', { removalPolicy: cdk.RemovalPolicy.DESTROY })], + removalPolicy: cdk.RemovalPolicy.DESTROY, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-redshift/lib/cluster.ts b/packages/@aws-cdk/aws-redshift/lib/cluster.ts index a94bfa31b7fb5..a43b6f6993c72 100644 --- a/packages/@aws-cdk/aws-redshift/lib/cluster.ts +++ b/packages/@aws-cdk/aws-redshift/lib/cluster.ts @@ -483,7 +483,7 @@ export class Cluster extends ClusterBase { dbName: props.defaultDatabaseName || 'default_db', publiclyAccessible: props.publiclyAccessible || false, // Encryption - kmsKeyId: props.encryptionKey && props.encryptionKey.keyArn, + kmsKeyId: props.encryptionKey?.keyId, encrypted: props.encrypted ?? true, }); diff --git a/packages/@aws-cdk/aws-redshift/test/cluster.test.ts b/packages/@aws-cdk/aws-redshift/test/cluster.test.ts index a1091815a30c1..d771e8e66b0de 100644 --- a/packages/@aws-cdk/aws-redshift/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-redshift/test/cluster.test.ts @@ -249,10 +249,7 @@ test('create an encrypted cluster with custom KMS key', () => { // THEN Template.fromStack(stack).hasResourceProperties('AWS::Redshift::Cluster', { KmsKeyId: { - 'Fn::GetAtt': [ - 'Key961B73FD', - 'Arn', - ], + Ref: 'Key961B73FD', }, }); }); diff --git a/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json b/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json index 16d60bb08c0d0..4cfb1faea5118 100644 --- a/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json +++ b/packages/@aws-cdk/aws-redshift/test/integ.database.expected.json @@ -580,6 +580,41 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "customkmskey377C6F9A": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "ClusterSubnetsDCFA5CB7": { "Type": "AWS::Redshift::ClusterSubnetGroup", "Properties": { @@ -680,6 +715,9 @@ "Ref": "ClusterSubnetsDCFA5CB7" }, "Encrypted": true, + "KmsKeyId": { + "Ref": "customkmskey377C6F9A" + }, "NumberOfNodes": 2, "PubliclyAccessible": true, "VpcSecurityGroupIds": [ diff --git a/packages/@aws-cdk/aws-redshift/test/integ.database.ts b/packages/@aws-cdk/aws-redshift/test/integ.database.ts index 3a3b955a2b5aa..6e4893c0c0089 100644 --- a/packages/@aws-cdk/aws-redshift/test/integ.database.ts +++ b/packages/@aws-cdk/aws-redshift/test/integ.database.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node /// !cdk-integ pragma:ignore-assets import * as ec2 from '@aws-cdk/aws-ec2'; +import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; import * as constructs from 'constructs'; import * as redshift from '../lib'; @@ -28,6 +29,7 @@ const cluster = new redshift.Cluster(stack, 'Cluster', { }, defaultDatabaseName: databaseName, publiclyAccessible: true, + encryptionKey: new kms.Key(stack, 'custom-kms-key'), }); const databaseOptions = { diff --git a/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts b/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts index 9098503b625f4..862a63e26e6d1 100644 --- a/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts +++ b/packages/@aws-cdk/aws-route53/lib/vpc-endpoint-service-domain-name.ts @@ -46,6 +46,11 @@ export class VpcEndpointServiceDomainName extends CoreConstruct { // Track all domain names created, so someone doesn't accidentally associate two domains with a single service private static readonly endpointServicesMap: { [endpointService: string]: string} = {}; + /** + * The domain name associated with the private DNS configuration + */ + public domainName: string; + // The way this class works is by using three custom resources and a TxtRecord in conjunction // The first custom resource tells the VPC endpoint service to use the given DNS name // The VPC endpoint service will then say: @@ -58,16 +63,16 @@ export class VpcEndpointServiceDomainName extends CoreConstruct { const serviceUniqueId = Names.nodeUniqueId(props.endpointService.node); const serviceId = props.endpointService.vpcEndpointServiceId; - const privateDnsName = props.domainName; + this.domainName = props.domainName; // Make sure a user doesn't accidentally add multiple domains this.validateProps(props); - VpcEndpointServiceDomainName.endpointServicesMap[serviceUniqueId] = privateDnsName; + VpcEndpointServiceDomainName.endpointServicesMap[serviceUniqueId] = this.domainName; VpcEndpointServiceDomainName.endpointServices.push(props.endpointService); // Enable Private DNS on the endpoint service and retrieve the AWS-generated configuration - const privateDnsConfiguration = this.getPrivateDnsConfiguration(serviceUniqueId, serviceId, privateDnsName); + const privateDnsConfiguration = this.getPrivateDnsConfiguration(serviceUniqueId, serviceId, this.domainName); // Tell AWS to verify that this account owns the domain attached to the service this.verifyPrivateDnsConfiguration(privateDnsConfiguration, props.publicHostedZone); diff --git a/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts b/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts index 35b7ac9c67596..506a25e2a26df 100644 --- a/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts +++ b/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts @@ -264,4 +264,20 @@ test('throws if creating multiple domains for a single service', () => { publicHostedZone: zone, }); }).toThrow(/Cannot create a VpcEndpointServiceDomainName for service/); +}); + + +test('endpoint domain name property equals input domain name', () => { + // GIVEN + vpces = new VpcEndpointService(stack, 'NameTest', { + vpcEndpointServiceLoadBalancers: [nlb], + }); + + const dn = new VpcEndpointServiceDomainName(stack, 'EndpointDomain', { + endpointService: vpces, + domainName: 'name-test.aws-cdk.dev', + publicHostedZone: zone, + }); + expect(dn.domainName).toEqual('name-test.aws-cdk.dev'); + }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index a0f47ee7519c1..cedf89eefe07c 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -110,9 +110,10 @@ The following example provides the field named `input` as the input to the `Task state that runs a Lambda function. ```ts +declare const fn: lambda.Function; const submitJob = new tasks.LambdaInvoke(this, 'Invoke Handler', { lambdaFunction: fn, - inputPath: '$.input' + inputPath: '$.input', }); ``` @@ -130,9 +131,10 @@ as well as other metadata. The following example assigns the output from the Task to a field named `result` ```ts +declare const fn: lambda.Function; const submitJob = new tasks.LambdaInvoke(this, 'Invoke Handler', { lambdaFunction: fn, - outputPath: '$.Payload.result' + outputPath: '$.Payload.result', }); ``` @@ -149,6 +151,7 @@ The following example extracts the output payload of a Lambda function Task and it with some static values and the state name from the context object. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke Handler', { lambdaFunction: fn, resultSelector: { @@ -159,7 +162,7 @@ new tasks.LambdaInvoke(this, 'Invoke Handler', { }, stateName: sfn.JsonPath.stringAt('$$.State.Name'), }, -}) +}); ``` ### ResultPath @@ -174,9 +177,10 @@ The following example adds the item from calling DynamoDB's `getItem` API to the input and passes it to the next state. ```ts +declare const myTable: dynamodb.Table; new tasks.DynamoPutItem(this, 'PutItem', { item: { - MessageId: tasks.DynamoAttributeValue.fromString('message-id') + MessageId: tasks.DynamoAttributeValue.fromString('message-id'), }, table: myTable, resultPath: `$.Item`, @@ -199,6 +203,7 @@ The following example provides the field named `input` as the input to the Lambd and invokes it asynchronously. ```ts +declare const fn: lambda.Function; const submitJob = new tasks.LambdaInvoke(this, 'Invoke Handler', { lambdaFunction: fn, payload: sfn.TaskInput.fromJsonPathAt('$.input'), @@ -258,14 +263,14 @@ const publishMessage = new tasks.SnsPublish(this, 'Publish message', { }); const wait = new sfn.Wait(this, 'Wait', { - time: sfn.WaitTime.secondsPath('$.waitSeconds') + time: sfn.WaitTime.secondsPath('$.waitSeconds'), }); new sfn.StateMachine(this, 'StateMachine', { definition: convertToSeconds .next(createMessage) .next(publishMessage) - .next(wait) + .next(wait), }); ``` @@ -286,15 +291,13 @@ Previous-generation REST APIs currently offer more features. More details can be The `CallApiGatewayRestApiEndpoint` calls the REST API endpoint. ```ts -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from `@aws-cdk/aws-stepfunctions-tasks`; - -const restApi = new apigateway.RestApi(stack, 'MyRestApi'); +import * as apigateway from '@aws-cdk/aws-apigateway'; +const restApi = new apigateway.RestApi(this, 'MyRestApi'); -const invokeTask = new tasks.CallApiGatewayRestApiEndpoint(stack, 'Call REST API', { +const invokeTask = new tasks.CallApiGatewayRestApiEndpoint(this, 'Call REST API', { api: restApi, stageName: 'prod', - method: HttpMethod.GET, + method: tasks.HttpMethod.GET, }); ``` @@ -303,15 +306,13 @@ const invokeTask = new tasks.CallApiGatewayRestApiEndpoint(stack, 'Call REST API The `CallApiGatewayHttpApiEndpoint` calls the HTTP API endpoint. ```ts -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from `@aws-cdk/aws-stepfunctions-tasks`; - -const httpApi = new apigatewayv2.HttpApi(stack, 'MyHttpApi'); +import * as apigatewayv2 from '@aws-cdk/aws-apigatewayv2'; +const httpApi = new apigatewayv2.HttpApi(this, 'MyHttpApi'); -const invokeTask = new tasks.CallApiGatewayHttpApiEndpoint(stack, 'Call HTTP API', { +const invokeTask = new tasks.CallApiGatewayHttpApiEndpoint(this, 'Call HTTP API', { apiId: httpApi.apiId, - apiStack: cdk.Stack.of(httpApi), - method: HttpMethod.GET, + apiStack: Stack.of(httpApi), + method: tasks.HttpMethod.GET, }); ``` @@ -324,6 +325,7 @@ You can use Step Functions' AWS SDK integrations to call any of the over two hun directly from your state machine, giving you access to over nine thousand API actions. ```ts +declare const myBucket: s3.Bucket; const getObject = new tasks.CallAwsService(this, 'GetObject', { service: 's3', action: 'getObject', @@ -348,7 +350,7 @@ const listBuckets = new tasks.CallAwsService(this, 'ListBuckets', { service: 's3', action: 'ListBuckets', iamResources: ['*'], - iamAction: 's3:ListAllMyBuckets' + iamAction: 's3:ListAllMyBuckets', }); ``` @@ -416,11 +418,15 @@ Step Functions supports [Batch](https://docs.aws.amazon.com/step-functions/lates The [SubmitJob](https://docs.aws.amazon.com/batch/latest/APIReference/API_SubmitJob.html) API submits an AWS Batch job from a job definition. -```ts fixture=with-batch-job +```ts +import * as batch from '@aws-cdk/aws-batch'; +declare const batchJobDefinition: batch.JobDefinition; +declare const batchQueue: batch.JobQueue; + const task = new tasks.BatchSubmitJob(this, 'Submit Job', { - jobDefinitionArn: batchJobDefinitionArn, + jobDefinitionArn: batchJobDefinition.jobDefinitionArn, jobName: 'MyJob', - jobQueueArn: batchQueueArn, + jobQueueArn: batchQueue.jobQueueArn, }); ``` @@ -471,6 +477,7 @@ Read more about calling DynamoDB APIs [here](https://docs.aws.amazon.com/step-fu The [GetItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) operation returns a set of attributes for the item with the given primary key. ```ts +declare const myTable: dynamodb.Table; new tasks.DynamoGetItem(this, 'Get Item', { key: { messageId: tasks.DynamoAttributeValue.fromString('message-007') }, table: myTable, @@ -482,6 +489,7 @@ new tasks.DynamoGetItem(this, 'Get Item', { The [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) operation creates a new item, or replaces an old item with a new item. ```ts +declare const myTable: dynamodb.Table; new tasks.DynamoPutItem(this, 'PutItem', { item: { MessageId: tasks.DynamoAttributeValue.fromString('message-007'), @@ -497,6 +505,7 @@ new tasks.DynamoPutItem(this, 'PutItem', { The [DeleteItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html) operation deletes a single item in a table by primary key. ```ts +declare const myTable: dynamodb.Table; new tasks.DynamoDeleteItem(this, 'DeleteItem', { key: { MessageId: tasks.DynamoAttributeValue.fromString('message-007') }, table: myTable, @@ -510,6 +519,7 @@ The [UpdateItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/ to the table if it does not already exist. ```ts +declare const myTable: dynamodb.Table; new tasks.DynamoUpdateItem(this, 'UpdateItem', { key: { MessageId: tasks.DynamoAttributeValue.fromString('message-007') @@ -547,8 +557,6 @@ The latest ACTIVE revision of the passed task definition is used for running the The following example runs a job from a task definition on EC2 ```ts -import * as ecs from '@aws-cdk/aws-ecs'; - const vpc = ec2.Vpc.fromLookup(this, 'Vpc', { isDefault: true, }); @@ -579,7 +587,7 @@ const runTask = new tasks.EcsRunTask(this, 'Run', { ecs.PlacementStrategy.randomly(), ], placementConstraints: [ - ecs.PlacementConstraint.memberOf('blieptuut') + ecs.PlacementConstraint.memberOf('blieptuut'), ], }), }); @@ -602,8 +610,6 @@ task definition is used for running the task. Learn more about The following example runs a job from a task definition on Fargate ```ts -import * as ecs from '@aws-cdk/aws-ecs'; - const vpc = ec2.Vpc.fromLookup(this, 'Vpc', { isDefault: true, }); @@ -648,7 +654,6 @@ Creates and starts running a cluster (job flow). Corresponds to the [`runJobFlow`](https://docs.aws.amazon.com/emr/latest/APIReference/API_RunJobFlow.html) API in EMR. ```ts - const clusterRole = new iam.Role(this, 'ClusterRole', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), }); @@ -689,7 +694,8 @@ and 256 inclusive, where the default concurrency of 1 means no step concurrency ```ts new tasks.EmrCreateCluster(this, 'Create Cluster', { - // ... + instances: {}, + name: sfn.TaskInput.fromJsonPathAt('$.ClusterName').value, stepConcurrencyLevel: 10, }); ``` @@ -715,7 +721,7 @@ Corresponds to the [`terminateJobFlows`](https://docs.aws.amazon.com/emr/latest/ ```ts new tasks.EmrTerminateCluster(this, 'Task', { - clusterId: 'ClusterId' + clusterId: 'ClusterId', }); ``` @@ -793,17 +799,15 @@ The following code snippet includes a Task state that uses eks:call to list the ```ts import * as eks from '@aws-cdk/aws-eks'; -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; const myEksCluster = new eks.Cluster(this, 'my sample cluster', { version: eks.KubernetesVersion.V1_18, clusterName: 'myEksCluster', }); -new tasks.EksCall(stack, 'Call a EKS Endpoint', { +new tasks.EksCall(this, 'Call a EKS Endpoint', { cluster: myEksCluster, - httpMethod: MethodType.GET, + httpMethod: tasks.HttpMethods.GET, httpPath: '/api/v1/namespaces/default/pods', }); ``` @@ -824,14 +828,12 @@ The following code snippet includes a Task state that uses events:putevents to s ```ts import * as events from '@aws-cdk/aws-events'; -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; -const myEventBus = events.EventBus(stack, 'EventBus', { +const myEventBus = new events.EventBus(this, 'EventBus', { eventBusName: 'MyEventBus1', }); -new tasks.EventBridgePutEvents(stack, 'Send an event to EventBridge', { +new tasks.EventBridgePutEvents(this, 'Send an event to EventBridge', { entries: [{ detail: sfn.TaskInput.fromObject({ Message: 'Hello from Step Functions!', @@ -855,8 +857,8 @@ new tasks.GlueStartJobRun(this, 'Task', { arguments: sfn.TaskInput.fromObject({ key: 'value', }), - timeout: cdk.Duration.minutes(30), - notifyDelayAfter: cdk.Duration.minutes(5), + timeout: Duration.minutes(30), + notifyDelayAfter: Duration.minutes(5), }); ``` @@ -884,6 +886,7 @@ The following snippet invokes a Lambda Function with the state input as the payl by referencing the `$` path. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke with state input', { lambdaFunction: fn, }); @@ -899,6 +902,7 @@ The following snippet invokes a Lambda Function by referencing the `$.Payload` p to reference the output of a Lambda executed before it. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke with empty object as payload', { lambdaFunction: fn, payload: sfn.TaskInput.fromObject({}), @@ -915,6 +919,7 @@ The following snippet invokes a Lambda and sets the task output to only include the Lambda function response. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke and set function response as task output', { lambdaFunction: fn, outputPath: '$.Payload', @@ -927,6 +932,7 @@ Lambda function ARN directly in the "Resource" string, but it conflicts with the integrationPattern, invocationType, clientContext, and qualifier properties. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke and combine function response with task input', { lambdaFunction: fn, payloadResponseOnly: true, @@ -945,6 +951,7 @@ The following snippet invokes a Lambda with the task token as part of the input to the Lambda. ```ts +declare const fn: lambda.Function; new tasks.LambdaInvoke(this, 'Invoke with callback', { lambdaFunction: fn, integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, @@ -998,11 +1005,11 @@ new tasks.SageMakerCreateTrainingJob(this, 'TrainSagemaker', { }, resourceConfig: { instanceCount: 1, - instanceType: new ec2.InstanceType(JsonPath.stringAt('$.InstanceType')), - volumeSize: cdk.Size.gibibytes(50), + instanceType: new ec2.InstanceType(sfn.JsonPath.stringAt('$.InstanceType')), + volumeSize: Size.gibibytes(50), }, // optional: default is 1 instance of EC2 `M4.XLarge` with `10GB` volume stoppingCondition: { - maxRuntime: cdk.Duration.hours(2), + maxRuntime: Duration.hours(2), }, // optional: default is 1 hour }); ``` @@ -1017,7 +1024,7 @@ new tasks.SageMakerCreateTransformJob(this, 'Batch Inference', { modelName: 'MyModelName', modelClientOptions: { invocationsMaxRetries: 3, // default is 0 - invocationsTimeout: cdk.Duration.minutes(5), // default is 60 seconds + invocationsTimeout: Duration.minutes(5), // default is 60 seconds }, transformInput: { transformDataSource: { @@ -1111,7 +1118,7 @@ const task1 = new tasks.SnsPublish(this, 'Publish1', { }, pic: { // BINARY must be explicitly set - type: MessageAttributeDataType.BINARY, + dataType: tasks.MessageAttributeDataType.BINARY, value: sfn.JsonPath.stringAt('$.pic'), }, people: { @@ -1170,6 +1177,7 @@ via the `associateWithParent` property. This allows the Step Functions UI to lin executions from parent executions, making it easier to trace execution flow across state machines. ```ts +declare const child: sfn.StateMachine; const task = new tasks.StepFunctionsStartExecution(this, 'ChildTask', { stateMachine: child, associateWithParent: true, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts index 10c35d847ecd1..e11a04c111856 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/base-types.ts @@ -206,8 +206,12 @@ export interface ResourceConfig { /** * ML compute instance type. * - * @example To provide an instance type from the task input, write - * `new ec2.InstanceType(sfn.JsonPath.stringAt('$.path.to.instanceType'))`, where the value in the task input is an EC2 instance type prepended with "ml.". + * To provide an instance type from the task input, supply an instance type in the following way + * where the value in the task input is an EC2 instance type prepended with "ml.": + * + * ```ts + * new ec2.InstanceType(sfn.JsonPath.stringAt('$.path.to.instanceType')); + * ``` * @see https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ResourceConfig.html#sagemaker-Type-ResourceConfig-InstanceType * * @default ec2.InstanceType(ec2.InstanceClass.M4, ec2.InstanceType.XLARGE) diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/default.ts-fixture index 11558a599e5f4..927b8e5fd6886 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/default.ts-fixture @@ -1,8 +1,9 @@ // Fixture with packages imported, but nothing else -import * as cdk from '@aws-cdk/core'; +import { Duration, RemovalPolicy, Size, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import * as ddb from '@aws-cdk/aws-dynamodb'; +import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as ec2 from '@aws-cdk/aws-ec2'; +import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; @@ -11,27 +12,10 @@ import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; -class Fixture extends cdk.Stack { +class Fixture extends Stack { constructor(scope: Construct, id: string) { super(scope, id); - const fn = new lambda.Function(this, 'lambdaFunction', { - code: lambda.Code.fromInline(`exports.handler = async () => { - return { "hello world"}; - `), - runtime: lambda.Runtime.NODEJS_12_X, - handler: 'index.handler', - }); - - const myTable = new ddb.Table(this, 'Messages', { - tableName: 'my-table', - partitionKey: { - name: 'MessageId', - type: ddb.AttributeType.STRING, - }, - removalPolicy: cdk.RemovalPolicy.DESTROY, - }); - /// here } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/with-batch-job.ts-fixture b/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/with-batch-job.ts-fixture deleted file mode 100644 index 47672ba140841..0000000000000 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/rosetta/with-batch-job.ts-fixture +++ /dev/null @@ -1,38 +0,0 @@ -// Fixture with packages imported, but nothing else -import { Stack } from '@aws-cdk/core'; -import { Construct } from 'constructs'; -import * as sfn from '@aws-cdk/aws-stepfunctions'; -import * as tasks from '@aws-cdk/aws-stepfunctions-tasks'; -import * as batch from '@aws-cdk/aws-batch'; -import * as ec2 from '@aws-cdk/aws-ec2'; -import * as ecs from '@aws-cdk/aws-ecs'; -import * as path from 'path'; - -class Fixture extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - const vpc = ec2.Vpc.fromLookup(this, 'Vpc', { - isDefault: true, - }); - - const batchQueue = new batch.JobQueue(this, 'JobQueue', { - computeEnvironments: [ - { - order: 1, - computeEnvironment: new batch.ComputeEnvironment(this, 'ComputeEnv', { - computeResources: { vpc }, - }), - }, - ], - }); - - const batchJobDefinition = new batch.JobDefinition(this, 'JobDefinition', { - container: { - image: ecs.ContainerImage.fromAsset(path.resolve(__dirname, 'batchjob-image')), - }, - }); - - /// here - } -} diff --git a/packages/aws-cdk/test/integ/cli/app/app.js b/packages/aws-cdk/test/integ/cli/app/app.js index 205d7014503dc..450662ac268e0 100644 --- a/packages/aws-cdk/test/integ/cli/app/app.js +++ b/packages/aws-cdk/test/integ/cli/app/app.js @@ -1,14 +1,28 @@ const path = require('path'); -const cdk = require('@aws-cdk/core'); -const ec2 = require('@aws-cdk/aws-ec2'); -const ssm = require('@aws-cdk/aws-ssm'); -const iam = require('@aws-cdk/aws-iam'); -const sns = require('@aws-cdk/aws-sns'); -const lambda = require('@aws-cdk/aws-lambda'); -const docker = require('@aws-cdk/aws-ecr-assets'); -const core = require('@aws-cdk/core') + +var constructs = require('constructs'); +if (process.env.PACKAGE_LAYOUT_VERSION === '1') { + var cdk = require('@aws-cdk/core'); + var ec2 = require('@aws-cdk/aws-ec2'); + var ssm = require('@aws-cdk/aws-ssm'); + var iam = require('@aws-cdk/aws-iam'); + var sns = require('@aws-cdk/aws-sns'); + var lambda = require('@aws-cdk/aws-lambda'); + var docker = require('@aws-cdk/aws-ecr-assets'); +} else { + var cdk = require('aws-cdk-lib'); + var { + aws_ec2: ec2, + aws_ssm: ssm, + aws_iam: iam, + aws_sns: sns, + aws_lambda: lambda, + aws_ecr_assets: docker + } = require('aws-cdk-lib'); +} + +const { Annotations } = cdk; const { StackWithNestedStack, StackWithNestedStackUsingParameters } = require('./nested-stack'); -const { Annotations } = require('@aws-cdk/core'); const stackPrefix = process.env.STACK_NAME_PREFIX; if (!stackPrefix) { @@ -48,11 +62,11 @@ class YourStack extends cdk.Stack { class StackUsingContext extends cdk.Stack { constructor(parent, id, props) { super(parent, id, props); - new core.CfnResource(this, 'Handle', { + new cdk.CfnResource(this, 'Handle', { type: 'AWS::CloudFormation::WaitConditionHandle' }); - new core.CfnOutput(this, 'Output', { + new cdk.CfnOutput(this, 'Output', { value: this.availabilityZones, }); } @@ -167,7 +181,7 @@ class MissingSSMParameterStack extends cdk.Stack { constructor(parent, id, props) { super(parent, id, props); - const parameterName = this.node.tryGetContext('test:ssm-parameter-name'); + const parameterName = constructs.Node.of(this).tryGetContext('test:ssm-parameter-name'); if (parameterName) { const param = getSsmParameterValue(this, parameterName); new iam.Role(this, 'PhonyRole', { assumedBy: new iam.AccountPrincipal(param) }); @@ -199,7 +213,7 @@ class DockerStack extends cdk.Stack { // Add at least a single resource (WaitConditionHandle), otherwise this stack will never // be deployed (and its assets never built) - new core.CfnResource(this, 'Handle', { + new cdk.CfnResource(this, 'Handle', { type: 'AWS::CloudFormation::WaitConditionHandle' }); } @@ -216,7 +230,7 @@ class DockerStackWithCustomFile extends cdk.Stack { // Add at least a single resource (WaitConditionHandle), otherwise this stack will never // be deployed (and its assets never built) - new core.CfnResource(this, 'Handle', { + new cdk.CfnResource(this, 'Handle', { type: 'AWS::CloudFormation::WaitConditionHandle' }); } @@ -228,7 +242,7 @@ class FailedStack extends cdk.Stack { super(parent, id, props); // fails on 'Property PolicyDocument cannot be empty'. - new core.CfnResource(this, 'EmptyPolicy', { + new cdk.CfnResource(this, 'EmptyPolicy', { type: 'AWS::IAM::Policy' }) @@ -243,9 +257,10 @@ class DefineVpcStack extends cdk.Stack { constructor(parent, id, props) { super(parent, id, props); - new ec2.Vpc(this, 'VPC', { + const vpc = new ec2.Vpc(this, 'VPC', { maxAzs: 1, - }).node.applyAspect(new cdk.Tag(VPC_TAG_NAME, VPC_TAG_VALUE)); + }) + cdk.Aspects.of(vpc).add(new cdk.Tag(VPC_TAG_NAME, VPC_TAG_VALUE)); } } diff --git a/packages/aws-cdk/test/integ/cli/app/nested-stack.js b/packages/aws-cdk/test/integ/cli/app/nested-stack.js index 3b3125963679b..87a45bb64b036 100644 --- a/packages/aws-cdk/test/integ/cli/app/nested-stack.js +++ b/packages/aws-cdk/test/integ/cli/app/nested-stack.js @@ -1,6 +1,14 @@ -const cfn = require('@aws-cdk/aws-cloudformation'); -const sns = require('@aws-cdk/aws-sns'); -const { Stack, CfnParameter } = require('@aws-cdk/core'); +if (process.env.PACKAGE_LAYOUT_VERSION === '1') { + var cfn = require('@aws-cdk/aws-cloudformation'); + var sns = require('@aws-cdk/aws-sns'); + var { Stack, CfnParameter } = require('@aws-cdk/core'); +} else { + var { + aws_cloudformation: cfn, + aws_sns: sns, + } = require('aws-cdk-lib'); + var { Stack, CfnParameter } = require('aws-cdk-lib'); +} class StackWithNestedStack extends Stack { constructor(scope, id) { diff --git a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts index 6fce054dbbf10..d7716a2ac3e70 100644 --- a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import { randomString, withDefaultFixture } from '../helpers/cdk'; +import { MAJOR_VERSION, randomString, withDefaultFixture } from '../helpers/cdk'; import { integTest } from '../helpers/test-helpers'; const timeout = process.env.CODEBUILD_BUILD_ID ? // if the process is running in CodeBuild @@ -129,22 +129,27 @@ integTest('deploy old style synthesis to new style bootstrap', withDefaultFixtur }); })); -integTest('deploying new style synthesis to old style bootstrap fails', withDefaultFixture(async (fixture) => { - const bootstrapStackName = fixture.bootstrapStackName; - - await fixture.cdkBootstrapLegacy({ - toolkitStackName: bootstrapStackName, - }); - - // Deploy stack that uses file assets, this fails because the bootstrap stack - // is version checked. - await expect(fixture.cdkDeploy('lambda', { - options: [ - '--toolkit-stack-name', bootstrapStackName, - '--context', '@aws-cdk/core:newStyleStackSynthesis=1', - ], - })).rejects.toThrow('exited with error'); -})); +if (MAJOR_VERSION === '1') { + // For v2, the default bootstrap is the modern bootstrap, so this test is predicated on invalid + // assumptions. + + integTest('deploying new style synthesis to old style bootstrap fails', withDefaultFixture(async (fixture) => { + const bootstrapStackName = fixture.bootstrapStackName; + + await fixture.cdkBootstrapLegacy({ + toolkitStackName: bootstrapStackName, + }); + + // Deploy stack that uses file assets, this fails because the bootstrap stack + // is version checked. + await expect(fixture.cdkDeploy('lambda', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + '--context', '@aws-cdk/core:newStyleStackSynthesis=1', + ], + })).rejects.toThrow('exited with error'); + })); +} integTest('can create a legacy bootstrap stack with --public-access-block-configuration=false', withDefaultFixture(async (fixture) => { const bootstrapStackName = fixture.bootstrapStackName; diff --git a/packages/aws-cdk/test/integ/cli/cli.integtest.ts b/packages/aws-cdk/test/integ/cli/cli.integtest.ts index 873e3a1787ee5..127734a136491 100644 --- a/packages/aws-cdk/test/integ/cli/cli.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/cli.integtest.ts @@ -2,7 +2,7 @@ import { promises as fs } from 'fs'; import * as os from 'os'; import * as path from 'path'; import { retry, sleep } from '../helpers/aws'; -import { cloneDirectory, shell, withDefaultFixture } from '../helpers/cdk'; +import { cloneDirectory, MAJOR_VERSION, shell, withDefaultFixture } from '../helpers/cdk'; import { integTest } from '../helpers/test-helpers'; jest.setTimeout(600 * 1000); @@ -40,7 +40,7 @@ integTest('Termination protection', withDefaultFixture(async (fixture) => { integTest('cdk synth', withDefaultFixture(async (fixture) => { await fixture.cdk(['synth', fixture.fullStackName('test-1')]); - expect(fixture.template('test-1')).toEqual({ + expect(fixture.template('test-1')).toEqual(expect.objectContaining({ Resources: { topic69831491: { Type: 'AWS::SNS::Topic', @@ -49,10 +49,10 @@ integTest('cdk synth', withDefaultFixture(async (fixture) => { }, }, }, - }); + })); await fixture.cdk(['synth', fixture.fullStackName('test-2')], { verbose: false }); - expect(fixture.template('test-2')).toEqual({ + expect(fixture.template('test-2')).toEqual(expect.objectContaining({ Resources: { topic152D84A37: { Type: 'AWS::SNS::Topic', @@ -67,8 +67,7 @@ integTest('cdk synth', withDefaultFixture(async (fixture) => { }, }, }, - }); - + })); })); integTest('ssm parameter provider error', withDefaultFixture(async (fixture) => { @@ -223,12 +222,12 @@ integTest('deploy with parameters', withDefaultFixture(async (fixture) => { StackName: stackArn, }); - expect(response.Stacks?.[0].Parameters).toEqual([ + expect(response.Stacks?.[0].Parameters).toContainEqual( { ParameterKey: 'TopicNameParam', ParameterValue: `${fixture.stackNamePrefix}bazinga`, }, - ]); + ); })); integTest('update to stack in ROLLBACK_COMPLETE state will delete stack and create a new one', withDefaultFixture(async (fixture) => { @@ -262,12 +261,12 @@ integTest('update to stack in ROLLBACK_COMPLETE state will delete stack and crea // THEN expect (stackArn).not.toEqual(newStackArn); // new stack was created expect(newStackResponse.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); - expect(newStackResponse.Stacks?.[0].Parameters).toEqual([ + expect(newStackResponse.Stacks?.[0].Parameters).toContainEqual( { ParameterKey: 'TopicNameParam', ParameterValue: `${fixture.stackNamePrefix}allgood`, }, - ]); + ); })); integTest('stack in UPDATE_ROLLBACK_COMPLETE state can be updated', withDefaultFixture(async (fixture) => { @@ -313,12 +312,12 @@ integTest('stack in UPDATE_ROLLBACK_COMPLETE state can be updated', withDefaultF // THEN expect(response.Stacks?.[0].StackStatus).toEqual('UPDATE_COMPLETE'); - expect(response.Stacks?.[0].Parameters).toEqual([ + expect(response.Stacks?.[0].Parameters).toContainEqual( { ParameterKey: 'TopicNameParam', ParameterValue: `${fixture.stackNamePrefix}allgood`, }, - ]); + ); })); integTest('deploy with wildcard and parameters', withDefaultFixture(async (fixture) => { @@ -348,16 +347,18 @@ integTest('deploy with parameters multi', withDefaultFixture(async (fixture) => StackName: stackArn, }); - expect(response.Stacks?.[0].Parameters).toEqual([ + expect(response.Stacks?.[0].Parameters).toContainEqual( { ParameterKey: 'DisplayNameParam', ParameterValue: paramVal1, }, + ); + expect(response.Stacks?.[0].Parameters).toContainEqual( { ParameterKey: 'OtherDisplayNameParam', ParameterValue: paramVal2, }, - ]); + ); })); integTest('deploy with notification ARN', withDefaultFixture(async (fixture) => { @@ -382,82 +383,86 @@ integTest('deploy with notification ARN', withDefaultFixture(async (fixture) => } })); -integTest('deploy with role', withDefaultFixture(async (fixture) => { - const roleName = `${fixture.stackNamePrefix}-test-role`; - - await deleteRole(); - - const createResponse = await fixture.aws.iam('createRole', { - RoleName: roleName, - AssumeRolePolicyDocument: JSON.stringify({ - Version: '2012-10-17', - Statement: [{ - Action: 'sts:AssumeRole', - Principal: { Service: 'cloudformation.amazonaws.com' }, - Effect: 'Allow', - }, { - Action: 'sts:AssumeRole', - Principal: { AWS: (await fixture.aws.sts('getCallerIdentity', {})).Arn }, - Effect: 'Allow', - }], - }), - }); - const roleArn = createResponse.Role.Arn; - try { - await fixture.aws.iam('putRolePolicy', { +if (MAJOR_VERSION === '1') { + // NOTE: this doesn't currently work with modern-style synthesis, as the bootstrap + // role by default will not have permission to iam:PassRole the created role. + integTest('deploy with role', withDefaultFixture(async (fixture) => { + const roleName = `${fixture.stackNamePrefix}-test-role`; + + await deleteRole(); + + const createResponse = await fixture.aws.iam('createRole', { RoleName: roleName, - PolicyName: 'DefaultPolicy', - PolicyDocument: JSON.stringify({ + AssumeRolePolicyDocument: JSON.stringify({ Version: '2012-10-17', Statement: [{ - Action: '*', - Resource: '*', + Action: 'sts:AssumeRole', + Principal: { Service: 'cloudformation.amazonaws.com' }, + Effect: 'Allow', + }, { + Action: 'sts:AssumeRole', + Principal: { AWS: (await fixture.aws.sts('getCallerIdentity', {})).Arn }, Effect: 'Allow', }], }), }); + const roleArn = createResponse.Role.Arn; + try { + await fixture.aws.iam('putRolePolicy', { + RoleName: roleName, + PolicyName: 'DefaultPolicy', + PolicyDocument: JSON.stringify({ + Version: '2012-10-17', + Statement: [{ + Action: '*', + Resource: '*', + Effect: 'Allow', + }], + }), + }); - await retry(fixture.output, 'Trying to assume fresh role', retry.forSeconds(300), async () => { - await fixture.aws.sts('assumeRole', { - RoleArn: roleArn, - RoleSessionName: 'testing', + await retry(fixture.output, 'Trying to assume fresh role', retry.forSeconds(300), async () => { + await fixture.aws.sts('assumeRole', { + RoleArn: roleArn, + RoleSessionName: 'testing', + }); }); - }); - // In principle, the role has replicated from 'us-east-1' to wherever we're testing. - // Give it a little more sleep to make sure CloudFormation is not hitting a box - // that doesn't have it yet. - await sleep(5000); + // In principle, the role has replicated from 'us-east-1' to wherever we're testing. + // Give it a little more sleep to make sure CloudFormation is not hitting a box + // that doesn't have it yet. + await sleep(5000); - await fixture.cdkDeploy('test-2', { - options: ['--role-arn', roleArn], - }); + await fixture.cdkDeploy('test-2', { + options: ['--role-arn', roleArn], + }); - // Immediately delete the stack again before we delete the role. - // - // Since roles are sticky, if we delete the role before the stack, subsequent DeleteStack - // operations will fail when CloudFormation tries to assume the role that's already gone. - await fixture.cdkDestroy('test-2'); + // Immediately delete the stack again before we delete the role. + // + // Since roles are sticky, if we delete the role before the stack, subsequent DeleteStack + // operations will fail when CloudFormation tries to assume the role that's already gone. + await fixture.cdkDestroy('test-2'); - } finally { - await deleteRole(); - } + } finally { + await deleteRole(); + } - async function deleteRole() { - try { - for (const policyName of (await fixture.aws.iam('listRolePolicies', { RoleName: roleName })).PolicyNames) { - await fixture.aws.iam('deleteRolePolicy', { - RoleName: roleName, - PolicyName: policyName, - }); + async function deleteRole() { + try { + for (const policyName of (await fixture.aws.iam('listRolePolicies', { RoleName: roleName })).PolicyNames) { + await fixture.aws.iam('deleteRolePolicy', { + RoleName: roleName, + PolicyName: policyName, + }); + } + await fixture.aws.iam('deleteRole', { RoleName: roleName }); + } catch (e) { + if (e.message.indexOf('cannot be found') > -1) { return; } + throw e; } - await fixture.aws.iam('deleteRole', { RoleName: roleName }); - } catch (e) { - if (e.message.indexOf('cannot be found') > -1) { return; } - throw e; } - } -})); + })); +} integTest('cdk diff', withDefaultFixture(async (fixture) => { const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); @@ -734,7 +739,7 @@ integTest('templates on disk contain metadata resource, also in nested assemblie expect(JSON.parse(templateContents).Resources.CDKMetadata).toBeTruthy(); // Load template from nested assembly - const nestedTemplateContents = await fixture.shell(['cat', 'cdk.out/assembly-*-stage/*-stage-StackInStage.template.json']); + const nestedTemplateContents = await fixture.shell(['cat', 'cdk.out/assembly-*-stage/*StackInStage*.template.json']); expect(JSON.parse(nestedTemplateContents).Resources.CDKMetadata).toBeTruthy(); })); diff --git a/packages/aws-cdk/test/integ/helpers/cdk.ts b/packages/aws-cdk/test/integ/helpers/cdk.ts index 4a09f43063600..edf996ba36ed6 100644 --- a/packages/aws-cdk/test/integ/helpers/cdk.ts +++ b/packages/aws-cdk/test/integ/helpers/cdk.ts @@ -12,10 +12,23 @@ const REGIONS = process.env.AWS_REGIONS ? process.env.AWS_REGIONS.split(',') : [process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? 'us-east-1']; -const FRAMEWORK_VERSION = process.env.FRAMEWORK_VERSION; +const FRAMEWORK_VERSION = process.env.FRAMEWORK_VERSION ?? '*'; + +export let MAJOR_VERSION = FRAMEWORK_VERSION.split('.')[0]; +if (MAJOR_VERSION === '*') { + if (process.env.REPO_ROOT) { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const releaseJson = require(path.resolve(process.env.REPO_ROOT, 'release.json')); + MAJOR_VERSION = `${releaseJson.majorVersion}`; + } else { + // eslint-disable-next-line no-console + console.error('[WARNING] Have to guess at major version. Guessing version 1 to not break anything, but this should not happen'); + MAJOR_VERSION = '1'; + } +} process.stdout.write(`Using regions: ${REGIONS}\n`); -process.stdout.write(`Using framework version: ${FRAMEWORK_VERSION}\n`); +process.stdout.write(`Using framework version: ${FRAMEWORK_VERSION} (major version ${MAJOR_VERSION})\n`); const REGION_POOL = new ResourcePool(REGIONS); @@ -63,17 +76,26 @@ export function withCdkApp(block: (context: let success = true; try { - const version = FRAMEWORK_VERSION ?? '*'; - await installNpmPackages(fixture, { - '@aws-cdk/core': version, - '@aws-cdk/aws-sns': version, - '@aws-cdk/aws-iam': version, - '@aws-cdk/aws-lambda': version, - '@aws-cdk/aws-ssm': version, - '@aws-cdk/aws-ecr-assets': version, - '@aws-cdk/aws-cloudformation': version, - '@aws-cdk/aws-ec2': version, - }); + const installationVersion = FRAMEWORK_VERSION; + + if (MAJOR_VERSION === '1') { + await installNpmPackages(fixture, { + '@aws-cdk/core': installationVersion, + '@aws-cdk/aws-sns': installationVersion, + '@aws-cdk/aws-iam': installationVersion, + '@aws-cdk/aws-lambda': installationVersion, + '@aws-cdk/aws-ssm': installationVersion, + '@aws-cdk/aws-ecr-assets': installationVersion, + '@aws-cdk/aws-cloudformation': installationVersion, + '@aws-cdk/aws-ec2': installationVersion, + 'constructs': '^3', + }); + } else { + await installNpmPackages(fixture, { + 'aws-cdk-lib': installationVersion, + 'constructs': '^10', + }); + } await ensureBootstrapped(fixture); @@ -377,6 +399,7 @@ export class TestFixture { AWS_REGION: this.aws.region, AWS_DEFAULT_REGION: this.aws.region, STACK_NAME_PREFIX: this.stackNamePrefix, + PACKAGE_LAYOUT_VERSION: MAJOR_VERSION, ...options.modEnv, }, }); diff --git a/packages/aws-cdk/test/integ/run-against-dist.bash b/packages/aws-cdk/test/integ/run-against-dist.bash index 0d5dc245df5e7..84162031f5068 100644 --- a/packages/aws-cdk/test/integ/run-against-dist.bash +++ b/packages/aws-cdk/test/integ/run-against-dist.bash @@ -4,6 +4,8 @@ npmws=/tmp/cdk-rundist rm -rf $npmws mkdir -p $npmws +set -x + # This script must create 1 or 2 traps, and the 'trap' command will replace # the previous trap, so get some 'dynamic traps' mechanism in place TRAPS=()