From 3adf84188947eb2fde6171f70d0d9c2dcdb78563 Mon Sep 17 00:00:00 2001 From: Peter Woodworth <44349620+peterwoodworth@users.noreply.github.com> Date: Thu, 1 Sep 2022 21:42:23 -0700 Subject: [PATCH 01/26] fix(stepfunctions): cfnSpec breaks definitionSubstitutions prop (#21887) Patches cfnspec to allow `CfnStateMachine.DefinitionSubstitutions` to synthesize again fixes: #21653 ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../000_cfn/904_StateMachine_Types_patch.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/904_StateMachine_Types_patch.json diff --git a/packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/904_StateMachine_Types_patch.json b/packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/904_StateMachine_Types_patch.json new file mode 100644 index 0000000000000..b5ca514fa9f4a --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/specification/000_cfn/904_StateMachine_Types_patch.json @@ -0,0 +1,16 @@ +{ + "ResourceTypes": { + "AWS::StepFunctions::StateMachine": { + "patch": { + "description": "Revert primitive item type on DefinitionSubstitutions property to string", + "operations": [ + { + "op": "replace", + "path": "/Properties/DefinitionSubstitutions/PrimitiveItemType", + "value": "String" + } + ] + } + } + } +} From 572f7815cc5447aac9413b374ebbfd92bfa610a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florin=20As=C4=83voaie?= Date: Fri, 2 Sep 2022 08:27:01 +0300 Subject: [PATCH 02/26] feat(ecs): add function for adding secrets to containers after instantiating them (#21826) ### Description Similar to `addEnvironment()`, an `addSecret()` method is useful to add secrets to ECS Containers after instantiating them via the constructor. ### Use Case The most important use-case is when writing Task Definition Extensions or Aspects to augment ECS services. For example, setting environment variables and secrets for a logging or monitoring solution. Right now, this can be done only using Escape Hatches and there is no higher level functionality to obtain this behaviour. ### Proposed Solution ```typescript const container = taskDefinition.addContainer('nginx', { image: ecs.ContainerImage.fromRegistry('nginx'), }); container.addSecret('SECRET_1', ecs.Secret.fromSecretsManager(secret)); container.addSecret('SECRET_2', ecs.Secret.fromSecretsManager(secretField, 'password')); ``` closes #18959 ---- ### All Submissions: * [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [X] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [X] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *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-ecs/README.md | 2 + .../aws-ecs/lib/base/task-definition.ts | 12 ++-- .../aws-ecs/lib/container-definition.ts | 45 +++++++----- .../aws-ecs/lib/fargate/fargate-service.ts | 13 ++-- packages/@aws-cdk/aws-ecs/package.json | 1 + .../aws-ecs/test/container-definition.test.ts | 21 +++++- .../test/ec2/integ.secret-json-field.ts | 9 ++- ...ws-ecs-integ-secret-json-field.assets.json | 6 +- ...-ecs-integ-secret-json-field.template.json | 14 ++++ ...efaultTestDeployAssert5B8058F0.assets.json | 19 ++++++ ...aultTestDeployAssert5B8058F0.template.json | 36 ++++++++++ .../secret-json-field.integ.snapshot/cdk.out | 2 +- .../integ.json | 12 ++-- .../manifest.json | 56 ++++++++++++++- .../tree.json | 68 ++++++++++++++++--- .../test/fargate/fargate-service.test.ts | 17 +++-- .../aws-ecs/test/fargate/integ.secret.ts | 9 ++- .../aws-ecs-integ-secret.assets.json | 6 +- .../aws-ecs-integ-secret.template.json | 14 ++++ ...efaultTestDeployAssert3A9C52E8.assets.json | 19 ++++++ ...aultTestDeployAssert3A9C52E8.template.json | 36 ++++++++++ .../fargate/secret.integ.snapshot/cdk.out | 2 +- .../fargate/secret.integ.snapshot/integ.json | 12 ++-- .../secret.integ.snapshot/manifest.json | 56 ++++++++++++++- .../fargate/secret.integ.snapshot/tree.json | 68 ++++++++++++++++--- 25 files changed, 469 insertions(+), 86 deletions(-) create mode 100644 packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.assets.json create mode 100644 packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.template.json create mode 100644 packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.assets.json create mode 100644 packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.template.json diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index aa83bf6c8f91b..51677a4c6fca5 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -479,6 +479,8 @@ const newContainer = taskDefinition.addContainer('container', { }, }); newContainer.addEnvironment('QUEUE_NAME', 'MyQueue'); +newContainer.addSecret('API_KEY', ecs.Secret.fromSecretsManager(secret)); +newContainer.addSecret('DB_PASSWORD', ecs.Secret.fromSecretsManager(secret, 'password')); ``` The task execution role is automatically granted read permissions on the secrets/parameters. Support for environment diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index 445b789b945d1..849948add5f13 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -379,8 +379,6 @@ export class TaskDefinition extends TaskDefinitionBase { private _passRoleStatement?: iam.PolicyStatement; - private _referencesSecretJsonField?: boolean; - private runtimePlatform?: RuntimePlatform; /** @@ -614,9 +612,6 @@ export class TaskDefinition extends TaskDefinitionBase { if (this.defaultContainer === undefined && container.essential) { this.defaultContainer = container; } - if (container.referencesSecretJsonField) { - this._referencesSecretJsonField = true; - } } /** @@ -695,7 +690,12 @@ export class TaskDefinition extends TaskDefinitionBase { * specific JSON field of a secret stored in Secrets Manager. */ public get referencesSecretJsonField(): boolean | undefined { - return this._referencesSecretJsonField; + for (const container of this.containers) { + if (container.referencesSecretJsonField) { + return true; + } + } + return false; } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index a5ac9e9b0cdf6..2e322f84e37cc 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -435,12 +435,6 @@ export class ContainerDefinition extends Construct { */ public readonly logDriverConfig?: LogDriverConfig; - /** - * Whether this container definition references a specific JSON field of a secret - * stored in Secrets Manager. - */ - public readonly referencesSecretJsonField?: boolean; - /** * The name of the image referenced by this container. */ @@ -458,7 +452,7 @@ export class ContainerDefinition extends Construct { private readonly imageConfig: ContainerImageConfig; - private readonly secrets?: CfnTaskDefinition.SecretProperty[]; + private readonly secrets: CfnTaskDefinition.SecretProperty[] = []; private readonly environment: { [key: string]: string }; @@ -486,16 +480,8 @@ export class ContainerDefinition extends Construct { } if (props.secrets) { - this.secrets = []; for (const [name, secret] of Object.entries(props.secrets)) { - if (secret.hasField) { - this.referencesSecretJsonField = true; - } - secret.grantRead(this.taskDefinition.obtainExecutionRole()); - this.secrets.push({ - name, - valueFrom: secret.arn, - }); + this.addSecret(name, secret); } } @@ -602,6 +588,18 @@ export class ContainerDefinition extends Construct { this.environment[name] = value; } + /** + * This method adds a secret as environment variable to the container. + */ + public addSecret(name: string, secret: Secret) { + secret.grantRead(this.taskDefinition.obtainExecutionRole()); + + this.secrets.push({ + name, + valueFrom: secret.arn, + }); + } + /** * This method adds one or more resources to the container. */ @@ -658,6 +656,19 @@ export class ContainerDefinition extends Construct { return undefined; } + /** + * Whether this container definition references a specific JSON field of a secret + * stored in Secrets Manager. + */ + public get referencesSecretJsonField(): boolean | undefined { + for (const secret of this.secrets) { + if (secret.valueFrom.endsWith('::')) { + return true; + } + } + return false; + } + /** * The inbound rules associated with the security group the task or service will use. * @@ -726,7 +737,7 @@ export class ContainerDefinition extends Construct { logConfiguration: this.logDriverConfig, environment: this.environment && Object.keys(this.environment).length ? renderKV(this.environment, 'name', 'value') : undefined, environmentFiles: this.environmentFiles && renderEnvironmentFiles(cdk.Stack.of(this).partition, this.environmentFiles), - secrets: this.secrets, + secrets: this.secrets.length ? this.secrets : undefined, extraHosts: this.props.extraHosts && renderKV(this.props.extraHosts, 'hostname', 'ipAddress'), healthCheck: this.props.healthCheck && renderHealthCheck(this.props.healthCheck), links: cdk.Lazy.list({ produce: () => this.links }, { omitEmpty: true }), diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index f0fcedbebe1f3..f008703635117 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -128,11 +128,6 @@ export class FargateService extends BaseService implements IFargateService { throw new Error('Only one of SecurityGroup or SecurityGroups can be populated.'); } - if (props.taskDefinition.referencesSecretJsonField - && props.platformVersion - && SECRET_JSON_FIELD_UNSUPPORTED_PLATFORM_VERSIONS.includes(props.platformVersion)) { - throw new Error(`The task definition of this service uses at least one container that references a secret JSON field. This feature requires platform version ${FargatePlatformVersion.VERSION1_4} or later.`); - } super(scope, id, { ...props, desiredCount: props.desiredCount, @@ -154,6 +149,14 @@ export class FargateService extends BaseService implements IFargateService { this.configureAwsVpcNetworkingWithSecurityGroups(props.cluster.vpc, props.assignPublicIp, props.vpcSubnets, securityGroups); + this.node.addValidation({ + validate: () => this.taskDefinition.referencesSecretJsonField + && props.platformVersion + && SECRET_JSON_FIELD_UNSUPPORTED_PLATFORM_VERSIONS.includes(props.platformVersion) + ? [`The task definition of this service uses at least one container that references a secret JSON field. This feature requires platform version ${FargatePlatformVersion.VERSION1_4} or later.`] + : [], + }); + this.node.addValidation({ validate: () => !this.taskDefinition.defaultContainer ? ['A TaskDefinition must have at least one essential container'] : [], }); diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index 01761ef396236..30b1cdc481c89 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -85,6 +85,7 @@ "@aws-cdk/aws-efs": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", + "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", diff --git a/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts b/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts index 1765d9a403466..97ef453ffd2b6 100644 --- a/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/container-definition.test.ts @@ -548,7 +548,7 @@ describe('container definition', () => { const actual = container.containerPort; const expected = 8080; expect(actual).toEqual(expected); - }).toThrow(/Container MyContainer hasn't defined any ports. Call addPortMappings()./); + }).toThrow(/Container MyContainer hasn't defined any ports. Call addPortMappings\(\)./); }); @@ -597,7 +597,7 @@ describe('container definition', () => { const actual = container.ingressPort; const expected = 8080; expect(actual).toEqual(expected); - }).toThrow(/Container MyContainer hasn't defined any ports. Call addPortMappings()./); + }).toThrow(/Container MyContainer hasn't defined any ports. Call addPortMappings\(\)./); }); @@ -1258,7 +1258,7 @@ describe('container definition', () => { }); // WHEN - taskDefinition.addContainer('cont', { + const container = taskDefinition.addContainer('cont', { image: ecs.ContainerImage.fromRegistry('test'), memoryLimitMiB: 1024, secrets: { @@ -1268,6 +1268,7 @@ describe('container definition', () => { SECRET_STAGE: ecs.Secret.fromSecretsManagerVersion(secret, { versionStage: 'version-stage' }), }, }); + container.addSecret('LATER_SECRET', ecs.Secret.fromSecretsManager(secret, 'field')); // THEN Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { @@ -1331,6 +1332,20 @@ describe('container definition', () => { ], }, }, + { + Name: 'LATER_SECRET', + ValueFrom: { + 'Fn::Join': [ + '', + [ + { + Ref: 'SecretA720EF05', + }, + ':field::', + ], + ], + }, + }, ], }), ], diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.secret-json-field.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.secret-json-field.ts index be876a08b1bdf..ef974361f8699 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.secret-json-field.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.secret-json-field.ts @@ -1,5 +1,6 @@ import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as cdk from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; import * as ecs from '../../lib'; const app = new cdk.App(); @@ -14,7 +15,7 @@ const secret = new secretsmanager.Secret(stack, 'Secret', { const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); -taskDefinition.addContainer('web', { +const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), memoryLimitMiB: 256, secrets: { @@ -22,4 +23,10 @@ taskDefinition.addContainer('web', { }, }); +container.addSecret('APIKEY', ecs.Secret.fromSecretsManager(secret, 'apikey')); + +new integ.IntegTest(app, 'aws-ecs-ec2-integ-secret-json-field', { + testCases: [stack], +}); + app.synth(); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/aws-ecs-integ-secret-json-field.assets.json b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/aws-ecs-integ-secret-json-field.assets.json index 5087539c5599b..fa1ab61842680 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/aws-ecs-integ-secret-json-field.assets.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/aws-ecs-integ-secret-json-field.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "b5f76aca81afb4973dcb922e825af78755db9cb1eb4fe457a9cd07c05e8bf0c7": { + "df25aa5385ee86c1e4753a1f126bc9ed3bd97ffd266ccb980e6bc49e8c32f124": { "source": { "path": "aws-ecs-integ-secret-json-field.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "b5f76aca81afb4973dcb922e825af78755db9cb1eb4fe457a9cd07c05e8bf0c7.json", + "objectKey": "df25aa5385ee86c1e4753a1f126bc9ed3bd97ffd266ccb980e6bc49e8c32f124.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/aws-ecs-integ-secret-json-field.template.json b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/aws-ecs-integ-secret-json-field.template.json index 033945b9f3c0f..67ea93f408b8b 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/aws-ecs-integ-secret-json-field.template.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/aws-ecs-integ-secret-json-field.template.json @@ -51,6 +51,20 @@ ] ] } + }, + { + "Name": "APIKEY", + "ValueFrom": { + "Fn::Join": [ + "", + [ + { + "Ref": "SecretA720EF05" + }, + ":apikey::" + ] + ] + } } ] } diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.assets.json b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.assets.json new file mode 100644 index 0000000000000..4c5cfd4feffd3 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.template.json b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/integ.json b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/integ.json index 6f1c8277322e1..80e588cf4745e 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/integ.json @@ -1,14 +1,12 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { - "integ.secret-json-field": { + "aws-ecs-ec2-integ-secret-json-field/DefaultTest": { "stacks": [ "aws-ecs-integ-secret-json-field" ], - "diffAssets": false, - "stackUpdateWorkflow": true + "assertionStack": "aws-ecs-ec2-integ-secret-json-field/DefaultTest/DeployAssert", + "assertionStackName": "awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0" } - }, - "synthContext": {}, - "enableLookups": false + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/manifest.json index c76a5c855aac8..916e064e6a35e 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b5f76aca81afb4973dcb922e825af78755db9cb1eb4fe457a9cd07c05e8bf0c7.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/df25aa5385ee86c1e4753a1f126bc9ed3bd97ffd266ccb980e6bc49e8c32f124.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -54,7 +54,10 @@ "/aws-ecs-integ-secret-json-field/TaskDef/Resource": [ { "type": "aws:cdk:logicalId", - "data": "TaskDef54694570" + "data": "TaskDef54694570", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_REPLACE" + ] } ], "/aws-ecs-integ-secret-json-field/TaskDef/ExecutionRole/Resource": [ @@ -83,6 +86,53 @@ ] }, "displayName": "aws-ecs-integ-secret-json-field" + }, + "awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "awsecsec2integsecretjsonfieldDefaultTestDeployAssert5B8058F0.assets" + ], + "metadata": { + "/aws-ecs-ec2-integ-secret-json-field/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-ecs-ec2-integ-secret-json-field/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-ecs-ec2-integ-secret-json-field/DefaultTest/DeployAssert" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/tree.json b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/tree.json index 88c9546e5a064..03a72862ac7a2 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/secret-json-field.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.92" } }, "aws-ecs-integ-secret-json-field": { @@ -108,6 +108,20 @@ ] ] } + }, + { + "name": "APIKEY", + "valueFrom": { + "Fn::Join": [ + "", + [ + { + "Ref": "SecretA720EF05" + }, + ":apikey::" + ] + ] + } } ] } @@ -132,7 +146,7 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-ecs.CfnTaskDefinition", + "fqn": "@aws-cdk/core.CfnResource", "version": "0.0.0" } }, @@ -140,8 +154,8 @@ "id": "web", "path": "aws-ecs-integ-secret-json-field/TaskDef/web", "constructInfo": { - "fqn": "@aws-cdk/aws-ecs.ContainerDefinition", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.1.92" } }, "ExecutionRole": { @@ -225,20 +239,56 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-ecs.Ec2TaskDefinition", + "fqn": "@aws-cdk/core.Resource", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "aws-ecs-ec2-integ-secret-json-field": { + "id": "aws-ecs-ec2-integ-secret-json-field", + "path": "aws-ecs-ec2-integ-secret-json-field", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "aws-ecs-ec2-integ-secret-json-field/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "aws-ecs-ec2-integ-secret-json-field/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "aws-ecs-ec2-integ-secret-json-field/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index 6d6c433d12f07..1bc1bea59ef92 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -587,13 +587,16 @@ describe('fargate service', () => { }, }); + // Errors on validation, not on construction. + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + platformVersion: ecs.FargatePlatformVersion.VERSION1_3, + }); + // THEN expect(() => { - new ecs.FargateService(stack, 'FargateService', { - cluster, - taskDefinition, - platformVersion: ecs.FargatePlatformVersion.VERSION1_3, - }); + Template.fromStack(stack); }).toThrow(new RegExp(`uses at least one container that references a secret JSON field.+platform version ${ecs.FargatePlatformVersion.VERSION1_4} or later`)); }); @@ -1262,7 +1265,7 @@ describe('fargate service', () => { containerPort: 8001, })], }); - }).toThrow(/No container named 'SideContainer'. Did you call "addContainer()"?/); + }).toThrow(/No container named 'SideContainer'. Did you call "addContainer\(\)"?/); }); }); @@ -2178,7 +2181,7 @@ describe('fargate service', () => { ], }); }); - }), + }); test('with serviceArn old format', () => { // GIVEN diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.secret.ts b/packages/@aws-cdk/aws-ecs/test/fargate/integ.secret.ts index 4e3599145dffd..362fe03e6fe89 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.secret.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.secret.ts @@ -1,5 +1,6 @@ import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as cdk from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; import * as ecs from '../../lib'; const app = new cdk.App(); @@ -14,7 +15,7 @@ const secret = new secretsmanager.Secret(stack, 'Secret', { const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); -taskDefinition.addContainer('web', { +const container = taskDefinition.addContainer('web', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), secrets: { SECRET: ecs.Secret.fromSecretsManager(secret), @@ -22,4 +23,10 @@ taskDefinition.addContainer('web', { }, }); +container.addSecret('APIKEY', ecs.Secret.fromSecretsManager(secret, 'apikey')); + +new integ.IntegTest(app, 'aws-ecs-fargate-integ-secret', { + testCases: [stack], +}); + app.synth(); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/aws-ecs-integ-secret.assets.json b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/aws-ecs-integ-secret.assets.json index 8cf9acb514687..4b09c2ff522c0 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/aws-ecs-integ-secret.assets.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/aws-ecs-integ-secret.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { - "1b68069d3b5ede524bed6fa719e69d034d31abf105b94943e5f9af1a18a44d3e": { + "4727087105a7e5f98c1d902ecfbeea59f5f9ae4a5dfef11195a6fa3e21f5e4f5": { "source": { "path": "aws-ecs-integ-secret.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "1b68069d3b5ede524bed6fa719e69d034d31abf105b94943e5f9af1a18a44d3e.json", + "objectKey": "4727087105a7e5f98c1d902ecfbeea59f5f9ae4a5dfef11195a6fa3e21f5e4f5.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/aws-ecs-integ-secret.template.json b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/aws-ecs-integ-secret.template.json index 5366e273ea8e7..728deafad878f 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/aws-ecs-integ-secret.template.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/aws-ecs-integ-secret.template.json @@ -56,6 +56,20 @@ ] ] } + }, + { + "Name": "APIKEY", + "ValueFrom": { + "Fn::Join": [ + "", + [ + { + "Ref": "SecretA720EF05" + }, + ":apikey::" + ] + ] + } } ] } diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.assets.json b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.assets.json new file mode 100644 index 0000000000000..e23e2af26082d --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.template.json b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/cdk.out index 588d7b269d34f..8ecc185e9dbee 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/integ.json b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/integ.json index 83717f26eb26a..562e992fb9ded 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/integ.json @@ -1,14 +1,12 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { - "integ.secret": { + "aws-ecs-fargate-integ-secret/DefaultTest": { "stacks": [ "aws-ecs-integ-secret" ], - "diffAssets": false, - "stackUpdateWorkflow": true + "assertionStack": "aws-ecs-fargate-integ-secret/DefaultTest/DeployAssert", + "assertionStackName": "awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8" } - }, - "synthContext": {}, - "enableLookups": false + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/manifest.json index 0bbc9a0397352..b2764bf286d5e 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/1b68069d3b5ede524bed6fa719e69d034d31abf105b94943e5f9af1a18a44d3e.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/4727087105a7e5f98c1d902ecfbeea59f5f9ae4a5dfef11195a6fa3e21f5e4f5.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -54,7 +54,10 @@ "/aws-ecs-integ-secret/TaskDef/Resource": [ { "type": "aws:cdk:logicalId", - "data": "TaskDef54694570" + "data": "TaskDef54694570", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_REPLACE" + ] } ], "/aws-ecs-integ-secret/TaskDef/ExecutionRole/Resource": [ @@ -83,6 +86,53 @@ ] }, "displayName": "aws-ecs-integ-secret" + }, + "awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "awsecsfargateintegsecretDefaultTestDeployAssert3A9C52E8.assets" + ], + "metadata": { + "/aws-ecs-fargate-integ-secret/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-ecs-fargate-integ-secret/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-ecs-fargate-integ-secret/DefaultTest/DeployAssert" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/tree.json b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/tree.json index 1cac8c24b1aae..ccb3f56d8d49e 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/secret.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.92" } }, "aws-ecs-integ-secret": { @@ -113,6 +113,20 @@ ] ] } + }, + { + "name": "APIKEY", + "valueFrom": { + "Fn::Join": [ + "", + [ + { + "Ref": "SecretA720EF05" + }, + ":apikey::" + ] + ] + } } ] } @@ -139,7 +153,7 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-ecs.CfnTaskDefinition", + "fqn": "@aws-cdk/core.CfnResource", "version": "0.0.0" } }, @@ -147,8 +161,8 @@ "id": "web", "path": "aws-ecs-integ-secret/TaskDef/web", "constructInfo": { - "fqn": "@aws-cdk/aws-ecs.ContainerDefinition", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.1.92" } }, "ExecutionRole": { @@ -232,20 +246,56 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-ecs.FargateTaskDefinition", + "fqn": "@aws-cdk/core.Resource", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "aws-ecs-fargate-integ-secret": { + "id": "aws-ecs-fargate-integ-secret", + "path": "aws-ecs-fargate-integ-secret", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "aws-ecs-fargate-integ-secret/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "aws-ecs-fargate-integ-secret/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "aws-ecs-fargate-integ-secret/DefaultTest/DeployAssert", + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file From 5a3db2d19dc5525bfef568f17fffa09657b6ef21 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy <36202692+kaizencc@users.noreply.github.com> Date: Fri, 2 Sep 2022 02:05:55 -0400 Subject: [PATCH 03/26] feat: compress aws-cdk-lib tablet file (#21854) This is part of the Reduce Module Size [project](https://github.com/aws/aws-cdk/projects/15). Most of the heavy lifting is done in jsii, this PR simply turns on the feature. I have tested this in our test pipeline and can confirm that the build output from there can be successfully ingested by contruct hub with transliterated examples in every language. Afaik, the tablet file included in the `aws-cdk-lib` package is _only_ for construct hub, because reproducing the tablet file would take ~3 hours. Title is a `feat` so that it shows up in the changelog; there are no unit or integ tests that can be run to demonstrate it is working. ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- scripts/run-rosetta.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/run-rosetta.sh b/scripts/run-rosetta.sh index e59e1e75e1f8d..c315861841144 100755 --- a/scripts/run-rosetta.sh +++ b/scripts/run-rosetta.sh @@ -68,6 +68,7 @@ echo "💎 Extracting code samples" >&2 time $ROSETTA extract \ --compile \ --verbose \ + --compress-tablet \ --cache ${rosetta_cache_file} \ --directory packages/aws-cdk-lib \ ${extract_opts} \ @@ -81,6 +82,7 @@ if $infuse; then time $ROSETTA extract \ --compile \ --verbose \ + --compress-tablet \ --cache ${rosetta_cache_file} \ --directory packages/aws-cdk-lib \ $(cat $jsii_pkgs_file) From 80cb527c01173a060064606b8fe286d5510f145e Mon Sep 17 00:00:00 2001 From: Daniel Neilson <53624638+ddneilson@users.noreply.github.com> Date: Fri, 2 Sep 2022 17:07:32 -0500 Subject: [PATCH 04/26] feat(assertions): add function for verifying the number of matching resource properties (#21707) This PR adds the `Template.resourcePropertiesCountIs()` method for counting the number of resources of a specified type whose `Properties` section matches given properties. Implements: https://github.com/aws/aws-cdk/issues/21706 ---- ### All Submissions: * [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/assertions/README.md | 11 +++ .../assertions/lib/private/resources.ts | 26 +++++- packages/@aws-cdk/assertions/lib/template.ts | 16 +++- .../@aws-cdk/assertions/test/template.test.ts | 87 +++++++++++++++++++ 4 files changed, 137 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/assertions/README.md b/packages/@aws-cdk/assertions/README.md index 01b93e7055664..83f86fe6e2f95 100644 --- a/packages/@aws-cdk/assertions/README.md +++ b/packages/@aws-cdk/assertions/README.md @@ -76,6 +76,17 @@ in a template. template.resourceCountIs('Foo::Bar', 2); ``` +You can also count the number of resources of a specific type whose `Properties` +section contains the specified properties: + +```ts +template.resourcePropertiesCountIs('Foo::Bar', { + Foo: 'Bar', + Baz: 5, + Qux: [ 'Waldo', 'Fred' ], +}, 1); +``` + ## Resource Matching & Retrieval Beyond resource counting, the module also allows asserting that a resource with diff --git a/packages/@aws-cdk/assertions/lib/private/resources.ts b/packages/@aws-cdk/assertions/lib/private/resources.ts index 00a57c05f9b26..1312a5bfa4ed7 100644 --- a/packages/@aws-cdk/assertions/lib/private/resources.ts +++ b/packages/@aws-cdk/assertions/lib/private/resources.ts @@ -32,11 +32,12 @@ export function hasResource(template: Template, type: string, props: any): strin } export function hasResourceProperties(template: Template, type: string, props: any): string | void { - // amended needs to be a deep copy to avoid modifying the template. - let amended = JSON.parse(JSON.stringify(template)); + let amended = template; // special case to exclude AbsentMatch because adding an empty Properties object will affect its evaluation. if (!Matcher.isMatcher(props) || !(props instanceof AbsentMatch)) { + // amended needs to be a deep copy to avoid modifying the template. + amended = JSON.parse(JSON.stringify(template)); amended = addEmptyProperties(amended); } @@ -52,6 +53,27 @@ export function countResources(template: Template, type: string): number { return Object.entries(types).length; } +export function countResourcesProperties(template: Template, type: string, props: any): number { + let amended = template; + + // special case to exclude AbsentMatch because adding an empty Properties object will affect its evaluation. + if (!Matcher.isMatcher(props) || !(props instanceof AbsentMatch)) { + // amended needs to be a deep copy to avoid modifying the template. + amended = JSON.parse(JSON.stringify(template)); + amended = addEmptyProperties(amended); + } + + const section = amended.Resources ?? {}; + const result = matchSection(filterType(section, type), Match.objectLike({ + Properties: props, + })); + + if (result.match) { + return Object.keys(result.matches).length; + } + return 0; +} + function addEmptyProperties(template: Template): Template { let section = template.Resources ?? {}; diff --git a/packages/@aws-cdk/assertions/lib/template.ts b/packages/@aws-cdk/assertions/lib/template.ts index 6399d3a971897..0dffd428da27a 100644 --- a/packages/@aws-cdk/assertions/lib/template.ts +++ b/packages/@aws-cdk/assertions/lib/template.ts @@ -8,7 +8,7 @@ import { checkTemplateForCyclicDependencies } from './private/cyclic'; import { findMappings, hasMapping } from './private/mappings'; import { findOutputs, hasOutput } from './private/outputs'; import { findParameters, hasParameter } from './private/parameters'; -import { countResources, findResources, hasResource, hasResourceProperties } from './private/resources'; +import { countResources, countResourcesProperties, findResources, hasResource, hasResourceProperties } from './private/resources'; import { Template as TemplateType } from './private/template'; /** @@ -71,6 +71,20 @@ export class Template { } } + /** + * Assert that the given number of resources of the given type and properties exists in the + * CloudFormation template. + * @param type the resource type; ex: `AWS::S3::Bucket` + * @param props the 'Properties' section of the resource as should be expected in the template. + * @param count number of expected instances + */ + public resourcePropertiesCountIs(type: string, props: any, count: number): void { + const counted = countResourcesProperties(this.template, type, props); + if (counted !== count) { + throw new Error(`Expected ${count} resources of type ${type} but found ${counted}`); + } + } + /** * Assert that a resource of the given type and properties exists in the * CloudFormation template. diff --git a/packages/@aws-cdk/assertions/test/template.test.ts b/packages/@aws-cdk/assertions/test/template.test.ts index c298f75cec161..bae0a27ce2b0d 100644 --- a/packages/@aws-cdk/assertions/test/template.test.ts +++ b/packages/@aws-cdk/assertions/test/template.test.ts @@ -124,6 +124,93 @@ describe('Template', () => { }); }); + describe('resourcePropertiesCountIs', () => { + test('resource exists', () => { + const stack = new Stack(); + new CfnResource(stack, 'Resource', { + type: 'Foo::Bar', + properties: { baz: 'qux' }, + }); + + const inspect = Template.fromStack(stack); + inspect.resourcePropertiesCountIs('Foo::Bar', { baz: 'qux' }, 1); + + expect(() => { + inspect.resourcePropertiesCountIs('Foo::Bar', { baz: 'qux' }, 0); + }).toThrow('Expected 0 resources of type Foo::Bar but found 1'); + expect(() => { + inspect.resourcePropertiesCountIs('Foo::Bar', { baz: 'qux' }, 2); + }).toThrow('Expected 2 resources of type Foo::Bar but found 1'); + expect(() => { + inspect.resourcePropertiesCountIs('Foo::Bar', { baz: 'nope' }, 1); + }).toThrow('Expected 1 resources of type Foo::Bar but found 0'); + expect(() => { + inspect.resourcePropertiesCountIs('Foo::Baz', { baz: 'qux' }, 1); + }).toThrow('Expected 1 resources of type Foo::Baz but found 0'); + }); + test('no resource', () => { + const stack = new Stack(); + + const inspect = Template.fromStack(stack); + inspect.resourcePropertiesCountIs('Foo::Bar', { baz: 'qux' }, 0); + + expect(() => { + inspect.resourcePropertiesCountIs('Foo::Bar', { baz: 'qux' }, 1); + }).toThrow('Expected 1 resources of type Foo::Bar but found 0'); + }); + test('absent - with properties', () => { + const stack = new Stack(); + new CfnResource(stack, 'Foo', { + type: 'Foo::Bar', + properties: { baz: 'qux' }, + }); + + const inspect = Template.fromStack(stack); + inspect.resourcePropertiesCountIs('Foo::Bar', { + bar: Match.absent(), + }, 1); + inspect.resourcePropertiesCountIs('Foo::Bar', { + baz: Match.absent(), + }, 0); + }); + test('absent - no properties', () => { + const stack = new Stack(); + new CfnResource(stack, 'Foo', { + type: 'Foo::Bar', + }); + + const inspect = Template.fromStack(stack); + inspect.resourcePropertiesCountIs('Foo::Bar', { + bar: Match.absent(), + baz: 'qux', + }, 0); + inspect.resourcePropertiesCountIs('Foo::Bar', Match.absent(), 1); + }); + test('not - with properties', () => { + const stack = new Stack(); + new CfnResource(stack, 'Foo', { + type: 'Foo::Bar', + properties: { baz: 'qux' }, + }); + + const inspect = Template.fromStack(stack); + inspect.resourcePropertiesCountIs('Foo::Bar', Match.not({ + baz: 'boo', + }), 1); + }); + test('not - no properties', () => { + const stack = new Stack(); + new CfnResource(stack, 'Foo', { + type: 'Foo::Bar', + }); + + const inspect = Template.fromStack(stack); + inspect.resourcePropertiesCountIs('Foo::Bar', Match.not({ + baz: 'qux', + }), 1); + }); + }); + describe('templateMatches', () => { test('matches', () => { const app = new App(); From 1bc4526261c2fbdd6ce6c371ba1d9da2f79e07bd Mon Sep 17 00:00:00 2001 From: John Mortlock Date: Sat, 3 Sep 2022 15:36:36 +0930 Subject: [PATCH 05/26] feat(batch): add propagate tags prop in job definition (#21904) Closes: #21740 Add support for propagateTags to the CDK construct. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-batch/lib/job-definition.ts | 15 +++- .../aws-batch/test/integ.job-definition.ts | 7 ++ .../BatchDefaultEnvVarsStack.assets.json | 4 +- .../BatchDefaultEnvVarsStack.template.json | 43 +++++++++ ...efaultTestDeployAssertC15EFFF2.assets.json | 4 +- ...aultTestDeployAssertC15EFFF2.template.json | 4 +- .../manifest.json | 10 ++- .../job-definition.integ.snapshot/tree.json | 87 +++++++++++++++++-- .../aws-batch/test/job-definition.test.ts | 15 ++++ 9 files changed, 175 insertions(+), 14 deletions(-) diff --git a/packages/@aws-cdk/aws-batch/lib/job-definition.ts b/packages/@aws-cdk/aws-batch/lib/job-definition.ts index 3666356c5b06f..6a412adfbd239 100644 --- a/packages/@aws-cdk/aws-batch/lib/job-definition.ts +++ b/packages/@aws-cdk/aws-batch/lib/job-definition.ts @@ -303,6 +303,18 @@ export interface JobDefinitionProps { * @default - EC2 */ readonly platformCapabilities?: PlatformCapabilities[]; + + /** + * Specifies whether to propagate the tags from the job or job definition to the corresponding Amazon ECS task. + * If no value is specified, the tags aren't propagated. + * Tags can only be propagated to the tasks during task creation. For tags with the same name, + * job tags are given priority over job definitions tags. + * If the total number of combined tags from the job and job definition is over 50, the job is moved to the `FAILED` state. + * + * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-jobdefinition.html#cfn-batch-jobdefinition-propagatetags + * @default - undefined + */ + readonly propagateTags?: boolean; } /** @@ -458,6 +470,7 @@ export class JobDefinition extends Resource implements IJobDefinition { attemptDurationSeconds: props.timeout ? props.timeout.toSeconds() : undefined, }, platformCapabilities: props.platformCapabilities ?? [PlatformCapabilities.EC2], + propagateTags: props.propagateTags, }); // add read secrets permission to execution role @@ -602,4 +615,4 @@ export class JobDefinition extends Resource implements IJobDefinition { }; }); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-batch/test/integ.job-definition.ts b/packages/@aws-cdk/aws-batch/test/integ.job-definition.ts index e83a95734e6d8..b4cfffc3d574b 100644 --- a/packages/@aws-cdk/aws-batch/test/integ.job-definition.ts +++ b/packages/@aws-cdk/aws-batch/test/integ.job-definition.ts @@ -14,6 +14,13 @@ new JobDefinition(stack, "JobDefinition", { }, }); +new JobDefinition(stack, "JobDefinitionTags", { + container: { + image: ContainerImage.fromRegistry("docker/whalesay"), + }, + propagateTags: true, +}); + const integ = new IntegTest(app, "IntegTest-BatchDefaultEnvVarsStack", { testCases: [stack], regions: ["us-east-1"], diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/BatchDefaultEnvVarsStack.assets.json b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/BatchDefaultEnvVarsStack.assets.json index 12e2c17d12ef1..b179fad6984b7 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/BatchDefaultEnvVarsStack.assets.json +++ b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/BatchDefaultEnvVarsStack.assets.json @@ -1,7 +1,7 @@ { "version": "21.0.0", "files": { - "0a50911921bcac3c1775e3a82fc9c4414e3f12ea24b1e2c73bf1fc4c625ee16c": { + "fa4cb3c0495612b2b5dea9687948520b07f859705061dad691367153183cbc90": { "source": { "path": "BatchDefaultEnvVarsStack.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "0a50911921bcac3c1775e3a82fc9c4414e3f12ea24b1e2c73bf1fc4c625ee16c.json", + "objectKey": "fa4cb3c0495612b2b5dea9687948520b07f859705061dad691367153183cbc90.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/BatchDefaultEnvVarsStack.template.json b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/BatchDefaultEnvVarsStack.template.json index 499ea08cc489a..6d794288a5646 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/BatchDefaultEnvVarsStack.template.json +++ b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/BatchDefaultEnvVarsStack.template.json @@ -41,6 +41,49 @@ }, "Timeout": {} } + }, + "JobDefinitionTags76FA063A": { + "Type": "AWS::Batch::JobDefinition", + "Properties": { + "Type": "container", + "ContainerProperties": { + "Environment": [ + { + "Name": "AWS_REGION", + "Value": { + "Ref": "AWS::Region" + } + }, + { + "Name": "AWS_ACCOUNT", + "Value": { + "Ref": "AWS::AccountId" + } + } + ], + "Image": "docker/whalesay", + "Privileged": false, + "ReadonlyRootFilesystem": false, + "ResourceRequirements": [ + { + "Type": "VCPU", + "Value": "1" + }, + { + "Type": "MEMORY", + "Value": "4" + } + ] + }, + "PlatformCapabilities": [ + "EC2" + ], + "PropagateTags": true, + "RetryStrategy": { + "Attempts": 1 + }, + "Timeout": {} + } } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2.assets.json b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2.assets.json index 7eb7d384ef06c..004a618fef948 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2.assets.json +++ b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2.assets.json @@ -14,7 +14,7 @@ } } }, - "de70b340a5b843502197c20700c9328cb928be11584f66762e1ac05dc5380bed": { + "674d442d4a977a672cccae088b5eabcf91e06ee46b2464e4437c0332e04cf1a8": { "source": { "path": "IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2.template.json", "packaging": "file" @@ -22,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "de70b340a5b843502197c20700c9328cb928be11584f66762e1ac05dc5380bed.json", + "objectKey": "674d442d4a977a672cccae088b5eabcf91e06ee46b2464e4437c0332e04cf1a8.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2.template.json b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2.template.json index be4d151ea7fed..0e6c0ecbbafc9 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2.template.json +++ b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/IntegTestBatchDefaultEnvVarsStackDefaultTestDeployAssertC15EFFF2.template.json @@ -15,7 +15,7 @@ "status": "ACTIVE" }, "flattenResponse": "true", - "salt": "1661947601911" + "salt": "1662181496271" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" @@ -36,7 +36,7 @@ ] }, "expected": "{\"$StringLike\":\"AWS_REGION\"}", - "salt": "1661947601912" + "salt": "1662181496272" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/manifest.json index 10e78afde8dab..19876e95fd885 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/manifest.json @@ -23,7 +23,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/0a50911921bcac3c1775e3a82fc9c4414e3f12ea24b1e2c73bf1fc4c625ee16c.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/fa4cb3c0495612b2b5dea9687948520b07f859705061dad691367153183cbc90.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -45,6 +45,12 @@ "data": "JobDefinition24FFE3ED" } ], + "/BatchDefaultEnvVarsStack/JobDefinitionTags/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "JobDefinitionTags76FA063A" + } + ], "/BatchDefaultEnvVarsStack/BootstrapVersion": [ { "type": "aws:cdk:logicalId", @@ -76,7 +82,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/de70b340a5b843502197c20700c9328cb928be11584f66762e1ac05dc5380bed.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/674d442d4a977a672cccae088b5eabcf91e06ee46b2464e4437c0332e04cf1a8.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/tree.json b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/tree.json index 27c2310208279..ee0b8dbfac82c 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-batch/test/job-definition.integ.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.92" } }, "BatchDefaultEnvVarsStack": { @@ -91,6 +91,83 @@ "fqn": "@aws-cdk/aws-batch.JobDefinition", "version": "0.0.0" } + }, + "JobDefinitionTags": { + "id": "JobDefinitionTags", + "path": "BatchDefaultEnvVarsStack/JobDefinitionTags", + "children": { + "Resource-Batch-Task-Definition-Role": { + "id": "Resource-Batch-Task-Definition-Role", + "path": "BatchDefaultEnvVarsStack/JobDefinitionTags/Resource-Batch-Task-Definition-Role", + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.LazyRole", + "version": "0.0.0" + } + }, + "Resource-Batch-Job-Container-Definition": { + "id": "Resource-Batch-Job-Container-Definition", + "path": "BatchDefaultEnvVarsStack/JobDefinitionTags/Resource-Batch-Job-Container-Definition", + "constructInfo": { + "fqn": "@aws-cdk/aws-ecs.ContainerDefinition", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "BatchDefaultEnvVarsStack/JobDefinitionTags/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Batch::JobDefinition", + "aws:cdk:cloudformation:props": { + "type": "container", + "containerProperties": { + "environment": [ + { + "name": "AWS_REGION", + "value": { + "Ref": "AWS::Region" + } + }, + { + "name": "AWS_ACCOUNT", + "value": { + "Ref": "AWS::AccountId" + } + } + ], + "image": "docker/whalesay", + "privileged": false, + "readonlyRootFilesystem": false, + "resourceRequirements": [ + { + "type": "VCPU", + "value": "1" + }, + { + "type": "MEMORY", + "value": "4" + } + ] + }, + "platformCapabilities": [ + "EC2" + ], + "propagateTags": true, + "retryStrategy": { + "attempts": 1 + }, + "timeout": {} + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-batch.CfnJobDefinition", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-batch.JobDefinition", + "version": "0.0.0" + } } }, "constructInfo": { @@ -111,7 +188,7 @@ "path": "IntegTest-BatchDefaultEnvVarsStack/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.92" } }, "DeployAssert": { @@ -131,7 +208,7 @@ "path": "IntegTest-BatchDefaultEnvVarsStack/DefaultTest/DeployAssert/AwsApiCallBatchdescribeJobDefinitions/SdkProvider/AssertionsProvider", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.92" } } }, @@ -171,7 +248,7 @@ "path": "IntegTest-BatchDefaultEnvVarsStack/DefaultTest/DeployAssert/AwsApiCallBatchdescribeJobDefinitions/AssertEqualsBatchdescribeJobDefinitions/AssertionProvider/AssertionsProvider", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.92" } } }, @@ -249,7 +326,7 @@ }, "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.92" } } }, diff --git a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts index 0174ad372b2ab..6793c0aee527d 100644 --- a/packages/@aws-cdk/aws-batch/test/job-definition.test.ts +++ b/packages/@aws-cdk/aws-batch/test/job-definition.test.ts @@ -284,6 +284,21 @@ describe('Batch Job Definition', () => { }); }); + test('Can propagate tags', () => { + // WHEN + new batch.JobDefinition(stack, 'job-def', { + container: { + image: ecs.ContainerImage.fromRegistry('docker/whalesay'), + }, + propagateTags: true, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Batch::JobDefinition', { + PropagateTags: true, + }); + }); + test('can use an ecr image', () => { // WHEN const repo = new ecr.Repository(stack, 'image-repo'); From e1794e346c2a04bf8f2e5f63138095a79f512cfe Mon Sep 17 00:00:00 2001 From: John Mortlock Date: Sat, 3 Sep 2022 17:04:11 +0930 Subject: [PATCH 06/26] feat(ec2): allow private non-nat subnets (#21699) ---- Closes: #21697 and might close #21699 Not all private subnets need to have a NAT gateway for egress; an example would be when using Transit Gateway. I have incorporated the idea expressed in https://github.com/aws/aws-cdk/issues/21189 to add a more generic `PRIVATE_WITH_EGRESS` subnet type. This PR is largely a rename and a small logic change in `determineNatGatewayCount` ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-apigatewayv2/lib/http/vpc-link.ts | 2 +- .../aws-appsync/test/appsync-rds.test.ts | 6 +- .../test/auto-scaling-group.test.ts | 2 +- .../test/compute-environment.test.ts | 2 +- packages/@aws-cdk/aws-cloud9/README.md | 2 +- .../test/cloud9.environment.test.ts | 4 +- .../@aws-cdk/aws-docdb/test/cluster.test.ts | 2 +- packages/@aws-cdk/aws-ec2/README.md | 13 +-- packages/@aws-cdk/aws-ec2/lib/util.ts | 1 + packages/@aws-cdk/aws-ec2/lib/vpc.ts | 49 ++++++++--- .../test/integ.reserved-private-subnet.ts | 2 +- .../aws-ec2/test/integ.vpc-networkacl.ts | 2 +- .../aws-ec2/test/vpc-endpoint.test.ts | 2 +- .../aws-ec2/test/vpc.from-lookup.test.ts | 2 +- packages/@aws-cdk/aws-ec2/test/vpc.test.ts | 85 ++++++++++++++++--- .../lib/base/scheduled-task-base.ts | 2 +- packages/@aws-cdk/aws-eks/README.md | 4 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 4 +- .../@aws-cdk/aws-eks/lib/fargate-profile.ts | 2 +- .../@aws-cdk/aws-eks/test/cluster.test.ts | 8 +- .../lib/load-balancer.ts | 4 +- .../test/loadbalancer.test.ts | 4 +- .../test/nlb/load-balancer.test.ts | 6 +- .../@aws-cdk/aws-elasticsearch/lib/domain.ts | 2 +- .../aws-events-targets/lib/ecs-task.ts | 2 +- .../test/kafka.test.ts | 18 ++-- .../aws-lambda/test/vpc-lambda.test.ts | 4 +- packages/@aws-cdk/aws-neptune/lib/cluster.ts | 2 +- .../@aws-cdk/aws-neptune/lib/subnet-group.ts | 2 +- .../@aws-cdk/aws-neptune/test/cluster.test.ts | 2 +- .../aws-neptune/test/integ.cluster.ts | 2 +- .../aws-neptune/test/subnet-group.test.ts | 2 +- .../aws-opensearchservice/lib/domain.ts | 2 +- packages/@aws-cdk/aws-rds/README.md | 10 +-- packages/@aws-cdk/aws-rds/lib/subnet-group.ts | 2 +- .../@aws-cdk/aws-rds/test/cluster.test.ts | 6 +- .../@aws-cdk/aws-rds/test/instance.test.ts | 8 +- .../aws-rds/test/subnet-group.test.ts | 4 +- packages/@aws-cdk/aws-redshift/lib/cluster.ts | 2 +- .../@aws-cdk/aws-redshift/lib/subnet-group.ts | 2 +- .../integ.interface-vpc-endpoint-target.ts | 2 +- .../lib/ecs/run-ecs-task-base.ts | 2 +- .../lib/ecs/run-task.ts | 3 +- .../test/provider-framework/provider.test.ts | 5 +- packages/@aws-cdk/pipelines/README.md | 4 +- 45 files changed, 191 insertions(+), 107 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts index c9a13e08d31ea..b27ca3cd21225 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts @@ -99,7 +99,7 @@ export class VpcLink extends Resource implements IVpcLink { this.vpcLinkId = cfnResource.ref; - const { subnets } = props.vpc.selectSubnets(props.subnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }); + const { subnets } = props.vpc.selectSubnets(props.subnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }); this.addSubnets(...subnets); if (props.securityGroups) { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts index c98b1bdedabab..2030c900d5977 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts @@ -36,7 +36,7 @@ describe('Rds Data Source configuration', () => { credentials: { username: 'clusteradmin' }, clusterIdentifier: 'db-endpoint-test', vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, securityGroups: [securityGroup], defaultDatabaseName: 'Animals', }); @@ -235,7 +235,7 @@ describe('adding rds data source from imported api', () => { credentials: { username: 'clusteradmin' }, clusterIdentifier: 'db-endpoint-test', vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, securityGroups: [securityGroup], defaultDatabaseName: 'Animals', }); @@ -269,4 +269,4 @@ describe('adding rds data source from imported api', () => { ApiId: { 'Fn::GetAtt': ['baseApiCDA4D43A', 'ApiId'] }, }); }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts index 0f836518ea204..9e1ab8c11850f 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts @@ -1825,7 +1825,7 @@ test('can use Vpc imported from unparseable list tokens', () => { vpc, allowAllOutbound: false, associatePublicIpAddress: false, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); // THEN diff --git a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts index 85c03a3c1e36f..bb1f54b6aed61 100644 --- a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts +++ b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts @@ -375,7 +375,7 @@ describe('Batch Compute Environment', () => { ], type: batch.ComputeResourceType.ON_DEMAND, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, } as batch.ComputeResources, enabled: false, diff --git a/packages/@aws-cdk/aws-cloud9/README.md b/packages/@aws-cdk/aws-cloud9/README.md index facf3c68a8ed3..84b00eb03218e 100644 --- a/packages/@aws-cdk/aws-cloud9/README.md +++ b/packages/@aws-cdk/aws-cloud9/README.md @@ -56,7 +56,7 @@ new cloud9.Ec2Environment(this, 'Cloud9Env2', { const c9env = new cloud9.Ec2Environment(this, 'Cloud9Env3', { vpc, subnetSelection: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, imageId: cloud9.ImageId.AMAZON_LINUX_2, }); diff --git a/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts b/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts index fb2f3bed3ef01..948cfa5bee9ec 100644 --- a/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts +++ b/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts @@ -28,7 +28,7 @@ test('create resource correctly with vpc, imageId, and subnetSelection', () => { new cloud9.Ec2Environment(stack, 'C9Env', { vpc, subnetSelection: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, imageId: cloud9.ImageId.AMAZON_LINUX_2, }); @@ -146,4 +146,4 @@ test.each([ ImageId: expected, SubnetId: Match.anyValue(), }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts index 0c553e51b04b9..9b2ba729a7934 100644 --- a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts @@ -108,7 +108,7 @@ describe('DatabaseCluster', () => { vpc, instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, }); }).toThrowError('Cluster requires at least 2 subnets, got 1'); diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 33529400c7f3c..ba6e78bcee75e 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -42,10 +42,11 @@ distinguishes three different subnet types: Internet Gateway. If you want your instances to have a public IP address and be directly reachable from the Internet, you must place them in a public subnet. -* **Private with Internet Access (`SubnetType.PRIVATE_WITH_NAT`)** - instances in private subnets are not directly routable from the - Internet, and connect out to the Internet via a NAT gateway. By default, a - NAT gateway is created in every public subnet for maximum availability. Be +* **Private with Internet Access (`SubnetType.PRIVATE_WITH_EGRESS`)** - instances in private subnets are not directly routable from the + Internet, and you must provide a way to connect out to the Internet. + By default, a NAT gateway is created in every public subnet for maximum availability. Be aware that you will be charged for NAT gateways. + Alternatively you can set `natGateways:0` and provide your own egress configuration (i.e through Transit Gateway) * **Isolated (`SubnetType.PRIVATE_ISOLATED`)** - isolated subnets do not route from or to the Internet, and as such do not require NAT gateways. They can only connect to or be connected to from other instances in the same VPC. A default VPC configuration @@ -260,7 +261,7 @@ const vpc = new ec2.Vpc(this, 'TheVPC', { { cidrMask: 24, name: 'Application', - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, { cidrMask: 28, @@ -363,12 +364,12 @@ const vpc = new ec2.Vpc(this, 'TheVPC', { { cidrMask: 26, name: 'Application1', - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, { cidrMask: 26, name: 'Application2', - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, reserved: true, // <---- This subnet group is reserved }, { diff --git a/packages/@aws-cdk/aws-ec2/lib/util.ts b/packages/@aws-cdk/aws-ec2/lib/util.ts index 9a1c572f02afe..8c54a0efbdb20 100644 --- a/packages/@aws-cdk/aws-ec2/lib/util.ts +++ b/packages/@aws-cdk/aws-ec2/lib/util.ts @@ -17,6 +17,7 @@ export function defaultSubnetName(type: SubnetType) { switch (type) { case SubnetType.PUBLIC: return 'Public'; case SubnetType.PRIVATE_WITH_NAT: + case SubnetType.PRIVATE_WITH_EGRESS: case SubnetType.PRIVATE: return 'Private'; case SubnetType.PRIVATE_ISOLATED: diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 093b2ba605d2b..c607ab434f112 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -188,6 +188,25 @@ export enum SubnetType { */ ISOLATED = 'Deprecated_Isolated', + /** + * Subnet that routes to the internet, but not vice versa. + * + * Instances in a private subnet can connect to the Internet, but will not + * allow connections to be initiated from the Internet. Egress to the internet will + * need to be provided. + * NAT Gateway(s) are the default solution to providing this subnet type the ability to route Internet traffic. + * If a NAT Gateway is not required or desired, set `natGateways:0` or use + * `SubnetType.PRIVATE_ISOLATED` instead. + * + * By default, a NAT gateway is created in every public subnet for maximum availability. + * Be aware that you will be charged for NAT gateways. + * + * Normally a Private subnet will use a NAT gateway in the same AZ, but + * if `natGateways` is used to reduce the number of NAT gateways, a NAT + * gateway from another AZ will be used instead. + */ + PRIVATE_WITH_EGRESS = 'Private', + /** * Subnet that routes to the internet (via a NAT gateway), but not vice versa. * @@ -202,8 +221,9 @@ export enum SubnetType { * Normally a Private subnet will use a NAT gateway in the same AZ, but * if `natGateways` is used to reduce the number of NAT gateways, a NAT * gateway from another AZ will be used instead. + * @deprecated use `PRIVATE_WITH_EGRESS` */ - PRIVATE_WITH_NAT = 'Private', + PRIVATE_WITH_NAT = 'Deprecated_Private_NAT', /** * Subnet that routes to the internet, but not vice versa. @@ -220,7 +240,7 @@ export enum SubnetType { * if `natGateways` is used to reduce the number of NAT gateways, a NAT * gateway from another AZ will be used instead. * - * @deprecated use `PRIVATE_WITH_NAT` + * @deprecated use `PRIVATE_WITH_EGRESS` */ PRIVATE = 'Deprecated_Private', @@ -251,7 +271,7 @@ export interface SubnetSelection { * * At most one of `subnetType` and `subnetGroupName` can be supplied. * - * @default SubnetType.PRIVATE_WITH_NAT (or ISOLATED or PUBLIC if there are no PRIVATE_WITH_NAT subnets) + * @default SubnetType.PRIVATE_WITH_EGRESS (or ISOLATED or PUBLIC if there are no PRIVATE_WITH_EGRESS subnets) */ readonly subnetType?: SubnetType; @@ -552,7 +572,7 @@ abstract class VpcBase extends Resource implements IVpc { subnets = this.selectSubnetObjectsByName(selection.subnetGroupName); } else { // Or specify by type - const type = selection.subnetType || SubnetType.PRIVATE_WITH_NAT; + const type = selection.subnetType || SubnetType.PRIVATE_WITH_EGRESS; subnets = this.selectSubnetObjectsByType(type); } @@ -588,6 +608,7 @@ abstract class VpcBase extends Resource implements IVpc { [SubnetType.PRIVATE_ISOLATED]: this.isolatedSubnets, [SubnetType.ISOLATED]: this.isolatedSubnets, [SubnetType.PRIVATE_WITH_NAT]: this.privateSubnets, + [SubnetType.PRIVATE_WITH_EGRESS]: this.privateSubnets, [SubnetType.PRIVATE]: this.privateSubnets, [SubnetType.PUBLIC]: this.publicSubnets, }; @@ -631,7 +652,7 @@ abstract class VpcBase extends Resource implements IVpc { if (placement.subnetType === undefined && placement.subnetGroupName === undefined && placement.subnets === undefined) { // Return default subnet type based on subnets that actually exist let subnetType = this.privateSubnets.length - ? SubnetType.PRIVATE_WITH_NAT : this.isolatedSubnets.length ? SubnetType.PRIVATE_ISOLATED : SubnetType.PUBLIC; + ? SubnetType.PRIVATE_WITH_EGRESS : this.isolatedSubnets.length ? SubnetType.PRIVATE_ISOLATED : SubnetType.PUBLIC; placement = { ...placement, subnetType: subnetType }; } @@ -915,7 +936,7 @@ export interface VpcProps { * { * cidrMask: 24, * name: 'application', - * subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + * subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, * }, * { * cidrMask: 28, @@ -1067,7 +1088,7 @@ export interface SubnetConfiguration { * * // Iterate the private subnets * const selection = vpc.selectSubnets({ - * subnetType: ec2.SubnetType.PRIVATE_WITH_NAT + * subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS * }); * * for (const subnet of selection.subnets) { @@ -1096,8 +1117,8 @@ export class Vpc extends VpcBase { name: defaultSubnetName(SubnetType.PUBLIC), }, { - subnetType: SubnetType.PRIVATE_WITH_NAT, - name: defaultSubnetName(SubnetType.PRIVATE_WITH_NAT), + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: defaultSubnetName(SubnetType.PRIVATE_WITH_EGRESS), }, ]; @@ -1531,6 +1552,7 @@ export class Vpc extends VpcBase { this.publicSubnets.push(publicSubnet); subnet = publicSubnet; break; + case SubnetType.PRIVATE_WITH_EGRESS: case SubnetType.PRIVATE_WITH_NAT: case SubnetType.PRIVATE: const privateSubnet = new PrivateSubnet(this, name, subnetProps); @@ -1561,6 +1583,7 @@ const SUBNETNAME_TAG = 'aws-cdk:subnet-name'; function subnetTypeTagValue(type: SubnetType) { switch (type) { case SubnetType.PUBLIC: return 'Public'; + case SubnetType.PRIVATE_WITH_EGRESS: case SubnetType.PRIVATE_WITH_NAT: case SubnetType.PRIVATE: return 'Private'; @@ -2007,7 +2030,7 @@ class ImportedVpc extends VpcBase { /* eslint-disable max-len */ const pub = new ImportSubnetGroup(props.publicSubnetIds, props.publicSubnetNames, props.publicSubnetRouteTableIds, SubnetType.PUBLIC, this.availabilityZones, 'publicSubnetIds', 'publicSubnetNames', 'publicSubnetRouteTableIds'); - const priv = new ImportSubnetGroup(props.privateSubnetIds, props.privateSubnetNames, props.privateSubnetRouteTableIds, SubnetType.PRIVATE_WITH_NAT, this.availabilityZones, 'privateSubnetIds', 'privateSubnetNames', 'privateSubnetRouteTableIds'); + const priv = new ImportSubnetGroup(props.privateSubnetIds, props.privateSubnetNames, props.privateSubnetRouteTableIds, SubnetType.PRIVATE_WITH_EGRESS, this.availabilityZones, 'privateSubnetIds', 'privateSubnetNames', 'privateSubnetRouteTableIds'); const iso = new ImportSubnetGroup(props.isolatedSubnetIds, props.isolatedSubnetNames, props.isolatedSubnetRouteTableIds, SubnetType.PRIVATE_ISOLATED, this.availabilityZones, 'isolatedSubnetIds', 'isolatedSubnetNames', 'isolatedSubnetRouteTableIds'); /* eslint-enable max-len */ @@ -2207,13 +2230,13 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat * They seem pointless but I see no reason to prevent it. */ function determineNatGatewayCount(requestedCount: number | undefined, subnetConfig: SubnetConfiguration[], azCount: number) { - const hasPrivateSubnets = subnetConfig.some(c => (c.subnetType === SubnetType.PRIVATE_WITH_NAT - || c.subnetType === SubnetType.PRIVATE) && !c.reserved); + const hasPrivateSubnets = subnetConfig.some(c => (c.subnetType === SubnetType.PRIVATE_WITH_EGRESS + || c.subnetType === SubnetType.PRIVATE || c.subnetType === SubnetType.PRIVATE_WITH_NAT) && !c.reserved); const hasPublicSubnets = subnetConfig.some(c => c.subnetType === SubnetType.PUBLIC); const count = requestedCount !== undefined ? Math.min(requestedCount, azCount) : (hasPrivateSubnets ? azCount : 0); - if (count === 0 && hasPrivateSubnets) { + if (count === 0 && hasPrivateSubnets && subnetConfig.some(c => c.subnetType === SubnetType.PRIVATE_WITH_NAT)) { // eslint-disable-next-line max-len throw new Error('If you do not want NAT gateways (natGateways=0), make sure you don\'t configure any PRIVATE subnets in \'subnetConfiguration\' (make them PUBLIC or ISOLATED instead)'); } diff --git a/packages/@aws-cdk/aws-ec2/test/integ.reserved-private-subnet.ts b/packages/@aws-cdk/aws-ec2/test/integ.reserved-private-subnet.ts index 76c0b35eb2d8e..a19e9864b257e 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.reserved-private-subnet.ts +++ b/packages/@aws-cdk/aws-ec2/test/integ.reserved-private-subnet.ts @@ -27,7 +27,7 @@ class VpcReservedPrivateSubnetStack extends cdk.Stack { }, { name: 'private', - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, reserved: true, }, ], diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-networkacl.ts b/packages/@aws-cdk/aws-ec2/test/integ.vpc-networkacl.ts index 348f18be18ed2..faf569911d390 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.vpc-networkacl.ts +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-networkacl.ts @@ -10,7 +10,7 @@ const vpc = new ec2.Vpc(stack, 'MyVpc'); const nacl1 = new ec2.NetworkAcl(stack, 'myNACL1', { vpc, - subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); nacl1.addEntry('AllowDNSEgress', { diff --git a/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts index df65f805670c8..e508f19054e75 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts @@ -63,7 +63,7 @@ describe('vpc endpoint', () => { subnetType: SubnetType.PUBLIC, }, { - subnetType: SubnetType.PRIVATE_WITH_NAT, + subnetType: SubnetType.PRIVATE_WITH_EGRESS, }, ], }, 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 30c0ab1cf2a80..0d133e9673fb2 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 @@ -165,7 +165,7 @@ describe('vpc from lookup', () => { }); // WHEN - const subnets = vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_WITH_NAT, onePerAz: true }); + const subnets = vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_WITH_EGRESS, onePerAz: true }); // THEN: we got 2 subnets and not 4 expect(subnets.subnets.map(s => s.availabilityZone)).toEqual(['us-east-1c', 'us-east-1d']); diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts index 27d3875f5fde6..b12eb7c788b07 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts @@ -31,6 +31,43 @@ import { describe('vpc', () => { describe('When creating a VPC', () => { + test('SubnetType.PRIVATE_WITH_NAT is equivalent to SubnetType.PRIVATE_WITH_EGRESS', () => { + + const stack1 = getTestStack(); + const stack2 = getTestStack(); + new Vpc(stack1, 'TheVPC', { + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_NAT, + name: 'subnet', + }, + { + subnetType: SubnetType.PUBLIC, + name: 'public', + }, + ], + }); + + new Vpc(stack2, 'TheVPC', { + subnetConfiguration: [ + { + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + name: 'subnet', + }, + { + subnetType: SubnetType.PUBLIC, + name: 'public', + }, + ], + }); + + const t1 = Template.fromStack(stack1); + const t2 = Template.fromStack(stack2); + + expect(t1.toJSON()).toEqual(t2.toJSON()); + + }); + test('SubnetType.PRIVATE is equivalent to SubnetType.PRIVATE_WITH_NAT', () => { const stack1 = getTestStack(); @@ -254,7 +291,7 @@ describe('vpc', () => { name: 'Public', }, { - subnetType: SubnetType.PRIVATE_WITH_NAT, + subnetType: SubnetType.PRIVATE_WITH_EGRESS, name: 'private', }, ], @@ -262,7 +299,7 @@ describe('vpc', () => { const nacl1 = new NetworkAcl(stack, 'myNACL1', { vpc, - subnetSelection: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + subnetSelection: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, }); new NetworkAclEntry(stack, 'AllowDNSEgress', { @@ -738,7 +775,7 @@ describe('vpc', () => { }); - test('natGateways = 0 throws if no PRIVATE subnets configured', () => { + test('natGateways = 0 throws if PRIVATE_WITH_NAT subnets configured', () => { const stack = getTestStack(); expect(() => { new Vpc(stack, 'VPC', { @@ -759,6 +796,28 @@ describe('vpc', () => { }); + test('natGateways = 0 succeeds if PRIVATE_WITH_EGRESS subnets configured', () => { + const stack = getTestStack(); + + new Vpc(stack, 'VPC', { + natGateways: 0, + subnetConfiguration: [ + { + name: 'public', + subnetType: SubnetType.PUBLIC, + }, + { + name: 'private', + subnetType: SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + Template.fromStack(stack).resourceCountIs('AWS::EC2::InternetGateway', 1); + Template.fromStack(stack).resourceCountIs('AWS::EC2::NatGateway', 0); + + }); + test('natGateway = 0 defaults with ISOLATED subnet', () => { const stack = getTestStack(); new Vpc(stack, 'VPC', { @@ -792,7 +851,7 @@ describe('vpc', () => { }, { name: 'private', - subnetType: SubnetType.PRIVATE_WITH_NAT, + subnetType: SubnetType.PRIVATE_WITH_EGRESS, reserved: true, }, ], @@ -818,7 +877,7 @@ describe('vpc', () => { { cidrMask: 24, name: 'application', - subnetType: SubnetType.PRIVATE_WITH_NAT, + subnetType: SubnetType.PRIVATE_WITH_EGRESS, }, ], natGatewayProvider: NatProvider.gateway({ eipAllocationIds: ['b'] }), @@ -842,7 +901,7 @@ describe('vpc', () => { { cidrMask: 24, name: 'private', - subnetType: SubnetType.PRIVATE_WITH_NAT, + subnetType: SubnetType.PRIVATE_WITH_EGRESS, }, ], natGatewaySubnets: { @@ -930,13 +989,13 @@ describe('vpc', () => { new Vpc(stack, 'VPC', { subnetConfiguration: [ { subnetType: SubnetType.PUBLIC, name: 'Public' }, - { subnetType: SubnetType.PRIVATE_WITH_NAT, name: 'Private' }, + { subnetType: SubnetType.PRIVATE_WITH_EGRESS, name: 'Private' }, { subnetType: SubnetType.PRIVATE_ISOLATED, name: 'Isolated' }, ], vpnGateway: true, vpnRoutePropagation: [ { - subnetType: SubnetType.PRIVATE_WITH_NAT, + subnetType: SubnetType.PRIVATE_WITH_EGRESS, }, { subnetType: SubnetType.PRIVATE_ISOLATED, @@ -1528,7 +1587,7 @@ describe('vpc', () => { const vpc = new Vpc(stack, 'VPC', { subnetConfiguration: [ { subnetType: SubnetType.PUBLIC, name: 'BlaBla' }, - { subnetType: SubnetType.PRIVATE_WITH_NAT, name: 'DontTalkToMe' }, + { subnetType: SubnetType.PRIVATE_WITH_EGRESS, name: 'DontTalkToMe' }, { subnetType: SubnetType.PRIVATE_ISOLATED, name: 'DontTalkAtAll' }, ], }); @@ -1547,7 +1606,7 @@ describe('vpc', () => { const vpc = new Vpc(stack, 'VPC', { subnetConfiguration: [ { subnetType: SubnetType.PUBLIC, name: 'BlaBla' }, - { subnetType: SubnetType.PRIVATE_WITH_NAT, name: 'DontTalkToMe' }, + { subnetType: SubnetType.PRIVATE_WITH_EGRESS, name: 'DontTalkToMe' }, { subnetType: SubnetType.PRIVATE_ISOLATED, name: 'DontTalkAtAll' }, ], }); @@ -1615,8 +1674,8 @@ describe('vpc', () => { maxAzs: 1, subnetConfiguration: [ { name: 'lb', subnetType: SubnetType.PUBLIC }, - { name: 'app', subnetType: SubnetType.PRIVATE_WITH_NAT }, - { name: 'db', subnetType: SubnetType.PRIVATE_WITH_NAT }, + { name: 'app', subnetType: SubnetType.PRIVATE_WITH_EGRESS }, + { name: 'db', subnetType: SubnetType.PRIVATE_WITH_EGRESS }, ], }); @@ -1783,7 +1842,7 @@ describe('vpc', () => { privateDnsEnabled: false, service: new InterfaceVpcEndpointService('com.amazonaws.vpce.us-east-1.vpce-svc-uuddlrlrbastrtsvc', 443), subnets: { - subnetType: SubnetType.PRIVATE_WITH_NAT, + subnetType: SubnetType.PRIVATE_WITH_EGRESS, availabilityZones: ['dummy1a', 'dummy1c'], }, }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts index 568898a3080dd..342e4fa1e08c3 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/scheduled-task-base.ts @@ -157,7 +157,7 @@ export abstract class ScheduledTaskBase extends Construct { throw new Error('You must specify a desiredTaskCount greater than 0'); } this.desiredTaskCount = props.desiredTaskCount || 1; - this.subnetSelection = props.subnetSelection || { subnetType: SubnetType.PRIVATE_WITH_NAT }; + this.subnetSelection = props.subnetSelection || { subnetType: SubnetType.PRIVATE_WITH_EGRESS }; this._securityGroups = props.securityGroups; // An EventRule that describes the event trigger (in this case a scheduled run) diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 271b42e78b517..62d76939194af 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -535,7 +535,7 @@ From the docs: > * It satisfies Kubernetes Service resources by provisioning Network Load Balancers. To deploy the controller on your EKS cluster, configure the `albController` property: - + ```ts new eks.Cluster(this, 'HelloEKS', { version: eks.KubernetesVersion.V1_21, @@ -580,7 +580,7 @@ declare const vpc: ec2.Vpc; new eks.Cluster(this, 'HelloEKS', { version: eks.KubernetesVersion.V1_21, vpc, - vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }], + vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }], }); ``` diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 5fd70cd5b1d45..c3a71fdb2256f 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -401,7 +401,7 @@ export interface CommonClusterOptions { * * For example, to only select private subnets, supply the following: * - * `vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }]` + * `vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }]` * * @default - All public and private subnets */ @@ -1337,7 +1337,7 @@ export class Cluster extends ClusterBase { description: 'EKS Control Plane Security Group', }); - this.vpcSubnets = props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }]; + this.vpcSubnets = props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }]; const selectedSubnetIdsPerGroup = this.vpcSubnets.map(s => this.vpc.selectSubnets(s).subnetIds); if (selectedSubnetIdsPerGroup.some(Token.isUnresolved) && selectedSubnetIdsPerGroup.length > 1) { diff --git a/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts b/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts index 1a22af23266b2..8d2c9de6f97e6 100644 --- a/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts +++ b/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts @@ -161,7 +161,7 @@ export class FargateProfile extends Construct implements ITaggable { let subnets: string[] | undefined; if (props.vpc) { - const selection: ec2.SubnetSelection = props.subnetSelection ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }; + const selection: ec2.SubnetSelection = props.subnetSelection ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }; subnets = props.vpc.selectSubnets(selection).subnetIds; } diff --git a/packages/@aws-cdk/aws-eks/test/cluster.test.ts b/packages/@aws-cdk/aws-eks/test/cluster.test.ts index 1f6ba84fc20fa..5232e647c3a53 100644 --- a/packages/@aws-cdk/aws-eks/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks/test/cluster.test.ts @@ -132,7 +132,7 @@ describe('cluster', () => { test('throws if selecting more than one subnet group', () => { expect(() => new eks.Cluster(stack, 'Cluster', { vpc: vpc, - vpcSubnets: [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }], + vpcSubnets: [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }], defaultCapacity: 0, version: eks.KubernetesVersion.V1_21, })).toThrow(/cannot select multiple subnet groups/); @@ -2798,7 +2798,7 @@ describe('cluster', () => { natGateways: 1, subnetConfiguration: [ { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, name: 'Private1', }, { @@ -2855,7 +2855,7 @@ describe('cluster', () => { for (let i = 0; i < 20; i++) { subnetConfiguration.push({ - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, name: `Private${i}`, }, ); @@ -2904,7 +2904,7 @@ describe('cluster', () => { for (let i = 0; i < 20; i++) { subnetConfiguration.push({ - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, name: `Private${i}`, }, ); diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts index 830373ec6cd95..e64d631ddf242 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts @@ -477,7 +477,7 @@ function loadBalancerSubnets(props: LoadBalancerProps): SelectedSubnets { }); } else { return props.vpc.selectSubnets({ - subnetType: SubnetType.PRIVATE_WITH_NAT, + subnetType: SubnetType.PRIVATE_WITH_EGRESS, }); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts index cc6c50ab8df8f..14003c49cf36e 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts @@ -151,12 +151,12 @@ describe('tests', () => { }, { name: 'private1', - subnetType: SubnetType.PRIVATE_WITH_NAT, + subnetType: SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 21, }, { name: 'private2', - subnetType: SubnetType.PRIVATE_WITH_NAT, + subnetType: SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 21, }, ], diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts index cb04a4c9058fb..5b9d4f76ce6cd 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts @@ -433,7 +433,7 @@ describe('tests', () => { }, { cidrMask: 24, name: 'Private', - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, { cidrMask: 28, name: 'Isolated', @@ -468,7 +468,7 @@ describe('tests', () => { }, { cidrMask: 24, name: 'Private', - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, { cidrMask: 28, name: 'Isolated', @@ -525,7 +525,7 @@ describe('tests', () => { }, { cidrMask: 24, name: 'Private', - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, { cidrMask: 28, name: 'Isolated', diff --git a/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts b/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts index e48d794670c0d..3aca6a16eb716 100644 --- a/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts +++ b/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts @@ -1502,7 +1502,7 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { let subnets: ec2.ISubnet[] | undefined; if (props.vpc) { - subnets = selectSubnets(props.vpc, props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }]); + subnets = selectSubnets(props.vpc, props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }]); securityGroups = props.securityGroups ?? [new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc, description: `Security group for domain ${this.node.id}`, diff --git a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts index 4a695f1259796..c4736c0acb338 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts @@ -160,7 +160,7 @@ export class EcsTask implements events.IRuleTarget { const taskCount = this.taskCount; const taskDefinitionArn = this.taskDefinition.taskDefinitionArn; - const subnetSelection = this.props.subnetSelection || { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }; + const subnetSelection = this.props.subnetSelection || { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }; const assignPublicIp = subnetSelection.subnetType === ec2.SubnetType.PUBLIC ? 'ENABLED' : 'DISABLED'; const baseEcsParameters = { taskCount, taskDefinitionArn }; diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts index fed711acb9b53..3d0924e26de67 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts @@ -240,7 +240,7 @@ describe('KafkaEventSource', () => { topic: kafkaTopic, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, securityGroup: sg, })); @@ -300,7 +300,7 @@ describe('KafkaEventSource', () => { secret: secret, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, securityGroup: sg, })); @@ -411,7 +411,7 @@ describe('KafkaEventSource', () => { secret: secret, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, })); }).toThrow(/securityGroup must be set/); @@ -437,7 +437,7 @@ describe('KafkaEventSource', () => { secret: secret, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, securityGroup: sg, authenticationMethod: sources.AuthenticationMethod.SASL_SCRAM_256_AUTH, })); @@ -472,7 +472,7 @@ describe('KafkaEventSource', () => { secret: secret, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, securityGroup: sg, authenticationMethod: sources.AuthenticationMethod.BASIC_AUTH, })); @@ -507,7 +507,7 @@ describe('KafkaEventSource', () => { secret: secret, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, securityGroup: sg, authenticationMethod: sources.AuthenticationMethod.CLIENT_CERTIFICATE_TLS_AUTH, })); @@ -543,7 +543,7 @@ describe('KafkaEventSource', () => { secret: secret, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, securityGroup: sg, authenticationMethod: sources.AuthenticationMethod.CLIENT_CERTIFICATE_TLS_AUTH, rootCACertificate: rootCACertificate, @@ -584,7 +584,7 @@ describe('KafkaEventSource', () => { topic: kafkaTopic, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, securityGroup: sg, rootCACertificate: rootCACertificate, })); @@ -619,7 +619,7 @@ describe('KafkaEventSource', () => { topic: kafkaTopic, startingPosition: lambda.StartingPosition.TRIM_HORIZON, vpc: vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, securityGroup: sg, rootCACertificate: rootCACertificate, })); diff --git a/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts index bdb742462194c..7efe2dfceab49 100644 --- a/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts @@ -238,7 +238,7 @@ describe('lambda + vpc', () => { handler: 'index.handler', runtime: lambda.Runtime.NODEJS_14_X, vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); // THEN @@ -303,7 +303,7 @@ describe('lambda + vpc', () => { }, { name: 'Private', - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, { name: 'Isolated', diff --git a/packages/@aws-cdk/aws-neptune/lib/cluster.ts b/packages/@aws-cdk/aws-neptune/lib/cluster.ts index eb40b6ec3589b..afbcf91d73059 100644 --- a/packages/@aws-cdk/aws-neptune/lib/cluster.ts +++ b/packages/@aws-cdk/aws-neptune/lib/cluster.ts @@ -439,7 +439,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu super(scope, id); this.vpc = props.vpc; - this.vpcSubnets = props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }; + this.vpcSubnets = props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }; // Determine the subnet(s) to deploy the Neptune cluster to const { subnetIds, internetConnectivityEstablished } = this.vpc.selectSubnets(this.vpcSubnets); diff --git a/packages/@aws-cdk/aws-neptune/lib/subnet-group.ts b/packages/@aws-cdk/aws-neptune/lib/subnet-group.ts index d46c0163cfa34..1141460a94238 100644 --- a/packages/@aws-cdk/aws-neptune/lib/subnet-group.ts +++ b/packages/@aws-cdk/aws-neptune/lib/subnet-group.ts @@ -74,7 +74,7 @@ export class SubnetGroup extends Resource implements ISubnetGroup { constructor(scope: Construct, id: string, props: SubnetGroupProps) { super(scope, id); - const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }); + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }); const subnetGroup = new CfnDBSubnetGroup(this, 'Resource', { dbSubnetGroupDescription: props.description || 'Subnet group for Neptune', diff --git a/packages/@aws-cdk/aws-neptune/test/cluster.test.ts b/packages/@aws-cdk/aws-neptune/test/cluster.test.ts index 4bf730e8aa8d0..212a344d56131 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-neptune/test/cluster.test.ts @@ -91,7 +91,7 @@ describe('DatabaseCluster', () => { instances: 1, vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, instanceType: InstanceType.R5_LARGE, }); diff --git a/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts b/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts index 1df64b274c226..890270388ac3c 100644 --- a/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts @@ -30,7 +30,7 @@ class TestStack extends cdk.Stack { const cluster = new DatabaseCluster(this, 'Database', { vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, instanceType: InstanceType.R5_LARGE, clusterParameterGroup: params, kmsKey, diff --git a/packages/@aws-cdk/aws-neptune/test/subnet-group.test.ts b/packages/@aws-cdk/aws-neptune/test/subnet-group.test.ts index b1b355b6369bd..f2dcb268fceaf 100644 --- a/packages/@aws-cdk/aws-neptune/test/subnet-group.test.ts +++ b/packages/@aws-cdk/aws-neptune/test/subnet-group.test.ts @@ -31,7 +31,7 @@ test('creates a subnet group from all properties', () => { description: 'My Shared Group', subnetGroupName: 'SharedGroup', vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); Template.fromStack(stack).hasResourceProperties('AWS::Neptune::DBSubnetGroup', { diff --git a/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts b/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts index 4d51c6c176c6e..06d450320890f 100644 --- a/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts +++ b/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts @@ -1236,7 +1236,7 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { let subnets: ec2.ISubnet[] | undefined; if (props.vpc) { - subnets = selectSubnets(props.vpc, props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }]); + subnets = selectSubnets(props.vpc, props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }]); securityGroups = props.securityGroups ?? [new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc, description: `Security group for domain ${this.node.id}`, diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 813a5f33dcca4..263ff91a6c08f 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -31,7 +31,7 @@ const cluster = new rds.DatabaseCluster(this, 'Database', { // optional , defaults to t3.medium instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, vpc, }, @@ -99,7 +99,7 @@ const instance = new rds.DatabaseInstance(this, 'Instance', { credentials: rds.Credentials.fromGeneratedSecret('syscdk'), // Optional - will default to 'admin' username and generated password vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, } }); ``` @@ -175,7 +175,7 @@ new rds.DatabaseInstance(this, 'Instance', { }), vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, publiclyAccessible: true, }); @@ -186,7 +186,7 @@ new rds.DatabaseCluster(this, 'DatabaseCluster', { instanceProps: { vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, publiclyAccessible: true, }, @@ -355,7 +355,7 @@ declare const instance: rds.DatabaseInstance; declare const myEndpoint: ec2.InterfaceVpcEndpoint; instance.addRotationSingleUser({ - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, // Place rotation Lambda in private subnets + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, // Place rotation Lambda in private subnets endpoint: myEndpoint, // Use VPC interface endpoint }); ``` diff --git a/packages/@aws-cdk/aws-rds/lib/subnet-group.ts b/packages/@aws-cdk/aws-rds/lib/subnet-group.ts index c1fcd071ff154..1bc903331bcf3 100644 --- a/packages/@aws-cdk/aws-rds/lib/subnet-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/subnet-group.ts @@ -72,7 +72,7 @@ export class SubnetGroup extends Resource implements ISubnetGroup { constructor(scope: Construct, id: string, props: SubnetGroupProps) { super(scope, id); - const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }); + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }); // Using 'Default' as the resource id for historical reasons (usage from `Instance` and `Cluster`). const subnetGroup = new CfnDBSubnetGroup(this, 'Default', { diff --git a/packages/@aws-cdk/aws-rds/test/cluster.test.ts b/packages/@aws-cdk/aws-rds/test/cluster.test.ts index a52c5e0ee353d..863798c87ae65 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-rds/test/cluster.test.ts @@ -947,7 +947,7 @@ describe('cluster', () => { cluster.addRotationSingleUser({ automaticallyAfter: cdk.Duration.days(15), excludeCharacters: '°_@', - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); // THEN @@ -1004,7 +1004,7 @@ describe('cluster', () => { secret: userSecret.attach(cluster), automaticallyAfter: cdk.Duration.days(15), excludeCharacters: '°_@', - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); // THEN @@ -2427,7 +2427,7 @@ describe('cluster', () => { instanceProps: { vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, publiclyAccessible: true, }, diff --git a/packages/@aws-cdk/aws-rds/test/instance.test.ts b/packages/@aws-cdk/aws-rds/test/instance.test.ts index 8ddc2b92dd697..1b5efc9961f4c 100644 --- a/packages/@aws-cdk/aws-rds/test/instance.test.ts +++ b/packages/@aws-cdk/aws-rds/test/instance.test.ts @@ -300,7 +300,7 @@ describe('instance', () => { credentials: rds.Credentials.fromUsername('syscdk'), vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, }); @@ -822,7 +822,7 @@ describe('instance', () => { instance.addRotationSingleUser({ automaticallyAfter: cdk.Duration.days(15), excludeCharacters: '°_@', - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); // THEN @@ -875,7 +875,7 @@ describe('instance', () => { secret: userSecret.attach(instance), automaticallyAfter: cdk.Duration.days(15), excludeCharacters: '°_@', - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); // THEN @@ -1519,7 +1519,7 @@ describe('instance', () => { }), vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, publiclyAccessible: true, }); diff --git a/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts b/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts index 397a1e6b774e4..598873bbaa53a 100644 --- a/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts +++ b/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts @@ -32,7 +32,7 @@ describe('subnet group', () => { description: 'My Shared Group', subnetGroupName: 'SharedGroup', vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBSubnetGroup', { @@ -51,7 +51,7 @@ describe('subnet group', () => { description: 'My Shared Group', subnetGroupName: parameter.valueAsString, vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBSubnetGroup', { diff --git a/packages/@aws-cdk/aws-redshift/lib/cluster.ts b/packages/@aws-cdk/aws-redshift/lib/cluster.ts index 0edf8e1ff0b58..f1d1a05c4903a 100644 --- a/packages/@aws-cdk/aws-redshift/lib/cluster.ts +++ b/packages/@aws-cdk/aws-redshift/lib/cluster.ts @@ -447,7 +447,7 @@ export class Cluster extends ClusterBase { this.vpc = props.vpc; this.vpcSubnets = props.vpcSubnets ?? { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }; const removalPolicy = props.removalPolicy ?? RemovalPolicy.RETAIN; diff --git a/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts b/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts index 35d8c53c8826f..ca2b3c80c3841 100644 --- a/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts +++ b/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts @@ -65,7 +65,7 @@ export class ClusterSubnetGroup extends Resource implements IClusterSubnetGroup constructor(scope: Construct, id: string, props: ClusterSubnetGroupProps) { super(scope, id); - const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }); + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }); const subnetGroup = new CfnClusterSubnetGroup(this, 'Default', { description: props.description, diff --git a/packages/@aws-cdk/aws-route53-targets/test/integ.interface-vpc-endpoint-target.ts b/packages/@aws-cdk/aws-route53-targets/test/integ.interface-vpc-endpoint-target.ts index 922d554267577..ce121283a1a10 100644 --- a/packages/@aws-cdk/aws-route53-targets/test/integ.interface-vpc-endpoint-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/test/integ.interface-vpc-endpoint-target.ts @@ -21,7 +21,7 @@ const interfaceVpcEndpoint = new ec2.InterfaceVpcEndpoint(stack, 'InterfaceEndpo }, privateDnsEnabled: false, subnets: { - subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, }); const zone = new route53.PrivateHostedZone(stack, 'PrivateZone', { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts index 99dbed1849b7a..40da9c23cf4ee 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts @@ -128,7 +128,7 @@ export class EcsRunTaskBase implements ec2.IConnectable, sfn.IStepFunctionsTask securityGroup?: ec2.ISecurityGroup) { if (subnetSelection === undefined) { - subnetSelection = { subnetType: assignPublicIp ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE_WITH_NAT }; + subnetSelection = { subnetType: assignPublicIp ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE_WITH_EGRESS }; } // If none is given here, one will be created later on during bind() diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts index 934bcd763a776..52403b989d8f8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts @@ -289,7 +289,8 @@ export class EcsRunTask extends sfn.TaskStateBase implements ec2.IConnectable { } private configureAwsVpcNetworking() { - const subnetSelection = this.props.subnets ?? { subnetType: this.props.assignPublicIp ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE_WITH_NAT }; + const subnetSelection = this.props.subnets ?? + { subnetType: this.props.assignPublicIp ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE_WITH_EGRESS }; this.networkConfiguration = { AwsvpcConfiguration: { diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts index a7188f5ec596b..290a79abbe7e6 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts @@ -29,7 +29,7 @@ test('security groups are applied to all framework functions', () => { runtime: lambda.Runtime.NODEJS_14_X, }), vpc: vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, securityGroups: [securityGroup], }); @@ -97,7 +97,7 @@ test('vpc is applied to all framework functions', () => { runtime: lambda.Runtime.NODEJS_14_X, }), vpc: vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, }); Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { @@ -419,4 +419,3 @@ describe('name', () => { }); }); }); - diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md index 7680f5437e982..ed0142737175c 100644 --- a/packages/@aws-cdk/pipelines/README.md +++ b/packages/@aws-cdk/pipelines/README.md @@ -725,7 +725,7 @@ new pipelines.CodeBuildStep('Synth', { // Control Elastic Network Interface creation vpc: vpc, - subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, securityGroups: [mySecurityGroup], // Control caching @@ -773,7 +773,7 @@ new pipelines.CodePipeline(this, 'Pipeline', { // Control Elastic Network Interface creation vpc: vpc, - subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, + subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, securityGroups: [mySecurityGroup], // Additional policy statements for the execution role From f3f4814b66ef2b0070fb6b25af9f6566bc1783a0 Mon Sep 17 00:00:00 2001 From: Raphael Manke Date: Sat, 3 Sep 2022 17:13:32 +0200 Subject: [PATCH 07/26] fix(events): additional plaintext header are not set on eventbridge connection (#21857) Fixes: https://github.com/aws/aws-cdk/issues/21855 While creating a Eventbridge connection to make api calls to an external api one sometimes have to add additional header parameters like `Content-Type = application/json` These additional headers can be either be a secret value or a plaintext value specified at deploy time. The connection class provides a HttpParameter class that alows you to set a static/unsecure/plaintext value for a header key ```javascript const connection = new Connection(this, "connection", { authorization: Authorization.apiKey( "authorization", secret.secretValue), headerParameters: { "Content-Type": HttpParameter.fromString("application/json"), }, }); ``` This should lead to api calls made with the connection have a Header present with key/value `"Content-Type": "application/json"`, The actual behavior was prior to this Fix that the header wasn't present in the api calls made with this connection. While debugging the issue I used the following aws cli commands to check what has been deployed by cdk/cloudformation `aws events describe-connection --name ` which result was similair to this ```JSON { "ConnectionArn": "arn:aws:events:eu-west-1:XXXXXXX:connection/SomeConnection/0848ec46-413a-4d40-8834-XXXXXX", "Name": "SomeConnection", "ConnectionState": "AUTHORIZED", "AuthorizationType": "API_KEY", "SecretArn": "arn:aws:secretsmanager:eu-west-1:XXXXXXX:secret:events!connection/SomeSecret/1e74cbb0-dfc6-4b77-a49f-b204e6b74a46-XXXXXX", "AuthParameters": { "ApiKeyAuthParameters": { "ApiKeyName": "authorization" }, "InvocationHttpParameters": { "HeaderParameters": [ { "Key": "Content-Type", "IsValueSecret": true } ] } }, "CreationTime": "2022-08-29T16:57:35+02:00", "LastModifiedTime": "2022-08-29T16:57:35+02:00", "LastAuthorizedTime": "2022-08-29T16:57:35+02:00" } ``` Which indicates that the header value is not set because it is treated as secret value and needs to be provided by the referenced secret. Then i checked the Cloudformation spec https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-connection-parameter.html There it is indicated that there is the property `isValueSecret` which indicates if the value is a secret or not. The next step was to check why cdk generates a template that doesn't work and thereby checked the HttpParameter class. This class is responsible for generating the `AWS::Events::Connection Parameter` properties. I noticed that only the `HttpParameter.fromSecret()` sets the `isValueSecret` flag. But it seems to be the case that for this property the default value is true by cloudformation, so omiting this attribute in the _render function results to `isValueSecret: true` at deploy time. After that i explicity set the value to false for the case the user specifies a plaintext value throught the `HttpParameter.fromString()` method. To make sure the correct values are deployed by cloudformation I added a integration test including an assertion that the deployed connection has the correct isValueSecret flag set and the value for the header is set. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-events/lib/connection.ts | 1 + ...efaultTestDeployAssertBA181C0F.assets.json | 32 + ...aultTestDeployAssertBA181C0F.template.json | 161 +++++ .../IntegConnectionStack.assets.json | 19 + .../IntegConnectionStack.template.json | 69 ++ .../index.js | 612 ++++++++++++++++++ .../test/connection.integ.snapshot/cdk.out | 1 + .../test/connection.integ.snapshot/integ.json | 12 + .../connection.integ.snapshot/manifest.json | 148 +++++ .../test/connection.integ.snapshot/tree.json | 260 ++++++++ .../aws-events/test/connection.test.ts | 55 ++ .../aws-events/test/integ.connection.ts | 41 ++ 12 files changed, 1411 insertions(+) create mode 100644 packages/@aws-cdk/aws-events/test/connection.integ.snapshot/ConnectionTestDefaultTestDeployAssertBA181C0F.assets.json create mode 100644 packages/@aws-cdk/aws-events/test/connection.integ.snapshot/ConnectionTestDefaultTestDeployAssertBA181C0F.template.json create mode 100644 packages/@aws-cdk/aws-events/test/connection.integ.snapshot/IntegConnectionStack.assets.json create mode 100644 packages/@aws-cdk/aws-events/test/connection.integ.snapshot/IntegConnectionStack.template.json create mode 100644 packages/@aws-cdk/aws-events/test/connection.integ.snapshot/asset.84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.bundle/index.js create mode 100644 packages/@aws-cdk/aws-events/test/connection.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-events/test/connection.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-events/test/connection.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-events/test/connection.integ.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-events/test/integ.connection.ts diff --git a/packages/@aws-cdk/aws-events/lib/connection.ts b/packages/@aws-cdk/aws-events/lib/connection.ts index 3908c0915a405..18d0a7fa6fe28 100644 --- a/packages/@aws-cdk/aws-events/lib/connection.ts +++ b/packages/@aws-cdk/aws-events/lib/connection.ts @@ -199,6 +199,7 @@ export abstract class HttpParameter { return { key: name, value, + isValueSecret: false, } as CfnConnection.ParameterProperty; } }(); diff --git a/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/ConnectionTestDefaultTestDeployAssertBA181C0F.assets.json b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/ConnectionTestDefaultTestDeployAssertBA181C0F.assets.json new file mode 100644 index 0000000000000..fe69e9ddecb3d --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/ConnectionTestDefaultTestDeployAssertBA181C0F.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7": { + "source": { + "path": "asset.84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "b67eb2559673644d8bc867113ad588bb685a8a274e1fcb3b8d226be5d9fd6d2e": { + "source": { + "path": "ConnectionTestDefaultTestDeployAssertBA181C0F.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "b67eb2559673644d8bc867113ad588bb685a8a274e1fcb3b8d226be5d9fd6d2e.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/ConnectionTestDefaultTestDeployAssertBA181C0F.template.json b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/ConnectionTestDefaultTestDeployAssertBA181C0F.template.json new file mode 100644 index 0000000000000..8245b7d7cb8ea --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/ConnectionTestDefaultTestDeployAssertBA181C0F.template.json @@ -0,0 +1,161 @@ +{ + "Resources": { + "AwsApiCallEventBridgedescribeConnection": { + "Type": "Custom::DeployAssert@SdkCallEventBridgedescribeConnection", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "EventBridge", + "api": "describeConnection", + "parameters": { + "Name": { + "Fn::ImportValue": "IntegConnectionStack:ExportsOutputRefConnection07624BCD5A8A23C8" + } + }, + "flattenResponse": "false", + "salt": "1662113441706" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "AwsApiCallEventBridgedescribeConnectionAssertEqualsEventBridgedescribeConnection641C4FA0": { + "Type": "Custom::DeployAssert@AssertEquals", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "actual": { + "Fn::GetAtt": [ + "AwsApiCallEventBridgedescribeConnection", + "apiCallResponse" + ] + }, + "expected": "{\"$ObjectLike\":{\"AuthParameters\":{\"ApiKeyAuthParameters\":{\"ApiKeyName\":\"keyname\"},\"InvocationHttpParameters\":{\"HeaderParameters\":[{\"Key\":\"content-type\",\"Value\":\"application/json\",\"IsValueSecret\":false}]}}}}", + "salt": "1662113441706" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "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": [ + { + "Action": [ + "eventbridge:DescribeConnection" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "events:DescribeConnection" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAssertEqualsEventBridgedescribeConnection": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallEventBridgedescribeConnectionAssertEqualsEventBridgedescribeConnection641C4FA0", + "data" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/IntegConnectionStack.assets.json b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/IntegConnectionStack.assets.json new file mode 100644 index 0000000000000..44a1d91bfe4ce --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/IntegConnectionStack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "e6e22d5747aaa38a1e1cec7566f5ac875bb6a03925a4a9fb46ef2d7315634d7a": { + "source": { + "path": "IntegConnectionStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "e6e22d5747aaa38a1e1cec7566f5ac875bb6a03925a4a9fb46ef2d7315634d7a.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/IntegConnectionStack.template.json b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/IntegConnectionStack.template.json new file mode 100644 index 0000000000000..5df0b55622f6e --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/IntegConnectionStack.template.json @@ -0,0 +1,69 @@ +{ + "Resources": { + "Connection07624BCD": { + "Type": "AWS::Events::Connection", + "Properties": { + "AuthorizationType": "API_KEY", + "AuthParameters": { + "ApiKeyAuthParameters": { + "ApiKeyName": "keyname", + "ApiKeyValue": "keyvalue" + }, + "InvocationHttpParameters": { + "HeaderParameters": [ + { + "IsValueSecret": false, + "Key": "content-type", + "Value": "application/json" + } + ] + } + } + } + } + }, + "Outputs": { + "ExportsOutputRefConnection07624BCD5A8A23C8": { + "Value": { + "Ref": "Connection07624BCD" + }, + "Export": { + "Name": "IntegConnectionStack:ExportsOutputRefConnection07624BCD5A8A23C8" + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/asset.84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.bundle/index.js b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/asset.84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.bundle/index.js new file mode 100644 index 0000000000000..ba956d47f51fe --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/asset.84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.bundle/index.js @@ -0,0 +1,612 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// ../assertions/lib/matcher.ts +var Matcher = class { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; + +// ../assertions/lib/private/matchers/absent.ts +var AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } +}; + +// ../assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } +}; +var LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } +}; +var ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + return result; + } +}; +var ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [`/${a}`], + message: "Unexpected key" + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [`/${patternKey}`], + message: `Missing key '${patternKey}' among {${Object.keys(actual).join(",")}}` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(`/${patternKey}`, inner); + } + return result; + } +}; +var SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } +}; +var NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } +}; +var AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } +}; +var StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } +}; + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + async handle() { + try { + console.log(`Event: ${JSON.stringify({ ...this.event, ResponseURL: "..." })}`); + const response = await this.processEvent(this.event.ResourceProperties); + console.log(`Event output : ${JSON.stringify(response)}`); + await this.respond({ + status: "SUCCESS", + reason: "OK", + data: response + }); + } catch (e) { + console.log(e); + await this.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + data: JSON.stringify({ + status: "fail", + message: [ + ...matchResult.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + if (request2.failDeployment) { + throw new Error(result.data); + } + } else { + result = { + data: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return Match.arrayWith(v[nested]); + case "$ObjectLike": + return Match.objectLike(v[nested]); + case "$StringLike": + return Match.stringLikeRegexp(v[nested]); + default: + return v; + } + }); + if (Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return Match.exact(final.matcher); + } catch { + return Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + const childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS.VERSION}`); + const service = new AWS[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + return request2.flattenResponse === "true" ? flatData : respond; + } +}; + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + const provider = createResourceHandler(event, context); + await provider.handle(); +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } + switch (event.ResourceType) { + case ASSERT_RESOURCE_TYPE: + return new AssertionHandler(event, context); + default: + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler +}); diff --git a/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/integ.json b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/integ.json new file mode 100644 index 0000000000000..9affac6ae40c3 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "ConnectionTest/DefaultTest": { + "stacks": [ + "IntegConnectionStack" + ], + "assertionStack": "ConnectionTest/DefaultTest/DeployAssert", + "assertionStackName": "ConnectionTestDefaultTestDeployAssertBA181C0F" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..7189ca97680c6 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/manifest.json @@ -0,0 +1,148 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "IntegConnectionStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "IntegConnectionStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "IntegConnectionStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "IntegConnectionStack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/e6e22d5747aaa38a1e1cec7566f5ac875bb6a03925a4a9fb46ef2d7315634d7a.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "IntegConnectionStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "IntegConnectionStack.assets" + ], + "metadata": { + "/IntegConnectionStack/Connection/Connection": [ + { + "type": "aws:cdk:logicalId", + "data": "Connection07624BCD" + } + ], + "/IntegConnectionStack/Exports/Output{\"Ref\":\"Connection07624BCD\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefConnection07624BCD5A8A23C8" + } + ], + "/IntegConnectionStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/IntegConnectionStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "IntegConnectionStack" + }, + "ConnectionTestDefaultTestDeployAssertBA181C0F.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "ConnectionTestDefaultTestDeployAssertBA181C0F.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "ConnectionTestDefaultTestDeployAssertBA181C0F": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "ConnectionTestDefaultTestDeployAssertBA181C0F.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b67eb2559673644d8bc867113ad588bb685a8a274e1fcb3b8d226be5d9fd6d2e.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "ConnectionTestDefaultTestDeployAssertBA181C0F.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "IntegConnectionStack", + "ConnectionTestDefaultTestDeployAssertBA181C0F.assets" + ], + "metadata": { + "/ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallEventBridgedescribeConnection" + } + ], + "/ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/AssertEqualsEventBridgedescribeConnection/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallEventBridgedescribeConnectionAssertEqualsEventBridgedescribeConnection641C4FA0" + } + ], + "/ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/AssertEqualsEventBridgedescribeConnection/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAssertEqualsEventBridgedescribeConnection" + } + ], + "/ConnectionTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/ConnectionTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/ConnectionTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/ConnectionTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "ConnectionTest/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/tree.json b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/tree.json new file mode 100644 index 0000000000000..81ac8a7d4ab14 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/connection.integ.snapshot/tree.json @@ -0,0 +1,260 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "IntegConnectionStack": { + "id": "IntegConnectionStack", + "path": "IntegConnectionStack", + "children": { + "Connection": { + "id": "Connection", + "path": "IntegConnectionStack/Connection", + "children": { + "Connection": { + "id": "Connection", + "path": "IntegConnectionStack/Connection/Connection", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Events::Connection", + "aws:cdk:cloudformation:props": { + "authorizationType": "API_KEY", + "authParameters": { + "apiKeyAuthParameters": { + "apiKeyName": "keyname", + "apiKeyValue": "keyvalue" + }, + "invocationHttpParameters": { + "headerParameters": [ + { + "key": "content-type", + "value": "application/json", + "isValueSecret": false + } + ] + } + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.CfnConnection", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.Connection", + "version": "0.0.0" + } + }, + "Exports": { + "id": "Exports", + "path": "IntegConnectionStack/Exports", + "children": { + "Output{\"Ref\":\"Connection07624BCD\"}": { + "id": "Output{\"Ref\":\"Connection07624BCD\"}", + "path": "IntegConnectionStack/Exports/Output{\"Ref\":\"Connection07624BCD\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "ConnectionTest": { + "id": "ConnectionTest", + "path": "ConnectionTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "ConnectionTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "ConnectionTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "ConnectionTest/DefaultTest/DeployAssert", + "children": { + "AwsApiCallEventBridgedescribeConnection": { + "id": "AwsApiCallEventBridgedescribeConnection", + "path": "ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/Default", + "children": { + "Default": { + "id": "Default", + "path": "ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertEqualsEventBridgedescribeConnection": { + "id": "AssertEqualsEventBridgedescribeConnection", + "path": "ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/AssertEqualsEventBridgedescribeConnection", + "children": { + "AssertionProvider": { + "id": "AssertionProvider", + "path": "ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/AssertEqualsEventBridgedescribeConnection/AssertionProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/AssertEqualsEventBridgedescribeConnection/AssertionProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/AssertEqualsEventBridgedescribeConnection/Default", + "children": { + "Default": { + "id": "Default", + "path": "ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/AssertEqualsEventBridgedescribeConnection/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "ConnectionTest/DefaultTest/DeployAssert/AwsApiCallEventBridgedescribeConnection/AssertEqualsEventBridgedescribeConnection/AssertionResults", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.EqualsAssertion", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "ConnectionTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "ConnectionTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "ConnectionTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "ConnectionTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.92" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/connection.test.ts b/packages/@aws-cdk/aws-events/test/connection.test.ts index ac02334050524..08d12dc9f8a1d 100644 --- a/packages/@aws-cdk/aws-events/test/connection.test.ts +++ b/packages/@aws-cdk/aws-events/test/connection.test.ts @@ -88,6 +88,7 @@ test('oauth connection', () => { HeaderParameters: [{ Key: 'oAuthHeaderKey', Value: 'oAuthHeaderValue', + IsValueSecret: false, }], }, }, @@ -102,3 +103,57 @@ test('oauth connection', () => { Description: 'ConnectionDescription', }); }); + +test('Additional plaintext headers', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new events.Connection(stack, 'Connection', { + authorization: events.Authorization.apiKey('keyname', SecretValue.unsafePlainText('keyvalue')), + headerParameters: { + 'content-type': events.HttpParameter.fromString('application/json'), + }, + }); + + // THEN + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Events::Connection', { + AuthParameters: { + InvocationHttpParameters: { + HeaderParameters: [{ + Key: 'content-type', + Value: 'application/json', + IsValueSecret: false, + }], + }, + }, + }); +}); + +test('Additional secret headers', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new events.Connection(stack, 'Connection', { + authorization: events.Authorization.apiKey('keyname', SecretValue.unsafePlainText('keyvalue')), + headerParameters: { + 'client-secret': events.HttpParameter.fromSecret(SecretValue.unsafePlainText('apiSecret')), + }, + }); + + // THEN + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Events::Connection', { + AuthParameters: { + InvocationHttpParameters: { + HeaderParameters: [{ + Key: 'client-secret', + Value: 'apiSecret', + IsValueSecret: true, + }], + }, + }, + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/integ.connection.ts b/packages/@aws-cdk/aws-events/test/integ.connection.ts new file mode 100644 index 0000000000000..8c000a2e50d75 --- /dev/null +++ b/packages/@aws-cdk/aws-events/test/integ.connection.ts @@ -0,0 +1,41 @@ +import { App, SecretValue, Stack } from '@aws-cdk/core'; +import { AssertionsProvider, ExpectedResult, IntegTest } from '@aws-cdk/integ-tests'; +import { Authorization, Connection, HttpParameter } from '../lib'; + +const app = new App(); + +const stack = new Stack(app, 'IntegConnectionStack'); + +const connection = new Connection(stack, 'Connection', { + authorization: Authorization.apiKey('keyname', SecretValue.unsafePlainText('keyvalue')), + headerParameters: { + 'content-type': HttpParameter.fromString('application/json'), + }, +}); +const testCase = new IntegTest(app, 'ConnectionTest', { + testCases: [stack], +}); + +const deployedConncention = testCase.assertions.awsApiCall('EventBridge', 'describeConnection', { Name: connection.connectionName }); + +deployedConncention.expect(ExpectedResult.objectLike({ + AuthParameters: { + ApiKeyAuthParameters: { + ApiKeyName: 'keyname', + }, + InvocationHttpParameters: { + HeaderParameters: [ + { + Key: 'content-type', + Value: 'application/json', + IsValueSecret: false, + }, + ], + }, + }, +})); + +const assertionProvider = deployedConncention.node.tryFindChild('SdkProvider') as AssertionsProvider; +assertionProvider.addPolicyStatementFromSdkCall('events', 'DescribeConnection'); + +app.synth(); From 5549f1692270bce06a1d9cde952f9cd23a04204b Mon Sep 17 00:00:00 2001 From: Varun Wachaspati J Date: Sat, 3 Sep 2022 22:03:19 +0530 Subject: [PATCH 08/26] fix(events-targets): cannot set retry policy to 0 retry attempts (#21900) Currently, it is not possible to set 0 retry attempts for any of the EventBridge targets that support a retry policy as 0 is falsy value and hence the following conditional doesn't evaluate to true - https://github.com/aws/aws-cdk/blob/c607ca51be1042f091b4e4419f20bec75863055c/packages/%40aws-cdk/aws-events-targets/lib/util.ts#L54-L59 Changed the conditional logic to allow 0 retry attempts for all supported targets along with unit tests. fixes [#21864 ](https://github.com/aws/aws-cdk/issues/21864) ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-events-targets/lib/util.ts | 2 +- .../test/batch/batch.test.ts | 52 +++++++++++++++ .../test/codebuild/codebuild.test.ts | 46 +++++++++++++ .../test/codepipeline/pipeline.test.ts | 50 +++++++++++++++ .../lambda-events.template.json | 2 +- .../test/lambda/integ.events.ts | 2 +- .../test/lambda/lambda.test.ts | 43 +++++++++++++ .../test/logs/log-group.test.ts | 64 +++++++++++++++++++ .../aws-events-targets/test/sqs/sqs.test.ts | 32 ++++++++++ .../test/stepfunctions/statemachine.test.ts | 46 +++++++++++++ 10 files changed, 336 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-events-targets/lib/util.ts b/packages/@aws-cdk/aws-events-targets/lib/util.ts index 373fc6ac86657..be85a77b6fc6e 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/util.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/util.ts @@ -51,7 +51,7 @@ export function bindBaseTargetConfig(props: TargetBaseProps) { return { deadLetterConfig: deadLetterQueue ? { arn: deadLetterQueue?.queueArn } : undefined, - retryPolicy: retryAttempts || maxEventAge + retryPolicy: (retryAttempts !== undefined && retryAttempts >= 0) || maxEventAge ? { maximumRetryAttempts: retryAttempts, maximumEventAgeInSeconds: maxEventAge?.toSeconds({ integral: true }), diff --git a/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts b/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts index 66d583db4263d..21df65066b389 100644 --- a/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/batch/batch.test.ts @@ -237,4 +237,56 @@ describe('Batch job event target', () => { ], }); }); + + test('specifying retry policy with 0 retryAttempts', () => { + // GIVEN + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 hour)'), + }); + + // WHEN + const eventInput = { + buildspecOverride: 'buildspecs/hourly.yml', + }; + + rule.addTarget(new targets.BatchJob( + jobQueue.jobQueueArn, + jobQueue, + jobDefinition.jobDefinitionArn, + jobDefinition, { + event: events.RuleTargetInput.fromObject(eventInput), + retryAttempts: 0, + }, + )); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + ScheduleExpression: 'rate(1 hour)', + State: 'ENABLED', + Targets: [ + { + Arn: { + Ref: 'MyQueueE6CA6235', + }, + BatchParameters: { + JobDefinition: { + Ref: 'MyJob8719E923', + }, + JobName: 'Rule', + }, + Id: 'Target0', + Input: JSON.stringify(eventInput), + RetryPolicy: { + MaximumRetryAttempts: 0, + }, + RoleArn: { + 'Fn::GetAtt': [ + 'MyJobEventsRoleCF43C336', + 'Arn', + ], + }, + }, + ], + }); + }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts b/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts index c0b7db6d75026..bb79f35cbb606 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts @@ -170,6 +170,52 @@ describe('CodeBuild event target', () => { }); }); + test('specifying retry policy with 0 retryAttempts', () => { + // GIVEN + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 hour)'), + }); + + // WHEN + const eventInput = { + buildspecOverride: 'buildspecs/hourly.yml', + }; + + rule.addTarget( + new targets.CodeBuildProject(project, { + event: events.RuleTargetInput.fromObject(eventInput), + retryAttempts: 0, + }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + ScheduleExpression: 'rate(1 hour)', + State: 'ENABLED', + Targets: [ + { + Arn: { + 'Fn::GetAtt': [ + 'MyProject39F7B0AE', + 'Arn', + ], + }, + Id: 'Target0', + Input: '{"buildspecOverride":"buildspecs/hourly.yml"}', + RetryPolicy: { + MaximumRetryAttempts: 0, + }, + RoleArn: { + 'Fn::GetAtt': [ + 'MyProjectEventsRole5B7D93F5', + 'Arn', + ], + }, + }, + ], + }); + }); + test('use a Dead Letter Queue for the rule target', () => { // GIVEN const rule = new events.Rule(stack, 'Rule', { diff --git a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts index aeccbee05fd7c..43412ae5ed756 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts @@ -157,6 +157,56 @@ describe('CodePipeline event target', () => { ], }); }); + + test('adds 0 retry attempts to the target configuration', () => { + // WHEN + rule.addTarget(new targets.CodePipeline(pipeline, { + retryAttempts: 0, + })); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + ScheduleExpression: 'rate(1 minute)', + State: 'ENABLED', + Targets: [ + { + Arn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':codepipeline:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':', + { + Ref: 'PipelineC660917D', + }, + ], + ], + }, + Id: 'Target0', + RetryPolicy: { + MaximumRetryAttempts: 0, + }, + RoleArn: { + 'Fn::GetAtt': [ + 'PipelineEventsRole46BEEA7C', + 'Arn', + ], + }, + }, + ], + }); + }); }); describe('with an explicit event role', () => { diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/events.integ.snapshot/lambda-events.template.json b/packages/@aws-cdk/aws-events-targets/test/lambda/events.integ.snapshot/lambda-events.template.json index 8c9c8074d7258..d7565c6f23b89 100644 --- a/packages/@aws-cdk/aws-events-targets/test/lambda/events.integ.snapshot/lambda-events.template.json +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/events.integ.snapshot/lambda-events.template.json @@ -148,7 +148,7 @@ "Id": "Target0", "RetryPolicy": { "MaximumEventAgeInSeconds": 7200, - "MaximumRetryAttempts": 2 + "MaximumRetryAttempts": 0 } } ] diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts b/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts index 4741d35310dab..9a83116ba85d2 100644 --- a/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts @@ -34,7 +34,7 @@ const queue = new sqs.Queue(stack, 'Queue'); timer3.addTarget(new targets.LambdaFunction(fn, { deadLetterQueue: queue, maxEventAge: cdk.Duration.hours(2), - retryAttempts: 2, + retryAttempts: 0, })); app.synth(); diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts index 0bdeec1a8a356..04f6925757602 100644 --- a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts @@ -376,6 +376,49 @@ test('specifying retry policy', () => { }); }); +test('specifying retry policy with 0 retryAttempts', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + + const fn = new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'bar', + runtime: lambda.Runtime.PYTHON_3_9, + }); + + // WHEN + new events.Rule(stack, 'Rule', { + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), + targets: [new targets.LambdaFunction(fn, { + retryAttempts: 0, + })], + }); + + // THEN + expect(() => app.synth()).not.toThrow(); + + // the Permission resource should be in the event stack + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + ScheduleExpression: 'rate(1 minute)', + State: 'ENABLED', + Targets: [ + { + Arn: { + 'Fn::GetAtt': [ + 'MyLambdaCCE802FB', + 'Arn', + ], + }, + Id: 'Target0', + RetryPolicy: { + MaximumRetryAttempts: 0, + }, + }, + ], + }); +}); + function newTestLambda(scope: constructs.Construct, suffix = '') { return new lambda.Function(scope, `MyLambda${suffix}`, { code: new lambda.InlineCode('foo'), diff --git a/packages/@aws-cdk/aws-events-targets/test/logs/log-group.test.ts b/packages/@aws-cdk/aws-events-targets/test/logs/log-group.test.ts index cd795c7065639..c38d846882296 100644 --- a/packages/@aws-cdk/aws-events-targets/test/logs/log-group.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/logs/log-group.test.ts @@ -295,3 +295,67 @@ testDeprecated('specifying retry policy and dead letter queue', () => { ], }); }); + +testDeprecated('specifying retry policy with 0 retryAttempts', () => { + // GIVEN + const stack = new cdk.Stack(); + const logGroup = new logs.LogGroup(stack, 'MyLogGroup', { + logGroupName: '/aws/events/MyLogGroup', + }); + const rule1 = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), + }); + + // WHEN + rule1.addTarget(new targets.CloudWatchLogGroup(logGroup, { + event: events.RuleTargetInput.fromObject({ + timestamp: events.EventField.time, + message: events.EventField.fromPath('$'), + }), + retryAttempts: 0, + })); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', { + ScheduleExpression: 'rate(1 minute)', + State: 'ENABLED', + Targets: [ + { + Arn: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'MyLogGroup5C0DAD85', + }, + ], + ], + }, + Id: 'Target0', + InputTransformer: { + InputPathsMap: { + time: '$.time', + f2: '$', + }, + InputTemplate: '{"timestamp":