From 003b6d43c6cfc990c720f3d211984340f63d8c4c Mon Sep 17 00:00:00 2001 From: Eric Tucker Date: Mon, 25 Jan 2021 03:04:50 -0800 Subject: [PATCH 01/70] docs(apigateway): replace AccessLogFormat to the correct AccessLogField in AccessLogFormat.custom reference in README (#12379) Fix `AccessLogFormat.custom` example to use `AccessLogField` for all field references. ---- *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-apigateway/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/README.md b/packages/@aws-cdk/aws-apigateway/README.md index 5e670032ef158..60d2cdcfa3654 100644 --- a/packages/@aws-cdk/aws-apigateway/README.md +++ b/packages/@aws-cdk/aws-apigateway/README.md @@ -809,9 +809,10 @@ new apigateway.RestApi(this, 'books', { deployOptions: { accessLogDestination: new apigateway.LogGroupLogDestination(logGroup), accessLogFormat: apigateway.AccessLogFormat.custom( - `${AccessLogFormat.contextRequestId()} ${AccessLogField.contextErrorMessage()} ${AccessLogField.contextErrorMessageString()}`); - }) -}; + `${AccessLogField.contextRequestId()} ${AccessLogField.contextErrorMessage()} ${AccessLogField.contextErrorMessageString()}` + ) + } +}); ``` You can use the `methodOptions` property to configure From 560915ece87a919f499a64452b919a0b291394ee Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Mon, 25 Jan 2021 12:21:20 +0000 Subject: [PATCH 02/70] fix(secretsmanager): fromSecretPartialArn() has incorrect grant policies (#12665) The grants for secrets imported by partial ARN were missing the '-??????' suffix to account for the SecretsManager-supplied suffix. A refactor unifies this logic better into one place. fixes #12411 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-secretsmanager/lib/secret.ts | 15 ++-- .../aws-secretsmanager/test/secret.test.ts | 68 +++++++++++++++++++ 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 2b5361572e804..92ca9cad73ee2 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -267,10 +267,14 @@ abstract class SecretBase extends Resource implements ISecret { } /** - * Provides an identifier for this secret for use in IAM policies. Typically, this is just the secret ARN. - * However, secrets imported by name require a different format. + * Provides an identifier for this secret for use in IAM policies. + * If there is a full ARN, this is just the ARN; + * if we have a partial ARN -- due to either importing by secret name or partial ARN -- + * then we need to add a suffix to capture the full ARN's format. */ - protected get arnForPolicies() { return this.secretArn; } + protected get arnForPolicies() { + return this.secretFullArn ? this.secretFullArn : `${this.secretArn}-??????`; + } /** * Attach a target to this secret @@ -351,11 +355,6 @@ export class Secret extends SecretBase { public readonly secretArn = this.partialArn; protected readonly autoCreatePolicy = false; public get secretFullArn() { return undefined; } - // Overrides the secretArn for grant* methods, where the secretArn must be in ARN format. - // Also adds a wildcard to the resource name to support the SecretsManager-provided suffix. - protected get arnForPolicies(): string { - return this.partialArn + '-??????'; - } // Creates a "partial" ARN from the secret name. The "full" ARN would include the SecretsManager-provided suffix. private get partialArn(): string { return Stack.of(this).formatArn({ diff --git a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts index de24743b9dde9..73fc366a6eb18 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts @@ -653,6 +653,40 @@ test('fromSecretCompleteArn', () => { expect(stack.resolve(secret.secretValueFromJson('password'))).toEqual(`{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); }); +test('fromSecretCompleteArn - grants', () => { + // GIVEN + const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; + const secret = secretsmanager.Secret.fromSecretCompleteArn(stack, 'Secret', secretArn); + const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); + + // WHEN + secret.grantRead(role); + secret.grantWrite(role); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: secretArn, + }, + { + Action: [ + 'secretsmanager:PutSecretValue', + 'secretsmanager:UpdateSecret', + ], + Effect: 'Allow', + Resource: secretArn, + }], + }, + }); +}); + test('fromSecretPartialArn', () => { // GIVEN const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret'; @@ -669,6 +703,40 @@ test('fromSecretPartialArn', () => { expect(stack.resolve(secret.secretValueFromJson('password'))).toEqual(`{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); }); +test('fromSecretPartialArn - grants', () => { + // GIVEN + const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret'; + const secret = secretsmanager.Secret.fromSecretPartialArn(stack, 'Secret', secretArn); + const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); + + // WHEN + secret.grantRead(role); + secret.grantWrite(role); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: `${secretArn}-??????`, + }, + { + Action: [ + 'secretsmanager:PutSecretValue', + 'secretsmanager:UpdateSecret', + ], + Effect: 'Allow', + Resource: `${secretArn}-??????`, + }], + }, + }); +}); + describe('fromSecretAttributes', () => { test('import by attributes', () => { // GIVEN From 80071fee3287ef8e6695ab90be7f64e4e1c57fba Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Mon, 25 Jan 2021 14:11:22 +0000 Subject: [PATCH 03/70] chore: ability to expire feature flags (#12688) Some feature flags in CDKv1 will be entirely removed in CDKv2, and their 'enabled' behaviour made the default. This change adds the ability to expire these flags. The list currently stands empty and will stand empty in the `master` branch. It will be populated with feature flags in the `v2-main` branch. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/core/lib/feature-flags.ts | 12 ++++++++++-- packages/@aws-cdk/cx-api/lib/features.ts | 7 +++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/core/lib/feature-flags.ts b/packages/@aws-cdk/core/lib/feature-flags.ts index 924283af30fcc..926a60168732f 100644 --- a/packages/@aws-cdk/core/lib/feature-flags.ts +++ b/packages/@aws-cdk/core/lib/feature-flags.ts @@ -24,6 +24,14 @@ export class FeatureFlags { * module. */ public isEnabled(featureFlag: string): boolean | undefined { - return this.construct.node.tryGetContext(featureFlag) ?? cxapi.futureFlagDefault(featureFlag); + const context = this.construct.node.tryGetContext(featureFlag); + if (cxapi.FUTURE_FLAGS_EXPIRED.includes(featureFlag)) { + if (context !== undefined) { + throw new Error(`Unsupported feature flag '${featureFlag}'. This flag existed on CDKv1 but has been removed in CDKv2.` + + ' CDK will now behave as the same as when the flag is enabled.'); + } + return true; + } + return context ?? cxapi.futureFlagDefault(featureFlag); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cx-api/lib/features.ts b/packages/@aws-cdk/cx-api/lib/features.ts index c91607b587eb0..a9773d86ead60 100644 --- a/packages/@aws-cdk/cx-api/lib/features.ts +++ b/packages/@aws-cdk/cx-api/lib/features.ts @@ -115,6 +115,13 @@ export const FUTURE_FLAGS = { // [NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: 'true', }; +/** + * The list of future flags that are now expired. This is going to be used to identify + * and block usages of old feature flags in the new major version of CDK. + */ +export const FUTURE_FLAGS_EXPIRED: string[] = [ +]; + /** * The set of defaults that should be applied if the feature flag is not * explicitly configured. From 4df91fc18a7dd11cfa6b4c43ea4c03c248539696 Mon Sep 17 00:00:00 2001 From: Ethan Bradley <12348673+ethanbradley@users.noreply.github.com> Date: Mon, 25 Jan 2021 16:33:20 +0000 Subject: [PATCH 04/70] docs(aws-cloudfront): typo in READMEs (#12679) Corrects typos in and closes #12678. ---- *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-cloudfront-origins/README.md | 2 +- packages/@aws-cdk/aws-cloudfront/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront-origins/README.md b/packages/@aws-cdk/aws-cloudfront-origins/README.md index c7741e37937e3..05d12bc1ae6f4 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/README.md +++ b/packages/@aws-cdk/aws-cloudfront-origins/README.md @@ -29,7 +29,7 @@ new cloudfront.Distribution(this, 'myDist', { The above will treat the bucket differently based on if `IBucket.isWebsite` is set or not. If the bucket is configured as a website, the bucket is treated as an HTTP origin, and the built-in S3 redirects and error pages can be used. Otherwise, the bucket is handled as a bucket origin and -CloudFront's redirect and error handling will be used. In the latter case, the Origin wil create an origin access identity and grant it access to the +CloudFront's redirect and error handling will be used. In the latter case, the Origin will create an origin access identity and grant it access to the underlying bucket. This can be used in conjunction with a bucket that is not public to require that your users access your content using CloudFront URLs and not S3 URLs directly. Alternatively, a custom origin access identity can be passed to the S3 origin in the properties. diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index b364cedd61b2a..f42d6a15f7abc 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -51,7 +51,7 @@ new cloudfront.Distribution(this, 'myDist', { The above will treat the bucket differently based on if `IBucket.isWebsite` is set or not. If the bucket is configured as a website, the bucket is treated as an HTTP origin, and the built-in S3 redirects and error pages can be used. Otherwise, the bucket is handled as a bucket origin and -CloudFront's redirect and error handling will be used. In the latter case, the Origin wil create an origin access identity and grant it access to the +CloudFront's redirect and error handling will be used. In the latter case, the Origin will create an origin access identity and grant it access to the underlying bucket. This can be used in conjunction with a bucket that is not public to require that your users access your content using CloudFront URLs and not S3 URLs directly. From 736b260db7f21d89e220591007580f62b22fea3a Mon Sep 17 00:00:00 2001 From: Philipp Garbe Date: Mon, 25 Jan 2021 18:10:13 +0100 Subject: [PATCH 05/70] fix(pipelines): can't use CodePipeline variables in Synth environment variables (#12602) Fixes #12061 Fixes #11178 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/synths/simple-synth-action.ts | 2 +- .../lib/validation/shell-script-action.ts | 8 +++ .../@aws-cdk/pipelines/test/builds.test.ts | 54 ++++++++++++++++--- .../pipelines/test/validation.test.ts | 36 ++++++++++++- 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts index d04408b9175cd..0a67af8a5609d 100644 --- a/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts +++ b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts @@ -320,7 +320,6 @@ export class SimpleSynthAction implements codepipeline.IAction, iam.IGrantable { const environmentVariables = { ...copyEnvironmentVariables(...this.props.copyEnvironmentVariables || []), - ...this.props.environmentVariables, }; // A hash over the values that make the CodeBuild Project unique (and necessary @@ -360,6 +359,7 @@ export class SimpleSynthAction implements codepipeline.IAction, iam.IGrantable { // Hence, the pipeline will be restarted. This is necessary if the users // adds (for example) build or test commands to the buildspec. environmentVariables: { + ...this.props.environmentVariables, _PROJECT_CONFIG_HASH: { value: projectConfigHash }, }, project, diff --git a/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts b/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts index 1b439fe309c7e..f70aa7897c1be 100644 --- a/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts +++ b/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts @@ -61,6 +61,13 @@ export interface ShellScriptActionProps { */ readonly environment?: codebuild.BuildEnvironment + /** + * Environment variables to send into build + * + * @default - No additional environment variables + */ + readonly environmentVariables?: Record; + /** * RunOrder for this action * @@ -210,6 +217,7 @@ export class ShellScriptAction implements codepipeline.IAction, iam.IGrantable { extraInputs: inputs.slice(1), runOrder: this.props.runOrder ?? 100, project: this._project, + environmentVariables: this.props.environmentVariables, }); // Replace the placeholder actionProperties at the last minute this._actionProperties = this._action.actionProperties; diff --git a/packages/@aws-cdk/pipelines/test/builds.test.ts b/packages/@aws-cdk/pipelines/test/builds.test.ts index 57521eb37c01b..8f9e75f12be45 100644 --- a/packages/@aws-cdk/pipelines/test/builds.test.ts +++ b/packages/@aws-cdk/pipelines/test/builds.test.ts @@ -149,6 +149,49 @@ test.each([['npm'], ['yarn']])('%s assumes no build step by default', (npmYarn) }); }); +test('environmentVariables must be rendered in the action', () => { + // WHEN + new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { + sourceArtifact, + cloudAssemblyArtifact, + synthAction: new cdkp.SimpleSynthAction({ + sourceArtifact, + cloudAssemblyArtifact, + environmentVariables: { + VERSION: { value: codepipeline.GlobalVariables.executionId }, + }, + synthCommand: 'synth', + }), + }); + + // THEN + const theHash = Capture.aString(); + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'Build', + Actions: [ + objectLike({ + Name: 'Synth', + Configuration: objectLike({ + EnvironmentVariables: encodedJson([ + { + name: 'VERSION', + type: 'PLAINTEXT', + value: '#{codepipeline.PipelineExecutionId}', + }, + { + name: '_PROJECT_CONFIG_HASH', + type: 'PLAINTEXT', + value: theHash.capture(), + }, + ]), + }), + }), + ], + }), + }); +}); + test('complex setup with environemnt variables still renders correct project', () => { // WHEN new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { @@ -184,11 +227,6 @@ test('complex setup with environemnt variables still renders correct project', ( Type: 'PLAINTEXT', Value: 'InnerValue', }, - { - Name: 'SOME_ENV_VAR', - Type: 'PLAINTEXT', - Value: 'SomeValue', - }, ], }), Source: { @@ -386,8 +424,10 @@ test('Pipeline action contains a hash that changes as the buildspec changes', () const hash4 = synthWithAction((sa, cxa) => cdkp.SimpleSynthAction.standardNpmSynth({ sourceArtifact: sa, cloudAssemblyArtifact: cxa, - environmentVariables: { - xyz: { value: 'SOME-VALUE' }, + environment: { + environmentVariables: { + xyz: { value: 'SOME-VALUE' }, + }, }, })); diff --git a/packages/@aws-cdk/pipelines/test/validation.test.ts b/packages/@aws-cdk/pipelines/test/validation.test.ts index 4f1cffbef61ec..8cfe55d558b34 100644 --- a/packages/@aws-cdk/pipelines/test/validation.test.ts +++ b/packages/@aws-cdk/pipelines/test/validation.test.ts @@ -1,4 +1,4 @@ -import { anything, arrayWith, deepObjectLike, encodedJson } from '@aws-cdk/assert'; +import { anything, arrayWith, deepObjectLike, encodedJson, objectLike } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; @@ -389,6 +389,40 @@ test('run ShellScriptAction with specified BuildEnvironment', () => { }); }); +test('run ShellScriptAction with specified environment variables', () => { + // WHEN + pipeline.addStage('Test').addActions(new cdkp.ShellScriptAction({ + actionName: 'imageAction', + additionalArtifacts: [integTestArtifact], + commands: ['true'], + environmentVariables: { + VERSION: { value: codepipeline.GlobalVariables.executionId }, + }, + })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: arrayWith({ + Name: 'Test', + Actions: [ + objectLike({ + Name: 'imageAction', + Configuration: objectLike({ + EnvironmentVariables: encodedJson([ + { + name: 'VERSION', + type: 'PLAINTEXT', + value: '#{codepipeline.PipelineExecutionId}', + }, + ]), + }), + }), + ], + }), + }); + +}); + class AppWithStackOutput extends Stage { public readonly output: CfnOutput; From c2f0e152d9e0414d7e9a923268e9221c24630e4d Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Mon, 25 Jan 2021 10:24:02 -0800 Subject: [PATCH 06/70] chore: deprecate all components and usages of the @aws-cloudformation module (#12670) This is a continuation of #12655. Since we cannot deprecate this package without removing it from our monolithic packages, and it's used in the public APIs of stable modules, deprecate all of those usages, and provide non-deprecated alternatives. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-cloudformation/lib/custom-resource.ts | 6 +++ .../aws-cloudformation/lib/nested-stack.ts | 4 +- .../lib/cloudformation/pipeline-actions.ts | 33 +++++++++++++-- .../test.cloudformation-pipeline-actions.ts | 42 +++++++++++++++++++ .../lib/actions/deploy-cdk-stack-action.ts | 7 ++-- packages/@aws-cdk/pipelines/package.json | 6 +-- 6 files changed, 84 insertions(+), 14 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudformation/lib/custom-resource.ts b/packages/@aws-cdk/aws-cloudformation/lib/custom-resource.ts index a36411c8504f3..9c30e6c08540c 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/custom-resource.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/custom-resource.ts @@ -8,11 +8,15 @@ import { Construct } from '@aws-cdk/core'; /** * Collection of arbitrary properties + * + * @deprecated this type has been deprecated in favor of using a key-value type directly */ export type Properties = {[key: string]: any}; /** * Configuration options for custom resource providers. + * + * @deprecated used in {@link ICustomResourceProvider} which is now deprecated */ export interface CustomResourceProviderConfig { /** @@ -37,6 +41,8 @@ export interface ICustomResourceProvider { /** * Represents a provider for an AWS CloudFormation custom resources. + * + * @deprecated use core.CustomResource instead */ export class CustomResourceProvider implements ICustomResourceProvider { /** diff --git a/packages/@aws-cdk/aws-cloudformation/lib/nested-stack.ts b/packages/@aws-cdk/aws-cloudformation/lib/nested-stack.ts index 81487ac1470b4..57268c5291bb9 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/nested-stack.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/nested-stack.ts @@ -8,7 +8,7 @@ import { Construct } from '@aws-cdk/core'; /** * Initialization props for the `NestedStack` construct. * - * @experimental + * @deprecated use core.NestedStackProps instead */ export interface NestedStackProps { /** @@ -64,7 +64,7 @@ export interface NestedStackProps { * nested stack will automatically be translated to stack parameters and * outputs. * - * @experimental + * @deprecated use core.NestedStack instead */ export class NestedStack extends core.NestedStack { constructor(scope: Construct, id: string, props: NestedStackProps = { }) { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts index b4a300d887ad4..63618e086ed91 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts @@ -168,9 +168,25 @@ interface CloudFormationDeployActionProps extends CloudFormationActionProps { * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities * @default None, unless `adminPermissions` is true + * @deprecated use {@link cfnCapabilities} instead */ readonly capabilities?: cloudformation.CloudFormationCapabilities[]; + /** + * Acknowledge certain changes made as part of deployment. + * + * For stacks that contain certain resources, + * explicit acknowledgement is required that AWS CloudFormation might create or update those resources. + * For example, you must specify `ANONYMOUS_IAM` or `NAMED_IAM` if your stack template contains AWS + * Identity and Access Management (IAM) resources. + * For more information, see the link below. + * + * @default None, unless `adminPermissions` is true + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities + */ + readonly cfnCapabilities?: cdk.CfnCapabilities[]; + /** * Whether to grant full permissions to CloudFormation while deploying this template. * @@ -301,9 +317,18 @@ abstract class CloudFormationDeployAction extends CloudFormationAction { SingletonPolicy.forRole(options.role).grantPassRole(this._deploymentRole); - const capabilities = this.props2.adminPermissions && this.props2.capabilities === undefined - ? [cloudformation.CloudFormationCapabilities.NAMED_IAM] - : this.props2.capabilities; + const providedCapabilities = this.props2.cfnCapabilities ?? + this.props2.capabilities?.map(c => { + switch (c) { + case cloudformation.CloudFormationCapabilities.NONE: return cdk.CfnCapabilities.NONE; + case cloudformation.CloudFormationCapabilities.ANONYMOUS_IAM: return cdk.CfnCapabilities.ANONYMOUS_IAM; + case cloudformation.CloudFormationCapabilities.NAMED_IAM: return cdk.CfnCapabilities.NAMED_IAM; + case cloudformation.CloudFormationCapabilities.AUTO_EXPAND: return cdk.CfnCapabilities.AUTO_EXPAND; + } + }); + const capabilities = this.props2.adminPermissions && providedCapabilities === undefined + ? [cdk.CfnCapabilities.NAMED_IAM] + : providedCapabilities; const actionConfig = super.bound(scope, stage, options); return { @@ -620,7 +645,7 @@ interface StatementTemplate { type StatementCondition = { [op: string]: { [attribute: string]: string } }; -function parseCapabilities(capabilities: cloudformation.CloudFormationCapabilities[] | undefined): string | undefined { +function parseCapabilities(capabilities: cdk.CfnCapabilities[] | undefined): string | undefined { if (capabilities === undefined) { return undefined; } else if (capabilities.length === 1) { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts index 31510ac049449..22bf46a15a641 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts @@ -558,6 +558,48 @@ export = { test.done(); }, + 'can use CfnCapabilities from the core module'(test: Test) { + // GIVEN + const stack = new TestFixture(); + + // WHEN + stack.deployStage.addAction(new cpactions.CloudFormationCreateUpdateStackAction({ + actionName: 'CreateUpdate', + stackName: 'MyStack', + templatePath: stack.sourceOutput.atPath('template.yaml'), + adminPermissions: false, + cfnCapabilities: [ + cdk.CfnCapabilities.NAMED_IAM, + cdk.CfnCapabilities.AUTO_EXPAND, + ], + })); + + // THEN: Action in Pipeline has named IAM and AUTOEXPAND capabilities + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + 'Stages': [ + { 'Name': 'Source' /* don't care about the rest */ }, + { + 'Name': 'Deploy', + 'Actions': [ + { + 'Configuration': { + 'Capabilities': 'CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND', + 'RoleArn': { 'Fn::GetAtt': ['PipelineDeployCreateUpdateRole515CB7D4', 'Arn'] }, + 'ActionMode': 'CREATE_UPDATE', + 'StackName': 'MyStack', + 'TemplatePath': 'SourceArtifact::template.yaml', + }, + 'InputArtifacts': [{ 'Name': 'SourceArtifact' }], + 'Name': 'CreateUpdate', + }, + ], + }, + ], + })); + + test.done(); + }, + 'cross-account CFN Pipeline': { 'correctly creates the deployment Role in the other account'(test: Test) { const app = new cdk.App(); diff --git a/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts b/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts index 9fe520112931f..592b5b93e3855 100644 --- a/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts +++ b/packages/@aws-cdk/pipelines/lib/actions/deploy-cdk-stack-action.ts @@ -1,11 +1,10 @@ import * as fs from 'fs'; import * as path from 'path'; -import * as cfn from '@aws-cdk/aws-cloudformation'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as cpactions from '@aws-cdk/aws-codepipeline-actions'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { Aws, Stack } from '@aws-cdk/core'; +import { Aws, CfnCapabilities, Stack } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct, Node } from 'constructs'; import { appOf, assemblyBuilderOf } from '../private/construct-internals'; @@ -249,7 +248,7 @@ export class DeployCdkStackAction implements codepipeline.IAction { role: props.actionRole, deploymentRole: props.cloudFormationExecutionRole, region: props.region, - capabilities: [cfn.CloudFormationCapabilities.NAMED_IAM, cfn.CloudFormationCapabilities.AUTO_EXPAND], + cfnCapabilities: [CfnCapabilities.NAMED_IAM, CfnCapabilities.AUTO_EXPAND], templateConfiguration: props.templateConfigurationPath ? props.cloudAssemblyInput.atPath(props.templateConfigurationPath) : undefined, }); this.executeChangeSetAction = new cpactions.CloudFormationExecuteChangeSetAction({ @@ -378,4 +377,4 @@ interface TemplateConfiguration { */ function writeTemplateConfiguration(filename: string, config: TemplateConfiguration) { fs.writeFileSync(filename, JSON.stringify(config, undefined, 2), { encoding: 'utf-8' }); -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/pipelines/package.json b/packages/@aws-cdk/pipelines/package.json index 6df06d00c1846..b13b5877cace7 100644 --- a/packages/@aws-cdk/pipelines/package.json +++ b/packages/@aws-cdk/pipelines/package.json @@ -50,8 +50,7 @@ "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", - "@aws-cdk/cx-api": "0.0.0", - "@aws-cdk/aws-cloudformation": "0.0.0" + "@aws-cdk/cx-api": "0.0.0" }, "dependencies": { "constructs": "^3.2.0", @@ -64,8 +63,7 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", - "@aws-cdk/cx-api": "0.0.0", - "@aws-cdk/aws-cloudformation": "0.0.0" + "@aws-cdk/cx-api": "0.0.0" }, "bundledDependencies": [], "keywords": [ From eacd2f7ea67c83d50c839acf29fbe953ae49d987 Mon Sep 17 00:00:00 2001 From: Vidit Ochani Date: Tue, 26 Jan 2021 00:30:55 +0530 Subject: [PATCH 07/70] feat(stepfunctions-tasks): support databrew startJobRun task (#12532) This adds support for AWS Glue DataBrew StartJobRun API as a task. Task documentation: https://docs.aws.amazon.com/step-functions/latest/dg/connect-databrew.html ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-stepfunctions-tasks/README.md | 13 + .../lib/databrew/start-job-run.ts | 80 ++++++ .../aws-stepfunctions-tasks/lib/index.ts | 1 + .../aws-stepfunctions-tasks/package.json | 2 + .../integ.start-job-run.expected.json | 255 ++++++++++++++++++ .../test/databrew/integ.start-job-run.ts | 124 +++++++++ .../test/databrew/start-job-run.test.ts | 114 ++++++++ 7 files changed, 589 insertions(+) create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/databrew/start-job-run.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.expected.json create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/start-job-run.test.ts diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index 1018e052660d1..80e1dab5ef73f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -55,6 +55,7 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw - [Modify Instance Fleet](#modify-instance-fleet) - [Modify Instance Group](#modify-instance-group) - [Glue](#glue) +- [Glue DataBrew](#glue-databrew) - [Lambda](#lambda) - [SageMaker](#sagemaker) - [Create Training Job](#create-training-job) @@ -680,6 +681,18 @@ new GlueStartJobRun(stack, 'Task', { }); ``` +## Glue DataBrew + +Step Functions supports [AWS Glue DataBrew](https://docs.aws.amazon.com/step-functions/latest/dg/connect-databrew.html) through the service integration pattern. + +You can call the [`StartJobRun`](https://docs.aws.amazon.com/databrew/latest/dg/API_StartJobRun.html) API from a `Task` state. + +```ts +new GlueDataBrewStartJobRun(stack, 'Task', { + Name: 'databrew-job', +}); +``` + ## Lambda [Invoke](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html) a Lambda function. diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/databrew/start-job-run.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/databrew/start-job-run.ts new file mode 100644 index 0000000000000..75b6abcb6edda --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/databrew/start-job-run.ts @@ -0,0 +1,80 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; + +/** + * Properties for starting a job run with StartJobRun + * @experimental + */ +export interface GlueDataBrewStartJobRunProps extends sfn.TaskStateBaseProps { + + /** + * Glue DataBrew Job to run + */ + readonly name: string; +} + +/** + * Start a Job run as a Task + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-databrew.html + * @experimental + */ +export class GlueDataBrewStartJobRun extends sfn.TaskStateBase { + + private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ + sfn.IntegrationPattern.REQUEST_RESPONSE, + sfn.IntegrationPattern.RUN_JOB, + ]; + + protected readonly taskMetrics?: sfn.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + + private readonly integrationPattern: sfn.IntegrationPattern; + + /** + * @experimental + */ + constructor(scope: Construct, id: string, private readonly props: GlueDataBrewStartJobRunProps) { + super(scope, id, props); + this.integrationPattern = props.integrationPattern ?? sfn.IntegrationPattern.REQUEST_RESPONSE; + + validatePatternSupported(this.integrationPattern, GlueDataBrewStartJobRun.SUPPORTED_INTEGRATION_PATTERNS); + + const actions = ['databrew:startJobRun']; + + if (this.integrationPattern === sfn.IntegrationPattern.RUN_JOB) { + actions.push('databrew:stopJobRun', 'databrew:listJobRuns'); + } + + this.taskPolicies = [ + new iam.PolicyStatement({ + resources: [ + cdk.Stack.of(this).formatArn({ + service: 'databrew', + resource: 'job', + // If the name comes from input, we cannot target the policy to a particular ARN prefix reliably. + resourceName: sfn.JsonPath.isEncodedJsonPath(this.props.name) ? '*' : this.props.name, + }), + ], + actions: actions, + }), + ]; + } + + /** + * Provides the Glue DataBrew Start Job Run task configuration + * @internal + */ + protected _renderTask(): any { + return { + Resource: integrationResourceArn('databrew', 'startJobRun', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject({ + Name: this.props.name, + }), + }; + } +} + diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts index 8e3567f2a8f88..84b790beff216 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts @@ -43,3 +43,4 @@ export * from './athena/start-query-execution'; export * from './athena/stop-query-execution'; export * from './athena/get-query-execution'; export * from './athena/get-query-results'; +export * from './databrew/start-job-run'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index b627761bae98f..3031ab05dc9a3 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -75,6 +75,7 @@ "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", + "@aws-cdk/aws-databrew": "0.0.0", "@aws-cdk/aws-dynamodb": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-ecr": "0.0.0", @@ -96,6 +97,7 @@ "@aws-cdk/aws-batch": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", + "@aws-cdk/aws-databrew": "0.0.0", "@aws-cdk/aws-dynamodb": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-ecr": "0.0.0", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.expected.json new file mode 100644 index 0000000000000..4b0dedef27b9b --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.expected.json @@ -0,0 +1,255 @@ +{ + "Resources": { + "JobOutputBucketACE3BC7B": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "DataBrewRole7E60F80D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "databrew.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSGlueDataBrewServiceRole" + ], + "Path": "/", + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::databrew-public-datasets-test-region/*", + "arn:aws:s3:::databrew-public-datasets-test-region", + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "JobOutputBucketACE3BC7B", + "Arn" + ] + }, + "/*" + ] + ] + }, + { + "Fn::GetAtt": [ + "JobOutputBucketACE3BC7B", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "DataBrewPolicy" + } + ] + } + }, + "DataBrewRecipe": { + "Type": "AWS::DataBrew::Recipe", + "Properties": { + "Name": "recipe-1", + "Steps": [ + { + "Action": { + "Operation": "UPPER_CASE", + "Parameters": { + "sourceColumn": "description" + } + } + }, + { + "Action": { + "Operation": "DELETE", + "Parameters": { + "sourceColumn": "doc_id" + } + } + } + ] + } + }, + "DataBrewDataset": { + "Type": "AWS::DataBrew::Dataset", + "Properties": { + "Input": { + "S3InputDefinition": { + "Bucket": "databrew-public-datasets-test-region", + "Key": "votes.csv" + } + }, + "Name": "dataset-1" + } + }, + "DataBrewProject": { + "Type": "AWS::DataBrew::Project", + "Properties": { + "DatasetName": "dataset-1", + "Name": "project-1", + "RecipeName": "recipe-1", + "RoleArn": { + "Fn::GetAtt": [ + "DataBrewRole7E60F80D", + "Arn" + ] + } + }, + "DependsOn": [ + "DataBrewDataset", + "DataBrewRecipe" + ] + }, + "DataBrewJob": { + "Type": "AWS::DataBrew::Job", + "Properties": { + "Name": "job-1", + "RoleArn": { + "Fn::GetAtt": [ + "DataBrewRole7E60F80D", + "Arn" + ] + }, + "Type": "RECIPE", + "Outputs": [ + { + "Location": { + "Bucket": { + "Ref": "JobOutputBucketACE3BC7B" + } + } + } + ], + "ProjectName": "project-1" + }, + "DependsOn": [ + "DataBrewProject" + ] + }, + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachineRoleDefaultPolicyDF1E6607": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "databrew:startJobRun", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":databrew:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":job/job-1" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": [ + { + "Ref": "StateMachineRoleB840431D" + } + ] + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Start DataBrew Job run\",\"States\":{\"Start DataBrew Job run\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::databrew:startJobRun\",\"Parameters\":{\"Name\":\"job-1\"}}},\"TimeoutSeconds\":30}" + ] + ] + } + }, + "DependsOn": [ + "StateMachineRoleDefaultPolicyDF1E6607", + "StateMachineRoleB840431D" + ] + } + }, + "Outputs": { + "stateMachineArn": { + "Value": { + "Ref": "StateMachine2E01A3A5" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.ts new file mode 100644 index 0000000000000..689f74a678a89 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.ts @@ -0,0 +1,124 @@ +import * as databrew from '@aws-cdk/aws-databrew'; +import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { GlueDataBrewStartJobRun } from '../../lib'; + +/* + * Stack verification steps: + * * aws stepfunctions start-execution --state-machine-arn : should return execution arn + * * aws stepfunctions describe-execution --execution-arn : should return status as SUCCEEDED + */ + +class GlueDataBrewJobStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props: cdk.StackProps = {}) { + super(scope, id, props); + + const region = process.env.CDK_DEPLOY_REGION || process.env.CDK_DEFAULT_REGION; + + const outputBucket = new s3.Bucket(this, 'JobOutputBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + const role = new iam.Role(this, 'DataBrew Role', { + managedPolicies: [{ + managedPolicyArn: 'arn:aws:iam::aws:policy/service-role/AWSGlueDataBrewServiceRole', + }], + path: '/', + assumedBy: new iam.ServicePrincipal('databrew.amazonaws.com'), + inlinePolicies: { + DataBrewPolicy: iam.PolicyDocument.fromJson({ + Statement: [{ + Effect: 'Allow', + Action: [ + 's3:GetObject', + 's3:PutObject', + 's3:DeleteObject', + 's3:ListBucket', + ], + Resource: [ + `arn:aws:s3:::databrew-public-datasets-${region}/*`, + `arn:aws:s3:::databrew-public-datasets-${region}`, + `${outputBucket.bucketArn}/*`, + `${outputBucket.bucketArn}`, + ], + }], + }), + }, + }); + + const recipe = new databrew.CfnRecipe(this, 'DataBrew Recipe', { + name: 'recipe-1', + steps: [ + { + action: { + operation: 'UPPER_CASE', + parameters: { + sourceColumn: 'description', + }, + }, + }, + { + action: { + operation: 'DELETE', + parameters: { + sourceColumn: 'doc_id', + }, + }, + }, + ], + }); + + const dataset = new databrew.CfnDataset(this, 'DataBrew Dataset', { + input: { + S3InputDefinition: { + Bucket: `databrew-public-datasets-${region}`, + Key: 'votes.csv', + }, + }, + name: 'dataset-1', + }); + + const project = new databrew.CfnProject(this, 'DataBrew Project', { + name: 'project-1', + roleArn: role.roleArn, + datasetName: dataset.name, + recipeName: recipe.name, + }); + project.addDependsOn(dataset); + project.addDependsOn(recipe); + + const job = new databrew.CfnJob(this, 'DataBrew Job', { + name: 'job-1', + type: 'RECIPE', + projectName: project.name, + roleArn: role.roleArn, + outputs: [{ + location: { + bucket: outputBucket.bucketName, + }, + }], + }); + job.addDependsOn(project); + + const startGlueDataBrewJob = new GlueDataBrewStartJobRun(this, 'Start DataBrew Job run', { + name: job.name, + }); + + const chain = sfn.Chain.start(startGlueDataBrewJob); + + const sm = new sfn.StateMachine(this, 'StateMachine', { + definition: chain, + timeout: cdk.Duration.seconds(30), + }); + + new cdk.CfnOutput(this, 'stateMachineArn', { + value: sm.stateMachineArn, + }); + } +} + +const app = new cdk.App(); +new GlueDataBrewJobStack(app, 'aws-stepfunctions-tasks-databrew-start-job-run-integ'); +app.synth(); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/start-job-run.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/start-job-run.test.ts new file mode 100644 index 0000000000000..706ea92041d7a --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/databrew/start-job-run.test.ts @@ -0,0 +1,114 @@ +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import { GlueDataBrewStartJobRun } from '../../lib/databrew/start-job-run'; + +describe('Start Job Run', () => { + + test('default settings', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const task = new GlueDataBrewStartJobRun(stack, 'JobRun', { + name: 'jobName', + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::databrew:startJobRun', + ], + ], + }, + End: true, + Parameters: { + Name: 'jobName', + }, + }); + }); + + test('create job with input from task', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const task = new GlueDataBrewStartJobRun(stack, 'JobRun', { + name: sfn.JsonPath.stringAt('$.Name'), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::databrew:startJobRun', + ], + ], + }, + End: true, + Parameters: { + 'Name.$': '$.Name', + }, + }); + }); + + test('sync integrationPattern', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const task = new GlueDataBrewStartJobRun(stack, 'JobRun', { + integrationPattern: sfn.IntegrationPattern.RUN_JOB, + name: 'jobName', + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::databrew:startJobRun.sync', + ], + ], + }, + End: true, + Parameters: { + Name: 'jobName', + }, + }); + }); + + + test('wait_for_task_token integrationPattern throws an error', () => { + // GIVEN + const stack = new cdk.Stack(); + + expect(() => { + new GlueDataBrewStartJobRun(stack, 'JobRun', { + integrationPattern: sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + name: 'jobName', + }); + }).toThrow(/Unsupported service integration pattern. Supported Patterns: REQUEST_RESPONSE,RUN_JOB. Received: WAIT_FOR_TASK_TOKEN/i); + }); +}); + From 70f64035caf8f3881aa850148fc9d5c491b28dd8 Mon Sep 17 00:00:00 2001 From: Graham Lea Date: Tue, 26 Jan 2021 23:03:47 +1100 Subject: [PATCH 08/70] docs(route53): Add example of A record for Elastic IP (#12176) It took me ages to figure out how to add an A record for an EIP. Using `instance.instancePublicIp` doesn't work, because it seems the instance is given another public IP before the EIP is associated with it, and that is what `instance.instancePublicIp` resolves to, which then quickly becomes wrong. It's not clear in `RecordTarget.fromIpAddresses` that it can take a reference to an EIP. I'm still not even sure why this works, but it does. So I thought I'd add an example to help others see how to do it. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-route53/README.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-route53/README.md b/packages/@aws-cdk/aws-route53/README.md index 116dd217748cd..cc517c5ad6937 100644 --- a/packages/@aws-cdk/aws-route53/README.md +++ b/packages/@aws-cdk/aws-route53/README.md @@ -60,7 +60,7 @@ new route53.TxtRecord(this, 'TXTRecord', { }); ``` -To add a A record to your zone: +To add an A record to your zone: ```ts import * as route53 from '@aws-cdk/aws-route53'; @@ -71,7 +71,28 @@ new route53.ARecord(this, 'ARecord', { }); ``` -To add a AAAA record pointing to a CloudFront distribution: +To add an A record for an EC2 instance with an Elastic IP (EIP) to your zone: + +```ts +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as route53 from '@aws-cdk/aws-route53'; + +const instance = new ec2.Instance(this, 'Instance', { + // ... +}); + +const elasticIp = new ec2.CfnEIP(this, 'EIP', { + domain: 'vpc', + instanceId: instance.instanceId +}); + +new route53.ARecord(this, 'ARecord', { + zone: myZone, + target: route53.RecordTarget.fromIpAddresses(elasticIp.ref) +}); +``` + +To add an AAAA record pointing to a CloudFront distribution: ```ts import * as route53 from '@aws-cdk/aws-route53'; From 532c91f98d715a051374da27ed252f3ad7dddc98 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 26 Jan 2021 13:17:40 +0000 Subject: [PATCH 09/70] chore(aws-cdk-lib): revert raising the min node version to v14 (#12704) This reverts commit 4aa64c82b6849b06ee6d18ad040d5dbaef6d8732 This change is incompatible with our build image 'jsii/superchain' that has node v10 as its requirement. We would still like the default version of this build image to stay at v10 since that is the minimum supported version of CDKv1. Additional work is required in this area before we can raise the node version requirement for this package. Reverting now so we don't have a blocked pipeline. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .yarnrc | 1 - tools/pkglint/lib/rules.ts | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.yarnrc b/.yarnrc index 46241e3f5e5bc..591e9c3d57b96 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,2 +1 @@ --install.check-files true # install will verify file tree of packages for consistency -ignore-engines true # the 'engines' key for 'aws-cdk-lib' has specifies node14 as min while v1 will remain at node10 diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index 4b3c88eab01cd..0d75c3b1113cf 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -1070,11 +1070,7 @@ export class MustHaveNodeEnginesDeclaration extends ValidationRule { public readonly name = 'package-info/engines'; public validate(pkg: PackageJson): void { - if (cdkMajorVersion() === 2) { - expectJSON(this.name, pkg, 'engines.node', '>= 14.15.0'); - } else { - expectJSON(this.name, pkg, 'engines.node', '>= 10.13.0 <13 || >=13.7.0'); - } + expectJSON(this.name, pkg, 'engines.node', '>= 10.13.0 <13 || >=13.7.0'); } } From a16f09c9ea675caf5b1e50a4e1cc288e5afd1237 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 26 Jan 2021 15:31:23 +0100 Subject: [PATCH 10/70] fix(pipelines): unable to publish assets inside VPC (#12331) The CodeBuild projects for the asset publishing could not be created inside a VPC, as the Exeuction Roles assigned to it were missing the required permissions. Normally the CodeBuild project would take care of these permissions, but since we are creating a single role that is shared between all CodeBuild projects (in order to not explode the number of roles in the account) and the role we manage is immutable, the pipeline library itself is responsible for adding the permissions. Fixes #11815 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/pipelines/lib/pipeline.ts | 36 +- .../@aws-cdk/pipelines/test/builds.test.ts | 39 +- .../pipelines/test/pipeline-assets.test.ts | 614 +++++++++--------- packages/@aws-cdk/pipelines/test/testutil.ts | 2 - 4 files changed, 377 insertions(+), 314 deletions(-) diff --git a/packages/@aws-cdk/pipelines/lib/pipeline.ts b/packages/@aws-cdk/pipelines/lib/pipeline.ts index 6e39f92b7582d..20f751078646e 100644 --- a/packages/@aws-cdk/pipelines/lib/pipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/pipeline.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { Annotations, App, CfnOutput, PhysicalName, Stack, Stage } from '@aws-cdk/core'; +import { Annotations, App, Aws, CfnOutput, PhysicalName, Stack, Stage } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { AssetType, DeployCdkStackAction, PublishAssetsAction, UpdatePipelineAction } from './actions'; import { appOf, assemblyBuilderOf } from './private/construct-internals'; @@ -516,6 +516,40 @@ class AssetPublishing extends CoreConstruct { // Artifact access this.pipeline.artifactBucket.grantRead(assetRole); + // VPC permissions required for CodeBuild + // Normally CodeBuild itself takes care of this but we're creating a singleton role so now + // we need to do this. + if (this.props.vpc) { + assetRole.attachInlinePolicy(new iam.Policy(assetRole, 'VpcPolicy', { + statements: [ + new iam.PolicyStatement({ + resources: [`arn:${Aws.PARTITION}:ec2:${Aws.REGION}:${Aws.ACCOUNT_ID}:network-interface/*`], + actions: ['ec2:CreateNetworkInterfacePermission'], + conditions: { + StringEquals: { + 'ec2:Subnet': this.props.vpc + .selectSubnets(this.props.subnetSelection).subnetIds + .map(si => `arn:${Aws.PARTITION}:ec2:${Aws.REGION}:${Aws.ACCOUNT_ID}:subnet/${si}`), + 'ec2:AuthorizedService': 'codebuild.amazonaws.com', + }, + }, + }), + new iam.PolicyStatement({ + resources: ['*'], + actions: [ + 'ec2:CreateNetworkInterface', + 'ec2:DescribeNetworkInterfaces', + 'ec2:DeleteNetworkInterface', + 'ec2:DescribeSubnets', + 'ec2:DescribeSecurityGroups', + 'ec2:DescribeDhcpOptions', + 'ec2:DescribeVpcs', + ], + }), + ], + })); + } + this.assetRoles[assetType] = assetRole.withoutPolicyUpdates(); return this.assetRoles[assetType]; } diff --git a/packages/@aws-cdk/pipelines/test/builds.test.ts b/packages/@aws-cdk/pipelines/test/builds.test.ts index 8f9e75f12be45..f0b18e1e36442 100644 --- a/packages/@aws-cdk/pipelines/test/builds.test.ts +++ b/packages/@aws-cdk/pipelines/test/builds.test.ts @@ -331,27 +331,27 @@ test('Standard (NPM) synth can run in a VPC', () => { expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { VpcConfig: { SecurityGroupIds: [ - { - 'Fn::GetAtt': [ - 'CdkPipelineBuildSynthCdkBuildProjectSecurityGroupEA44D7C2', - 'GroupId', - ], - }, + { 'Fn::GetAtt': ['CdkPipelineBuildSynthCdkBuildProjectSecurityGroupEA44D7C2', 'GroupId'] }, ], Subnets: [ - { - Ref: 'NpmSynthTestVpcPrivateSubnet1Subnet81E3AA56', - }, - { - Ref: 'NpmSynthTestVpcPrivateSubnet2SubnetC1CA3EF0', - }, - { - Ref: 'NpmSynthTestVpcPrivateSubnet3SubnetA04163EE', - }, + { Ref: 'NpmSynthTestVpcPrivateSubnet1Subnet81E3AA56' }, + { Ref: 'NpmSynthTestVpcPrivateSubnet2SubnetC1CA3EF0' }, + { Ref: 'NpmSynthTestVpcPrivateSubnet3SubnetA04163EE' }, ], - VpcId: { - Ref: 'NpmSynthTestVpc5E703F25', - }, + VpcId: { Ref: 'NpmSynthTestVpc5E703F25' }, + }, + }); + + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Roles: [ + { Ref: 'CdkPipelineBuildSynthCdkBuildProjectRole5E173C62' }, + ], + PolicyDocument: { + Statement: arrayWith({ + Action: arrayWith('ec2:DescribeSecurityGroups'), + Effect: 'Allow', + Resource: '*', + }), }, }); }); @@ -520,6 +520,9 @@ test('SimpleSynthAction can reference an imported ECR repo', () => { }, }), }); + + // THEN -- no exception (necessary for linter) + expect(true).toBeTruthy(); }); function npmYarnBuild(npmYarn: string) { diff --git a/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts index c10906e7ad7bb..01b1d815bea17 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts +++ b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import { arrayWith, deepObjectLike, encodedJson, notMatching, objectLike, stringLike } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import * as cp from '@aws-cdk/aws-codepipeline'; +import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecr_assets from '@aws-cdk/aws-ecr-assets'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import { Stack, Stage, StageProps } from '@aws-cdk/core'; @@ -15,366 +16,393 @@ let app: TestApp; let pipelineStack: Stack; let pipeline: cdkp.CdkPipeline; -beforeEach(() => { - app = new TestApp(); - pipelineStack = new Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); - pipeline = new TestGitHubNpmPipeline(pipelineStack, 'Cdk'); -}); - -afterEach(() => { - app.cleanup(); -}); -test('no assets stage if the application has no assets', () => { - // WHEN - pipeline.addApplicationStage(new PlainStackApp(app, 'App')); +describe('basic pipeline', () => { + beforeEach(() => { + app = new TestApp(); + pipelineStack = new Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + pipeline = new TestGitHubNpmPipeline(pipelineStack, 'Cdk'); + }); - // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: notMatching(arrayWith(objectLike({ - Name: 'Assets', - }))), + afterEach(() => { + app.cleanup(); }); -}); -describe('asset stage placement', () => { - test('assets stage comes before any user-defined stages', () => { + test('no assets stage if the application has no assets', () => { // WHEN - pipeline.addApplicationStage(new FileAssetApp(app, 'App')); + pipeline.addApplicationStage(new PlainStackApp(app, 'App')); // THEN expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: [ - objectLike({ Name: 'Source' }), - objectLike({ Name: 'Build' }), - objectLike({ Name: 'UpdatePipeline' }), - objectLike({ Name: 'Assets' }), - objectLike({ Name: 'App' }), - ], + Stages: notMatching(arrayWith(objectLike({ + Name: 'Assets', + }))), }); }); - test('assets stage inserted after existing pipeline actions', () => { - // WHEN - const sourceArtifact = new cp.Artifact(); - const cloudAssemblyArtifact = new cp.Artifact(); - const existingCodePipeline = new cp.Pipeline(pipelineStack, 'CodePipeline', { - stages: [ - { - stageName: 'CustomSource', - actions: [new TestGitHubAction(sourceArtifact)], - }, - { - stageName: 'CustomBuild', - actions: [cdkp.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact })], - }, - ], + describe('asset stage placement', () => { + test('assets stage comes before any user-defined stages', () => { + // WHEN + pipeline.addApplicationStage(new FileAssetApp(app, 'App')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + objectLike({ Name: 'Source' }), + objectLike({ Name: 'Build' }), + objectLike({ Name: 'UpdatePipeline' }), + objectLike({ Name: 'Assets' }), + objectLike({ Name: 'App' }), + ], + }); }); - pipeline = new cdkp.CdkPipeline(pipelineStack, 'CdkEmptyPipeline', { - cloudAssemblyArtifact: cloudAssemblyArtifact, - selfMutating: false, - codePipeline: existingCodePipeline, - // No source/build actions + + test('assets stage inserted after existing pipeline actions', () => { + // WHEN + const sourceArtifact = new cp.Artifact(); + const cloudAssemblyArtifact = new cp.Artifact(); + const existingCodePipeline = new cp.Pipeline(pipelineStack, 'CodePipeline', { + stages: [ + { + stageName: 'CustomSource', + actions: [new TestGitHubAction(sourceArtifact)], + }, + { + stageName: 'CustomBuild', + actions: [cdkp.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact })], + }, + ], + }); + pipeline = new cdkp.CdkPipeline(pipelineStack, 'CdkEmptyPipeline', { + cloudAssemblyArtifact: cloudAssemblyArtifact, + selfMutating: false, + codePipeline: existingCodePipeline, + // No source/build actions + }); + pipeline.addApplicationStage(new FileAssetApp(app, 'App')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + objectLike({ Name: 'CustomSource' }), + objectLike({ Name: 'CustomBuild' }), + objectLike({ Name: 'Assets' }), + objectLike({ Name: 'App' }), + ], + }); }); - pipeline.addApplicationStage(new FileAssetApp(app, 'App')); - // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: [ - objectLike({ Name: 'CustomSource' }), - objectLike({ Name: 'CustomBuild' }), - objectLike({ Name: 'Assets' }), - objectLike({ Name: 'App' }), - ], + test('up to 50 assets fit in a single stage', () => { + // WHEN + pipeline.addApplicationStage(new MegaAssetsApp(app, 'App', { numAssets: 50 })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + objectLike({ Name: 'Source' }), + objectLike({ Name: 'Build' }), + objectLike({ Name: 'UpdatePipeline' }), + objectLike({ Name: 'Assets' }), + objectLike({ Name: 'App' }), + ], + }); + }); + + test('51 assets triggers a second stage', () => { + // WHEN + pipeline.addApplicationStage(new MegaAssetsApp(app, 'App', { numAssets: 51 })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + objectLike({ Name: 'Source' }), + objectLike({ Name: 'Build' }), + objectLike({ Name: 'UpdatePipeline' }), + objectLike({ Name: 'Assets' }), + objectLike({ Name: 'Assets2' }), + objectLike({ Name: 'App' }), + ], + }); + }); + + test('101 assets triggers a third stage', () => { + // WHEN + pipeline.addApplicationStage(new MegaAssetsApp(app, 'App', { numAssets: 101 })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + objectLike({ Name: 'Source' }), + objectLike({ Name: 'Build' }), + objectLike({ Name: 'UpdatePipeline' }), + objectLike({ Name: 'Assets' }), + objectLike({ Name: 'Assets2' }), + objectLike({ Name: 'Assets3' }), + objectLike({ Name: 'App' }), + ], + }); }); }); - test('up to 50 assets fit in a single stage', () => { + test('command line properly locates assets in subassembly', () => { // WHEN - pipeline.addApplicationStage(new MegaAssetsApp(app, 'App', { numAssets: 50 })); + pipeline.addApplicationStage(new FileAssetApp(app, 'FileAssetApp')); // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: [ - objectLike({ Name: 'Source' }), - objectLike({ Name: 'Build' }), - objectLike({ Name: 'UpdatePipeline' }), - objectLike({ Name: 'Assets' }), - objectLike({ Name: 'App' }), - ], + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Environment: { + Image: 'aws/codebuild/standard:4.0', + }, + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + build: { + commands: arrayWith(`cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH}:current_account-current_region"`), + }, + }, + })), + }, }); }); - test('51 assets triggers a second stage', () => { + test('multiple assets are published in parallel', () => { // WHEN - pipeline.addApplicationStage(new MegaAssetsApp(app, 'App', { numAssets: 51 })); + pipeline.addApplicationStage(new TwoFileAssetsApp(app, 'FileAssetApp')); // THEN expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: [ - objectLike({ Name: 'Source' }), - objectLike({ Name: 'Build' }), - objectLike({ Name: 'UpdatePipeline' }), - objectLike({ Name: 'Assets' }), - objectLike({ Name: 'Assets2' }), - objectLike({ Name: 'App' }), - ], + Stages: arrayWith({ + Name: 'Assets', + Actions: [ + objectLike({ RunOrder: 1 }), + objectLike({ RunOrder: 1 }), + ], + }), }); }); - test('101 assets triggers a third stage', () => { + test('assets are also published when using the lower-level addStackArtifactDeployment', () => { + // GIVEN + const asm = new FileAssetApp(app, 'FileAssetApp').synth(); + // WHEN - pipeline.addApplicationStage(new MegaAssetsApp(app, 'App', { numAssets: 101 })); + pipeline.addStage('SomeStage').addStackArtifactDeployment(asm.getStackByName('FileAssetApp-Stack')); // THEN expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: [ - objectLike({ Name: 'Source' }), - objectLike({ Name: 'Build' }), - objectLike({ Name: 'UpdatePipeline' }), - objectLike({ Name: 'Assets' }), - objectLike({ Name: 'Assets2' }), - objectLike({ Name: 'Assets3' }), - objectLike({ Name: 'App' }), - ], + Stages: arrayWith({ + Name: 'Assets', + Actions: [ + objectLike({ + Name: 'FileAsset1', + RunOrder: 1, + }), + ], + }), }); }); -}); -test('command line properly locates assets in subassembly', () => { - // WHEN - pipeline.addApplicationStage(new FileAssetApp(app, 'FileAssetApp')); + test('file image asset publishers do not use privilegedmode, have right AssumeRole', () => { + // WHEN + pipeline.addApplicationStage(new FileAssetApp(app, 'FileAssetApp')); - // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { - Environment: { - Image: 'aws/codebuild/standard:4.0', - }, - Source: { - BuildSpec: encodedJson(deepObjectLike({ - phases: { - build: { - commands: arrayWith(`cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH}:current_account-current_region"`), + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + build: { + commands: arrayWith(stringLike('cdk-assets *')), + }, }, - }, - })), - }, - }); -}); - -test('multiple assets are published in parallel', () => { - // WHEN - pipeline.addApplicationStage(new TwoFileAssetsApp(app, 'FileAssetApp')); - - // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ - Name: 'Assets', - Actions: [ - objectLike({ RunOrder: 1 }), - objectLike({ RunOrder: 1 }), - ], - }), - }); -}); + })), + }, + Environment: objectLike({ + PrivilegedMode: false, + Image: 'aws/codebuild/standard:4.0', + }), + }); -test('assets are also published when using the lower-level addStackArtifactDeployment', () => { - // GIVEN - const asm = new FileAssetApp(app, 'FileAssetApp').synth(); - - // WHEN - pipeline.addStage('SomeStage').addStackArtifactDeployment(asm.getStackByName('FileAssetApp-Stack')); - - // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ - Name: 'Assets', - Actions: [ - objectLike({ - Name: 'FileAsset1', - RunOrder: 1, + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith({ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Resource: 'arn:*:iam::*:role/*-file-publishing-role-*', }), - ], - }), + }, + }); }); -}); -test('file image asset publishers do not use privilegedmode, have right AssumeRole', () => { - // WHEN - pipeline.addApplicationStage(new FileAssetApp(app, 'FileAssetApp')); - - // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { - Source: { - BuildSpec: encodedJson(deepObjectLike({ - phases: { - build: { - commands: arrayWith(stringLike('cdk-assets *')), - }, - }, - })), - }, - Environment: objectLike({ - PrivilegedMode: false, - Image: 'aws/codebuild/standard:4.0', - }), - }); + test('docker image asset publishers use privilegedmode, have right AssumeRole', () => { + // WHEN + pipeline.addApplicationStage(new DockerAssetApp(app, 'DockerAssetApp')); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: arrayWith({ - Action: 'sts:AssumeRole', - Effect: 'Allow', - Resource: 'arn:*:iam::*:role/*-file-publishing-role-*', + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + build: { + commands: arrayWith(stringLike('cdk-assets *')), + }, + }, + })), + }, + Environment: objectLike({ + Image: 'aws/codebuild/standard:4.0', + PrivilegedMode: true, }), - }, + }); + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: arrayWith({ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Resource: 'arn:*:iam::*:role/*-image-publishing-role-*', + }), + }, + }); }); -}); -test('docker image asset publishers use privilegedmode, have right AssumeRole', () => { - // WHEN - pipeline.addApplicationStage(new DockerAssetApp(app, 'DockerAssetApp')); - - // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { - Source: { - BuildSpec: encodedJson(deepObjectLike({ - phases: { - build: { - commands: arrayWith(stringLike('cdk-assets *')), + test('can control fix/CLI version used in pipeline selfupdate', () => { + // WHEN + const stack2 = new Stack(app, 'Stack2', { env: PIPELINE_ENV }); + const pipeline2 = new TestGitHubNpmPipeline(stack2, 'Cdk2', { + cdkCliVersion: '1.2.3', + }); + pipeline2.addApplicationStage(new FileAssetApp(stack2, 'FileAssetApp')); + + // THEN + expect(stack2).toHaveResourceLike('AWS::CodeBuild::Project', { + Environment: { + Image: 'aws/codebuild/standard:4.0', + }, + Source: { + BuildSpec: encodedJson(deepObjectLike({ + phases: { + install: { + commands: 'npm install -g cdk-assets@1.2.3', + }, }, - }, - })), - }, - Environment: objectLike({ - Image: 'aws/codebuild/standard:4.0', - PrivilegedMode: true, - }), - }); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: arrayWith({ - Action: 'sts:AssumeRole', - Effect: 'Allow', - Resource: 'arn:*:iam::*:role/*-image-publishing-role-*', - }), - }, + })), + }, + }); }); -}); -test('docker image asset can use a VPC', () => { - // WHEN - pipeline.addApplicationStage(new DockerAssetApp(app, 'DockerAssetApp')); - - // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { - VpcConfig: objectLike({ - SecurityGroupIds: [ - { - 'Fn::GetAtt': [ - 'CdkAssetsDockerAsset1SecurityGroup078F5C66', - 'GroupId', - ], - }, - ], - Subnets: [ - { - Ref: 'TestVpcPrivateSubnet1SubnetCC65D771', - }, - { - Ref: 'TestVpcPrivateSubnet2SubnetDE0C64A2', + describe('asset roles and policies', () => { + test('includes file publishing assets role for apps with file assets', () => { + pipeline.addApplicationStage(new FileAssetApp(app, 'App1')); + + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'codebuild.amazonaws.com', + AWS: { + 'Fn::Join': ['', [ + 'arn:', { Ref: 'AWS::Partition' }, `:iam::${PIPELINE_ENV.account}:root`, + ]], + }, + }, + }], }, - { - Ref: 'TestVpcPrivateSubnet3Subnet2311D32F', + }); + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + expectedAssetRolePolicy('arn:*:iam::*:role/*-file-publishing-role-*', 'CdkAssetsFileRole6BE17A07')); + }); + + test('includes image publishing assets role for apps with Docker assets', () => { + pipeline.addApplicationStage(new DockerAssetApp(app, 'App1')); + + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [{ + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'codebuild.amazonaws.com', + AWS: { + 'Fn::Join': ['', [ + 'arn:', { Ref: 'AWS::Partition' }, `:iam::${PIPELINE_ENV.account}:root`, + ]], + }, + }, + }], }, - ], - VpcId: { - Ref: 'TestVpcE77CE678', - }, - }), + }); + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + expectedAssetRolePolicy('arn:*:iam::*:role/*-image-publishing-role-*', 'CdkAssetsDockerRole484B6DD3')); + }); + + test('includes both roles for apps with both file and Docker assets', () => { + pipeline.addApplicationStage(new FileAssetApp(app, 'App1')); + pipeline.addApplicationStage(new DockerAssetApp(app, 'App2')); + + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + expectedAssetRolePolicy('arn:*:iam::*:role/*-file-publishing-role-*', 'CdkAssetsFileRole6BE17A07')); + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + expectedAssetRolePolicy('arn:*:iam::*:role/*-image-publishing-role-*', 'CdkAssetsDockerRole484B6DD3')); + }); }); }); -test('can control fix/CLI version used in pipeline selfupdate', () => { - // WHEN - const stack2 = new Stack(app, 'Stack2', { env: PIPELINE_ENV }); - const pipeline2 = new TestGitHubNpmPipeline(stack2, 'Cdk2', { - cdkCliVersion: '1.2.3', +describe('pipeline with VPC', () => { + let vpc: ec2.Vpc; + beforeEach(() => { + app = new TestApp(); + pipelineStack = new Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + vpc = new ec2.Vpc(pipelineStack, 'Vpc'); + pipeline = new TestGitHubNpmPipeline(pipelineStack, 'Cdk', { + vpc, + }); }); - pipeline2.addApplicationStage(new FileAssetApp(stack2, 'FileAssetApp')); - // THEN - expect(stack2).toHaveResourceLike('AWS::CodeBuild::Project', { - Environment: { - Image: 'aws/codebuild/standard:4.0', - }, - Source: { - BuildSpec: encodedJson(deepObjectLike({ - phases: { - install: { - commands: 'npm install -g cdk-assets@1.2.3', - }, - }, - })), - }, + afterEach(() => { + app.cleanup(); }); -}); -describe('asset roles and policies', () => { - test('includes file publishing assets role for apps with file assets', () => { - pipeline.addApplicationStage(new FileAssetApp(app, 'App1')); + test('asset CodeBuild Project uses VPC subnets', () => { + // WHEN + pipeline.addApplicationStage(new DockerAssetApp(app, 'DockerAssetApp')); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Role', { - AssumeRolePolicyDocument: { - Statement: [{ - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { - Service: 'codebuild.amazonaws.com', - AWS: { - 'Fn::Join': ['', [ - 'arn:', { Ref: 'AWS::Partition' }, `:iam::${PIPELINE_ENV.account}:root`, - ]], - }, - }, - }], - }, + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + VpcConfig: objectLike({ + SecurityGroupIds: [ + { 'Fn::GetAtt': ['CdkAssetsDockerAsset1SecurityGroup078F5C66', 'GroupId'] }, + ], + Subnets: [ + { Ref: 'VpcPrivateSubnet1Subnet536B997A' }, + { Ref: 'VpcPrivateSubnet2Subnet3788AAA1' }, + { Ref: 'VpcPrivateSubnet3SubnetF258B56E' }, + ], + VpcId: { Ref: 'Vpc8378EB38' }, + }), }); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', - expectedAssetRolePolicy('arn:*:iam::*:role/*-file-publishing-role-*', 'CdkAssetsFileRole6BE17A07')); }); - test('includes image publishing assets role for apps with Docker assets', () => { - pipeline.addApplicationStage(new DockerAssetApp(app, 'App1')); + test('Pipeline-generated CodeBuild Projects have appropriate execution role permissions', () => { + // WHEN + pipeline.addApplicationStage(new DockerAssetApp(app, 'DockerAssetApp')); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Role', { - AssumeRolePolicyDocument: { - Statement: [{ - Action: 'sts:AssumeRole', + // THEN + + // Assets Project + expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Roles: [ + { Ref: 'CdkAssetsDockerRole484B6DD3' }, + ], + PolicyDocument: { + Statement: arrayWith({ + Action: arrayWith('ec2:DescribeSecurityGroups'), Effect: 'Allow', - Principal: { - Service: 'codebuild.amazonaws.com', - AWS: { - 'Fn::Join': ['', [ - 'arn:', { Ref: 'AWS::Partition' }, `:iam::${PIPELINE_ENV.account}:root`, - ]], - }, - }, - }], + Resource: '*', + }), }, }); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', - expectedAssetRolePolicy('arn:*:iam::*:role/*-image-publishing-role-*', 'CdkAssetsDockerRole484B6DD3')); - }); - - test('includes both roles for apps with both file and Docker assets', () => { - pipeline.addApplicationStage(new FileAssetApp(app, 'App1')); - pipeline.addApplicationStage(new DockerAssetApp(app, 'App2')); - - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', - expectedAssetRolePolicy('arn:*:iam::*:role/*-file-publishing-role-*', 'CdkAssetsFileRole6BE17A07')); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', - expectedAssetRolePolicy('arn:*:iam::*:role/*-image-publishing-role-*', 'CdkAssetsDockerRole484B6DD3')); }); }); diff --git a/packages/@aws-cdk/pipelines/test/testutil.ts b/packages/@aws-cdk/pipelines/test/testutil.ts index 40135256b39d7..f3513eee6c5ce 100644 --- a/packages/@aws-cdk/pipelines/test/testutil.ts +++ b/packages/@aws-cdk/pipelines/test/testutil.ts @@ -2,7 +2,6 @@ import * as fs from 'fs'; import * as path from 'path'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; -import * as ec2 from '@aws-cdk/aws-ec2'; import * as s3 from '@aws-cdk/aws-s3'; import { App, AppProps, Environment, SecretValue, Stack, StackProps, Stage } from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -46,7 +45,6 @@ export class TestGitHubNpmPipeline extends cdkp.CdkPipeline { sourceArtifact, cloudAssemblyArtifact, }), - vpc: new ec2.Vpc(scope, 'TestVpc'), cloudAssemblyArtifact, ...props, }); From 5c3dce56c71083321069a31213aaa5bce40f51d3 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 26 Jan 2021 16:08:34 +0100 Subject: [PATCH 11/70] fix(pipelines): assets broken in Pipelines synthesized from Windows (#12573) Missed a place where we forgot to call `toPosixPath()`. No test because I'm unsure how to best test this. Ideas welcome. Fixes #12540. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/pipelines/lib/actions/publish-assets-action.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts b/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts index 0c661b61e9251..10b1bccbab965 100644 --- a/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts +++ b/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts @@ -10,6 +10,7 @@ import { Construct } from 'constructs'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. // eslint-disable-next-line import { Construct as CoreConstruct } from '@aws-cdk/core'; +import { toPosixPath } from '../private/fs'; /** * Type of the asset that is being published @@ -150,7 +151,7 @@ export class PublishAssetsAction extends CoreConstruct implements codepipeline.I * Manifest path should be relative to the root Cloud Assembly. */ public addPublishCommand(relativeManifestPath: string, assetSelector: string) { - const command = `cdk-assets --path "${relativeManifestPath}" --verbose publish "${assetSelector}"`; + const command = `cdk-assets --path "${toPosixPath(relativeManifestPath)}" --verbose publish "${assetSelector}"`; if (!this.commands.includes(command)) { this.commands.push(command); } From 10bb23b55a9c8f2356b034f25dce678d709acec5 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 26 Jan 2021 16:22:54 +0000 Subject: [PATCH 12/70] chore: upgrade github-api to mitigate axios security alert (#12706) Alert: https://github.com/aws/aws-cdk/security/dependabot/yarn.lock/axios/open axios is a transitive dependency of github-api. Upgrade to the latest github-api version that doesn't depend on this affected version of axios. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- tools/prlint/package.json | 4 +- yarn.lock | 134 ++++++++++++++++++++++++++++++++------ 2 files changed, 116 insertions(+), 22 deletions(-) diff --git a/tools/prlint/package.json b/tools/prlint/package.json index 22a7fd221163d..c9e6e895d56d4 100644 --- a/tools/prlint/package.json +++ b/tools/prlint/package.json @@ -11,11 +11,11 @@ }, "license": "Apache-2.0", "dependencies": { - "github-api": "^3.3.0" + "github-api": "^3.4.0" }, "scripts": { "build": "echo success", - "test": "echo sucees", + "test": "echo success", "build+test": "npm run build test && npm run test" } } diff --git a/yarn.lock b/yarn.lock index 04ec4d7447fea..aaee849150073 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2068,6 +2068,11 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" +app-root-path@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" + integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== + append-transform@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" @@ -2310,6 +2315,21 @@ aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" +aws-sdk@^2.596.0: + version "2.831.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.831.0.tgz#02607cc911a2136e5aabe624c1282e821830aef2" + integrity sha512-lrOjbGFpjk2xpESyUx2PGsTZgptCy5xycZazPeakNbFO19cOoxjHx3xyxOHsMCYb3pQwns35UvChQT60B4u6cw== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + aws-sdk@^2.637.0, aws-sdk@^2.830.0: version "2.830.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.830.0.tgz#1d3631d573d18c48373046da7ad92855a7fd1636" @@ -2335,12 +2355,12 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@^0.19.0: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== dependencies: - follow-redirects "1.5.10" + follow-redirects "^1.10.0" babel-jest@^26.6.3: version "26.6.3" @@ -3463,7 +3483,7 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -debug@3.1.0, debug@=3.1.0: +debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -3758,6 +3778,16 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" +dotenv-json@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dotenv-json/-/dotenv-json-1.0.0.tgz#fc7f672aafea04bed33818733b9f94662332815c" + integrity sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ== + +dotenv@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + dotgitignore@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/dotgitignore/-/dotgitignore-2.1.0.tgz#a4b15a4e4ef3cf383598aaf1dfa4a04bcc089b7b" @@ -3981,6 +4011,11 @@ escodegen@^1.14.1, escodegen@^1.8.1: optionalDependencies: source-map "~0.6.1" +eslint-config-standard@^14.1.1: + version "14.1.1" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" + integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== + eslint-import-resolver-node@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" @@ -4008,6 +4043,14 @@ eslint-module-utils@^2.6.0: debug "^2.6.9" pkg-dir "^2.0.0" +eslint-plugin-es@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" + integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== + dependencies: + eslint-utils "^2.0.0" + regexpp "^3.0.0" + eslint-plugin-import@^2.22.1: version "2.22.1" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" @@ -4034,11 +4077,33 @@ eslint-plugin-jest@^24.1.3: dependencies: "@typescript-eslint/experimental-utils" "^4.0.1" +eslint-plugin-node@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" + integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== + dependencies: + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" + ignore "^5.1.1" + minimatch "^3.0.4" + resolve "^1.10.1" + semver "^6.1.0" + +eslint-plugin-promise@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" + integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== + eslint-plugin-rulesdir@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-rulesdir/-/eslint-plugin-rulesdir-0.1.0.tgz#ad144d7e98464fda82963eff3fab331aecb2bf08" integrity sha1-rRRNfphGT9qClj7/P6szGuyyvwg= +eslint-plugin-standard@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz#0c3bf3a67e853f8bbbc580fb4945fbf16f41b7c5" + integrity sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ== + eslint-scope@^5.0.0, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -4494,12 +4559,10 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" +follow-redirects@^1.10.0: + version "1.13.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147" + integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA== follow-redirects@^1.11.0: version "1.13.0" @@ -4816,12 +4879,12 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" -github-api@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/github-api/-/github-api-3.3.0.tgz#5aaa2cc6787ff5bc27b3d01cba8bddfa7a7f6aad" - integrity sha512-30pABj/1ciHmlqmjnWXn+A4JL8j9qB2IcQgibrJ7euGbaNRkAj+T6QhJwjLcPx4Hxlj+BP1TcdvaQ/7resw+VA== +github-api@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/github-api/-/github-api-3.4.0.tgz#5da2f56442d4839d324e9faf0ffb2cf30f7650b8" + integrity sha512-2yYqYS6Uy4br1nw0D3VrlYWxtGTkUhIZrumBrcBwKdBOzMT8roAe8IvI6kjIOkxqxapKR5GkEsHtz3Du/voOpA== dependencies: - axios "^0.19.0" + axios "^0.21.1" debug "^2.2.0" js-base64 "^2.1.9" utf8 "^2.1.1" @@ -5149,7 +5212,7 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.4, ignore@^5.1.8, ignore@~5.1.8: +ignore@^5.1.1, ignore@^5.1.4, ignore@^5.1.8, ignore@~5.1.8: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== @@ -5335,7 +5398,7 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.0.0: +is-core-module@^2.0.0, is-core-module@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== @@ -6425,6 +6488,24 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +lambda-leak@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lambda-leak/-/lambda-leak-2.0.0.tgz#771985d3628487f6e885afae2b54510dcfb2cd7e" + integrity sha1-dxmF02KEh/boha+uK1RRDc+yzX4= + +lambda-tester@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/lambda-tester/-/lambda-tester-3.6.0.tgz#ceb7d4f4f0da768487a05cff37dcd088508b5247" + integrity sha512-F2ZTGWCLyIR95o/jWK46V/WnOCFAEUG/m/V7/CLhPJ7PCM+pror1rZ6ujP3TkItSGxUfpJi0kqwidw+M/nEqWw== + dependencies: + app-root-path "^2.2.1" + dotenv "^8.0.0" + dotenv-json "^1.0.0" + lambda-leak "^2.0.0" + semver "^6.1.1" + uuid "^3.3.2" + vandium-utils "^1.1.1" + lazystream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" @@ -8605,6 +8686,14 @@ resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.13.1, resolve@^1.17 dependencies: path-parse "^1.0.6" +resolve@^1.10.1: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + resolve@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" @@ -8762,7 +8851,7 @@ semver@7.x, semver@^7.1.1, semver@^7.2.1, semver@^7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -10128,6 +10217,11 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" +vandium-utils@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/vandium-utils/-/vandium-utils-1.2.0.tgz#44735de4b7641a05de59ebe945f174e582db4f59" + integrity sha1-RHNd5LdkGgXeWevpRfF05YLbT1k= + verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" From d7e028a1bf086164e1c5801cfe2aa99789afa445 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 26 Jan 2021 18:31:44 +0000 Subject: [PATCH 13/70] chore(cdk-build-tools): temporarily disable feature flag test (#12711) This was originally introduced in 31b1b3289b99c4ec4ed19c51705d66f0e83783dd. However, the forward merge of this change onto the v2-main branch is failing unit tests in consuming modules that implicitly depend on the future flag's behaviour. For example, unit tests in the `aws-s3` module depend on the behaviour of the `@aws-cdk/aws-kms:defaultKeyPolicies` flag. Temporarily disable this feature until these can be resolved. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- tools/cdk-build-tools/lib/feature-flag.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/cdk-build-tools/lib/feature-flag.ts b/tools/cdk-build-tools/lib/feature-flag.ts index 6e994a2435d30..eec1d28d6e9e4 100644 --- a/tools/cdk-build-tools/lib/feature-flag.ts +++ b/tools/cdk-build-tools/lib/feature-flag.ts @@ -30,9 +30,9 @@ export function testFutureBehavior( const major = cdkMajorVersion(repoRoot); if (major === 2) { - // In CDKv2, the default behaviour is as if the feature flags are enabled. So, ignore the feature flags passed. - const app = new cdkApp(); - return test(name, async () => fn(app)); + // Temporaily disable CDKv2 behaviour + // const app = new cdkApp(); + // return test(name, async () => fn(app)); } const app = new cdkApp({ context: flags }); return test(name, () => fn(app)); @@ -59,8 +59,8 @@ export function testLegacyBehavior( const major = cdkMajorVersion(repoRoot); if (major === 2) { - // In CDKv2, legacy behaviour is not supported. Skip the test. - return; + // Temporarily disable CDKv2 behaviour + // return; } const app = new cdkApp(); return test(name, () => fn(app)); From 05683907d6ffc9ab12b6744c1b59b0df096789e1 Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Tue, 26 Jan 2021 20:41:24 +0000 Subject: [PATCH 14/70] feat(aws-codebuild): add `enableBatchBuilds()` to Project (#12531) In order for a CodeBuild to run in batch mode, a batch service role is needed, as described [here in the docs](https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build.html). >Batch builds introduce a new security role in the batch configuration. This new role is required as CodeBuild must be able to call the StartBuild, StopBuild, and RetryBuild actions on your behalf to run builds as part of a batch. Customers should use a new role, and not the same role they use in their build... At first I thought lets add this by default, but then I realised when `BatchConfiguration` is set to something, in the aws console the default 'start build' button behaviour changes to start a batch build by default instead :/ So now this adds a new `supportBatchBuildType` option, which when `true` adds minimum amount of `BatchConfiguration` needed for batch builds to run. I also updated the doc blocks for the webhook option and CodePipeline action option, because users of those also need to set this option. It would be nice to auto-enable this if a webhook or CodeBuild action is configured, but that sounds pretty complicated. I'm not sure why anyone would need to customise this role, given it appears to only be used internally to do those 3 things, so this PR does not make it configurable. My thinking is that this could be added later if needed, but this PR just gets batch builds working. In the future if people want control of the other `BatchConfiguration` options I was thinking these could be added and would require `supportBatchBuildType` to be `true`. related: https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/621 --- packages/@aws-cdk/aws-codebuild/README.md | 17 ++ .../@aws-cdk/aws-codebuild/lib/project.ts | 58 +++++- packages/@aws-cdk/aws-codebuild/lib/source.ts | 11 +- .../integ.github-webhook-batch.expected.json | 188 ++++++++++++++++++ .../test/integ.github-webhook-batch.ts | 29 +++ .../aws-codebuild/test/test.codebuild.ts | 135 +++++++++++-- .../lib/codebuild/build-action.ts | 3 + ...eg.pipeline-code-build-batch.expected.json | 55 +++++ 8 files changed, 477 insertions(+), 19 deletions(-) create mode 100644 packages/@aws-cdk/aws-codebuild/test/integ.github-webhook-batch.expected.json create mode 100644 packages/@aws-cdk/aws-codebuild/test/integ.github-webhook-batch.ts diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 7eff891a9ca59..9f35a81656b56 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -600,3 +600,20 @@ new codebuild.Project(stack, 'MyProject', { Here's a CodeBuild project with a simple example that creates a project mounted on AWS EFS: [Minimal Example](./test/integ.project-file-system-location.ts) + +## Batch builds + +To enable batch builds you should call `enableBatchBuilds()` on the project instance. + +It returns an object containing the batch service role that was created, +or `undefined` if batch builds could not be enabled, for example if the project was imported. + +```ts +import * as codebuild from '@aws-cdk/aws-codebuild'; + +const project = new codebuild.Project(this, 'MyProject', { ... }); + +if (project.enableBatchBuilds()) { + console.log('Batch builds were enabled'); +} +``` diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 796422b379782..c98718bd744bf 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -28,6 +28,14 @@ import { CODEPIPELINE_SOURCE_ARTIFACTS_TYPE, NO_SOURCE_TYPE } from './source-typ // eslint-disable-next-line import { Construct as CoreConstruct } from '@aws-cdk/core'; +/** + * The type returned from {@link IProject#enableBatchBuilds}. + */ +export interface BatchBuildConfig { + /** The IAM batch service Role of this Project. */ + readonly role: iam.IRole; +} + export interface IProject extends IResource, iam.IGrantable, ec2.IConnectable { /** * The ARN of this Project. @@ -44,6 +52,14 @@ export interface IProject extends IResource, iam.IGrantable, ec2.IConnectable { /** The IAM service Role of this Project. Undefined for imported Projects. */ readonly role?: iam.IRole; + /** + * Enable batch builds. + * + * Returns an object contining the batch service role if batch builds + * could be enabled. + */ + enableBatchBuilds(): BatchBuildConfig | undefined; + addToRolePolicy(policyStatement: iam.PolicyStatement): void; /** @@ -196,6 +212,10 @@ abstract class ProjectBase extends Resource implements IProject { return this._connections; } + public enableBatchBuilds(): BatchBuildConfig | undefined { + return undefined; + } + /** * Add a permission only if there's a policy attached. * @param statement The permissions statement to add @@ -729,6 +749,7 @@ export class Project extends ProjectBase { private readonly _secondaryArtifacts: CfnProject.ArtifactsProperty[]; private _encryptionKey?: kms.IKey; private readonly _fileSystemLocations: CfnProject.ProjectFileSystemLocationProperty[]; + private _batchServiceRole?: iam.Role; constructor(scope: Construct, id: string, props: ProjectProps) { super(scope, id, { @@ -813,6 +834,14 @@ export class Project extends ProjectBase { sourceVersion: sourceConfig.sourceVersion, vpcConfig: this.configureVpc(props), logsConfig: this.renderLoggingConfiguration(props.logging), + buildBatchConfig: Lazy.any({ + produce: () => { + const config: CfnProject.ProjectBuildBatchConfigProperty | undefined = this._batchServiceRole ? { + serviceRole: this._batchServiceRole.roleArn, + } : undefined; + return config; + }, + }), }); this.addVpcRequiredPermissions(props, resource); @@ -853,6 +882,27 @@ export class Project extends ProjectBase { } } + public enableBatchBuilds(): BatchBuildConfig | undefined { + if (!this._batchServiceRole) { + this._batchServiceRole = new iam.Role(this, 'BatchServiceRole', { + assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'), + }); + this._batchServiceRole.addToPrincipalPolicy(new iam.PolicyStatement({ + resources: [Lazy.string({ + produce: () => this.projectArn, + })], + actions: [ + 'codebuild:StartBuild', + 'codebuild:StopBuild', + 'codebuild:RetryBuild', + ], + })); + } + return { + role: this._batchServiceRole, + }; + } + /** * Adds a secondary source to the Project. * @@ -1139,8 +1189,8 @@ export class Project extends ProjectBase { return undefined; } - let s3Config: CfnProject.S3LogsConfigProperty|undefined = undefined; - let cloudwatchConfig: CfnProject.CloudWatchLogsConfigProperty|undefined = undefined; + let s3Config: CfnProject.S3LogsConfigProperty | undefined = undefined; + let cloudwatchConfig: CfnProject.CloudWatchLogsConfigProperty | undefined = undefined; if (props.s3) { const s3Logs = props.s3; @@ -1350,10 +1400,10 @@ export interface IBuildImage { } /** Optional arguments to {@link IBuildImage.binder} - currently empty. */ -export interface BuildImageBindOptions {} +export interface BuildImageBindOptions { } /** The return type from {@link IBuildImage.binder} - currently empty. */ -export interface BuildImageConfig {} +export interface BuildImageConfig { } // @deprecated(not in tsdoc on purpose): add bind() to IBuildImage // and get rid of IBindableBuildImage diff --git a/packages/@aws-cdk/aws-codebuild/lib/source.ts b/packages/@aws-cdk/aws-codebuild/lib/source.ts index b8002f2db9e36..7706328a92bc7 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/source.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/source.ts @@ -485,6 +485,8 @@ interface ThirdPartyGitSourceProps extends GitSourceProps { /** * Trigger a batch build from a webhook instead of a standard one. * + * Enabling this will enable batch builds on the CodeBuild project. + * * @default false */ readonly webhookTriggersBatchBuild?: boolean; @@ -518,7 +520,7 @@ abstract class ThirdPartyGitSource extends GitSource { this.webhookTriggersBatchBuild = props.webhookTriggersBatchBuild; } - public bind(_scope: CoreConstruct, _project: IProject): SourceConfig { + public bind(_scope: CoreConstruct, project: IProject): SourceConfig { const anyFilterGroupsProvided = this.webhookFilters.length > 0; const webhook = this.webhook === undefined ? (anyFilterGroupsProvided ? true : undefined) : this.webhook; @@ -530,7 +532,12 @@ abstract class ThirdPartyGitSource extends GitSource { throw new Error('`webhookTriggersBatchBuild` cannot be used when `webhook` is `false`'); } - const superConfig = super.bind(_scope, _project); + const superConfig = super.bind(_scope, project); + + if (this.webhookTriggersBatchBuild) { + project.enableBatchBuilds(); + } + return { sourceProperty: { ...superConfig.sourceProperty, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.github-webhook-batch.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.github-webhook-batch.expected.json new file mode 100644 index 0000000000000..b58305bddf2f3 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/integ.github-webhook-batch.expected.json @@ -0,0 +1,188 @@ +{ + "Resources": { + "MyProjectRole9BBE5233": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyProjectRoleDefaultPolicyB19B7C29": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + }, + ":*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyProjectRoleDefaultPolicyB19B7C29", + "Roles": [ + { + "Ref": "MyProjectRole9BBE5233" + } + ] + } + }, + "MyProjectBatchServiceRole6B35CF0E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyProjectBatchServiceRoleDefaultPolicy7A0E5721": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codebuild:StartBuild", + "codebuild:StopBuild", + "codebuild:RetryBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyProject39F7B0AE", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyProjectBatchServiceRoleDefaultPolicy7A0E5721", + "Roles": [ + { + "Ref": "MyProjectBatchServiceRole6B35CF0E" + } + ] + } + }, + "MyProject39F7B0AE": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "NO_ARTIFACTS" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "MyProjectRole9BBE5233", + "Arn" + ] + }, + "Source": { + "Location": "https://github.com/aws/aws-cdk.git", + "ReportBuildStatus": false, + "Type": "GITHUB" + }, + "BuildBatchConfig": { + "ServiceRole": { + "Fn::GetAtt": [ + "MyProjectBatchServiceRole6B35CF0E", + "Arn" + ] + } + }, + "EncryptionKey": "alias/aws/s3", + "Triggers": { + "BuildType": "BUILD_BATCH", + "FilterGroups": [ + [ + { + "Pattern": "PUSH", + "Type": "EVENT" + } + ] + ], + "Webhook": true + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.github-webhook-batch.ts b/packages/@aws-cdk/aws-codebuild/test/integ.github-webhook-batch.ts new file mode 100644 index 0000000000000..d8fd006956035 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/integ.github-webhook-batch.ts @@ -0,0 +1,29 @@ +import * as cdk from '@aws-cdk/core'; +import * as codebuild from '../lib'; + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string) { + super(scope, id); + + const source = codebuild.Source.gitHub({ + owner: 'aws', + repo: 'aws-cdk', + reportBuildStatus: false, + webhook: true, + webhookTriggersBatchBuild: true, + webhookFilters: [ + codebuild.FilterGroup.inEventOf(codebuild.EventAction.PUSH), + ], + }); + new codebuild.Project(this, 'MyProject', { + source, + grantReportGroupPermissions: false, + }); + } +} + +const app = new cdk.App(); + +new TestStack(app, 'test-codebuild-github-webhook-batch'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index 5a7766918c67b..d6341771d71b7 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -712,6 +712,51 @@ export = { Webhook: true, BuildType: 'BUILD_BATCH', }, + BuildBatchConfig: { + ServiceRole: { + 'Fn::GetAtt': [ + 'ProjectBatchServiceRoleF97A1CFB', + 'Arn', + ], + }, + }, + })); + + expect(stack).to(haveResourceLike('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'codebuild.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + })); + + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'codebuild:StartBuild', + 'codebuild:StopBuild', + 'codebuild:RetryBuild', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'ProjectC78D97AD', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, })); test.done(); @@ -1275,12 +1320,11 @@ export = { }); expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { - 'Artifacts': - { - 'Name': ABSENT, - 'ArtifactIdentifier': 'artifact1', - 'OverrideArtifactName': true, - }, + 'Artifacts': { + 'Name': ABSENT, + 'ArtifactIdentifier': 'artifact1', + 'OverrideArtifactName': true, + }, })); test.done(); @@ -1302,12 +1346,11 @@ export = { }); expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { - 'Artifacts': - { - 'ArtifactIdentifier': 'artifact1', - 'Name': 'specificname', - 'OverrideArtifactName': ABSENT, - }, + 'Artifacts': { + 'ArtifactIdentifier': 'artifact1', + 'Name': 'specificname', + 'OverrideArtifactName': ABSENT, + }, })); test.done(); @@ -1481,7 +1524,7 @@ export = { '', [ '111', - { twotwotwo: '222' }, + { twotwotwo: '222' }, ], ], }, @@ -1862,4 +1905,70 @@ export = { }, }, }, + + 'enableBatchBuilds()'(test: Test) { + const stack = new cdk.Stack(); + + const project = new codebuild.Project(stack, 'Project', { + source: codebuild.Source.gitHub({ + owner: 'testowner', + repo: 'testrepo', + }), + }); + + const returnVal = project.enableBatchBuilds(); + if (!returnVal?.role) { + throw new Error('Expecting return value with role'); + } + + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + BuildBatchConfig: { + ServiceRole: { + 'Fn::GetAtt': [ + 'ProjectBatchServiceRoleF97A1CFB', + 'Arn', + ], + }, + }, + })); + + expect(stack).to(haveResourceLike('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'codebuild.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + })); + + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'codebuild:StartBuild', + 'codebuild:StopBuild', + 'codebuild:RetryBuild', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'ProjectC78D97AD', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + })); + + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts index 468684664cc5f..238efebae4658 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts @@ -97,6 +97,8 @@ export interface CodeBuildActionProps extends codepipeline.CommonAwsActionProps /** * Trigger a batch build. * + * Enabling this will enable batch builds on the CodeBuild project. + * * @default false */ readonly executeBatchBuild?: boolean; @@ -213,6 +215,7 @@ export class CodeBuildAction extends Action { } if (this.props.executeBatchBuild) { configuration.BatchEnabled = 'true'; + this.props.project.enableBatchBuilds(); } return { configuration, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-batch.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-batch.expected.json index d489a5712eeba..13357ba22c85b 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-batch.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-batch.expected.json @@ -480,8 +480,63 @@ "Source": { "Type": "CODEPIPELINE" }, + "BuildBatchConfig": { + "ServiceRole": { + "Fn::GetAtt": [ + "MyBuildProjectBatchServiceRole531F3056", + "Arn" + ] + } + }, "EncryptionKey": "alias/aws/s3" } + }, + "MyBuildProjectBatchServiceRole531F3056": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyBuildProjectBatchServiceRoleDefaultPolicy816785FC": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codebuild:StartBuild", + "codebuild:StopBuild", + "codebuild:RetryBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyBuildProject30DB9D6E", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyBuildProjectBatchServiceRoleDefaultPolicy816785FC", + "Roles": [ + { + "Ref": "MyBuildProjectBatchServiceRole531F3056" + } + ] + } } } } From fe37174ec29b7d3b60b252df08ceecf1aa057098 Mon Sep 17 00:00:00 2001 From: Abdul Kader Maliyakkal Date: Tue, 26 Jan 2021 15:33:21 -0800 Subject: [PATCH 15/70] feat(batch): Compute Resources placement group (#12203) Closes https://github.com/aws/aws-cdk/issues/12175 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-batch/lib/compute-environment.ts | 8 ++++++++ .../@aws-cdk/aws-batch/test/compute-environment.test.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts index e4b6f4ca22c33..96c356ac13d50 100644 --- a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts +++ b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts @@ -178,6 +178,13 @@ export interface ComputeResources { */ readonly minvCpus?: number; + /** + * The Amazon EC2 placement group to associate with your compute resources. + * + * @default - No placement group will be used. + */ + readonly placementGroup?: string; + /** * The EC2 key pair that is used for instances launched in the compute environment. * If no key is defined, then SSH access is not allowed to provisioned compute resources. @@ -365,6 +372,7 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment launchTemplate: props.computeResources.launchTemplate, maxvCpus: props.computeResources.maxvCpus || 256, minvCpus: props.computeResources.minvCpus || 0, + placementGroup: props.computeResources.placementGroup, securityGroupIds: this.buildSecurityGroupIds(props.computeResources.vpc, props.computeResources.securityGroups), spotIamFleetRole: spotFleetRole ? spotFleetRole.roleArn : undefined, subnets: props.computeResources.vpc.selectSubnets(props.computeResources.vpcSubnets).subnetIds, 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 f069ab3a5a080..153eb932eec37 100644 --- a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts +++ b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts @@ -184,6 +184,7 @@ describe('Batch Compute Evironment', () => { ], maxvCpus: 4, minvCpus: 1, + placementGroup: 'example-cluster-group', securityGroups: [ new ec2.SecurityGroup(stack, 'test-sg', { vpc, @@ -230,6 +231,7 @@ describe('Batch Compute Evironment', () => { ], MaxvCpus: props.computeResources.maxvCpus, MinvCpus: props.computeResources.minvCpus, + PlacementGroup: props.computeResources.placementGroup, SecurityGroupIds: [ { 'Fn::GetAtt': [ From faf6d7f6028b90896d192719563344fb55dabc70 Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Wed, 27 Jan 2021 09:57:39 +0000 Subject: [PATCH 16/70] chore(release): 1.87.0 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ version.v1.json | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53464a6ced7a9..737569579273f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.87.0](https://github.com/aws/aws-cdk/compare/v1.86.0...v1.87.0) (2021-01-27) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **s3-deployment:** User metadata keys of bucket objects will change from `x-amz-meta-x-amz-meta-x-amzn-meta-mykey` to `x-amz-meta-mykey`. +* **core:** users of modern synthesis (`DefaultSynthesizer`, +used by CDK Pipelines) must upgrade their bootstrap stacks. Run `cdk bootstrap`. + +### Features + +* **aws-codebuild:** add `enableBatchBuilds()` to Project ([#12531](https://github.com/aws/aws-cdk/issues/12531)) ([0568390](https://github.com/aws/aws-cdk/commit/05683907d6ffc9ab12b6744c1b59b0df096789e1)) +* **aws-codepipeline-actions:** Add Full Clone support for CodeCommit ([#12558](https://github.com/aws/aws-cdk/issues/12558)) ([d169688](https://github.com/aws/aws-cdk/commit/d169688f35bc78c88c44ff9a7d8fa0dfea71f904)), closes [#12236](https://github.com/aws/aws-cdk/issues/12236) +* **batch:** Compute Resources placement group ([#12203](https://github.com/aws/aws-cdk/issues/12203)) ([fe37174](https://github.com/aws/aws-cdk/commit/fe37174ec29b7d3b60b252df08ceecf1aa057098)) +* **eks:** Graduate to stable ([#12640](https://github.com/aws/aws-cdk/issues/12640)) ([b5ba7cd](https://github.com/aws/aws-cdk/commit/b5ba7cdd61714bcfbf2135240790340a77ee1a8b)) +* **stepfunctions-tasks:** EcsRunTask now uses taskDefinition family instead of ARN ([#12436](https://github.com/aws/aws-cdk/issues/12436)) ([abde96b](https://github.com/aws/aws-cdk/commit/abde96b046358fc5435545692eba4fd63d503914)), closes [#12080](https://github.com/aws/aws-cdk/issues/12080) +* **stepfunctions-tasks:** support databrew startJobRun task ([#12532](https://github.com/aws/aws-cdk/issues/12532)) ([eacd2f7](https://github.com/aws/aws-cdk/commit/eacd2f7ea67c83d50c839acf29fbe953ae49d987)) + + +### Bug Fixes + +* **apigatewayv2:** multiple http integrations are created for each route ([#12528](https://github.com/aws/aws-cdk/issues/12528)) ([855ce59](https://github.com/aws/aws-cdk/commit/855ce59039a577d142d68720e86d81610edffc64)), closes [40aws-cdk/aws-apigatewayv2/lib/http/route.ts#L128](https://github.com/40aws-cdk/aws-apigatewayv2/lib/http/route.ts/issues/L128) +* **core:** modern deployments fail if bootstrap stack is renamed ([#12594](https://github.com/aws/aws-cdk/issues/12594)) ([e5c616f](https://github.com/aws/aws-cdk/commit/e5c616f73eac395492636341f57fb6a716d1ea69)), closes [#11952](https://github.com/aws/aws-cdk/issues/11952) [#11420](https://github.com/aws/aws-cdk/issues/11420) [#9053](https://github.com/aws/aws-cdk/issues/9053) +* **pipelines:** assets broken in Pipelines synthesized from Windows ([#12573](https://github.com/aws/aws-cdk/issues/12573)) ([5c3dce5](https://github.com/aws/aws-cdk/commit/5c3dce56c71083321069a31213aaa5bce40f51d3)), closes [#12540](https://github.com/aws/aws-cdk/issues/12540) +* **pipelines:** can't use CodePipeline variables in Synth environment variables ([#12602](https://github.com/aws/aws-cdk/issues/12602)) ([736b260](https://github.com/aws/aws-cdk/commit/736b260db7f21d89e220591007580f62b22fea3a)), closes [#12061](https://github.com/aws/aws-cdk/issues/12061) [#11178](https://github.com/aws/aws-cdk/issues/11178) +* **pipelines:** unable to publish assets inside VPC ([#12331](https://github.com/aws/aws-cdk/issues/12331)) ([a16f09c](https://github.com/aws/aws-cdk/commit/a16f09c9ea675caf5b1e50a4e1cc288e5afd1237)), closes [#11815](https://github.com/aws/aws-cdk/issues/11815) +* **s3-deployment:** User metadata keys have redundant triple `x-amz` prefix ([#12414](https://github.com/aws/aws-cdk/issues/12414)) ([6716181](https://github.com/aws/aws-cdk/commit/671618152dc585ef0703f6c3501f6ee5a366b4a9)), closes [#8459](https://github.com/aws/aws-cdk/issues/8459) +* **secretsmanager:** fromSecretPartialArn() has incorrect grant policies ([#12665](https://github.com/aws/aws-cdk/issues/12665)) ([560915e](https://github.com/aws/aws-cdk/commit/560915ece87a919f499a64452b919a0b291394ee)), closes [#12411](https://github.com/aws/aws-cdk/issues/12411) + ## [1.86.0](https://github.com/aws/aws-cdk/compare/v1.85.0...v1.86.0) (2021-01-21) diff --git a/version.v1.json b/version.v1.json index 549ac5ace19cd..d6ecf4be3e6fc 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.86.0" + "version": "1.87.0" } From c429bb7df2406346426dce22d716cabc484ec7e6 Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Wed, 27 Jan 2021 04:19:39 -0700 Subject: [PATCH 17/70] fix(cloudfront): use node addr for edgeStackId name (#12702) Closes #12323 BREAKING CHANGE: experimental EdgeFunction stack names have changed from 'edge-lambda-stack-${region}' to 'edge-lambda-stack-${stackid}' to support multiple independent CloudFront distributions with EdgeFunctions. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-cloudfront/lib/experimental/edge-function.ts | 2 +- .../aws-cloudfront/test/experimental/edge-function.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts b/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts index e1d7a5dd9c960..f97b7e176d874 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts @@ -214,7 +214,7 @@ export class EdgeFunction extends Resource implements lambda.IVersion { throw new Error('stacks which use EdgeFunctions must have an explicitly set region'); } - const edgeStackId = stackId ?? `edge-lambda-stack-${region}`; + const edgeStackId = stackId ?? `edge-lambda-stack-${this.stack.node.addr}`; let edgeStack = stage.node.tryFindChild(edgeStackId) as Stack; if (!edgeStack) { edgeStack = new Stack(stage, edgeStackId, { diff --git a/packages/@aws-cdk/aws-cloudfront/test/experimental/edge-function.test.ts b/packages/@aws-cdk/aws-cloudfront/test/experimental/edge-function.test.ts index 372caea5c3fb6..4154f79cabff2 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/experimental/edge-function.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/experimental/edge-function.test.ts @@ -250,10 +250,10 @@ function defaultEdgeFunctionProps(stackId?: string) { code: lambda.Code.fromInline('foo'), handler: 'index.handler', runtime: lambda.Runtime.NODEJS_12_X, - stackId: stackId ?? 'edge-lambda-stack-testregion', + stackId: stackId, }; } -function getFnStack(region: string = 'testregion'): cdk.Stack { - return app.node.findChild(`edge-lambda-stack-${region}`) as cdk.Stack; +function getFnStack(): cdk.Stack { + return app.node.findChild(`edge-lambda-stack-${stack.node.addr}`) as cdk.Stack; } From 59db763e7d05d68fd85b6fd37246d69d4670d7d5 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 27 Jan 2021 11:56:38 +0000 Subject: [PATCH 18/70] fix(lambda): codeguru profiler not set up for Node runtime (#12712) The CodeGuru profiler is only supported for Java and Python runtimes as part of its lambda integration. Codify this into the CDK and add a validation to fail if this is used with unsupported runtimes. fixes #12624 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda/README.md | 11 +++++- packages/@aws-cdk/aws-lambda/lib/function.ts | 9 +++-- packages/@aws-cdk/aws-lambda/lib/runtime.ts | 38 ++++++++++++++++--- .../@aws-cdk/aws-lambda/test/function.test.ts | 24 +++++++++--- 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 5b7ee7cd3240e..e2b317b8faba3 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -270,17 +270,24 @@ to learn more about AWS Lambda's X-Ray support. ## Lambda with Profiling +The following code configures the lambda function with CodeGuru profiling. By default, this creates a new CodeGuru +profiling group - + ```ts import * as lambda from '@aws-cdk/aws-lambda'; const fn = new lambda.Function(this, 'MyFunction', { - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.PYTHON_3_6, handler: 'index.handler', - code: lambda.Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'), + code: lambda.Code.fromAsset('lambda-handler'), profiling: true }); ``` +The `profilingGroup` property can be used to configure an existing CodeGuru profiler group. + +CodeGuru profiling is supported for all Java runtimes and Python3.6+ runtimes. + See [the AWS documentation](https://docs.aws.amazon.com/codeguru/latest/profiler-ug/setting-up-lambda.html) to learn more about AWS Lambda's Profiling support. diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 0e56ee695d4e5..fdcf4b4e0ec24 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -574,7 +574,7 @@ export class Function extends FunctionBase { let profilingGroupEnvironmentVariables: { [key: string]: string } = {}; if (props.profilingGroup && props.profiling !== false) { - this.validateProfilingEnvironmentVariables(props); + this.validateProfiling(props); props.profilingGroup.grantPublish(this.role); profilingGroupEnvironmentVariables = { AWS_CODEGURU_PROFILER_GROUP_ARN: Stack.of(scope).formatArn({ @@ -585,7 +585,7 @@ export class Function extends FunctionBase { AWS_CODEGURU_PROFILER_ENABLED: 'TRUE', }; } else if (props.profiling) { - this.validateProfilingEnvironmentVariables(props); + this.validateProfiling(props); const profilingGroup = new ProfilingGroup(this, 'ProfilingGroup', { computePlatform: ComputePlatform.AWS_LAMBDA, }); @@ -941,7 +941,10 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett }; } - private validateProfilingEnvironmentVariables(props: FunctionProps) { + private validateProfiling(props: FunctionProps) { + if (!props.runtime.supportsCodeGuruProfiling) { + throw new Error(`CodeGuru profiling is not supported by runtime ${props.runtime.name}`); + } if (props.environment && (props.environment.AWS_CODEGURU_PROFILER_GROUP_ARN || props.environment.AWS_CODEGURU_PROFILER_ENABLED)) { throw new Error('AWS_CODEGURU_PROFILER_GROUP_ARN and AWS_CODEGURU_PROFILER_ENABLED must not be set when profiling options enabled'); } diff --git a/packages/@aws-cdk/aws-lambda/lib/runtime.ts b/packages/@aws-cdk/aws-lambda/lib/runtime.ts index 1930641f45f04..1d98212628588 100644 --- a/packages/@aws-cdk/aws-lambda/lib/runtime.ts +++ b/packages/@aws-cdk/aws-lambda/lib/runtime.ts @@ -12,6 +12,12 @@ export interface LambdaRuntimeProps { * @default - the latest docker image "amazon/aws-sam-cli-build-image-" from https://hub.docker.com/u/amazon */ readonly bundlingDockerImage?: string; + + /** + * Whether this runtime is integrated with and supported for profiling using Amazon CodeGuru Profiler. + * @default false + */ + readonly supportsCodeGuruProfiling?: boolean; } export enum RuntimeFamily { @@ -80,32 +86,46 @@ export class Runtime { /** * The Python 3.6 runtime (python3.6) */ - public static readonly PYTHON_3_6 = new Runtime('python3.6', RuntimeFamily.PYTHON, { supportsInlineCode: true }); + public static readonly PYTHON_3_6 = new Runtime('python3.6', RuntimeFamily.PYTHON, { + supportsInlineCode: true, + supportsCodeGuruProfiling: true, + }); /** * The Python 3.7 runtime (python3.7) */ - public static readonly PYTHON_3_7 = new Runtime('python3.7', RuntimeFamily.PYTHON, { supportsInlineCode: true }); + public static readonly PYTHON_3_7 = new Runtime('python3.7', RuntimeFamily.PYTHON, { + supportsInlineCode: true, + supportsCodeGuruProfiling: true, + }); /** * The Python 3.8 runtime (python3.8) */ - public static readonly PYTHON_3_8 = new Runtime('python3.8', RuntimeFamily.PYTHON); + public static readonly PYTHON_3_8 = new Runtime('python3.8', RuntimeFamily.PYTHON, { + supportsCodeGuruProfiling: true, + }); /** * The Java 8 runtime (java8) */ - public static readonly JAVA_8 = new Runtime('java8', RuntimeFamily.JAVA); + public static readonly JAVA_8 = new Runtime('java8', RuntimeFamily.JAVA, { + supportsCodeGuruProfiling: true, + }); /** * The Java 8 Corretto runtime (java8.al2) */ - public static readonly JAVA_8_CORRETTO = new Runtime('java8.al2', RuntimeFamily.JAVA); + public static readonly JAVA_8_CORRETTO = new Runtime('java8.al2', RuntimeFamily.JAVA, { + supportsCodeGuruProfiling: true, + }); /** * The Java 11 runtime (java11) */ - public static readonly JAVA_11 = new Runtime('java11', RuntimeFamily.JAVA); + public static readonly JAVA_11 = new Runtime('java11', RuntimeFamily.JAVA, { + supportsCodeGuruProfiling: true, + }); /** * The .NET Core 1.0 runtime (dotnetcore1.0) @@ -178,6 +198,11 @@ export class Runtime { */ public readonly supportsInlineCode: boolean; + /** + * Whether this runtime is integrated with and supported for profiling using Amazon CodeGuru Profiler. + */ + public readonly supportsCodeGuruProfiling: boolean; + /** * The runtime family. */ @@ -194,6 +219,7 @@ export class Runtime { this.family = family; const imageName = props.bundlingDockerImage ?? `amazon/aws-sam-cli-build-image-${name}`; this.bundlingDockerImage = BundlingDockerImage.fromRegistry(imageName); + this.supportsCodeGuruProfiling = props.supportsCodeGuruProfiling ?? false; Runtime.ALL.push(this); } diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index 707b32428912b..51cfe70fd878c 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -1611,7 +1611,7 @@ describe('function', () => { new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.PYTHON_3_6, profiling: true, }); @@ -1660,7 +1660,7 @@ describe('function', () => { new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.PYTHON_3_6, profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), }); @@ -1712,7 +1712,7 @@ describe('function', () => { new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.PYTHON_3_6, profiling: false, profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), }); @@ -1743,7 +1743,7 @@ describe('function', () => { expect(() => new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.PYTHON_3_6, profiling: true, environment: { AWS_CODEGURU_PROFILER_GROUP_ARN: 'profiler_group_arn', @@ -1758,7 +1758,7 @@ describe('function', () => { expect(() => new lambda.Function(stack, 'MyLambda', { code: new lambda.InlineCode('foo'), handler: 'index.handler', - runtime: lambda.Runtime.NODEJS_10_X, + runtime: lambda.Runtime.PYTHON_3_6, profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), environment: { AWS_CODEGURU_PROFILER_GROUP_ARN: 'profiler_group_arn', @@ -1766,6 +1766,20 @@ describe('function', () => { }, })).toThrow(/AWS_CODEGURU_PROFILER_GROUP_ARN and AWS_CODEGURU_PROFILER_ENABLED must not be set when profiling options enabled/); }); + + test('throws an error when used with an unsupported runtime', () => { + const stack = new cdk.Stack(); + expect(() => new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + profilingGroup: new ProfilingGroup(stack, 'ProfilingGroup'), + environment: { + AWS_CODEGURU_PROFILER_GROUP_ARN: 'profiler_group_arn', + AWS_CODEGURU_PROFILER_ENABLED: 'yes', + }, + })).toThrow(/not supported by runtime/); + }); }); describe('currentVersion', () => { From 5448388bd0574322a1f5943065d79328fc84b570 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 27 Jan 2021 12:34:02 +0000 Subject: [PATCH 19/70] chore(s3): bucket.test.ts is using jest (#12709) Move `stack.test.ts` to use native jest APIs instead of `nodeunitshim`. Motivation As part of the work for feature flags in v2, it's easier to inject conditional testing using jest's native APIs than to work with nodeunitshim. Nevertheless, we should be using native jest APIs everywhere. ---- *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-s3/test/bucket.test.ts | 880 +++++++++---------- 1 file changed, 440 insertions(+), 440 deletions(-) diff --git a/packages/@aws-cdk/aws-s3/test/bucket.test.ts b/packages/@aws-cdk/aws-s3/test/bucket.test.ts index 0d4e5b03f8258..adf4858bae8c8 100644 --- a/packages/@aws-cdk/aws-s3/test/bucket.test.ts +++ b/packages/@aws-cdk/aws-s3/test/bucket.test.ts @@ -1,22 +1,22 @@ +import '@aws-cdk/assert/jest'; import { EOL } from 'os'; -import { countResources, expect, haveResource, haveResourceLike, ResourcePart, SynthUtils, arrayWith, objectLike } from '@aws-cdk/assert'; +import { ResourcePart, SynthUtils, arrayWith, objectLike } from '@aws-cdk/assert'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; -import { nodeunitShim, Test } from 'nodeunit-shim'; import * as s3 from '../lib'; // to make it easy to copy & paste from output: /* eslint-disable quote-props */ -nodeunitShim({ - 'default bucket'(test: Test) { +describe('bucket', () => { + test('default bucket', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket'); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -26,29 +26,29 @@ nodeunitShim({ }, }); - test.done(); - }, - 'CFN properties are type-validated during resolution'(test: Test) { + }); + + test('CFN properties are type-validated during resolution', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { bucketName: cdk.Token.asString(5), // Oh no }); - test.throws(() => { + expect(() => { SynthUtils.synthesize(stack); - }, /bucketName: 5 should be a string/); + }).toThrow(/bucketName: 5 should be a string/); + - test.done(); - }, + }); - 'bucket without encryption'(test: Test) { + test('bucket without encryption', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.UNENCRYPTED, }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -58,16 +58,16 @@ nodeunitShim({ }, }); - test.done(); - }, - 'bucket with managed encryption'(test: Test) { + }); + + test('bucket with managed encryption', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.KMS_MANAGED, }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -87,34 +87,34 @@ nodeunitShim({ }, }, }); - test.done(); - }, - 'valid bucket names'(test: Test) { + }); + + test('valid bucket names', () => { const stack = new cdk.Stack(); - test.doesNotThrow(() => new s3.Bucket(stack, 'MyBucket1', { + expect(() => new s3.Bucket(stack, 'MyBucket1', { bucketName: 'abc.xyz-34ab', - })); + })).not.toThrow(); - test.doesNotThrow(() => new s3.Bucket(stack, 'MyBucket2', { + expect(() => new s3.Bucket(stack, 'MyBucket2', { bucketName: '124.pp--33', - })); + })).not.toThrow(); + - test.done(); - }, + }); - 'bucket validation skips tokenized values'(test: Test) { + test('bucket validation skips tokenized values', () => { const stack = new cdk.Stack(); - test.doesNotThrow(() => new s3.Bucket(stack, 'MyBucket', { + expect(() => new s3.Bucket(stack, 'MyBucket', { bucketName: cdk.Lazy.string({ produce: () => '_BUCKET' }), - })); + })).not.toThrow(); + - test.done(); - }, + }); - 'fails with message on invalid bucket names'(test: Test) { + test('fails with message on invalid bucket names', () => { const stack = new cdk.Stack(); const bucket = `-buckEt.-${new Array(65).join('$')}`; const expectedErrors = [ @@ -126,135 +126,135 @@ nodeunitShim({ 'Bucket name must not have dash next to period, or period next to dash, or consecutive periods (offset: 7)', ].join(EOL); - test.throws(() => new s3.Bucket(stack, 'MyBucket', { + expect(() => new s3.Bucket(stack, 'MyBucket', { bucketName: bucket, - }), expectedErrors); + })).toThrow(expectedErrors); - test.done(); - }, - 'fails if bucket name has less than 3 or more than 63 characters'(test: Test) { + }); + + test('fails if bucket name has less than 3 or more than 63 characters', () => { const stack = new cdk.Stack(); - test.throws(() => new s3.Bucket(stack, 'MyBucket1', { + expect(() => new s3.Bucket(stack, 'MyBucket1', { bucketName: 'a', - }), /at least 3/); + })).toThrow(/at least 3/); - test.throws(() => new s3.Bucket(stack, 'MyBucket2', { + expect(() => new s3.Bucket(stack, 'MyBucket2', { bucketName: new Array(65).join('x'), - }), /no more than 63/); + })).toThrow(/no more than 63/); + - test.done(); - }, + }); - 'fails if bucket name has invalid characters'(test: Test) { + test('fails if bucket name has invalid characters', () => { const stack = new cdk.Stack(); - test.throws(() => new s3.Bucket(stack, 'MyBucket1', { + expect(() => new s3.Bucket(stack, 'MyBucket1', { bucketName: 'b@cket', - }), /offset: 1/); + })).toThrow(/offset: 1/); - test.throws(() => new s3.Bucket(stack, 'MyBucket2', { + expect(() => new s3.Bucket(stack, 'MyBucket2', { bucketName: 'bucKet', - }), /offset: 3/); + })).toThrow(/offset: 3/); - test.throws(() => new s3.Bucket(stack, 'MyBucket3', { + expect(() => new s3.Bucket(stack, 'MyBucket3', { bucketName: 'bučket', - }), /offset: 2/); + })).toThrow(/offset: 2/); + - test.done(); - }, + }); - 'fails if bucket name does not start or end with lowercase character or number'(test: Test) { + test('fails if bucket name does not start or end with lowercase character or number', () => { const stack = new cdk.Stack(); - test.throws(() => new s3.Bucket(stack, 'MyBucket1', { + expect(() => new s3.Bucket(stack, 'MyBucket1', { bucketName: '-ucket', - }), /offset: 0/); + })).toThrow(/offset: 0/); - test.throws(() => new s3.Bucket(stack, 'MyBucket2', { + expect(() => new s3.Bucket(stack, 'MyBucket2', { bucketName: 'bucke.', - }), /offset: 5/); + })).toThrow(/offset: 5/); - test.done(); - }, - 'fails only if bucket name has the consecutive symbols (..), (.-), (-.)'(test: Test) { + }); + + test('fails only if bucket name has the consecutive symbols (..), (.-), (-.)', () => { const stack = new cdk.Stack(); - test.throws(() => new s3.Bucket(stack, 'MyBucket1', { + expect(() => new s3.Bucket(stack, 'MyBucket1', { bucketName: 'buc..ket', - }), /offset: 3/); + })).toThrow(/offset: 3/); - test.throws(() => new s3.Bucket(stack, 'MyBucket2', { + expect(() => new s3.Bucket(stack, 'MyBucket2', { bucketName: 'buck.-et', - }), /offset: 4/); + })).toThrow(/offset: 4/); - test.throws(() => new s3.Bucket(stack, 'MyBucket3', { + expect(() => new s3.Bucket(stack, 'MyBucket3', { bucketName: 'b-.ucket', - }), /offset: 1/); + })).toThrow(/offset: 1/); - test.doesNotThrow(() => new s3.Bucket(stack, 'MyBucket4', { + expect(() => new s3.Bucket(stack, 'MyBucket4', { bucketName: 'bu--cket', - })); + })).not.toThrow(); + - test.done(); - }, + }); - 'fails only if bucket name resembles IP address'(test: Test) { + test('fails only if bucket name resembles IP address', () => { const stack = new cdk.Stack(); - test.throws(() => new s3.Bucket(stack, 'MyBucket1', { + expect(() => new s3.Bucket(stack, 'MyBucket1', { bucketName: '1.2.3.4', - }), /must not resemble an IP address/); + })).toThrow(/must not resemble an IP address/); - test.doesNotThrow(() => new s3.Bucket(stack, 'MyBucket2', { + expect(() => new s3.Bucket(stack, 'MyBucket2', { bucketName: '1.2.3', - })); + })).not.toThrow(); - test.doesNotThrow(() => new s3.Bucket(stack, 'MyBucket3', { + expect(() => new s3.Bucket(stack, 'MyBucket3', { bucketName: '1.2.3.a', - })); + })).not.toThrow(); - test.doesNotThrow(() => new s3.Bucket(stack, 'MyBucket4', { + expect(() => new s3.Bucket(stack, 'MyBucket4', { bucketName: '1000.2.3.4', - })); + })).not.toThrow(); - test.done(); - }, - 'fails if encryption key is used with managed encryption'(test: Test) { + }); + + test('fails if encryption key is used with managed encryption', () => { const stack = new cdk.Stack(); const myKey = new kms.Key(stack, 'MyKey'); - test.throws(() => new s3.Bucket(stack, 'MyBucket', { + expect(() => new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.KMS_MANAGED, encryptionKey: myKey, - }), /encryptionKey is specified, so 'encryption' must be set to KMS/); + })).toThrow(/encryptionKey is specified, so 'encryption' must be set to KMS/); + - test.done(); - }, + }); - 'fails if encryption key is used with encryption set to unencrypted'(test: Test) { + test('fails if encryption key is used with encryption set to unencrypted', () => { const stack = new cdk.Stack(); const myKey = new kms.Key(stack, 'MyKey'); - test.throws(() => new s3.Bucket(stack, 'MyBucket', { + expect(() => new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.UNENCRYPTED, encryptionKey: myKey, - }), /encryptionKey is specified, so 'encryption' must be set to KMS/); + })).toThrow(/encryptionKey is specified, so 'encryption' must be set to KMS/); + - test.done(); - }, + }); - 'encryptionKey can specify kms key'(test: Test) { + test('encryptionKey can specify kms key', () => { const stack = new cdk.Stack(); const encryptionKey = new kms.Key(stack, 'MyKey', { description: 'hello, world' }); new s3.Bucket(stack, 'MyBucket', { encryptionKey, encryption: s3.BucketEncryption.KMS }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyKey6AB29FA6': { 'Type': 'AWS::KMS::Key', @@ -332,15 +332,15 @@ nodeunitShim({ }, }, }); - test.done(); - }, - 'bucketKeyEnabled can be enabled'(test: Test) { + }); + + test('bucketKeyEnabled can be enabled', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { bucketKeyEnabled: true, encryption: s3.BucketEncryption.KMS }); - expect(stack).to(haveResource('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { 'BucketEncryption': { 'ServerSideEncryptionConfiguration': [ { @@ -357,30 +357,30 @@ nodeunitShim({ }, ], }, - }), - ); - test.done(); - }, + }); + - 'throws error if bucketKeyEnabled is set, but encryption is not KMS'(test: Test) { + }); + + test('throws error if bucketKeyEnabled is set, but encryption is not KMS', () => { const stack = new cdk.Stack(); - test.throws(() => { + expect(() => { new s3.Bucket(stack, 'MyBucket', { bucketKeyEnabled: true, encryption: s3.BucketEncryption.S3_MANAGED }); - }, "bucketKeyEnabled is specified, so 'encryption' must be set to KMS (value: S3MANAGED)"); - test.throws(() => { + }).toThrow("bucketKeyEnabled is specified, so 'encryption' must be set to KMS (value: S3MANAGED)"); + expect(() => { new s3.Bucket(stack, 'MyBucket3', { bucketKeyEnabled: true }); - }, "bucketKeyEnabled is specified, so 'encryption' must be set to KMS (value: NONE)"); - test.done(); - }, + }).toThrow("bucketKeyEnabled is specified, so 'encryption' must be set to KMS (value: NONE)"); + + }); - 'bucket with versioning turned on'(test: Test) { + test('bucket with versioning turned on', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { versioned: true, }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -394,16 +394,16 @@ nodeunitShim({ }, }, }); - test.done(); - }, - 'bucket with block public access set to BlockAll'(test: Test) { + }); + + test('bucket with block public access set to BlockAll', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -420,16 +420,16 @@ nodeunitShim({ }, }, }); - test.done(); - }, - 'bucket with block public access set to BlockAcls'(test: Test) { + }); + + test('bucket with block public access set to BlockAcls', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS, }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -444,16 +444,16 @@ nodeunitShim({ }, }, }); - test.done(); - }, - 'bucket with custom block public access setting'(test: Test) { + }); + + test('bucket with custom block public access setting', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { blockPublicAccess: new s3.BlockPublicAccess({ restrictPublicBuckets: true }), }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -467,16 +467,16 @@ nodeunitShim({ }, }, }); - test.done(); - }, - 'bucket with custom canned access control'(test: Test) { + }); + + test('bucket with custom canned access control', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { accessControl: s3.BucketAccessControl.LOG_DELIVERY_WRITE, }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -488,12 +488,12 @@ nodeunitShim({ }, }, }); - test.done(); - }, - 'permissions': { + }); + + describe('permissions', () => { - 'addPermission creates a bucket policy'(test: Test) { + test('addPermission creates a bucket policy', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.UNENCRYPTED }); @@ -503,7 +503,7 @@ nodeunitShim({ principals: [new iam.AnyPrincipal()], })); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -532,10 +532,10 @@ nodeunitShim({ }, }); - test.done(); - }, - 'forBucket returns a permission statement associated with the bucket\'s ARN'(test: Test) { + }); + + test('forBucket returns a permission statement associated with the bucket\'s ARN', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.UNENCRYPTED }); @@ -546,17 +546,17 @@ nodeunitShim({ principals: [new iam.AnyPrincipal()], }); - test.deepEqual(stack.resolve(x.toStatementJson()), { + expect(stack.resolve(x.toStatementJson())).toEqual({ Action: 's3:ListBucket', Effect: 'Allow', Principal: '*', Resource: { 'Fn::GetAtt': ['MyBucketF68F3FF0', 'Arn'] }, }); - test.done(); - }, - 'arnForObjects returns a permission statement associated with objects in the bucket'(test: Test) { + }); + + test('arnForObjects returns a permission statement associated with objects in the bucket', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.UNENCRYPTED }); @@ -567,7 +567,7 @@ nodeunitShim({ principals: [new iam.AnyPrincipal()], }); - test.deepEqual(stack.resolve(p.toStatementJson()), { + expect(stack.resolve(p.toStatementJson())).toEqual({ Action: 's3:GetObject', Effect: 'Allow', Principal: '*', @@ -579,10 +579,10 @@ nodeunitShim({ }, }); - test.done(); - }, - 'arnForObjects accepts multiple arguments and FnConcats them'(test: Test) { + }); + + test('arnForObjects accepts multiple arguments and FnConcats them', () => { const stack = new cdk.Stack(); @@ -598,7 +598,7 @@ nodeunitShim({ principals: [new iam.AnyPrincipal()], }); - test.deepEqual(stack.resolve(p.toStatementJson()), { + expect(stack.resolve(p.toStatementJson())).toEqual({ Action: 's3:GetObject', Effect: 'Allow', Principal: '*', @@ -617,18 +617,18 @@ nodeunitShim({ }, }); - test.done(); - }, - }, - 'removal policy can be used to specify behavior upon delete'(test: Test) { + }); + }); + + test('removal policy can be used to specify behavior upon delete', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { removalPolicy: cdk.RemovalPolicy.RETAIN, encryption: s3.BucketEncryption.UNENCRYPTED, }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ Resources: { MyBucketF68F3FF0: { Type: 'AWS::S3::Bucket', @@ -638,12 +638,12 @@ nodeunitShim({ }, }); - test.done(); - }, - 'import/export': { + }); + + describe('import/export', () => { - 'static import(ref) allows importing an external/existing bucket'(test: Test) { + test('static import(ref) allows importing an external/existing bucket', () => { const stack = new cdk.Stack(); const bucketArn = 'arn:aws:s3:::my-bucket'; @@ -663,21 +663,21 @@ nodeunitShim({ }); // it is possible to obtain a permission statement for a ref - test.deepEqual(p.toStatementJson(), { + expect(p.toStatementJson()).toEqual({ Action: 's3:ListBucket', Effect: 'Allow', Principal: '*', Resource: 'arn:aws:s3:::my-bucket', }); - test.deepEqual(bucket.bucketArn, bucketArn); - test.deepEqual(stack.resolve(bucket.bucketName), 'my-bucket'); + expect(bucket.bucketArn).toEqual(bucketArn); + expect(stack.resolve(bucket.bucketName)).toEqual('my-bucket'); - test.deepEqual(SynthUtils.synthesize(stack).template, {}, 'the ref is not a real resource'); - test.done(); - }, + expect(SynthUtils.synthesize(stack).template).toEqual({}); - 'import does not create any resources'(test: Test) { + }); + + test('import does not create any resources', () => { const stack = new cdk.Stack(); const bucket = s3.Bucket.fromBucketAttributes(stack, 'ImportedBucket', { bucketArn: 'arn:aws:s3:::my-bucket' }); bucket.addToResourcePolicy(new iam.PolicyStatement({ @@ -687,11 +687,11 @@ nodeunitShim({ })); // at this point we technically didn't create any resources in the consuming stack. - expect(stack).toMatch({}); - test.done(); - }, + expect(stack).toMatchTemplate({}); + + }); - 'import can also be used to import arbitrary ARNs'(test: Test) { + test('import can also be used to import arbitrary ARNs', () => { const stack = new cdk.Stack(); const bucket = s3.Bucket.fromBucketAttributes(stack, 'ImportedBucket', { bucketArn: 'arn:aws:s3:::my-bucket' }); bucket.addToResourcePolicy(new iam.PolicyStatement({ resources: ['*'], actions: ['*'] })); @@ -704,7 +704,7 @@ nodeunitShim({ actions: ['s3:*'], })); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyUserDC45028B': { 'Type': 'AWS::IAM::User', @@ -733,10 +733,10 @@ nodeunitShim({ }, }); - test.done(); - }, - 'import can explicitly set bucket region'(test: Test) { + }); + + test('import can explicitly set bucket region', () => { const stack = new cdk.Stack(undefined, undefined, { env: { region: 'us-east-1' }, }); @@ -746,19 +746,19 @@ nodeunitShim({ region: 'eu-west-1', }); - test.equals(bucket.bucketRegionalDomainName, `myBucket.s3.eu-west-1.${stack.urlSuffix}`); - test.equals(bucket.bucketWebsiteDomainName, `myBucket.s3-website-eu-west-1.${stack.urlSuffix}`); + expect(bucket.bucketRegionalDomainName).toEqual(`myBucket.s3.eu-west-1.${stack.urlSuffix}`); + expect(bucket.bucketWebsiteDomainName).toEqual(`myBucket.s3-website-eu-west-1.${stack.urlSuffix}`); - test.done(); - }, - }, - 'grantRead'(test: Test) { + }); + }); + + test('grantRead', () => { const stack = new cdk.Stack(); const reader = new iam.User(stack, 'Reader'); const bucket = new s3.Bucket(stack, 'MyBucket'); bucket.grantRead(reader); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'ReaderF7BF189D': { 'Type': 'AWS::IAM::User', @@ -816,17 +816,17 @@ nodeunitShim({ }, }, }); - test.done(); - }, - 'grantReadWrite': { - 'can be used to grant reciprocal permissions to an identity'(test: Test) { + }); + + describe('grantReadWrite', () => { + test('can be used to grant reciprocal permissions to an identity', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket'); const user = new iam.User(stack, 'MyUser'); bucket.grantReadWrite(user); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -888,10 +888,10 @@ nodeunitShim({ }, }); - test.done(); - }, - 'grant permissions to non-identity principal'(test: Test) { + }); + + test('grant permissions to non-identity principal', () => { // GIVEN const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.KMS }); @@ -900,7 +900,7 @@ nodeunitShim({ bucket.grantRead(new iam.OrganizationPrincipal('o-1234')); // THEN - expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { PolicyDocument: { 'Version': '2012-10-17', 'Statement': [ @@ -916,9 +916,9 @@ nodeunitShim({ }, ], }, - })); + }); - expect(stack).to(haveResource('AWS::KMS::Key', { + expect(stack).toHaveResource('AWS::KMS::Key', { 'KeyPolicy': { 'Statement': [ { @@ -946,18 +946,18 @@ nodeunitShim({ 'Version': '2012-10-17', }, - })); + }); + - test.done(); - }, + }); - 'if an encryption key is included, encrypt/decrypt permissions are also added both ways'(test: Test) { + test('if an encryption key is included, encrypt/decrypt permissions are also added both ways', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.KMS }); const user = new iam.User(stack, 'MyUser'); bucket.grantReadWrite(user); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketKeyC17130CF': { 'Type': 'AWS::KMS::Key', @@ -1123,10 +1123,10 @@ nodeunitShim({ }, }); - test.done(); - }, - 'does not grant PutObjectAcl when the S3_GRANT_WRITE_WITHOUT_ACL feature is enabled'(test: Test) { + }); + + test('does not grant PutObjectAcl when the S3_GRANT_WRITE_WITHOUT_ACL feature is enabled', () => { const app = new cdk.App({ context: { [cxapi.S3_GRANT_WRITE_WITHOUT_ACL]: true, @@ -1138,7 +1138,7 @@ nodeunitShim({ bucket.grantReadWrite(user); - expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1162,20 +1162,20 @@ nodeunitShim({ }, ], }, - })); + }); + - test.done(); - }, - }, + }); + }); - 'grantWrite': { - 'with KMS key has appropriate permissions for multipart uploads'(test: Test) { + describe('grantWrite', () => { + test('with KMS key has appropriate permissions for multipart uploads', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.KMS }); const user = new iam.User(stack, 'MyUser'); bucket.grantWrite(user); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketKeyC17130CF': { 'Type': 'AWS::KMS::Key', @@ -1336,10 +1336,10 @@ nodeunitShim({ }, }); - test.done(); - }, - 'does not grant PutObjectAcl when the S3_GRANT_WRITE_WITHOUT_ACL feature is enabled'(test: Test) { + }); + + test('does not grant PutObjectAcl when the S3_GRANT_WRITE_WITHOUT_ACL feature is enabled', () => { const app = new cdk.App({ context: { [cxapi.S3_GRANT_WRITE_WITHOUT_ACL]: true, @@ -1351,7 +1351,7 @@ nodeunitShim({ bucket.grantWrite(user); - expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1372,14 +1372,14 @@ nodeunitShim({ }, ], }, - })); + }); - test.done(); - }, - }, - 'grantPut': { - 'does not grant PutObjectAcl when the S3_GRANT_WRITE_WITHOUT_ACL feature is enabled'(test: Test) { + }); + }); + + describe('grantPut', () => { + test('does not grant PutObjectAcl when the S3_GRANT_WRITE_WITHOUT_ACL feature is enabled', () => { const app = new cdk.App({ context: { [cxapi.S3_GRANT_WRITE_WITHOUT_ACL]: true, @@ -1391,7 +1391,7 @@ nodeunitShim({ bucket.grantPut(user); - expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1408,13 +1408,13 @@ nodeunitShim({ }, ], }, - })); + }); - test.done(); - }, - }, - 'more grants'(test: Test) { + }); + }); + + test('more grants', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.KMS }); const putter = new iam.User(stack, 'Putter'); @@ -1428,13 +1428,13 @@ nodeunitShim({ const resources = SynthUtils.synthesize(stack).template.Resources; const actions = (id: string) => resources[id].Properties.PolicyDocument.Statement[0].Action; - test.deepEqual(actions('WriterDefaultPolicyDC585BCE'), ['s3:DeleteObject*', 's3:PutObject*', 's3:Abort*']); - test.deepEqual(actions('PutterDefaultPolicyAB138DD3'), ['s3:PutObject*', 's3:Abort*']); - test.deepEqual(actions('DeleterDefaultPolicyCD33B8A0'), 's3:DeleteObject*'); - test.done(); - }, + expect(actions('WriterDefaultPolicyDC585BCE')).toEqual(['s3:DeleteObject*', 's3:PutObject*', 's3:Abort*']); + expect(actions('PutterDefaultPolicyAB138DD3')).toEqual(['s3:PutObject*', 's3:Abort*']); + expect(actions('DeleterDefaultPolicyCD33B8A0')).toEqual('s3:DeleteObject*'); - 'grantDelete, with a KMS Key'(test: Test) { + }); + + test('grantDelete, with a KMS Key', () => { // given const stack = new cdk.Stack(); const key = new kms.Key(stack, 'MyKey'); @@ -1449,7 +1449,7 @@ nodeunitShim({ bucket.grantDelete(deleter); // then - expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1473,13 +1473,13 @@ nodeunitShim({ ], 'Version': '2012-10-17', }, - })); + }); + - test.done(); - }, + }); - 'cross-stack permissions': { - 'in the same account and region'(test: Test) { + describe('cross-stack permissions', () => { + test('in the same account and region', () => { const app = new cdk.App(); const stackA = new cdk.Stack(app, 'stackA'); const bucketFromStackA = new s3.Bucket(stackA, 'MyBucket'); @@ -1488,7 +1488,7 @@ nodeunitShim({ const user = new iam.User(stackB, 'UserWhoNeedsAccess'); bucketFromStackA.grantRead(user); - expect(stackA).toMatch({ + expect(stackA).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -1511,7 +1511,7 @@ nodeunitShim({ }, }); - expect(stackB).toMatch({ + expect(stackB).toMatchTemplate({ 'Resources': { 'UserWhoNeedsAccessF8959C3D': { 'Type': 'AWS::IAM::User', @@ -1559,10 +1559,10 @@ nodeunitShim({ }, }); - test.done(); - }, - 'in different accounts'(test: Test) { + }); + + test('in different accounts', () => { // given const stackA = new cdk.Stack(undefined, 'StackA', { env: { account: '123456789012' } }); const bucketFromStackA = new s3.Bucket(stackA, 'MyBucket', { @@ -1579,7 +1579,7 @@ nodeunitShim({ bucketFromStackA.grantRead(roleFromStackB); // then - expect(stackA).to(haveResourceLike('AWS::S3::BucketPolicy', { + expect(stackA).toHaveResourceLike('AWS::S3::BucketPolicy', { 'PolicyDocument': { 'Statement': [ { @@ -1606,9 +1606,9 @@ nodeunitShim({ }, ], }, - })); + }); - expect(stackB).to(haveResourceLike('AWS::IAM::Policy', { + expect(stackB).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1647,12 +1647,12 @@ nodeunitShim({ }, ], }, - })); + }); - test.done(); - }, - 'in different accounts, with a KMS Key'(test: Test) { + }); + + test('in different accounts, with a KMS Key', () => { // given const stackA = new cdk.Stack(undefined, 'StackA', { env: { account: '123456789012' } }); const key = new kms.Key(stackA, 'MyKey'); @@ -1672,7 +1672,7 @@ nodeunitShim({ bucketFromStackA.grantRead(roleFromStackB); // then - expect(stackA).to(haveResourceLike('AWS::KMS::Key', { + expect(stackA).toHaveResourceLike('AWS::KMS::Key', { 'KeyPolicy': { 'Statement': [ { @@ -1701,9 +1701,9 @@ nodeunitShim({ }, ], }, - })); + }); - expect(stackB).to(haveResourceLike('AWS::IAM::Policy', { + expect(stackB).toHaveResourceLike('AWS::IAM::Policy', { 'PolicyDocument': { 'Statement': [ { @@ -1719,13 +1719,13 @@ nodeunitShim({ }, ], }, - })); + }); + - test.done(); - }, - }, + }); + }); - 'urlForObject returns a token with the S3 URL of the token'(test: Test) { + test('urlForObject returns a token with the S3 URL of the token', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket'); @@ -1733,7 +1733,7 @@ nodeunitShim({ new cdk.CfnOutput(stack, 'MyFileURL', { value: bucket.urlForObject('my/file.txt') }); new cdk.CfnOutput(stack, 'YourFileURL', { value: bucket.urlForObject('/your/file.txt') }); // "/" is optional - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -1810,10 +1810,10 @@ nodeunitShim({ }, }); - test.done(); - }, - 's3UrlForObject returns a token with the S3 URL of the token'(test: Test) { + }); + + test('s3UrlForObject returns a token with the S3 URL of the token', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket'); @@ -1821,7 +1821,7 @@ nodeunitShim({ new cdk.CfnOutput(stack, 'MyFileS3URL', { value: bucket.s3UrlForObject('my/file.txt') }); new cdk.CfnOutput(stack, 'YourFileS3URL', { value: bucket.s3UrlForObject('/your/file.txt') }); // "/" is optional - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -1874,11 +1874,11 @@ nodeunitShim({ }, }); - test.done(); - }, - 'grantPublicAccess': { - 'by default, grants s3:GetObject to all objects'(test: Test) { + }); + + describe('grantPublicAccess', () => { + test('by default, grants s3:GetObject to all objects', () => { // GIVEN const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'b'); @@ -1887,7 +1887,7 @@ nodeunitShim({ bucket.grantPublicAccess(); // THEN - expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { 'PolicyDocument': { 'Statement': [ { @@ -1899,11 +1899,11 @@ nodeunitShim({ ], 'Version': '2012-10-17', }, - })); - test.done(); - }, + }); - '"keyPrefix" can be used to only grant access to certain objects'(test: Test) { + }); + + test('"keyPrefix" can be used to only grant access to certain objects', () => { // GIVEN const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'b'); @@ -1912,7 +1912,7 @@ nodeunitShim({ bucket.grantPublicAccess('only/access/these/*'); // THEN - expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { 'PolicyDocument': { 'Statement': [ { @@ -1924,11 +1924,11 @@ nodeunitShim({ ], 'Version': '2012-10-17', }, - })); - test.done(); - }, + }); + + }); - '"allowedActions" can be used to specify actions explicitly'(test: Test) { + test('"allowedActions" can be used to specify actions explicitly', () => { // GIVEN const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'b'); @@ -1937,7 +1937,7 @@ nodeunitShim({ bucket.grantPublicAccess('*', 's3:GetObject', 's3:PutObject'); // THEN - expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { 'PolicyDocument': { 'Statement': [ { @@ -1949,11 +1949,11 @@ nodeunitShim({ ], 'Version': '2012-10-17', }, - })); - test.done(); - }, + }); - 'returns the PolicyStatement which can be then customized'(test: Test) { + }); + + test('returns the PolicyStatement which can be then customized', () => { // GIVEN const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'b'); @@ -1963,7 +1963,7 @@ nodeunitShim({ result.resourceStatement!.addCondition('IpAddress', { 'aws:SourceIp': '54.240.143.0/24' }); // THEN - expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { 'PolicyDocument': { 'Statement': [ { @@ -1978,11 +1978,11 @@ nodeunitShim({ ], 'Version': '2012-10-17', }, - })); - test.done(); - }, + }); + + }); - 'throws when blockPublicPolicy is set to true'(test: Test) { + test('throws when blockPublicPolicy is set to true', () => { // GIVEN const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket', { @@ -1990,62 +1990,62 @@ nodeunitShim({ }); // THEN - test.throws(() => bucket.grantPublicAccess(), /blockPublicPolicy/); + expect(() => bucket.grantPublicAccess()).toThrow(/blockPublicPolicy/); - test.done(); - }, - }, - 'website configuration': { - 'only index doc'(test: Test) { + }); + }); + + describe('website configuration', () => { + test('only index doc', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'Website', { websiteIndexDocument: 'index2.html', }); - expect(stack).to(haveResource('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { WebsiteConfiguration: { IndexDocument: 'index2.html', }, - })); - test.done(); - }, - 'fails if only error doc is specified'(test: Test) { + }); + + }); + test('fails if only error doc is specified', () => { const stack = new cdk.Stack(); - test.throws(() => { + expect(() => { new s3.Bucket(stack, 'Website', { websiteErrorDocument: 'error.html', }); - }, /"websiteIndexDocument" is required if "websiteErrorDocument" is set/); - test.done(); - }, - 'error and index docs'(test: Test) { + }).toThrow(/"websiteIndexDocument" is required if "websiteErrorDocument" is set/); + + }); + test('error and index docs', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'Website', { websiteIndexDocument: 'index2.html', websiteErrorDocument: 'error.html', }); - expect(stack).to(haveResource('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { WebsiteConfiguration: { IndexDocument: 'index2.html', ErrorDocument: 'error.html', }, - })); - test.done(); - }, - 'exports the WebsiteURL'(test: Test) { + }); + + }); + test('exports the WebsiteURL', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'Website', { websiteIndexDocument: 'index.html', }); - test.deepEqual(stack.resolve(bucket.bucketWebsiteUrl), { 'Fn::GetAtt': ['Website32962D0B', 'WebsiteURL'] }); - test.done(); - }, - 'exports the WebsiteDomain'(test: Test) { + expect(stack.resolve(bucket.bucketWebsiteUrl)).toEqual({ 'Fn::GetAtt': ['Website32962D0B', 'WebsiteURL'] }); + + }); + test('exports the WebsiteDomain', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'Website', { websiteIndexDocument: 'index.html', }); - test.deepEqual(stack.resolve(bucket.bucketWebsiteDomainName), { + expect(stack.resolve(bucket.bucketWebsiteDomainName)).toEqual({ 'Fn::Select': [ 2, { @@ -2053,12 +2053,12 @@ nodeunitShim({ }, ], }); - test.done(); - }, - 'exports the WebsiteURL for imported buckets'(test: Test) { + + }); + test('exports the WebsiteURL for imported buckets', () => { const stack = new cdk.Stack(); const bucket = s3.Bucket.fromBucketName(stack, 'Website', 'my-test-bucket'); - test.deepEqual(stack.resolve(bucket.bucketWebsiteUrl), { + expect(stack.resolve(bucket.bucketWebsiteUrl)).toEqual({ 'Fn::Join': [ '', [ @@ -2069,7 +2069,7 @@ nodeunitShim({ ], ], }); - test.deepEqual(stack.resolve(bucket.bucketWebsiteDomainName), { + expect(stack.resolve(bucket.bucketWebsiteDomainName)).toEqual({ 'Fn::Join': [ '', [ @@ -2080,19 +2080,19 @@ nodeunitShim({ ], ], }); - test.done(); - }, - 'exports the WebsiteURL for imported buckets with url'(test: Test) { + + }); + test('exports the WebsiteURL for imported buckets with url', () => { const stack = new cdk.Stack(); const bucket = s3.Bucket.fromBucketAttributes(stack, 'Website', { bucketName: 'my-test-bucket', bucketWebsiteUrl: 'http://my-test-bucket.my-test.suffix', }); - test.deepEqual(stack.resolve(bucket.bucketWebsiteUrl), 'http://my-test-bucket.my-test.suffix'); - test.deepEqual(stack.resolve(bucket.bucketWebsiteDomainName), 'my-test-bucket.my-test.suffix'); - test.done(); - }, - 'adds RedirectAllRequestsTo property'(test: Test) { + expect(stack.resolve(bucket.bucketWebsiteUrl)).toEqual('http://my-test-bucket.my-test.suffix'); + expect(stack.resolve(bucket.bucketWebsiteDomainName)).toEqual('my-test-bucket.my-test.suffix'); + + }); + test('adds RedirectAllRequestsTo property', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'Website', { websiteRedirect: { @@ -2100,19 +2100,19 @@ nodeunitShim({ protocol: s3.RedirectProtocol.HTTPS, }, }); - expect(stack).to(haveResource('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { WebsiteConfiguration: { RedirectAllRequestsTo: { HostName: 'www.example.com', Protocol: 'https', }, }, - })); - test.done(); - }, - 'fails if websiteRedirect and websiteIndex and websiteError are specified'(test: Test) { + }); + + }); + test('fails if websiteRedirect and websiteIndex and websiteError are specified', () => { const stack = new cdk.Stack(); - test.throws(() => { + expect(() => { new s3.Bucket(stack, 'Website', { websiteIndexDocument: 'index.html', websiteErrorDocument: 'error.html', @@ -2120,22 +2120,22 @@ nodeunitShim({ hostName: 'www.example.com', }, }); - }, /"websiteIndexDocument", "websiteErrorDocument" and, "websiteRoutingRules" cannot be set if "websiteRedirect" is used/); - test.done(); - }, - 'fails if websiteRedirect and websiteRoutingRules are specified'(test: Test) { + }).toThrow(/"websiteIndexDocument", "websiteErrorDocument" and, "websiteRoutingRules" cannot be set if "websiteRedirect" is used/); + + }); + test('fails if websiteRedirect and websiteRoutingRules are specified', () => { const stack = new cdk.Stack(); - test.throws(() => { + expect(() => { new s3.Bucket(stack, 'Website', { websiteRoutingRules: [], websiteRedirect: { hostName: 'www.example.com', }, }); - }, /"websiteIndexDocument", "websiteErrorDocument" and, "websiteRoutingRules" cannot be set if "websiteRedirect" is used/); - test.done(); - }, - 'adds RedirectRules property'(test: Test) { + }).toThrow(/"websiteIndexDocument", "websiteErrorDocument" and, "websiteRoutingRules" cannot be set if "websiteRedirect" is used/); + + }); + test('adds RedirectRules property', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'Website', { websiteRoutingRules: [{ @@ -2149,7 +2149,7 @@ nodeunitShim({ }, }], }); - expect(stack).to(haveResource('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { WebsiteConfiguration: { RoutingRules: [{ RedirectRule: { @@ -2164,40 +2164,40 @@ nodeunitShim({ }, }], }, - })); - test.done(); - }, - 'fails if routingRule condition object is empty'(test: Test) { + }); + + }); + test('fails if routingRule condition object is empty', () => { const stack = new cdk.Stack(); - test.throws(() => { + expect(() => { new s3.Bucket(stack, 'Website', { websiteRoutingRules: [{ httpRedirectCode: '303', condition: {}, }], }); - }, /The condition property cannot be an empty object/); - test.done(); - }, - 'isWebsite set properly with': { - 'only index doc'(test: Test) { + }).toThrow(/The condition property cannot be an empty object/); + + }); + describe('isWebsite set properly with', () => { + test('only index doc', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'Website', { websiteIndexDocument: 'index2.html', }); - test.equal(bucket.isWebsite, true); - test.done(); - }, - 'error and index docs'(test: Test) { + expect(bucket.isWebsite).toEqual(true); + + }); + test('error and index docs', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'Website', { websiteIndexDocument: 'index2.html', websiteErrorDocument: 'error.html', }); - test.equal(bucket.isWebsite, true); - test.done(); - }, - 'redirects'(test: Test) { + expect(bucket.isWebsite).toEqual(true); + + }); + test('redirects', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'Website', { websiteRedirect: { @@ -2205,36 +2205,36 @@ nodeunitShim({ protocol: s3.RedirectProtocol.HTTPS, }, }); - test.equal(bucket.isWebsite, true); - test.done(); - }, - 'no website properties set'(test: Test) { + expect(bucket.isWebsite).toEqual(true); + + }); + test('no website properties set', () => { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'Website'); - test.equal(bucket.isWebsite, false); - test.done(); - }, - 'imported website buckets'(test: Test) { + expect(bucket.isWebsite).toEqual(false); + + }); + test('imported website buckets', () => { const stack = new cdk.Stack(); const bucket = s3.Bucket.fromBucketAttributes(stack, 'Website', { bucketArn: 'arn:aws:s3:::my-bucket', isWebsite: true, }); - test.equal(bucket.isWebsite, true); - test.done(); - }, - 'imported buckets'(test: Test) { + expect(bucket.isWebsite).toEqual(true); + + }); + test('imported buckets', () => { const stack = new cdk.Stack(); const bucket = s3.Bucket.fromBucketAttributes(stack, 'NotWebsite', { bucketArn: 'arn:aws:s3:::my-bucket', }); - test.equal(bucket.isWebsite, false); - test.done(); - }, - }, - }, + expect(bucket.isWebsite).toEqual(false); - 'Bucket.fromBucketArn'(test: Test) { + }); + }); + }); + + test('Bucket.fromBucketArn', () => { // GIVEN const stack = new cdk.Stack(); @@ -2242,12 +2242,12 @@ nodeunitShim({ const bucket = s3.Bucket.fromBucketArn(stack, 'my-bucket', 'arn:aws:s3:::my_corporate_bucket'); // THEN - test.deepEqual(bucket.bucketName, 'my_corporate_bucket'); - test.deepEqual(bucket.bucketArn, 'arn:aws:s3:::my_corporate_bucket'); - test.done(); - }, + expect(bucket.bucketName).toEqual('my_corporate_bucket'); + expect(bucket.bucketArn).toEqual('arn:aws:s3:::my_corporate_bucket'); + + }); - 'Bucket.fromBucketName'(test: Test) { + test('Bucket.fromBucketName', () => { // GIVEN const stack = new cdk.Stack(); @@ -2255,24 +2255,24 @@ nodeunitShim({ const bucket = s3.Bucket.fromBucketName(stack, 'imported-bucket', 'my-bucket-name'); // THEN - test.deepEqual(bucket.bucketName, 'my-bucket-name'); - test.deepEqual(stack.resolve(bucket.bucketArn), { + expect(bucket.bucketName).toEqual('my-bucket-name'); + expect(stack.resolve(bucket.bucketArn)).toEqual({ 'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':s3:::my-bucket-name']], }); - test.done(); - }, - 'if a kms key is specified, it implies bucket is encrypted with kms (dah)'(test: Test) { + }); + + test('if a kms key is specified, it implies bucket is encrypted with kms (dah)', () => { // GIVEN const stack = new cdk.Stack(); const key = new kms.Key(stack, 'k'); // THEN new s3.Bucket(stack, 'b', { encryptionKey: key }); - test.done(); - }, - 'Bucket with Server Access Logs'(test: Test) { + }); + + test('Bucket with Server Access Logs', () => { // GIVEN const stack = new cdk.Stack(); @@ -2283,18 +2283,18 @@ nodeunitShim({ }); // THEN - expect(stack).to(haveResource('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { LoggingConfiguration: { DestinationBucketName: { Ref: 'AccessLogs8B620ECA', }, }, - })); + }); + - test.done(); - }, + }); - 'Bucket with Server Access Logs with Prefix'(test: Test) { + test('Bucket with Server Access Logs with Prefix', () => { // GIVEN const stack = new cdk.Stack(); @@ -2306,19 +2306,19 @@ nodeunitShim({ }); // THEN - expect(stack).to(haveResource('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { LoggingConfiguration: { DestinationBucketName: { Ref: 'AccessLogs8B620ECA', }, LogFilePrefix: 'hello', }, - })); + }); + - test.done(); - }, + }); - 'Access log prefix given without bucket'(test: Test) { + test('Access log prefix given without bucket', () => { // GIVEN const stack = new cdk.Stack(); @@ -2327,15 +2327,15 @@ nodeunitShim({ }); // THEN - expect(stack).to(haveResource('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { LoggingConfiguration: { LogFilePrefix: 'hello', }, - })); - test.done(); - }, + }); - 'Bucket Allow Log delivery changes bucket Access Control should fail'(test: Test) { + }); + + test('Bucket Allow Log delivery changes bucket Access Control should fail', () => { // GIVEN const stack = new cdk.Stack(); @@ -2343,18 +2343,18 @@ nodeunitShim({ const accessLogBucket = new s3.Bucket(stack, 'AccessLogs', { accessControl: s3.BucketAccessControl.AUTHENTICATED_READ, }); - test.throws(() => + expect(() => new s3.Bucket(stack, 'MyBucket', { serverAccessLogsBucket: accessLogBucket, serverAccessLogsPrefix: 'hello', accessControl: s3.BucketAccessControl.AUTHENTICATED_READ, - }) - , /Cannot enable log delivery to this bucket because the bucket's ACL has been set and can't be changed/); + }), + ).toThrow(/Cannot enable log delivery to this bucket because the bucket's ACL has been set and can't be changed/); + - test.done(); - }, + }); - 'Defaults for an inventory bucket'(test: Test) { + test('Defaults for an inventory bucket', () => { // Given const stack = new cdk.Stack(); @@ -2369,7 +2369,7 @@ nodeunitShim({ ], }); - expect(stack).to(haveResourceLike('AWS::S3::Bucket', { + expect(stack).toHaveResourceLike('AWS::S3::Bucket', { InventoryConfigurations: [ { Enabled: true, @@ -2382,9 +2382,9 @@ nodeunitShim({ Id: 'MyBucketInventory0', }, ], - })); + }); - expect(stack).to(haveResourceLike('AWS::S3::BucketPolicy', { + expect(stack).toHaveResourceLike('AWS::S3::BucketPolicy', { Bucket: { Ref: 'InventoryBucketA869B8CB' }, PolicyDocument: { Statement: arrayWith(objectLike({ @@ -2400,17 +2400,17 @@ nodeunitShim({ ], })), }, - })); + }); + - test.done(); - }, + }); - 'Bucket with objectOwnership set to BUCKET_OWNER_PREFERRED'(test: Test) { + test('Bucket with objectOwnership set to BUCKET_OWNER_PREFERRED', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_PREFERRED, }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -2428,15 +2428,15 @@ nodeunitShim({ }, }, }); - test.done(); - }, - 'Bucket with objectOwnership set to OBJECT_WRITER'(test: Test) { + }); + + test('Bucket with objectOwnership set to OBJECT_WRITER', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { objectOwnership: s3.ObjectOwnership.OBJECT_WRITER, }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -2454,15 +2454,15 @@ nodeunitShim({ }, }, }); - test.done(); - }, - 'Bucket with objectOwnerships set to undefined'(test: Test) { + }); + + test('Bucket with objectOwnerships set to undefined', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { objectOwnership: undefined, }); - expect(stack).toMatch({ + expect(stack).toMatchTemplate({ 'Resources': { 'MyBucketF68F3FF0': { 'Type': 'AWS::S3::Bucket', @@ -2471,10 +2471,10 @@ nodeunitShim({ }, }, }); - test.done(); - }, - 'with autoDeleteObjects'(test: Test) { + }); + + test('with autoDeleteObjects', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { @@ -2482,12 +2482,12 @@ nodeunitShim({ autoDeleteObjects: true, }); - expect(stack).to(haveResource('AWS::S3::Bucket', { + expect(stack).toHaveResource('AWS::S3::Bucket', { UpdateReplacePolicy: 'Delete', DeletionPolicy: 'Delete', - }, ResourcePart.CompleteDefinition)); + }, ResourcePart.CompleteDefinition); - expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { Bucket: { Ref: 'MyBucketF68F3FF0', }, @@ -2535,9 +2535,9 @@ nodeunitShim({ ], 'Version': '2012-10-17', }, - })); + }); - expect(stack).to(haveResource('Custom::S3AutoDeleteObjects', { + expect(stack).toHaveResource('Custom::S3AutoDeleteObjects', { 'Properties': { 'ServiceToken': { 'Fn::GetAtt': [ @@ -2552,12 +2552,12 @@ nodeunitShim({ 'DependsOn': [ 'MyBucketPolicyE7FBAC7B', ], - }, ResourcePart.CompleteDefinition)); + }, ResourcePart.CompleteDefinition); + - test.done(); - }, + }); - 'with autoDeleteObjects on multiple buckets'(test: Test) { + test('with autoDeleteObjects on multiple buckets', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'Bucket1', { @@ -2570,18 +2570,18 @@ nodeunitShim({ autoDeleteObjects: true, }); - expect(stack).to(countResources('AWS::Lambda::Function', 1)); + expect(stack).toCountResources('AWS::Lambda::Function', 1); - test.done(); - }, - 'autoDeleteObjects throws if RemovalPolicy is not DESTROY'(test: Test) { + }); + + test('autoDeleteObjects throws if RemovalPolicy is not DESTROY', () => { const stack = new cdk.Stack(); - test.throws(() => new s3.Bucket(stack, 'MyBucket', { + expect(() => new s3.Bucket(stack, 'MyBucket', { autoDeleteObjects: true, - }), /Cannot use \'autoDeleteObjects\' property on a bucket without setting removal policy to \'DESTROY\'/); + })).toThrow(/Cannot use \'autoDeleteObjects\' property on a bucket without setting removal policy to \'DESTROY\'/); + - test.done(); - }, + }); }); From 1a73d761ad2363842567a1b6e0488ceb093e70b2 Mon Sep 17 00:00:00 2001 From: Jacob Doetsch Date: Wed, 27 Jan 2021 03:11:30 -1000 Subject: [PATCH 20/70] fix(ec2): ARM-backed bastion hosts try to run x86-based Amazon Linux AMI (#12280) Fixes #12279 ---- *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-ec2/lib/bastion-host.ts | 33 +- .../@aws-cdk/aws-ec2/lib/instance-types.ts | 37 + .../aws-ec2/test/bastion-host.test.ts | 41 +- .../@aws-cdk/aws-ec2/test/instance.test.ts | 51 +- ...teg.bastion-host-arm-support.expected.json | 659 ++++++++++++++++++ .../test/integ.bastion-host-arm-support.ts | 26 + .../test/integ.bastion-host.expected.json | 2 +- 7 files changed, 837 insertions(+), 12 deletions(-) create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.expected.json create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.ts diff --git a/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts b/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts index dbc6c45fe415a..0656440ea3ff4 100644 --- a/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts +++ b/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts @@ -1,10 +1,10 @@ import { IPrincipal, IRole, PolicyStatement } from '@aws-cdk/aws-iam'; import { CfnOutput, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { AmazonLinuxGeneration, InstanceClass, InstanceSize, InstanceType } from '.'; +import { AmazonLinuxGeneration, InstanceArchitecture, InstanceClass, InstanceSize, InstanceType } from '.'; import { Connections } from './connections'; import { IInstance, Instance } from './instance'; -import { IMachineImage, MachineImage } from './machine-image'; +import { AmazonLinuxCpuType, IMachineImage, MachineImage } from './machine-image'; import { IPeer } from './peer'; import { Port } from './port'; import { ISecurityGroup } from './security-group'; @@ -60,10 +60,10 @@ export interface BastionHostLinuxProps { readonly instanceType?: InstanceType; /** - * The machine image to use + * The machine image to use, assumed to have SSM Agent preinstalled. * * @default - An Amazon Linux 2 image which is kept up-to-date automatically (the instance - * may be replaced on every deployment). + * may be replaced on every deployment) and already has SSM Agent installed. */ readonly machineImage?: IMachineImage; @@ -146,14 +146,17 @@ export class BastionHostLinux extends Resource implements IInstance { constructor(scope: Construct, id: string, props: BastionHostLinuxProps) { super(scope, id); this.stack = Stack.of(scope); - + const instanceType = props.instanceType ?? InstanceType.of(InstanceClass.T3, InstanceSize.NANO); this.instance = new Instance(this, 'Resource', { vpc: props.vpc, availabilityZone: props.availabilityZone, securityGroup: props.securityGroup, instanceName: props.instanceName ?? 'BastionHost', - instanceType: props.instanceType ?? InstanceType.of(InstanceClass.T3, InstanceSize.NANO), - machineImage: props.machineImage ?? MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2 }), + instanceType, + machineImage: props.machineImage ?? MachineImage.latestAmazonLinux({ + generation: AmazonLinuxGeneration.AMAZON_LINUX_2, + cpuType: this.toAmazonLinuxCpuType(instanceType.architecture), + }), vpcSubnets: props.subnetSelection ?? {}, blockDevices: props.blockDevices ?? undefined, }); @@ -165,8 +168,6 @@ export class BastionHostLinux extends Resource implements IInstance { ], resources: ['*'], })); - this.instance.addUserData('yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm'); - this.connections = this.instance.connections; this.role = this.instance.role; this.grantPrincipal = this.instance.role; @@ -183,6 +184,20 @@ export class BastionHostLinux extends Resource implements IInstance { }); } + /** + * Returns the AmazonLinuxCpuType corresponding to the given instance architecture + * @param architecture the instance architecture value to convert + */ + private toAmazonLinuxCpuType(architecture: InstanceArchitecture): AmazonLinuxCpuType { + if (architecture === InstanceArchitecture.ARM_64) { + return AmazonLinuxCpuType.ARM_64; + } else if (architecture === InstanceArchitecture.X86_64) { + return AmazonLinuxCpuType.X86_64; + } + + throw new Error(`Unsupported instance architecture '${architecture}'`); + } + /** * Allow SSH access from the given peer or peers * diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index be2a58561b181..c9a7f8ac74509 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -473,6 +473,21 @@ export enum InstanceClass { INF1 = 'inf1' } +/** + * Identifies an instance's CPU architecture + */ +export enum InstanceArchitecture { + /** + * ARM64 architecture + */ + ARM_64 = 'arm64', + + /** + * x86-64 architecture + */ + X86_64 = 'x86_64', +} + /** * What size of instance to use */ @@ -597,4 +612,26 @@ export class InstanceType { public toString(): string { return this.instanceTypeIdentifier; } + + /** + * The instance's CPU architecture + */ + public get architecture(): InstanceArchitecture { + // capture the family, generation, capabilities, and size portions of the instance type id + const instanceTypeComponents = this.instanceTypeIdentifier.match(/^([a-z]+)(\d{1,2})([a-z]*)\.([a-z0-9]+)$/); + if (instanceTypeComponents == null) { + throw new Error('Malformed instance type identifier'); + } + + const family = instanceTypeComponents[1]; + const capabilities = instanceTypeComponents[3]; + + // Instance family `a` are first-gen Graviton instances + // Capability `g` indicates the instance is Graviton2 powered + if (family === 'a' || capabilities.includes('g')) { + return InstanceArchitecture.ARM_64; + } + + return InstanceArchitecture.X86_64; + } } diff --git a/packages/@aws-cdk/aws-ec2/test/bastion-host.test.ts b/packages/@aws-cdk/aws-ec2/test/bastion-host.test.ts index e950a397707b8..6c547d7859c1a 100644 --- a/packages/@aws-cdk/aws-ec2/test/bastion-host.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/bastion-host.test.ts @@ -1,7 +1,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { Stack } from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; -import { BastionHostLinux, BlockDeviceVolume, SubnetType, Vpc } from '../lib'; +import { BastionHostLinux, BlockDeviceVolume, InstanceClass, InstanceSize, InstanceType, SubnetType, Vpc } from '../lib'; nodeunitShim({ 'default instance is created in basic'(test: Test) { @@ -83,6 +83,45 @@ nodeunitShim({ ], })); + test.done(); + }, + 'x86-64 instances use x86-64 image by default'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + + // WHEN + new BastionHostLinux(stack, 'Bastion', { + vpc, + }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::Instance', { + ImageId: { + Ref: 'SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter', + }, + })); + + test.done(); + }, + 'arm instances use arm image by default'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + + // WHEN + new BastionHostLinux(stack, 'Bastion', { + vpc, + instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.NANO), + }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::Instance', { + ImageId: { + Ref: 'SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmarm64gp2C96584B6F00A464EAD1953AFF4B05118Parameter', + }, + })); + test.done(); }, }); diff --git a/packages/@aws-cdk/aws-ec2/test/instance.test.ts b/packages/@aws-cdk/aws-ec2/test/instance.test.ts index 6d002e38af568..a2049fb31e86b 100644 --- a/packages/@aws-cdk/aws-ec2/test/instance.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/instance.test.ts @@ -8,7 +8,7 @@ import { Stack } from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; import { AmazonLinuxImage, BlockDeviceVolume, CloudFormationInit, - EbsDeviceVolumeType, InitCommand, Instance, InstanceClass, InstanceSize, InstanceType, UserData, Vpc, + EbsDeviceVolumeType, InitCommand, Instance, InstanceArchitecture, InstanceClass, InstanceSize, InstanceType, UserData, Vpc, } from '../lib'; @@ -107,7 +107,56 @@ nodeunitShim({ test.done(); }, + 'instance architecture is correctly discerned for arm instances'(test: Test) { + // GIVEN + const sampleInstanceClasses = [ + 'a1', 't4g', 'c6g', 'c6gd', 'c6gn', 'm6g', 'm6gd', 'r6g', 'r6gd', // current Graviton-based instance classes + 'a13', 't11g', 'y10ng', 'z11ngd', // theoretical future Graviton-based instance classes + ]; + + for (const instanceClass of sampleInstanceClasses) { + // WHEN + const instanceType = InstanceType.of(instanceClass as InstanceClass, InstanceSize.XLARGE18); + + // THEN + expect(instanceType.architecture).toBe(InstanceArchitecture.ARM_64); + } + + test.done(); + }, + 'instance architecture is correctly discerned for x86-64 instance'(test: Test) { + // GIVEN + const sampleInstanceClasses = ['c5', 'm5ad', 'r5n', 'm6', 't3a']; // A sample of x86-64 instance classes + for (const instanceClass of sampleInstanceClasses) { + // WHEN + const instanceType = InstanceType.of(instanceClass as InstanceClass, InstanceSize.XLARGE18); + + // THEN + expect(instanceType.architecture).toBe(InstanceArchitecture.X86_64); + } + + test.done(); + }, + 'instance architecture throws an error when instance type is invalid'(test: Test) { + // GIVEN + const malformedInstanceTypes = ['t4', 't4g.nano.', 't4gnano', '']; + + for (const malformedInstanceType of malformedInstanceTypes) { + // WHEN + const instanceType = new InstanceType(malformedInstanceType); + + // THEN + try { + instanceType.architecture; + expect(true).toBe(false); // The line above should have thrown an error + } catch (err) { + expect(err.message).toBe('Malformed instance type identifier'); + } + } + + test.done(); + }, blockDeviceMappings: { 'can set blockDeviceMappings'(test: Test) { // WHEN diff --git a/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.expected.json new file mode 100644 index 0000000000000..81f4ae3377d40 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.expected.json @@ -0,0 +1,659 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet3Subnet631C5E25": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTable98AE0E14": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTableAssociation427FE0C6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + } + } + }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet3EIPAD4BC883": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3NATGatewayD3048F5C": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet3EIPAD4BC883", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCPrivateSubnet3Subnet3EDCD457": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTable192186F8": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTableAssociationC28D144E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + } + }, + "VPCPrivateSubnet3DefaultRoute27F311AE": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet3NATGatewayD3048F5C" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "BastionHostInstanceSecurityGroupE75D4274": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "TestStack/BastionHost/Resource/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "BastionHost" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "BastionHostInstanceRoleDD3FA5F1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "BastionHost" + } + ] + } + }, + "BastionHostInstanceRoleDefaultPolicy17347525": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ssmmessages:*", + "ssm:UpdateInstanceInformation", + "ec2messages:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "BastionHostInstanceRoleDefaultPolicy17347525", + "Roles": [ + { + "Ref": "BastionHostInstanceRoleDD3FA5F1" + } + ] + } + }, + "BastionHostInstanceProfile770FCA07": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "BastionHostInstanceRoleDD3FA5F1" + } + ] + } + }, + "BastionHost30F9ED05": { + "Type": "AWS::EC2::Instance", + "Properties": { + "AvailabilityZone": "test-region-1a", + "IamInstanceProfile": { + "Ref": "BastionHostInstanceProfile770FCA07" + }, + "ImageId": { + "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmarm64gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t4g.nano", + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "BastionHostInstanceSecurityGroupE75D4274", + "GroupId" + ] + } + ], + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + "Tags": [ + { + "Key": "Name", + "Value": "BastionHost" + } + ], + "UserData": { + "Fn::Base64": "#!/bin/bash" + } + }, + "DependsOn": [ + "BastionHostInstanceRoleDefaultPolicy17347525", + "BastionHostInstanceRoleDD3FA5F1" + ] + } + }, + "Outputs": { + "BastionHostBastionHostIdC743CBD6": { + "Description": "Instance ID of the bastion host. Use this to connect via SSM Session Manager", + "Value": { + "Ref": "BastionHost30F9ED05" + } + } + }, + "Parameters": { + "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmarm64gp2C96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.ts b/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.ts new file mode 100644 index 0000000000000..06d6d12557ba9 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.ts @@ -0,0 +1,26 @@ +/* + * Stack verification steps: + * * aws ssm start-session --target + * * lscpu # Architecture should be aarch64 + */ +import * as cdk from '@aws-cdk/core'; +import * as ec2 from '../lib'; + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const vpc = new ec2.Vpc(this, 'VPC'); + + new ec2.BastionHostLinux(this, 'BastionHost', { + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.NANO), + }); + } +} + +new TestStack(app, 'TestStack'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-ec2/test/integ.bastion-host.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.bastion-host.expected.json index 73b52ab630a76..4943873897e75 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.bastion-host.expected.json +++ b/packages/@aws-cdk/aws-ec2/test/integ.bastion-host.expected.json @@ -633,7 +633,7 @@ } ], "UserData": { - "Fn::Base64": "#!/bin/bash\nyum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm" + "Fn::Base64": "#!/bin/bash" } }, "DependsOn": [ From 1dd3d0518dc2a70c725f87dd5d4377338389125c Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Wed, 27 Jan 2021 14:23:54 +0000 Subject: [PATCH 21/70] feat(elbv2): support for 2020 SSL policy (#12710) Adds the new 'ELBSecurityPolicy-FS-1-2-Res-2020-10' SSL policy. closes #12595 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts index 89f2c88d7ad7e..be25c49b96513 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts @@ -82,6 +82,12 @@ export enum SslPolicy { */ RECOMMENDED = 'ELBSecurityPolicy-2016-08', + /** + * Strong foward secrecy ciphers and TLV1.2 only (2020 edition). + * Same as FORWARD_SECRECY_TLS12_RES, but only supports GCM versions of the TLS ciphers + */ + FORWARD_SECRECY_TLS12_RES_GCM = 'ELBSecurityPolicy-FS-1-2-Res-2020-10', + /** * Strong forward secrecy ciphers and TLS1.2 only */ From 126a6935cacc1f68b1d1155e484912d4ed6978f2 Mon Sep 17 00:00:00 2001 From: Ayush Goyal Date: Wed, 27 Jan 2021 21:44:07 +0530 Subject: [PATCH 22/70] feat(aws-route53): cross account DNS delegations (#12680) feat(aws-route53): cross account DNS delegations closes #8776 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-route53/.gitignore | 3 +- packages/@aws-cdk/aws-route53/.npmignore | 3 +- packages/@aws-cdk/aws-route53/README.md | 23 ++ packages/@aws-cdk/aws-route53/jest.config.js | 2 + .../index.ts | 66 ++++++ .../@aws-cdk/aws-route53/lib/hosted-zone.ts | 29 +++ .../@aws-cdk/aws-route53/lib/record-set.ts | 64 ++++- packages/@aws-cdk/aws-route53/package.json | 7 +- .../index.test.ts | 119 ++++++++++ ...ovider.ts => hosted-zone-provider.test.ts} | 6 +- .../aws-route53/test/hosted-zone.test.ts | 149 ++++++++++++ ...ross-account-zone-delegation.expected.json | 219 ++++++++++++++++++ .../integ.cross-account-zone-delegation.ts | 23 ++ ...{test.record-set.ts => record-set.test.ts} | 55 ++++- .../test/{test.route53.ts => route53.test.ts} | 6 +- .../aws-route53/test/test.hosted-zone.ts | 64 ----- .../test/{test.util.ts => util.test.ts} | 6 +- .../vpc-endpoint-service-domain-name.test.ts | 10 +- 18 files changed, 770 insertions(+), 84 deletions(-) create mode 100644 packages/@aws-cdk/aws-route53/jest.config.js create mode 100644 packages/@aws-cdk/aws-route53/lib/cross-account-zone-delegation-handler/index.ts create mode 100644 packages/@aws-cdk/aws-route53/test/cross-account-zone-delegation-handler/index.test.ts rename packages/@aws-cdk/aws-route53/test/{test.hosted-zone-provider.ts => hosted-zone-provider.test.ts} (97%) create mode 100644 packages/@aws-cdk/aws-route53/test/hosted-zone.test.ts create mode 100644 packages/@aws-cdk/aws-route53/test/integ.cross-account-zone-delegation.expected.json create mode 100644 packages/@aws-cdk/aws-route53/test/integ.cross-account-zone-delegation.ts rename packages/@aws-cdk/aws-route53/test/{test.record-set.ts => record-set.test.ts} (88%) rename packages/@aws-cdk/aws-route53/test/{test.route53.ts => route53.test.ts} (98%) delete mode 100644 packages/@aws-cdk/aws-route53/test/test.hosted-zone.ts rename packages/@aws-cdk/aws-route53/test/{test.util.ts => util.test.ts} (97%) diff --git a/packages/@aws-cdk/aws-route53/.gitignore b/packages/@aws-cdk/aws-route53/.gitignore index 86fc837df8fca..a82230b5888d0 100644 --- a/packages/@aws-cdk/aws-route53/.gitignore +++ b/packages/@aws-cdk/aws-route53/.gitignore @@ -15,4 +15,5 @@ nyc.config.js *.snk !.eslintrc.js -junit.xml \ No newline at end of file +junit.xml +!jest.config.js \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/.npmignore b/packages/@aws-cdk/aws-route53/.npmignore index a94c531529866..9e88226921c33 100644 --- a/packages/@aws-cdk/aws-route53/.npmignore +++ b/packages/@aws-cdk/aws-route53/.npmignore @@ -23,4 +23,5 @@ tsconfig.json # exclude cdk artifacts **/cdk.out junit.xml -test/ \ No newline at end of file +test/ +jest.config.js \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/README.md b/packages/@aws-cdk/aws-route53/README.md index cc517c5ad6937..49947e4791092 100644 --- a/packages/@aws-cdk/aws-route53/README.md +++ b/packages/@aws-cdk/aws-route53/README.md @@ -109,6 +109,29 @@ Constructs are available for A, AAAA, CAA, CNAME, MX, NS, SRV and TXT records. Use the `CaaAmazonRecord` construct to easily restrict certificate authorities allowed to issue certificates for a domain to Amazon only. +To add a NS record to a HostedZone in different account + +```ts +import * as route53 from '@aws-cdk/aws-route53'; + +// In the account containing the HostedZone +const parentZone = new route53.PublicHostedZone(this, 'HostedZone', { + zoneName: 'someexample.com', + crossAccountZoneDelegationPrinciple: new iam.AccountPrincipal('12345678901') +}); + +// In this account +const subZone = new route53.PublicHostedZone(this, 'SubZone', { + zoneName: 'sub.someexample.com' +}); + +new route53.CrossAccountZoneDelegationRecord(this, 'delegate', { + delegatedZone: subZone, + parentHostedZoneId: parentZone.hostedZoneId, + delegationRole: parentZone.crossAccountDelegationRole +}); +``` + ## Imports If you don't know the ID of the Hosted Zone to import, you can use the diff --git a/packages/@aws-cdk/aws-route53/jest.config.js b/packages/@aws-cdk/aws-route53/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-route53/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-route53/lib/cross-account-zone-delegation-handler/index.ts b/packages/@aws-cdk/aws-route53/lib/cross-account-zone-delegation-handler/index.ts new file mode 100644 index 0000000000000..3c711d283d7e5 --- /dev/null +++ b/packages/@aws-cdk/aws-route53/lib/cross-account-zone-delegation-handler/index.ts @@ -0,0 +1,66 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Credentials, Route53, STS } from 'aws-sdk'; + +interface ResourceProperties { + AssumeRoleArn: string, + ParentZoneId: string, + DelegatedZoneName: string, + DelegatedZoneNameServers: string[], + TTL: number, +} + +export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent) { + const resourceProps = event.ResourceProperties as unknown as ResourceProperties; + + switch (event.RequestType) { + case 'Create': + case 'Update': + return cfnEventHandler(resourceProps, false); + case 'Delete': + return cfnEventHandler(resourceProps, true); + } +} + +async function cfnEventHandler(props: ResourceProperties, isDeleteEvent: boolean) { + const { AssumeRoleArn, ParentZoneId, DelegatedZoneName, DelegatedZoneNameServers, TTL } = props; + + const credentials = await getCrossAccountCredentials(AssumeRoleArn); + const route53 = new Route53({ credentials }); + + await route53.changeResourceRecordSets({ + HostedZoneId: ParentZoneId, + ChangeBatch: { + Changes: [{ + Action: isDeleteEvent ? 'DELETE' : 'UPSERT', + ResourceRecordSet: { + Name: DelegatedZoneName, + Type: 'NS', + TTL, + ResourceRecords: DelegatedZoneNameServers.map(ns => ({ Value: ns })), + }, + }], + }, + }).promise(); +} + +async function getCrossAccountCredentials(roleArn: string): Promise { + const sts = new STS(); + const timestamp = (new Date()).getTime(); + + const { Credentials: assumedCredentials } = await sts + .assumeRole({ + RoleArn: roleArn, + RoleSessionName: `cross-account-zone-delegation-${timestamp}`, + }) + .promise(); + + if (!assumedCredentials) { + throw Error('Error getting assume role credentials'); + } + + return new Credentials({ + accessKeyId: assumedCredentials.AccessKeyId, + secretAccessKey: assumedCredentials.SecretAccessKey, + sessionToken: assumedCredentials.SessionToken, + }); +} diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts index f11e9ae180e7f..1ca835cb258d9 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts @@ -1,4 +1,5 @@ import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { ContextProvider, Duration, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -190,6 +191,13 @@ export interface PublicHostedZoneProps extends CommonHostedZoneProps { * @default false */ readonly caaAmazon?: boolean; + + /** + * A principal which is trusted to assume a role for zone delegation + * + * @default - No delegation configuration + */ + readonly crossAccountZoneDelegationPrincipal?: iam.IPrincipal; } /** @@ -222,6 +230,11 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone { return new Import(scope, id); } + /** + * Role for cross account zone delegation + */ + public readonly crossAccountZoneDelegationRole?: iam.Role; + constructor(scope: Construct, id: string, props: PublicHostedZoneProps) { super(scope, id, props); @@ -230,6 +243,22 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone { zone: this, }); } + + if (props.crossAccountZoneDelegationPrincipal) { + this.crossAccountZoneDelegationRole = new iam.Role(this, 'CrossAccountZoneDelegationRole', { + assumedBy: props.crossAccountZoneDelegationPrincipal, + inlinePolicies: { + delegation: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + actions: ['route53:ChangeResourceRecordSets'], + resources: [this.hostedZoneArn], + }), + ], + }), + }, + }); + } } public addVpc(_vpc: ec2.IVpc) { diff --git a/packages/@aws-cdk/aws-route53/lib/record-set.ts b/packages/@aws-cdk/aws-route53/lib/record-set.ts index 3111375a8682d..577af5c1a3a57 100644 --- a/packages/@aws-cdk/aws-route53/lib/record-set.ts +++ b/packages/@aws-cdk/aws-route53/lib/record-set.ts @@ -1,10 +1,18 @@ -import { Duration, IResource, Resource, Token } from '@aws-cdk/core'; +import * as path from 'path'; +import * as iam from '@aws-cdk/aws-iam'; +import { CustomResource, CustomResourceProvider, CustomResourceProviderRuntime, Duration, IResource, Resource, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IAliasRecordTarget } from './alias-record-target'; import { IHostedZone } from './hosted-zone-ref'; import { CfnRecordSet } from './route53.generated'; import { determineFullyQualifiedDomainName } from './util'; +const CROSS_ACCOUNT_ZONE_DELEGATION_RESOURCE_TYPE = 'Custom::CrossAccountZoneDelegation'; + +// v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. +// eslint-disable-next-line +import { Construct as CoreConstruct } from '@aws-cdk/core'; + /** * A record set */ @@ -559,3 +567,57 @@ export class ZoneDelegationRecord extends RecordSet { }); } } + +/** + * Construction properties for a CrossAccountZoneDelegationRecord + */ +export interface CrossAccountZoneDelegationRecordProps { + /** + * The zone to be delegated + */ + readonly delegatedZone: IHostedZone; + + /** + * The hosted zone id in the parent account + */ + readonly parentHostedZoneId: string; + + /** + * The delegation role in the parent account + */ + readonly delegationRole: iam.IRole; + + /** + * The resource record cache time to live (TTL). + * + * @default Duration.days(2) + */ + readonly ttl?: Duration; +} + +/** + * A Cross Account Zone Delegation record + */ +export class CrossAccountZoneDelegationRecord extends CoreConstruct { + constructor(scope: Construct, id: string, props: CrossAccountZoneDelegationRecordProps) { + super(scope, id); + + const serviceToken = CustomResourceProvider.getOrCreate(this, CROSS_ACCOUNT_ZONE_DELEGATION_RESOURCE_TYPE, { + codeDirectory: path.join(__dirname, 'cross-account-zone-delegation-handler'), + runtime: CustomResourceProviderRuntime.NODEJS_12, + policyStatements: [{ Effect: 'Allow', Action: 'sts:AssumeRole', Resource: props.delegationRole.roleArn }], + }); + + new CustomResource(this, 'CrossAccountZoneDelegationCustomResource', { + resourceType: CROSS_ACCOUNT_ZONE_DELEGATION_RESOURCE_TYPE, + serviceToken, + properties: { + AssumeRoleArn: props.delegationRole.roleArn, + ParentZoneId: props.parentHostedZoneId, + DelegatedZoneName: props.delegatedZone.zoneName, + DelegatedZoneNameServers: props.delegatedZone.hostedZoneNameServers!, + TTL: (props.ttl || Duration.days(2)).toSeconds(), + }, + }); + } +} diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 89990b82cc61f..70aa8fc17f4a8 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -55,7 +55,8 @@ "cloudformation": "AWS::Route53", "env": { "AWSLINT_BASE_CONSTRUCT": true - } + }, + "jest": true }, "keywords": [ "aws", @@ -77,11 +78,12 @@ "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "jest": "^26.6.0", - "nodeunit": "^0.11.3", + "nodeunit-shim": "0.0.0", "pkglint": "0.0.0" }, "dependencies": { "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/cloud-assembly-schema": "0.0.0", @@ -91,6 +93,7 @@ "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/cloud-assembly-schema": "0.0.0", diff --git a/packages/@aws-cdk/aws-route53/test/cross-account-zone-delegation-handler/index.test.ts b/packages/@aws-cdk/aws-route53/test/cross-account-zone-delegation-handler/index.test.ts new file mode 100644 index 0000000000000..8c8dcafcbd9c7 --- /dev/null +++ b/packages/@aws-cdk/aws-route53/test/cross-account-zone-delegation-handler/index.test.ts @@ -0,0 +1,119 @@ +import { handler } from '../../lib/cross-account-zone-delegation-handler'; + +const mockStsClient = { + assumeRole: jest.fn().mockReturnThis(), + promise: jest.fn(), +}; +const mockRoute53Client = { + changeResourceRecordSets: jest.fn().mockReturnThis(), + promise: jest.fn(), +}; + +jest.mock('aws-sdk', () => { + return { + ...(jest.requireActual('aws-sdk') as any), + STS: jest.fn(() => mockStsClient), + Route53: jest.fn(() => mockRoute53Client), + }; +}); + +beforeEach(() => { + mockStsClient.assumeRole.mockReturnThis(); + mockRoute53Client.changeResourceRecordSets.mockReturnThis(); +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +test('throws error if getting credentials fails', async () => { + // GIVEN + mockStsClient.promise.mockResolvedValueOnce({ Credentials: undefined }); + + // WHEN + const event= getCfnEvent(); + + // THEN + await expect(invokeHandler(event)).rejects.toThrow(/Error getting assume role credentials/); + + expect(mockStsClient.assumeRole).toHaveBeenCalledTimes(1); + expect(mockStsClient.assumeRole).toHaveBeenCalledWith({ + RoleArn: 'roleArn', + RoleSessionName: expect.any(String), + }); +}); + +test('calls create resouce record set with Upsert for Create event', async () => { + // GIVEN + mockStsClient.promise.mockResolvedValueOnce({ Credentials: { AccessKeyId: 'K', SecretAccessKey: 'S', SessionToken: 'T' } }); + mockRoute53Client.promise.mockResolvedValueOnce({}); + + // WHEN + const event= getCfnEvent(); + await invokeHandler(event); + + // THEN + expect(mockRoute53Client.changeResourceRecordSets).toHaveBeenCalledTimes(1); + expect(mockRoute53Client.changeResourceRecordSets).toHaveBeenCalledWith({ + HostedZoneId: '1', + ChangeBatch: { + Changes: [{ + Action: 'UPSERT', + ResourceRecordSet: { + Name: 'recordName', + Type: 'NS', + TTL: 172800, + ResourceRecords: [{ Value: 'one' }, { Value: 'two' }], + }, + }], + }, + }); +}); + +test('calls create resouce record set with DELETE for Delete event', async () => { + // GIVEN + mockStsClient.promise.mockResolvedValueOnce({ Credentials: { AccessKeyId: 'K', SecretAccessKey: 'S', SessionToken: 'T' } }); + mockRoute53Client.promise.mockResolvedValueOnce({}); + + // WHEN + const event= getCfnEvent({ RequestType: 'Delete' }); + await invokeHandler(event); + + // THEN + expect(mockRoute53Client.changeResourceRecordSets).toHaveBeenCalledTimes(1); + expect(mockRoute53Client.changeResourceRecordSets).toHaveBeenCalledWith({ + HostedZoneId: '1', + ChangeBatch: { + Changes: [{ + Action: 'DELETE', + ResourceRecordSet: { + Name: 'recordName', + Type: 'NS', + TTL: 172800, + ResourceRecords: [{ Value: 'one' }, { Value: 'two' }], + }, + }], + }, + }); +}); + +function getCfnEvent(event?: Partial): Partial { + return { + RequestType: 'Create', + ResourceProperties: { + ServiceToken: 'Foo', + AssumeRoleArn: 'roleArn', + ParentZoneId: '1', + DelegatedZoneName: 'recordName', + DelegatedZoneNameServers: ['one', 'two'], + TTL: 172800, + }, + ...event, + }; +} + +// helper function to get around TypeScript expecting a complete event object, +// even though our tests only need some of the fields +async function invokeHandler(event: Partial) { + return handler(event as AWSLambda.CloudFormationCustomResourceEvent); +} diff --git a/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts b/packages/@aws-cdk/aws-route53/test/hosted-zone-provider.test.ts similarity index 97% rename from packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts rename to packages/@aws-cdk/aws-route53/test/hosted-zone-provider.test.ts index 98778fe9d5107..07917bb83ba40 100644 --- a/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts +++ b/packages/@aws-cdk/aws-route53/test/hosted-zone-provider.test.ts @@ -1,9 +1,9 @@ import { SynthUtils } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import { HostedZone } from '../lib'; -export = { +nodeunitShim({ 'Hosted Zone Provider': { 'HostedZoneProvider will return context values if available'(test: Test) { // GIVEN @@ -82,4 +82,4 @@ export = { test.done(); }, }, -}; +}); diff --git a/packages/@aws-cdk/aws-route53/test/hosted-zone.test.ts b/packages/@aws-cdk/aws-route53/test/hosted-zone.test.ts new file mode 100644 index 0000000000000..6e7810824c2bf --- /dev/null +++ b/packages/@aws-cdk/aws-route53/test/hosted-zone.test.ts @@ -0,0 +1,149 @@ +import { expect } from '@aws-cdk/assert'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import { nodeunitShim, Test } from 'nodeunit-shim'; +import { HostedZone, PublicHostedZone } from '../lib'; + +nodeunitShim({ + 'Hosted Zone': { + 'Hosted Zone constructs the ARN'(test: Test) { + // GIVEN + const stack = new cdk.Stack(undefined, 'TestStack', { + env: { account: '123456789012', region: 'us-east-1' }, + }); + + const testZone = new HostedZone(stack, 'HostedZone', { + zoneName: 'testZone', + }); + + test.deepEqual(stack.resolve(testZone.hostedZoneArn), { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':route53:::hostedzone/', + { Ref: 'HostedZoneDB99F866' }, + ], + ], + }); + + test.done(); + }, + }, + + 'Supports tags'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const hostedZone = new HostedZone(stack, 'HostedZone', { + zoneName: 'test.zone', + }); + cdk.Tags.of(hostedZone).add('zoneTag', 'inMyZone'); + + // THEN + expect(stack).toMatch({ + Resources: { + HostedZoneDB99F866: { + Type: 'AWS::Route53::HostedZone', + Properties: { + Name: 'test.zone.', + HostedZoneTags: [ + { + Key: 'zoneTag', + Value: 'inMyZone', + }, + ], + }, + }, + }, + }); + + test.done(); + }, + + 'with crossAccountZoneDelegationPrinciple'(test: Test) { + // GIVEN + const stack = new cdk.Stack(undefined, 'TestStack', { + env: { account: '123456789012', region: 'us-east-1' }, + }); + + // WHEN + new PublicHostedZone(stack, 'HostedZone', { + zoneName: 'testZone', + crossAccountZoneDelegationPrincipal: new iam.AccountPrincipal('223456789012'), + }); + + // THEN + expect(stack).toMatch({ + Resources: { + HostedZoneDB99F866: { + Type: 'AWS::Route53::HostedZone', + Properties: { + Name: 'testZone.', + }, + }, + HostedZoneCrossAccountZoneDelegationRole685DF755: { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::223456789012:root', + ], + ], + }, + }, + }, + ], + Version: '2012-10-17', + }, + Policies: [ + { + PolicyDocument: { + Statement: [ + { + Action: 'route53:ChangeResourceRecordSets', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':route53:::hostedzone/', + { + Ref: 'HostedZoneDB99F866', + }, + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'delegation', + }, + ], + }, + }, + }, + }); + + test.done(); + }, +}); diff --git a/packages/@aws-cdk/aws-route53/test/integ.cross-account-zone-delegation.expected.json b/packages/@aws-cdk/aws-route53/test/integ.cross-account-zone-delegation.expected.json new file mode 100644 index 0000000000000..919a54f8b5051 --- /dev/null +++ b/packages/@aws-cdk/aws-route53/test/integ.cross-account-zone-delegation.expected.json @@ -0,0 +1,219 @@ +{ + "Resources": { + "ParentHostedZoneC2BD86E1": { + "Type": "AWS::Route53::HostedZone", + "Properties": { + "Name": "myzone.com." + } + }, + "ParentHostedZoneCrossAccountZoneDelegationRole95B1C36E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "route53:ChangeResourceRecordSets", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":route53:::hostedzone/", + { + "Ref": "ParentHostedZoneC2BD86E1" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "delegation" + } + ] + } + }, + "ChildHostedZone4B14AC71": { + "Type": "AWS::Route53::HostedZone", + "Properties": { + "Name": "sub.myzone.com." + } + }, + "DelegationCrossAccountZoneDelegationCustomResourceFADC27F0": { + "Type": "Custom::CrossAccountZoneDelegation", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCrossAccountZoneDelegationCustomResourceProviderHandler44A84265", + "Arn" + ] + }, + "AssumeRoleArn": { + "Fn::GetAtt": [ + "ParentHostedZoneCrossAccountZoneDelegationRole95B1C36E", + "Arn" + ] + }, + "ParentZoneId": { + "Ref": "ParentHostedZoneC2BD86E1" + }, + "DelegatedZoneName": "sub.myzone.com", + "DelegatedZoneNameServers": { + "Fn::GetAtt": [ + "ChildHostedZone4B14AC71", + "NameServers" + ] + }, + "TTL": 172800 + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomCrossAccountZoneDelegationCustomResourceProviderRoleED64687B": { + "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": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": { + "Fn::GetAtt": [ + "ParentHostedZoneCrossAccountZoneDelegationRole95B1C36E", + "Arn" + ] + } + } + ] + } + } + ] + } + }, + "CustomCrossAccountZoneDelegationCustomResourceProviderHandler44A84265": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters3c971020239d152fff59dc3bdbabbbc6d3d9140574e45fd4eb7313ced117113aS3Bucket8B462894" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters3c971020239d152fff59dc3bdbabbbc6d3d9140574e45fd4eb7313ced117113aS3VersionKeyFDEC5E1D" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters3c971020239d152fff59dc3bdbabbbc6d3d9140574e45fd4eb7313ced117113aS3VersionKeyFDEC5E1D" + } + ] + } + ] + } + ] + ] + } + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomCrossAccountZoneDelegationCustomResourceProviderRoleED64687B", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + }, + "DependsOn": [ + "CustomCrossAccountZoneDelegationCustomResourceProviderRoleED64687B" + ] + } + }, + "Parameters": { + "AssetParameters3c971020239d152fff59dc3bdbabbbc6d3d9140574e45fd4eb7313ced117113aS3Bucket8B462894": { + "Type": "String", + "Description": "S3 bucket for asset \"3c971020239d152fff59dc3bdbabbbc6d3d9140574e45fd4eb7313ced117113a\"" + }, + "AssetParameters3c971020239d152fff59dc3bdbabbbc6d3d9140574e45fd4eb7313ced117113aS3VersionKeyFDEC5E1D": { + "Type": "String", + "Description": "S3 key for asset version \"3c971020239d152fff59dc3bdbabbbc6d3d9140574e45fd4eb7313ced117113a\"" + }, + "AssetParameters3c971020239d152fff59dc3bdbabbbc6d3d9140574e45fd4eb7313ced117113aArtifactHash4F367D8C": { + "Type": "String", + "Description": "Artifact hash for asset \"3c971020239d152fff59dc3bdbabbbc6d3d9140574e45fd4eb7313ced117113a\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53/test/integ.cross-account-zone-delegation.ts b/packages/@aws-cdk/aws-route53/test/integ.cross-account-zone-delegation.ts new file mode 100644 index 0000000000000..75f9e86152eb0 --- /dev/null +++ b/packages/@aws-cdk/aws-route53/test/integ.cross-account-zone-delegation.ts @@ -0,0 +1,23 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import { PublicHostedZone, CrossAccountZoneDelegationRecord } from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-route53-cross-account-integ'); + +const parentZone = new PublicHostedZone(stack, 'ParentHostedZone', { + zoneName: 'myzone.com', + crossAccountZoneDelegationPrincipal: new iam.AccountPrincipal(cdk.Aws.ACCOUNT_ID), +}); + +const childZone = new PublicHostedZone(stack, 'ChildHostedZone', { + zoneName: 'sub.myzone.com', +}); +new CrossAccountZoneDelegationRecord(stack, 'Delegation', { + delegatedZone: childZone, + parentHostedZoneId: parentZone.hostedZoneId, + delegationRole: parentZone.crossAccountZoneDelegationRole!, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-route53/test/test.record-set.ts b/packages/@aws-cdk/aws-route53/test/record-set.test.ts similarity index 88% rename from packages/@aws-cdk/aws-route53/test/test.record-set.ts rename to packages/@aws-cdk/aws-route53/test/record-set.test.ts index 8da38f60d9720..373464f455992 100644 --- a/packages/@aws-cdk/aws-route53/test/test.record-set.ts +++ b/packages/@aws-cdk/aws-route53/test/record-set.test.ts @@ -1,9 +1,10 @@ import { expect, haveResource } from '@aws-cdk/assert'; +import * as iam from '@aws-cdk/aws-iam'; import { Duration, Stack } from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import * as route53 from '../lib'; -export = { +nodeunitShim({ 'with default ttl'(test: Test) { // GIVEN const stack = new Stack(); @@ -513,4 +514,52 @@ export = { })); test.done(); }, -}; + + 'Cross account zone delegation record'(test: Test) { + // GIVEN + const stack = new Stack(); + const parentZone = new route53.PublicHostedZone(stack, 'ParentHostedZone', { + zoneName: 'myzone.com', + crossAccountZoneDelegationPrincipal: new iam.AccountPrincipal('123456789012'), + }); + + // WHEN + const childZone = new route53.PublicHostedZone(stack, 'ChildHostedZone', { + zoneName: 'sub.myzone.com', + }); + new route53.CrossAccountZoneDelegationRecord(stack, 'Delegation', { + delegatedZone: childZone, + parentHostedZoneId: parentZone.hostedZoneId, + delegationRole: parentZone.crossAccountZoneDelegationRole!, + ttl: Duration.seconds(60), + }); + + // THEN + expect(stack).to(haveResource('Custom::CrossAccountZoneDelegation', { + ServiceToken: { + 'Fn::GetAtt': [ + 'CustomCrossAccountZoneDelegationCustomResourceProviderHandler44A84265', + 'Arn', + ], + }, + AssumeRoleArn: { + 'Fn::GetAtt': [ + 'ParentHostedZoneCrossAccountZoneDelegationRole95B1C36E', + 'Arn', + ], + }, + ParentZoneId: { + Ref: 'ParentHostedZoneC2BD86E1', + }, + DelegatedZoneName: 'sub.myzone.com', + DelegatedZoneNameServers: { + 'Fn::GetAtt': [ + 'ChildHostedZone4B14AC71', + 'NameServers', + ], + }, + TTL: 60, + })); + test.done(); + }, +}); diff --git a/packages/@aws-cdk/aws-route53/test/test.route53.ts b/packages/@aws-cdk/aws-route53/test/route53.test.ts similarity index 98% rename from packages/@aws-cdk/aws-route53/test/test.route53.ts rename to packages/@aws-cdk/aws-route53/test/route53.test.ts index 4655e1c10fda8..8f58486bebcbc 100644 --- a/packages/@aws-cdk/aws-route53/test/test.route53.ts +++ b/packages/@aws-cdk/aws-route53/test/route53.test.ts @@ -1,10 +1,10 @@ import { beASupersetOfTemplate, exactlyMatchTemplate, expect, haveResource } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import { HostedZone, PrivateHostedZone, PublicHostedZone, TxtRecord } from '../lib'; -export = { +nodeunitShim({ 'default properties': { 'public hosted zone'(test: Test) { const app = new TestApp(); @@ -215,7 +215,7 @@ export = { })); test.done(); }, -}; +}); class TestApp { public readonly stack: cdk.Stack; diff --git a/packages/@aws-cdk/aws-route53/test/test.hosted-zone.ts b/packages/@aws-cdk/aws-route53/test/test.hosted-zone.ts deleted file mode 100644 index 37d80a5908a9b..0000000000000 --- a/packages/@aws-cdk/aws-route53/test/test.hosted-zone.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { expect } from '@aws-cdk/assert'; -import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; -import { HostedZone } from '../lib'; - -export = { - 'Hosted Zone': { - 'Hosted Zone constructs the ARN'(test: Test) { - // GIVEN - const stack = new cdk.Stack(undefined, 'TestStack', { - env: { account: '123456789012', region: 'us-east-1' }, - }); - - const testZone = new HostedZone(stack, 'HostedZone', { - zoneName: 'testZone', - }); - - test.deepEqual(stack.resolve(testZone.hostedZoneArn), { - 'Fn::Join': [ - '', - [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':route53:::hostedzone/', - { Ref: 'HostedZoneDB99F866' }, - ], - ], - }); - - test.done(); - }, - }, - - 'Supports tags'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const hostedZone = new HostedZone(stack, 'HostedZone', { - zoneName: 'test.zone', - }); - cdk.Tags.of(hostedZone).add('zoneTag', 'inMyZone'); - - // THEN - expect(stack).toMatch({ - Resources: { - HostedZoneDB99F866: { - Type: 'AWS::Route53::HostedZone', - Properties: { - Name: 'test.zone.', - HostedZoneTags: [ - { - Key: 'zoneTag', - Value: 'inMyZone', - }, - ], - }, - }, - }, - }); - - test.done(); - }, -}; diff --git a/packages/@aws-cdk/aws-route53/test/test.util.ts b/packages/@aws-cdk/aws-route53/test/util.test.ts similarity index 97% rename from packages/@aws-cdk/aws-route53/test/test.util.ts rename to packages/@aws-cdk/aws-route53/test/util.test.ts index d589b058e40cc..c6ded4e74f7b4 100644 --- a/packages/@aws-cdk/aws-route53/test/test.util.ts +++ b/packages/@aws-cdk/aws-route53/test/util.test.ts @@ -1,9 +1,9 @@ import * as cdk from '@aws-cdk/core'; -import { Test } from 'nodeunit'; +import { nodeunitShim, Test } from 'nodeunit-shim'; import { HostedZone } from '../lib'; import * as util from '../lib/util'; -export = { +nodeunitShim({ 'throws when zone name ending with a \'.\''(test: Test) { test.throws(() => util.validateZoneName('zone.name.'), /trailing dot/); test.done(); @@ -78,4 +78,4 @@ export = { test.equal(qualified, 'test.domain.com.'); test.done(); }, -}; +}); diff --git a/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts b/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts index 70ba07c201d5f..86edd9992776d 100644 --- a/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts +++ b/packages/@aws-cdk/aws-route53/test/vpc-endpoint-service-domain-name.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable jest/no-disabled-tests */ import { expect as cdkExpect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import { IVpcEndpointServiceLoadBalancer, VpcEndpointService } from '@aws-cdk/aws-ec2'; @@ -56,7 +57,7 @@ test('create domain name resource', () => { }, }, physicalResourceId: { - id: 'EndpointDomain', + id: 'VPCES', }, }, Update: { @@ -69,7 +70,7 @@ test('create domain name resource', () => { }, }, physicalResourceId: { - id: 'EndpointDomain', + id: 'VPCES', }, }, Delete: { @@ -236,6 +237,9 @@ test('create domain name resource', () => { test('throws if creating multiple domains for a single service', () => { // GIVEN + vpces = new VpcEndpointService(stack, 'VPCES-2', { + vpcEndpointServiceLoadBalancers: [nlb], + }); new VpcEndpointServiceDomainName(stack, 'EndpointDomain', { endpointService: vpces, @@ -250,5 +254,5 @@ test('throws if creating multiple domains for a single service', () => { domainName: 'my-stuff-2.aws-cdk.dev', publicHostedZone: zone, }); - }).toThrow(); + }).toThrow(/Cannot create a VpcEndpointServiceDomainName for service/); }); \ No newline at end of file From 2f6521a1d8670b2653f7dee281309351181cf918 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 27 Jan 2021 19:34:54 +0100 Subject: [PATCH 23/70] fix(iam): cannot use the same Role for multiple Config Rules (#12724) This, or any other construct that adds Managed Policies to a Role by default; Managed Policy ARNs need to be deduplicated, otherwise CloudFormation will throw an error upon creation. Slightly more complicated than you'd expect in order to deal with Tokens. Fixes #12714. ---- *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-iam/lib/role.ts | 6 +-- packages/@aws-cdk/aws-iam/lib/util.ts | 44 ++++++++++++++++++++- packages/@aws-cdk/aws-iam/test/role.test.ts | 28 +++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index 74e251bd7bc07..9b81e4f174152 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -1,4 +1,4 @@ -import { Duration, Lazy, Resource, Stack, Token, TokenComparison } from '@aws-cdk/core'; +import { Duration, Resource, Stack, Token, TokenComparison } from '@aws-cdk/core'; import { Construct, Node } from 'constructs'; import { Grant } from './grant'; import { CfnRole } from './iam.generated'; @@ -9,7 +9,7 @@ import { PolicyDocument } from './policy-document'; import { PolicyStatement } from './policy-statement'; import { AddToPrincipalPolicyResult, ArnPrincipal, IPrincipal, PrincipalPolicyFragment } from './principals'; import { ImmutableRole } from './private/immutable-role'; -import { AttachedPolicies } from './util'; +import { AttachedPolicies, UniqueStringSet } from './util'; /** * Properties for defining an IAM Role @@ -326,7 +326,7 @@ export class Role extends Resource implements IRole { const role = new CfnRole(this, 'Resource', { assumeRolePolicyDocument: this.assumeRolePolicy as any, - managedPolicyArns: Lazy.list({ produce: () => this.managedPolicies.map(p => p.managedPolicyArn) }, { omitEmpty: true }), + managedPolicyArns: UniqueStringSet.from(() => this.managedPolicies.map(p => p.managedPolicyArn)), policies: _flatten(this.inlinePolicies), path: props.path, permissionsBoundary: this.permissionsBoundary ? this.permissionsBoundary.managedPolicyArn : undefined, diff --git a/packages/@aws-cdk/aws-iam/lib/util.ts b/packages/@aws-cdk/aws-iam/lib/util.ts index b5f1700baefe7..19fcbffe09639 100644 --- a/packages/@aws-cdk/aws-iam/lib/util.ts +++ b/packages/@aws-cdk/aws-iam/lib/util.ts @@ -1,4 +1,4 @@ -import { DefaultTokenResolver, Lazy, StringConcat, Tokenization } from '@aws-cdk/core'; +import { captureStackTrace, DefaultTokenResolver, IPostProcessor, IResolvable, IResolveContext, Lazy, StringConcat, Token, Tokenization } from '@aws-cdk/core'; import { IConstruct } from 'constructs'; import { IPolicy } from './policy'; @@ -82,3 +82,45 @@ export function mergePrincipal(target: { [key: string]: string[] }, source: { [k return target; } + +/** + * Lazy string set token that dedupes entries + * + * Needs to operate post-resolve, because the inputs could be + * `[ '${Token[TOKEN.9]}', '${Token[TOKEN.10]}', '${Token[TOKEN.20]}' ]`, which + * still all resolve to the same string value. + * + * Needs to JSON.stringify() results because strings could resolve to literal + * strings but could also resolve to `{ Fn::Join: [...] }`. + */ +export class UniqueStringSet implements IResolvable, IPostProcessor { + public static from(fn: () => string[]) { + return Token.asList(new UniqueStringSet(fn)); + } + + public readonly creationStack: string[]; + + private constructor(private readonly fn: () => string[]) { + this.creationStack = captureStackTrace(); + } + + public resolve(context: IResolveContext) { + context.registerPostProcessor(this); + return this.fn(); + } + + public postProcess(input: any, _context: IResolveContext) { + if (!Array.isArray(input)) { return input; } + if (input.length === 0) { return undefined; } + + const uniq: Record = {}; + for (const el of input) { + uniq[JSON.stringify(el)] = el; + } + return Object.values(uniq); + } + + public toString(): string { + return Token.asString(this); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/role.test.ts b/packages/@aws-cdk/aws-iam/test/role.test.ts index 510416f580f39..8f0415c45598d 100644 --- a/packages/@aws-cdk/aws-iam/test/role.test.ts +++ b/packages/@aws-cdk/aws-iam/test/role.test.ts @@ -535,3 +535,31 @@ describe('IAM role', () => { expect(() => app.synth()).toThrow(/A PolicyStatement used in a resource-based policy must specify at least one IAM principal/); }); }); + +test('managed policy ARNs are deduplicated', () => { + const app = new App(); + const stack = new Stack(app, 'my-stack'); + const role = new Role(stack, 'MyRole', { + assumedBy: new ServicePrincipal('sns.amazonaws.com'), + managedPolicies: [ + ManagedPolicy.fromAwsManagedPolicyName('SuperDeveloper'), + ManagedPolicy.fromAwsManagedPolicyName('SuperDeveloper'), + ], + }); + role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('SuperDeveloper')); + + expect(stack).toHaveResource('AWS::IAM::Role', { + ManagedPolicyArns: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::aws:policy/SuperDeveloper', + ], + ], + }, + ], + }); +}); \ No newline at end of file From 38cbe620859d6efabda95dbdd3185a480ab43894 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 28 Jan 2021 10:22:30 +0000 Subject: [PATCH 24/70] fix(apigateway): stack update fails to replace api key This reverts commit 96cbe32d2399d82a2ad6c3bf6dc1fd65396882d4. The above commit changed the logical id layout of API keys. It turns out that ApiKey resource types cannot be replaced without explicitly specifying and changing the API key name. See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-name fixes #12698 --- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 4 ++- .../test/integ.restapi.expected.json | 2 +- .../integ.usage-plan.multikey.expected.json | 2 +- .../aws-apigateway/test/usage-plan.test.ts | 28 ------------------- 4 files changed, 5 insertions(+), 31 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index 6a1c5a5091bda..ad807d4a7d2d0 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -179,8 +179,10 @@ export class UsagePlan extends Resource { * @param apiKey */ public addApiKey(apiKey: IApiKey): void { + const prefix = 'UsagePlanKeyResource'; + // Postfixing apikey id only from the 2nd child, to keep physicalIds of UsagePlanKey for existing CDK apps unmodifed. - const id = `UsagePlanKeyResource:${Names.nodeUniqueId(apiKey.node)}`; + const id = this.node.tryFindChild(prefix) ? `${prefix}:${Names.nodeUniqueId(apiKey.node)}` : prefix; new CfnUsagePlanKey(this, id, { keyId: apiKey.keyId, diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json index a0fb6357db3c7..91af3471593eb 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -602,7 +602,7 @@ "UsagePlanName": "Basic" } }, - "myapiUsagePlanUsagePlanKeyResourcetestapigatewayrestapimyapiApiKeyC43601CB600D112D": { + "myapiUsagePlanUsagePlanKeyResource050D133F": { "Type": "AWS::ApiGateway::UsagePlanKey", "Properties": { "KeyId": { diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.usage-plan.multikey.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.usage-plan.multikey.expected.json index 9dee2e7aa07b0..8e761f40e2a26 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.usage-plan.multikey.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.usage-plan.multikey.expected.json @@ -3,7 +3,7 @@ "myusageplan4B391740": { "Type": "AWS::ApiGateway::UsagePlan" }, - "myusageplanUsagePlanKeyResourcetestapigatewayusageplanmultikeymyapikey1DDABC389A2809A73": { + "myusageplanUsagePlanKeyResource095B4EA9": { "Type": "AWS::ApiGateway::UsagePlanKey", "Properties": { "KeyId": { diff --git a/packages/@aws-cdk/aws-apigateway/test/usage-plan.test.ts b/packages/@aws-cdk/aws-apigateway/test/usage-plan.test.ts index 854c0a65a6562..f183d08796388 100644 --- a/packages/@aws-cdk/aws-apigateway/test/usage-plan.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/usage-plan.test.ts @@ -205,32 +205,4 @@ describe('usage plan', () => { }, }, ResourcePart.Properties); }); - - test('UsagePlanKeys have unique logical ids', () => { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'my-stack'); - const usagePlan = new apigateway.UsagePlan(stack, 'my-usage-plan'); - const apiKey1 = new apigateway.ApiKey(stack, 'my-api-key-1', { - apiKeyName: 'my-api-key-1', - }); - const apiKey2 = new apigateway.ApiKey(stack, 'my-api-key-2', { - apiKeyName: 'my-api-key-2', - }); - - // WHEN - usagePlan.addApiKey(apiKey1); - usagePlan.addApiKey(apiKey2); - - // THEN - const template = app.synth().getStackByName(stack.stackName).template; - const logicalIds = Object.entries(template.Resources) - .filter(([_, v]) => (v as any).Type === 'AWS::ApiGateway::UsagePlanKey') - .map(([k, _]) => k); - - expect(logicalIds).toEqual([ - 'myusageplanUsagePlanKeyResourcemystackmyapikey1EE9AA1B359121274', - 'myusageplanUsagePlanKeyResourcemystackmyapikey2B4E8EB1456DC88E9', - ]); - }); }); From fc95ba1b14de4777ecb000c801a7719187ec307a Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 28 Jan 2021 11:20:38 +0000 Subject: [PATCH 25/70] chore(release): 1.87.1 --- CHANGELOG.md | 7 +++++++ version.v1.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 737569579273f..076f51750bfe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [1.87.1](https://github.com/aws/aws-cdk/compare/v1.87.0...v1.87.1) (2021-01-28) + + +### Bug Fixes + +* **apigateway:** stack update fails to replace api key ([38cbe62](https://github.com/aws/aws-cdk/commit/38cbe620859d6efabda95dbdd3185a480ab43894)), closes [#12698](https://github.com/aws/aws-cdk/issues/12698) + ## [1.87.0](https://github.com/aws/aws-cdk/compare/v1.86.0...v1.87.0) (2021-01-27) diff --git a/version.v1.json b/version.v1.json index d6ecf4be3e6fc..816b141bbbf4a 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.87.0" + "version": "1.87.1" } From dfc765af44c755f10be8f6c1c2eae55f62e2aa08 Mon Sep 17 00:00:00 2001 From: Dominic Fezzie Date: Thu, 28 Jan 2021 10:14:01 -0800 Subject: [PATCH 26/70] feat(appmesh): change VirtualService provider to a union-like class (#11978) Fixes #9490 BREAKING CHANGE: the properties virtualRouter and virtualNode of VirtualServiceProps have been replaced with the union-like class VirtualServiceProvider * **appmesh**: the method `addVirtualService` has been removed from `IMesh` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/extensions/appmesh.ts | 3 +- packages/@aws-cdk/aws-appmesh/README.md | 14 +- packages/@aws-cdk/aws-appmesh/lib/mesh.ts | 16 -- .../aws-appmesh/lib/virtual-service.ts | 157 +++++++++++------- .../aws-appmesh/test/integ.mesh.expected.json | 83 ++++----- .../@aws-cdk/aws-appmesh/test/integ.mesh.ts | 24 +-- .../aws-appmesh/test/test.gateway-route.ts | 6 +- .../@aws-cdk/aws-appmesh/test/test.mesh.ts | 119 +------------ .../aws-appmesh/test/test.virtual-gateway.ts | 10 +- .../aws-appmesh/test/test.virtual-node.ts | 6 +- .../aws-appmesh/test/test.virtual-router.ts | 8 +- .../aws-appmesh/test/test.virtual-service.ts | 86 ++++++++++ 12 files changed, 252 insertions(+), 280 deletions(-) diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts index fb93375423798..35436d11ce691 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts @@ -316,8 +316,7 @@ export class AppMeshExtension extends ServiceExtension { // Now create a virtual service. Relationship goes like this: // virtual service -> virtual router -> virtual node this.virtualService = new appmesh.VirtualService(this.scope, `${this.parentService.id}-virtual-service`, { - mesh: this.mesh, - virtualRouter: this.virtualRouter, + virtualServiceProvider: appmesh.VirtualServiceProvider.virtualRouter(this.virtualRouter), virtualServiceName: serviceName, }); } diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md index 2253b12d2987d..c400cbb0af05d 100644 --- a/packages/@aws-cdk/aws-appmesh/README.md +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -109,23 +109,21 @@ When creating a virtual service: Adding a virtual router as the provider: ```ts -mesh.addVirtualService('virtual-service', { - virtualRouter: router, - virtualServiceName: 'my-service.default.svc.cluster.local', +new appmesh.VirtualService('virtual-service', { + virtualServiceName: 'my-service.default.svc.cluster.local', // optional + virtualServiceProvider: appmesh.VirtualServiceProvider.virtualRouter(router), }); ``` Adding a virtual node as the provider: ```ts -mesh.addVirtualService('virtual-service', { - virtualNode: node, - virtualServiceName: `my-service.default.svc.cluster.local`, +new appmesh.VirtualService('virtual-service', { + virtualServiceName: `my-service.default.svc.cluster.local`, // optional + virtualServiceProvider: appmesh.VirtualServiceProvider.virtualNode(node), }); ``` -**Note** that only one must of `virtualNode` or `virtualRouter` must be chosen. - ## Adding a VirtualNode A `virtual node` acts as a logical pointer to a particular task group, such as an Amazon ECS service or a Kubernetes deployment. diff --git a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts index cc0596695aae2..a1cd5b49904a5 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts @@ -4,7 +4,6 @@ import { CfnMesh } from './appmesh.generated'; import { VirtualGateway, VirtualGatewayBaseProps } from './virtual-gateway'; import { VirtualNode, VirtualNodeBaseProps } from './virtual-node'; import { VirtualRouter, VirtualRouterBaseProps } from './virtual-router'; -import { VirtualService, VirtualServiceBaseProps } from './virtual-service'; /** * A utility enum defined for the egressFilter type property, the default of DROP_ALL, @@ -46,11 +45,6 @@ export interface IMesh extends cdk.IResource { */ addVirtualRouter(id: string, props?: VirtualRouterBaseProps): VirtualRouter; - /** - * Adds a VirtualService with the given id - */ - addVirtualService(id: string, props?: VirtualServiceBaseProps): VirtualService; - /** * Adds a VirtualNode to the Mesh */ @@ -86,16 +80,6 @@ abstract class MeshBase extends cdk.Resource implements IMesh { }); } - /** - * Adds a VirtualService with the given id - */ - public addVirtualService(id: string, props: VirtualServiceBaseProps = {}): VirtualService { - return new VirtualService(this, id, { - ...props, - mesh: this, - }); - } - /** * Adds a VirtualNode to the Mesh */ diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts index 9ca5d5010b7f4..5685b8b08c1f8 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts @@ -36,9 +36,9 @@ export interface IVirtualService extends cdk.IResource { } /** - * The base properties which all classes in VirtualService will inherit from + * The properties applied to the VirtualService being defined */ -export interface VirtualServiceBaseProps { +export interface VirtualServiceProps { /** * The name of the VirtualService. * @@ -50,36 +50,17 @@ export interface VirtualServiceBaseProps { */ readonly virtualServiceName?: string; - /** - * The VirtualRouter which the VirtualService uses as provider - * - * @default - At most one of virtualRouter and virtualNode is allowed. - */ - readonly virtualRouter?: IVirtualRouter; - - /** - * The VirtualNode attached to the virtual service - * - * @default - At most one of virtualRouter and virtualNode is allowed. - */ - readonly virtualNode?: IVirtualNode; - /** * Client policy for this Virtual Service * * @default - none */ readonly clientPolicy?: ClientPolicy; -} -/** - * The properties applied to the VirtualService being define - */ -export interface VirtualServiceProps extends VirtualServiceBaseProps { /** - * The Mesh which the VirtualService belongs to + * The VirtualNode or VirtualRouter which the VirtualService uses as its provider */ - readonly mesh: IMesh; + readonly virtualServiceProvider: VirtualServiceProvider; } /** @@ -135,59 +116,35 @@ export class VirtualService extends cdk.Resource implements IVirtualService { public readonly clientPolicy?: ClientPolicy; - private readonly virtualServiceProvider?: CfnVirtualService.VirtualServiceProviderProperty; - constructor(scope: Construct, id: string, props: VirtualServiceProps) { super(scope, id, { physicalName: props.virtualServiceName || cdk.Lazy.string({ produce: () => cdk.Names.uniqueId(this) }), }); - if (props.virtualNode && props.virtualRouter) { - throw new Error('Must provide only one of virtualNode or virtualRouter for the provider'); - } - - this.mesh = props.mesh; this.clientPolicy = props.clientPolicy; - - // Check which provider to use node or router (or neither) - if (props.virtualRouter) { - this.virtualServiceProvider = this.addVirtualRouter(props.virtualRouter.virtualRouterName); - } - if (props.virtualNode) { - this.virtualServiceProvider = this.addVirtualNode(props.virtualNode.virtualNodeName); - } + const providerConfig = props.virtualServiceProvider.bind(this); + this.mesh = providerConfig.mesh; const svc = new CfnVirtualService(this, 'Resource', { meshName: this.mesh.meshName, virtualServiceName: this.physicalName, spec: { - provider: this.virtualServiceProvider, + provider: providerConfig.virtualNodeProvider || providerConfig.virtualRouterProvider + ? { + virtualNode: providerConfig.virtualNodeProvider, + virtualRouter: providerConfig.virtualRouterProvider, + } + : undefined, }, }); this.virtualServiceName = this.getResourceNameAttribute(svc.attrVirtualServiceName); this.virtualServiceArn = this.getResourceArnAttribute(svc.ref, { service: 'appmesh', - resource: `mesh/${props.mesh.meshName}/virtualService`, + resource: `mesh/${this.mesh.meshName}/virtualService`, resourceName: this.physicalName, }); } - - private addVirtualRouter(name: string): CfnVirtualService.VirtualServiceProviderProperty { - return { - virtualRouter: { - virtualRouterName: name, - }, - }; - } - - private addVirtualNode(name: string): CfnVirtualService.VirtualServiceProviderProperty { - return { - virtualNode: { - virtualNodeName: name, - }, - }; - } } /** @@ -211,3 +168,91 @@ export interface VirtualServiceAttributes { */ readonly clientPolicy?: ClientPolicy; } + +/** + * Properties for a VirtualService provider + */ +export interface VirtualServiceProviderConfig { + /** + * Virtual Node based provider + * + * @default - none + */ + readonly virtualNodeProvider?: CfnVirtualService.VirtualNodeServiceProviderProperty; + + /** + * Virtual Router based provider + * + * @default - none + */ + readonly virtualRouterProvider?: CfnVirtualService.VirtualRouterServiceProviderProperty; + + /** + * Mesh the Provider is using + * + * @default - none + */ + readonly mesh: IMesh; +} + +/** + * Represents the properties needed to define the provider for a VirtualService + */ +export abstract class VirtualServiceProvider { + /** + * Returns a VirtualNode based Provider for a VirtualService + */ + public static virtualNode(virtualNode: IVirtualNode): VirtualServiceProvider { + return new VirtualServiceProviderImpl(virtualNode, undefined); + } + + /** + * Returns a VirtualRouter based Provider for a VirtualService + */ + public static virtualRouter(virtualRouter: IVirtualRouter): VirtualServiceProvider { + return new VirtualServiceProviderImpl(undefined, virtualRouter); + } + + /** + * Returns an Empty Provider for a VirtualService. This provides no routing capabilities + * and should only be used as a placeholder + */ + public static none(mesh: IMesh): VirtualServiceProvider { + return new VirtualServiceProviderImpl(undefined, undefined, mesh); + } + + /** + * Enforces mutual exclusivity for VirtualService provider types. + */ + public abstract bind(_construct: Construct): VirtualServiceProviderConfig; +} + +class VirtualServiceProviderImpl extends VirtualServiceProvider { + private readonly virtualNode?: IVirtualNode; + private readonly virtualRouter?: IVirtualRouter; + private readonly mesh: IMesh; + + constructor(virtualNode?: IVirtualNode, virtualRouter?: IVirtualRouter, mesh?: IMesh) { + super(); + this.virtualNode = virtualNode; + this.virtualRouter = virtualRouter; + const providedMesh = this.virtualNode?.mesh ?? this.virtualRouter?.mesh ?? mesh!; + this.mesh = providedMesh; + } + + public bind(_construct: Construct): VirtualServiceProviderConfig { + return { + mesh: this.mesh, + virtualNodeProvider: this.virtualNode + ? { + virtualNodeName: this.virtualNode.virtualNodeName, + } + : undefined, + virtualRouterProvider: this.virtualRouter + ? { + virtualRouterName: this.virtualRouter.virtualRouterName, + } + : undefined, + }; + } +} diff --git a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json index db9277d071432..5f4a9ca206725 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json @@ -625,41 +625,6 @@ } } }, - "meshserviceE06ECED5": { - "Type": "AWS::AppMesh::VirtualService", - "Properties": { - "MeshName": { - "Fn::GetAtt": [ - "meshACDFE68E", - "MeshName" - ] - }, - "Spec": { - "Provider": { - "VirtualRouter": { - "VirtualRouterName": { - "Fn::GetAtt": [ - "meshrouter81B8087E", - "VirtualRouterName" - ] - } - } - } - }, - "VirtualServiceName": "service1.domain.local" - } - }, - "cert56CA94EB": { - "Type": "AWS::CertificateManager::Certificate", - "Properties": { - "DomainName":"node1.domain.local", - "DomainValidationOptions": [{ - "DomainName":"node1.domain.local", - "ValidationDomain":"local" - }], - "ValidationMethod": "EMAIL" - } - }, "meshnode726C787D": { "Type": "AWS::AppMesh::VirtualNode", "Properties": { @@ -675,7 +640,7 @@ "VirtualService": { "VirtualServiceName": { "Fn::GetAtt": [ - "meshserviceE06ECED5", + "service6D174F83", "VirtualServiceName" ] } @@ -706,16 +671,6 @@ "PortMapping": { "Port": 8080, "Protocol": "http" - }, - "TLS": { - "Certificate": { - "ACM": { - "CertificateArn": { - "Ref": "cert56CA94EB" - } - } - }, - "Mode": "STRICT" } } ], @@ -743,8 +698,8 @@ "TLS": { "Validation": { "Trust": { - "ACM": { - "CertificateAuthorityArns": ["arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012"] + "File": { + "CertificateChain": "path/to/cert" } } } @@ -891,7 +846,7 @@ "VirtualService": { "VirtualServiceName": { "Fn::GetAtt": [ - "meshserviceE06ECED5", + "service6D174F83", "VirtualServiceName" ] } @@ -928,7 +883,7 @@ "VirtualService": { "VirtualServiceName": { "Fn::GetAtt": [ - "meshserviceE06ECED5", + "service6D174F83", "VirtualServiceName" ] } @@ -965,7 +920,7 @@ "VirtualService": { "VirtualServiceName": { "Fn::GetAtt": [ - "meshserviceE06ECED5", + "service6D174F83", "VirtualServiceName" ] } @@ -975,7 +930,7 @@ "Match": { "ServiceName": { "Fn::GetAtt": [ - "meshserviceE06ECED5", + "service6D174F83", "VirtualServiceName" ] } @@ -990,6 +945,30 @@ } } }, + "service6D174F83": { + "Type": "AWS::AppMesh::VirtualService", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "meshACDFE68E", + "MeshName" + ] + }, + "Spec": { + "Provider": { + "VirtualRouter": { + "VirtualRouterName": { + "Fn::GetAtt": [ + "meshrouter81B8087E", + "VirtualRouterName" + ] + } + } + } + }, + "VirtualServiceName": "service1.domain.local" + } + }, "service27C65CF7D": { "Type": "AWS::AppMesh::VirtualService", "Properties": { diff --git a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts index 730418aef7970..90e54586f7f51 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts @@ -1,5 +1,3 @@ -import * as acmpca from '@aws-cdk/aws-acmpca'; -import * as acm from '@aws-cdk/aws-certificatemanager'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import * as cdk from '@aws-cdk/core'; @@ -25,15 +23,11 @@ const router = mesh.addVirtualRouter('router', { ], }); -const virtualService = mesh.addVirtualService('service', { - virtualRouter: router, +const virtualService = new appmesh.VirtualService(stack, 'service', { + virtualServiceProvider: appmesh.VirtualServiceProvider.virtualRouter(router), virtualServiceName: 'service1.domain.local', }); -const cert = new acm.Certificate(stack, 'cert', { - domainName: `node1.${namespace.namespaceName}`, -}); - const node = mesh.addVirtualNode('node', { serviceDiscovery: appmesh.ServiceDiscovery.dns(`node1.${namespace.namespaceName}`), listeners: [appmesh.VirtualNodeListener.http({ @@ -41,10 +35,6 @@ const node = mesh.addVirtualNode('node', { healthyThreshold: 3, path: '/check-path', }, - tlsCertificate: appmesh.TlsCertificate.acm({ - certificate: cert, - tlsMode: appmesh.TlsMode.STRICT, - }), })], backends: [ virtualService, @@ -53,7 +43,7 @@ const node = mesh.addVirtualNode('node', { node.addBackend(new appmesh.VirtualService(stack, 'service-2', { virtualServiceName: 'service2.domain.local', - mesh, + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), }), ); @@ -75,8 +65,6 @@ router.addRoute('route-1', { }), }); -const certificateAuthorityArn = 'arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012'; - const node2 = mesh.addVirtualNode('node2', { serviceDiscovery: appmesh.ServiceDiscovery.dns(`node2.${namespace.namespaceName}`), listeners: [appmesh.VirtualNodeListener.http({ @@ -90,13 +78,13 @@ const node2 = mesh.addVirtualNode('node2', { unhealthyThreshold: 2, }, })], - backendsDefaultClientPolicy: appmesh.ClientPolicy.acmTrust({ - certificateAuthorities: [acmpca.CertificateAuthority.fromCertificateAuthorityArn(stack, 'certificate', certificateAuthorityArn)], + backendsDefaultClientPolicy: appmesh.ClientPolicy.fileTrust({ + certificateChain: 'path/to/cert', }), backends: [ new appmesh.VirtualService(stack, 'service-3', { virtualServiceName: 'service3.domain.local', - mesh, + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), }), ], }); diff --git a/packages/@aws-cdk/aws-appmesh/test/test.gateway-route.ts b/packages/@aws-cdk/aws-appmesh/test/test.gateway-route.ts index a741ec0b0d1d8..fed290d36a3e2 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.gateway-route.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.gateway-route.ts @@ -21,7 +21,7 @@ export = { }); const virtualService = new appmesh.VirtualService(stack, 'vs-1', { - mesh: mesh, + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), virtualServiceName: 'target.local', }); @@ -121,7 +121,9 @@ export = { meshName: 'test-mesh', }); - const virtualService = mesh.addVirtualService('testVirtualService'); + const virtualService = new appmesh.VirtualService(stack, 'testVirtualService', { + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), + }); test.throws(() => appmesh.GatewayRouteSpec.http({ routeTarget: virtualService, match: { diff --git a/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts b/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts index c6d2fbfbbb294..ce50c1402a7c3 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.mesh.ts @@ -125,120 +125,6 @@ export = { test.done(); }, - 'When adding a VirtualService to a mesh': { - 'with VirtualRouter and VirtualNode as providers': { - 'should throw error'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const mesh = new appmesh.Mesh(stack, 'mesh', { - meshName: 'test-mesh', - }); - - const testNode = new appmesh.VirtualNode(stack, 'test-node', { - mesh, - serviceDiscovery: appmesh.ServiceDiscovery.dns('test-node'), - }); - - const testRouter = mesh.addVirtualRouter('router', { - listeners: [ - appmesh.VirtualRouterListener.http(), - ], - }); - - // THEN - test.throws(() => { - mesh.addVirtualService('service', { - virtualServiceName: 'test-service.domain.local', - virtualNode: testNode, - virtualRouter: testRouter, - }); - }); - - test.done(); - }, - }, - 'with single virtual router provider resource': { - 'should create service'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const mesh = new appmesh.Mesh(stack, 'mesh', { - meshName: 'test-mesh', - }); - - const testRouter = mesh.addVirtualRouter('test-router', { - listeners: [ - appmesh.VirtualRouterListener.http(), - ], - }); - - mesh.addVirtualService('service', { - virtualServiceName: 'test-service.domain.local', - virtualRouter: testRouter, - }); - - // THEN - expect(stack).to( - haveResource('AWS::AppMesh::VirtualService', { - Spec: { - Provider: { - VirtualRouter: { - VirtualRouterName: { - 'Fn::GetAtt': ['meshtestrouterF78D72DD', 'VirtualRouterName'], - }, - }, - }, - }, - }), - ); - - test.done(); - }, - }, - 'with single virtual node provider resource': { - 'should create service'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const mesh = new appmesh.Mesh(stack, 'mesh', { - meshName: 'test-mesh', - }); - - const node = mesh.addVirtualNode('test-node', { - serviceDiscovery: appmesh.ServiceDiscovery.dns('test.domain.local'), - listeners: [appmesh.VirtualNodeListener.http({ - port: 8080, - })], - }); - - mesh.addVirtualService('service2', { - virtualServiceName: 'test-service.domain.local', - virtualNode: node, - }); - - // THEN - expect(stack).to( - haveResource('AWS::AppMesh::VirtualService', { - Spec: { - Provider: { - VirtualNode: { - VirtualNodeName: { - 'Fn::GetAtt': ['meshtestnodeF93946D4', 'VirtualNodeName'], - }, - }, - }, - }, - }), - ); - - test.done(); - }, - }, - }, 'When adding a VirtualNode to a mesh': { 'with empty default listeners and backends': { 'should create default resource'(test: Test) { @@ -376,7 +262,7 @@ export = { const service1 = new appmesh.VirtualService(stack, 'service-1', { virtualServiceName: 'service1.domain.local', - mesh, + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), }); mesh.addVirtualNode('test-node', { @@ -415,8 +301,9 @@ export = { const stack2 = new cdk.Stack(); const mesh2 = appmesh.Mesh.fromMeshName(stack2, 'imported-mesh', 'abc'); - mesh2.addVirtualService('service', { + new appmesh.VirtualService(stack2, 'service', { virtualServiceName: 'test.domain.local', + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh2), }); // THEN diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts index 7b4a563c90d34..b9d3ed70cae43 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts @@ -306,7 +306,9 @@ export = { mesh: mesh, }); - const virtualService = mesh.addVirtualService('virtualService', {}); + const virtualService = new appmesh.VirtualService(stack, 'virtualService', { + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), + }); virtualGateway.addGatewayRoute('testGatewayRoute', { gatewayRouteName: 'test-gateway-route', @@ -324,7 +326,7 @@ export = { Target: { VirtualService: { VirtualServiceName: { - 'Fn::GetAtt': ['meshvirtualService93460D43', 'VirtualServiceName'], + 'Fn::GetAtt': ['virtualService03A04B87', 'VirtualServiceName'], }, }, }, @@ -349,7 +351,9 @@ export = { meshName: 'test-mesh', }); - const virtualService = mesh.addVirtualService('virtualService', {}); + const virtualService = new appmesh.VirtualService(stack, 'virtualService', { + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), + }); const virtualGateway = mesh.addVirtualGateway('gateway'); virtualGateway.addGatewayRoute('testGatewayRoute', { diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts index 9fb05931a2a44..4337973230854 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-node.ts @@ -19,11 +19,11 @@ export = { const service1 = new appmesh.VirtualService(stack, 'service-1', { virtualServiceName: 'service1.domain.local', - mesh, + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), }); const service2 = new appmesh.VirtualService(stack, 'service-2', { virtualServiceName: 'service2.domain.local', - mesh, + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), }); const node = new appmesh.VirtualNode(stack, 'test-node', { @@ -319,7 +319,7 @@ export = { const service1 = new appmesh.VirtualService(stack, 'service-1', { virtualServiceName: 'service1.domain.local', - mesh, + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), clientPolicy: appmesh.ClientPolicy.fileTrust({ certificateChain: 'path-to-certificate', ports: [8080, 8081], diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts index aafe3dff8ce7f..2732adb4cba17 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-router.ts @@ -101,7 +101,7 @@ export = { const service1 = new appmesh.VirtualService(stack, 'service-1', { virtualServiceName: 'service1.domain.local', - mesh, + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), }); const node = mesh.addVirtualNode('test-node', { @@ -170,11 +170,11 @@ export = { const service1 = new appmesh.VirtualService(stack, 'service-1', { virtualServiceName: 'service1.domain.local', - mesh, + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), }); const service2 = new appmesh.VirtualService(stack, 'service-2', { virtualServiceName: 'service2.domain.local', - mesh, + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), }); const node = mesh.addVirtualNode('test-node', { @@ -332,7 +332,7 @@ export = { const service1 = new appmesh.VirtualService(stack, 'service-1', { virtualServiceName: 'service1.domain.local', - mesh, + virtualServiceProvider: appmesh.VirtualServiceProvider.none(mesh), }); const node = mesh.addVirtualNode('test-node', { diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-service.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-service.ts index c09c156ac75ea..c60c98f8e7b94 100644 --- a/packages/@aws-cdk/aws-appmesh/test/test.virtual-service.ts +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-service.ts @@ -1,3 +1,4 @@ +import { expect, haveResource } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; @@ -19,6 +20,7 @@ export = { test.done(); }, + 'Can import Virtual Services using attributes'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -34,6 +36,90 @@ export = { // THEN test.equal(virtualService.mesh.meshName, meshName); test.equal(virtualService.virtualServiceName, virtualServiceName); + test.done(); }, + + 'When adding a VirtualService to a mesh': { + 'with single virtual router provider resource': { + 'should create service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const testRouter = mesh.addVirtualRouter('test-router', { + listeners: [ + appmesh.VirtualRouterListener.http(), + ], + }); + + new appmesh.VirtualService(stack, 'service', { + virtualServiceName: 'test-service.domain.local', + virtualServiceProvider: appmesh.VirtualServiceProvider.virtualRouter(testRouter), + }); + + // THEN + expect(stack).to( + haveResource('AWS::AppMesh::VirtualService', { + Spec: { + Provider: { + VirtualRouter: { + VirtualRouterName: { + 'Fn::GetAtt': ['meshtestrouterF78D72DD', 'VirtualRouterName'], + }, + }, + }, + }, + }), + ); + + test.done(); + }, + }, + + 'with single virtual node provider resource': { + 'should create service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const node = mesh.addVirtualNode('test-node', { + serviceDiscovery: appmesh.ServiceDiscovery.dns('test.domain.local'), + listeners: [appmesh.VirtualNodeListener.http({ + port: 8080, + })], + }); + + new appmesh.VirtualService(stack, 'service2', { + virtualServiceName: 'test-service.domain.local', + virtualServiceProvider: appmesh.VirtualServiceProvider.virtualNode(node), + }); + + // THEN + expect(stack).to( + haveResource('AWS::AppMesh::VirtualService', { + Spec: { + Provider: { + VirtualNode: { + VirtualNodeName: { + 'Fn::GetAtt': ['meshtestnodeF93946D4', 'VirtualNodeName'], + }, + }, + }, + }, + }), + ); + + test.done(); + }, + }, + }, }; From ffe7e425e605144a465cea9befa68d4fe19f9d8c Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 28 Jan 2021 18:50:19 +0000 Subject: [PATCH 27/70] fix(apigateway): stack update fails to replace api key (#12745) This reverts commit 96cbe32d2399d82a2ad6c3bf6dc1fd65396882d4. The above commit changed the logical id layout of API keys. It turns out that ApiKey resource types cannot be replaced without explicitly specifying, and changing, the API key name. See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-name fixes #12698 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 4 ++- .../test/integ.restapi.expected.json | 2 +- .../integ.usage-plan.multikey.expected.json | 2 +- .../aws-apigateway/test/usage-plan.test.ts | 28 ------------------- 4 files changed, 5 insertions(+), 31 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index 6a1c5a5091bda..ad807d4a7d2d0 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -179,8 +179,10 @@ export class UsagePlan extends Resource { * @param apiKey */ public addApiKey(apiKey: IApiKey): void { + const prefix = 'UsagePlanKeyResource'; + // Postfixing apikey id only from the 2nd child, to keep physicalIds of UsagePlanKey for existing CDK apps unmodifed. - const id = `UsagePlanKeyResource:${Names.nodeUniqueId(apiKey.node)}`; + const id = this.node.tryFindChild(prefix) ? `${prefix}:${Names.nodeUniqueId(apiKey.node)}` : prefix; new CfnUsagePlanKey(this, id, { keyId: apiKey.keyId, diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json index a0fb6357db3c7..91af3471593eb 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -602,7 +602,7 @@ "UsagePlanName": "Basic" } }, - "myapiUsagePlanUsagePlanKeyResourcetestapigatewayrestapimyapiApiKeyC43601CB600D112D": { + "myapiUsagePlanUsagePlanKeyResource050D133F": { "Type": "AWS::ApiGateway::UsagePlanKey", "Properties": { "KeyId": { diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.usage-plan.multikey.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.usage-plan.multikey.expected.json index 9dee2e7aa07b0..8e761f40e2a26 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.usage-plan.multikey.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.usage-plan.multikey.expected.json @@ -3,7 +3,7 @@ "myusageplan4B391740": { "Type": "AWS::ApiGateway::UsagePlan" }, - "myusageplanUsagePlanKeyResourcetestapigatewayusageplanmultikeymyapikey1DDABC389A2809A73": { + "myusageplanUsagePlanKeyResource095B4EA9": { "Type": "AWS::ApiGateway::UsagePlanKey", "Properties": { "KeyId": { diff --git a/packages/@aws-cdk/aws-apigateway/test/usage-plan.test.ts b/packages/@aws-cdk/aws-apigateway/test/usage-plan.test.ts index 854c0a65a6562..f183d08796388 100644 --- a/packages/@aws-cdk/aws-apigateway/test/usage-plan.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/usage-plan.test.ts @@ -205,32 +205,4 @@ describe('usage plan', () => { }, }, ResourcePart.Properties); }); - - test('UsagePlanKeys have unique logical ids', () => { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'my-stack'); - const usagePlan = new apigateway.UsagePlan(stack, 'my-usage-plan'); - const apiKey1 = new apigateway.ApiKey(stack, 'my-api-key-1', { - apiKeyName: 'my-api-key-1', - }); - const apiKey2 = new apigateway.ApiKey(stack, 'my-api-key-2', { - apiKeyName: 'my-api-key-2', - }); - - // WHEN - usagePlan.addApiKey(apiKey1); - usagePlan.addApiKey(apiKey2); - - // THEN - const template = app.synth().getStackByName(stack.stackName).template; - const logicalIds = Object.entries(template.Resources) - .filter(([_, v]) => (v as any).Type === 'AWS::ApiGateway::UsagePlanKey') - .map(([k, _]) => k); - - expect(logicalIds).toEqual([ - 'myusageplanUsagePlanKeyResourcemystackmyapikey1EE9AA1B359121274', - 'myusageplanUsagePlanKeyResourcemystackmyapikey2B4E8EB1456DC88E9', - ]); - }); }); From 238742e4323310ce850d8edc70abe4b0e9f53186 Mon Sep 17 00:00:00 2001 From: Matthew Bonig Date: Thu, 28 Jan 2021 12:26:38 -0700 Subject: [PATCH 28/70] fix(codedeploy): wrong syntax on Windows 'installAgent' flag (#12736) Resolves #12734 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/server/deployment-group.ts | 3 +- .../test/server/test.deployment-group.ts | 72 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts index eb4bbafade216..0864d603d2e28 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts @@ -372,7 +372,8 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { asg.addUserData( 'Set-Variable -Name TEMPDIR -Value (New-TemporaryFile).DirectoryName', `aws s3 cp s3://aws-codedeploy-${cdk.Stack.of(this).region}/latest/codedeploy-agent.msi $TEMPDIR\\codedeploy-agent.msi`, - '$TEMPDIR\\codedeploy-agent.msi /quiet /l c:\\temp\\host-agent-install-log.txt', + 'cd $TEMPDIR', + '.\\codedeploy-agent.msi /quiet /l c:\\temp\\host-agent-install-log.txt', ); break; } diff --git a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts index fd486f20f1eb2..761d0e38eb6a9 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts @@ -42,6 +42,78 @@ export = { test.done(); }, + 'uses good linux install agent script'(test: Test) { + const stack = new cdk.Stack(); + + const asg = new autoscaling.AutoScalingGroup(stack, 'ASG', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.STANDARD3, ec2.InstanceSize.SMALL), + machineImage: new ec2.AmazonLinuxImage(), + vpc: new ec2.Vpc(stack, 'VPC'), + }); + + new codedeploy.ServerDeploymentGroup(stack, 'DeploymentGroup', { + autoScalingGroups: [asg], + installAgent: true, + }); + + expect(stack).to(haveResource('AWS::AutoScaling::LaunchConfiguration', { + 'UserData': { + 'Fn::Base64': { + 'Fn::Join': [ + '', + [ + '#!/bin/bash\nPKG_CMD=`which yum 2>/dev/null`\nif [ -z "$PKG_CMD" ]; then\nPKG_CMD=apt-get\nelse\nPKG=CMD=yum\nfi\n$PKG_CMD update -y\n$PKG_CMD install -y ruby2.0\nif [ $? -ne 0 ]; then\n$PKG_CMD install -y ruby\nfi\n$PKG_CMD install -y awscli\nTMP_DIR=`mktemp -d`\ncd $TMP_DIR\naws s3 cp s3://aws-codedeploy-', + { + 'Ref': 'AWS::Region', + }, + '/latest/install . --region ', + { + 'Ref': 'AWS::Region', + }, + '\nchmod +x ./install\n./install auto\nrm -fr $TMP_DIR', + ], + ], + }, + }, + })); + + test.done(); + }, + + 'uses good windows install agent script'(test: Test) { + const stack = new cdk.Stack(); + + const asg = new autoscaling.AutoScalingGroup(stack, 'ASG', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.STANDARD3, ec2.InstanceSize.SMALL), + machineImage: new ec2.WindowsImage(ec2.WindowsVersion.WINDOWS_SERVER_2019_ENGLISH_FULL_BASE, {}), + vpc: new ec2.Vpc(stack, 'VPC'), + }); + + new codedeploy.ServerDeploymentGroup(stack, 'DeploymentGroup', { + autoScalingGroups: [asg], + installAgent: true, + }); + + expect(stack).to(haveResource('AWS::AutoScaling::LaunchConfiguration', { + 'UserData': { + 'Fn::Base64': { + 'Fn::Join': [ + '', + [ + 'Set-Variable -Name TEMPDIR -Value (New-TemporaryFile).DirectoryName\naws s3 cp s3://aws-codedeploy-', + { + 'Ref': 'AWS::Region', + }, + '/latest/codedeploy-agent.msi $TEMPDIR\\codedeploy-agent.msi\ncd $TEMPDIR\n.\\codedeploy-agent.msi /quiet /l c:\\temp\\host-agent-install-log.txt', + ], + ], + }, + }, + })); + + test.done(); + }, + 'created with ASGs contains the ASG names'(test: Test) { const stack = new cdk.Stack(); From 99fd074a07ead624f64d3fe64685ba67c798976e Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Fri, 29 Jan 2021 01:09:57 -0800 Subject: [PATCH 29/70] fix(codepipeline): permission denied for Action-level environment variables (#12761) We correctly added permissions for SSM and SecretsManager-type environment variables set on the CodeBuild Project itself, but we forgot that environment variables could also be set on the CodeBuild CodePipeline action. Fixes #12742 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-codebuild/lib/project.ts | 98 +++++++++---------- .../lib/codebuild/build-action.ts | 2 +- ...g.pipeline-code-commit-build.expected.json | 26 ++++- 3 files changed, 70 insertions(+), 56 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index c98718bd744bf..2f31bc897f9f8 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -695,9 +695,11 @@ export class Project extends ProjectBase { * @returns an array of {@link CfnProject.EnvironmentVariableProperty} instances */ public static serializeEnvVariables(environmentVariables: { [name: string]: BuildEnvironmentVariable }, - validateNoPlainTextSecrets: boolean = false): CfnProject.EnvironmentVariableProperty[] { + validateNoPlainTextSecrets: boolean = false, principal?: iam.IGrantable): CfnProject.EnvironmentVariableProperty[] { const ret = new Array(); + const ssmVariables = new Array(); + const secretsManagerSecrets = new Array(); for (const [name, envVariable] of Object.entries(environmentVariables)) { const cfnEnvVariable: CfnProject.EnvironmentVariableProperty = { @@ -720,6 +722,46 @@ export class Project extends ProjectBase { } } } + + if (principal) { + // save the SSM env variables + if (envVariable.type === BuildEnvironmentVariableType.PARAMETER_STORE) { + const envVariableValue = envVariable.value.toString(); + ssmVariables.push(Stack.of(principal).formatArn({ + service: 'ssm', + resource: 'parameter', + // If the parameter name starts with / the resource name is not separated with a double '/' + // arn:aws:ssm:region:1111111111:parameter/PARAM_NAME + resourceName: envVariableValue.startsWith('/') + ? envVariableValue.substr(1) + : envVariableValue, + })); + } + + // save SecretsManager env variables + if (envVariable.type === BuildEnvironmentVariableType.SECRETS_MANAGER) { + secretsManagerSecrets.push(Stack.of(principal).formatArn({ + service: 'secretsmanager', + resource: 'secret', + // we don't know the exact ARN of the Secret just from its name, but we can get close + resourceName: `${envVariable.value}-??????`, + sep: ':', + })); + } + } + } + + if (ssmVariables.length !== 0) { + principal?.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['ssm:GetParameters'], + resources: ssmVariables, + })); + } + if (secretsManagerSecrets.length !== 0) { + principal?.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['secretsmanager:GetSecretValue'], + resources: secretsManagerSecrets, + })); } return ret; @@ -854,7 +896,6 @@ export class Project extends ProjectBase { this.projectName = this.getResourceNameAttribute(resource.ref); this.addToRolePolicy(this.createLoggingPermission()); - this.addEnvVariablesPermissions(props.environmentVariables); // add permissions to create and use test report groups // with names starting with the project's name, // unless the customer explicitly opts out of it @@ -1007,57 +1048,6 @@ export class Project extends ProjectBase { }); } - private addEnvVariablesPermissions(environmentVariables: { [name: string]: BuildEnvironmentVariable } | undefined): void { - this.addParameterStorePermissions(environmentVariables); - this.addSecretsManagerPermissions(environmentVariables); - } - - private addParameterStorePermissions(environmentVariables: { [name: string]: BuildEnvironmentVariable } | undefined): void { - const resources = Object.values(environmentVariables || {}) - .filter(envVariable => envVariable.type === BuildEnvironmentVariableType.PARAMETER_STORE) - .map(envVariable => - // If the parameter name starts with / the resource name is not separated with a double '/' - // arn:aws:ssm:region:1111111111:parameter/PARAM_NAME - (envVariable.value as string).startsWith('/') - ? (envVariable.value as string).substr(1) - : envVariable.value) - .map(envVariable => Stack.of(this).formatArn({ - service: 'ssm', - resource: 'parameter', - resourceName: envVariable, - })); - - if (resources.length === 0) { - return; - } - - this.addToRolePolicy(new iam.PolicyStatement({ - actions: ['ssm:GetParameters'], - resources, - })); - } - - private addSecretsManagerPermissions(environmentVariables: { [name: string]: BuildEnvironmentVariable } | undefined): void { - const resources = Object.values(environmentVariables || {}) - .filter(envVariable => envVariable.type === BuildEnvironmentVariableType.SECRETS_MANAGER) - .map(envVariable => Stack.of(this).formatArn({ - service: 'secretsmanager', - resource: 'secret', - // we don't know the exact ARN of the Secret just from its name, but we can get close - resourceName: `${envVariable.value}-??????`, - sep: ':', - })); - - if (resources.length === 0) { - return; - } - - this.addToRolePolicy(new iam.PolicyStatement({ - actions: ['secretsmanager:GetSecretValue'], - resources, - })); - } - private renderEnvironment( props: ProjectProps, projectVars: { [name: string]: BuildEnvironmentVariable } = {}): CfnProject.EnvironmentProperty { @@ -1118,7 +1108,7 @@ export class Project extends ProjectBase { privilegedMode: env.privileged || false, computeType: env.computeType || this.buildImage.defaultComputeType, environmentVariables: hasEnvironmentVars - ? Project.serializeEnvVariables(vars, props.checkSecretsInPlainTextEnvVariables ?? true) + ? Project.serializeEnvVariables(vars, props.checkSecretsInPlainTextEnvVariables ?? true, this) : undefined, }; } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts index 238efebae4658..1fc1611bd9ff2 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts @@ -207,7 +207,7 @@ export class CodeBuildAction extends Action { ProjectName: this.props.project.projectName, EnvironmentVariables: this.props.environmentVariables && cdk.Stack.of(scope).toJsonString(codebuild.Project.serializeEnvVariables(this.props.environmentVariables, - this.props.checkSecretsInPlainTextEnvVariables ?? true)), + this.props.checkSecretsInPlainTextEnvVariables ?? true, this.props.project)), }; if ((this.actionProperties.inputs || []).length > 1) { // lazy, because the Artifact name might be generated lazily diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.expected.json index 7fe649e0c2f8c..dbf8c85f91394 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.expected.json @@ -149,6 +149,30 @@ ] } }, + { + "Action": "ssm:GetParameters", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ssm:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":parameter/param_store" + ] + ] + } + }, { "Action": [ "s3:GetObject*", @@ -898,4 +922,4 @@ } } } -} \ No newline at end of file +} From 8990e8fbdd7fa6fc6d31fa7090cb916d1b7917c0 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 29 Jan 2021 10:07:57 +0000 Subject: [PATCH 30/70] chore: fix changelog heading so Github publisher will recognize it (#12753) The Github publisher in the CDK team's CI pipeline is failing to publish 1.87.1 since the wrong header level was used. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 076f51750bfe8..b5d66ae0944d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. -### [1.87.1](https://github.com/aws/aws-cdk/compare/v1.87.0...v1.87.1) (2021-01-28) +## [1.87.1](https://github.com/aws/aws-cdk/compare/v1.87.0...v1.87.1) (2021-01-28) ### Bug Fixes From 7dc45b2d8fc2f63497adb624fdfe4207c4eca269 Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Fri, 29 Jan 2021 03:27:32 -0700 Subject: [PATCH 31/70] chore(cloudfront): use standard file naming convention for OAI and Web Distribution (#12752) cc @njlynch ---- *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-cloudfront/lib/index.ts | 4 ++-- .../{origin_access_identity.ts => origin-access-identity.ts} | 0 .../lib/{web_distribution.ts => web-distribution.ts} | 2 +- .../{web_distribution.test.ts => web-distribution.test.ts} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename packages/@aws-cdk/aws-cloudfront/lib/{origin_access_identity.ts => origin-access-identity.ts} (100%) rename packages/@aws-cdk/aws-cloudfront/lib/{web_distribution.ts => web-distribution.ts} (99%) rename packages/@aws-cdk/aws-cloudfront/test/{web_distribution.test.ts => web-distribution.test.ts} (100%) diff --git a/packages/@aws-cdk/aws-cloudfront/lib/index.ts b/packages/@aws-cdk/aws-cloudfront/lib/index.ts index b0bd550231be3..726a1d1d01948 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/index.ts @@ -2,9 +2,9 @@ export * from './cache-policy'; export * from './distribution'; export * from './geo-restriction'; export * from './origin'; -export * from './origin_access_identity'; +export * from './origin-access-identity'; export * from './origin-request-policy'; -export * from './web_distribution'; +export * from './web-distribution'; export * as experimental from './experimental'; diff --git a/packages/@aws-cdk/aws-cloudfront/lib/origin_access_identity.ts b/packages/@aws-cdk/aws-cloudfront/lib/origin-access-identity.ts similarity index 100% rename from packages/@aws-cdk/aws-cloudfront/lib/origin_access_identity.ts rename to packages/@aws-cdk/aws-cloudfront/lib/origin-access-identity.ts diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts similarity index 99% rename from packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts rename to packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts index dad54c3b1488a..c62e1e09ed3f4 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts @@ -7,7 +7,7 @@ import { Construct } from 'constructs'; import { CfnDistribution } from './cloudfront.generated'; import { HttpVersion, IDistribution, LambdaEdgeEventType, OriginProtocolPolicy, PriceClass, ViewerProtocolPolicy, SSLMethod, SecurityPolicyProtocol } from './distribution'; import { GeoRestriction } from './geo-restriction'; -import { IOriginAccessIdentity } from './origin_access_identity'; +import { IOriginAccessIdentity } from './origin-access-identity'; /** * HTTP status code to failover to second origin diff --git a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts similarity index 100% rename from packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts rename to packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts From 3cbf38b09a9e66a6c009f833481fb25b8c5fc26c Mon Sep 17 00:00:00 2001 From: Richie Hughes Date: Sat, 30 Jan 2021 00:54:23 +0000 Subject: [PATCH 32/70] feat(ecs-patterns): Add PlatformVersion option to ScheduledFargateTask props (#12676) Add the platformversion as an extra option to the Fargate Scheduled Task closes #12623 ---- *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-patterns/README.md | 14 +++++ .../lib/base/scheduled-task-base.ts | 11 +++- .../lib/fargate/scheduled-fargate-task.ts | 25 ++++++++- .../fargate/test.scheduled-fargate-task.ts | 54 +++++++++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index 7a80d93aad346..38915031f4346 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -427,3 +427,17 @@ const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(sta }, }); ``` + +### Set PlatformVersion for ScheduledFargateTask + +```ts +const scheduledFargateTask = new ScheduledFargateTask(stack, 'ScheduledFargateTask', { + cluster, + scheduledFargateTaskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }, + schedule: events.Schedule.expression('rate(1 minute)'), + platformVersion: ecs.FargatePlatformVersion.VERSION1_4, +}); +``` 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 259e375b1973c..38ac4e30a79af 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 @@ -165,11 +165,20 @@ export abstract class ScheduledTaskBase extends CoreConstruct { subnetSelection: this.subnetSelection, }); - this.eventRule.addTarget(eventRuleTarget); + this.addTaskAsTarget(eventRuleTarget); return eventRuleTarget; } + /** + * Adds task as a target of the scheduled event rule. + * + * @param ecsTaskTarget the EcsTask to add to the event rule + */ + protected addTaskAsTarget(ecsTaskTarget: EcsTask) { + this.eventRule.addTarget(ecsTaskTarget); + } + /** * Returns the default cluster. */ diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts index 8ad898693b6bd..27b9d8b6ad224 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts @@ -1,4 +1,5 @@ -import { FargateTaskDefinition } from '@aws-cdk/aws-ecs'; +import { FargateTaskDefinition, FargatePlatformVersion } from '@aws-cdk/aws-ecs'; +import { EcsTask } from '@aws-cdk/aws-events-targets'; import { Construct } from 'constructs'; import { ScheduledTaskBase, ScheduledTaskBaseProps, ScheduledTaskImageProps } from '../base/scheduled-task-base'; @@ -21,6 +22,17 @@ export interface ScheduledFargateTaskProps extends ScheduledTaskBaseProps { * @default none */ readonly scheduledFargateTaskImageOptions?: ScheduledFargateTaskImageOptions; + + /** + * The platform version on which to run your service. + * + * If one is not specified, the LATEST platform version is used by default. For more information, see + * [AWS Fargate Platform Versions](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/platform_versions.html) + * in the Amazon Elastic Container Service Developer Guide. + * + * @default Latest + */ + readonly platformVersion?: FargatePlatformVersion; } /** @@ -109,6 +121,15 @@ export class ScheduledFargateTask extends ScheduledTaskBase { throw new Error('You must specify one of: taskDefinition or image'); } - this.addTaskDefinitionToEventTarget(this.taskDefinition); + // Use the EcsTask as the target of the EventRule + const eventRuleTarget = new EcsTask( { + cluster: this.cluster, + taskDefinition: this.taskDefinition, + taskCount: this.desiredTaskCount, + subnetSelection: this.subnetSelection, + platformVersion: props.platformVersion, + }); + + this.addTaskAsTarget(eventRuleTarget); } } diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts index 27367249ee3cb..fa62f079862f1 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.scheduled-fargate-task.ts @@ -294,6 +294,60 @@ export = { ], })); + test.done(); + }, + 'Scheduled Fargate Task - with platformVersion defined'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + + new ScheduledFargateTask(stack, 'ScheduledFargateTask', { + cluster, + scheduledFargateTaskImageOptions: { + image: ecs.ContainerImage.fromRegistry('henk'), + memoryLimitMiB: 512, + }, + schedule: events.Schedule.expression('rate(1 minute)'), + platformVersion: ecs.FargatePlatformVersion.VERSION1_4, + }); + + // THEN + expect(stack).to(haveResource('AWS::Events::Rule', { + Targets: [ + { + Arn: { 'Fn::GetAtt': ['EcsCluster97242B84', 'Arn'] }, + EcsParameters: { + LaunchType: 'FARGATE', + NetworkConfiguration: { + AwsVpcConfiguration: { + AssignPublicIp: 'DISABLED', + SecurityGroups: [ + { + 'Fn::GetAtt': [ + 'ScheduledFargateTaskScheduledTaskDefSecurityGroupE075BC19', + 'GroupId', + ], + }, + ], + Subnets: [ + { + Ref: 'VpcPrivateSubnet1Subnet536B997A', + }, + ], + }, + }, + PlatformVersion: '1.4.0', + TaskCount: 1, + TaskDefinitionArn: { Ref: 'ScheduledFargateTaskScheduledTaskDef521FA675' }, + }, + Id: 'Target0', + Input: '{}', + RoleArn: { 'Fn::GetAtt': ['ScheduledFargateTaskScheduledTaskDefEventsRole6CE19522', 'Arn'] }, + }, + ], + })); + test.done(); }, }; From 1a9f2a8100a64405c6cd2006a9682048b6ff0b80 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Sun, 31 Jan 2021 01:35:28 -0800 Subject: [PATCH 33/70] chore: add new interfaces for Assets (#12700) In V2, we want to get rid of the `@aws-cdk/assets` module, as it's considered deprecated in V1. Unfortunately, the module contains an interface, `CopyOptions`, that is used, through interface inheritance, in the public API of many stable CDK modules like ECS, Lambda, ECR, CodeBuild, etc. While we have a `CopyOptions` interface in `@aws-cdk/core`, it unfortunately shares the same name, `follow`, with the property in the "old" `CopyOptions`. But the two different `follow` properties have different types. For that reason, if we're going to remove the "old" `CopyOptions` using JSII's "strip deprecated" option, we can't use the "new" `CopyOptions` in the inheritance hierarchy alongside the "old" `CopyOptions`, as the two definitions of `follow` would conflict. Because of that, create a new `FileCopyOptions` interface which renames the `follow` property to `followSymlinks`. Also add a `FileFingerprintOptions` interface that does a similar trick to the `FingerprintOptions` interface (which extends `CopyOptions`), which is used in the public API of modules that use Docker assets. Also extract a few module-private interfaces to avoid duplication of properties between all of these interfaces. After this change, an interface from one of the non-deprecated assets libraries (S3 or ECR) using `FileOptions` from `@aws-cdk/assets` will look like this: ```ts // this is in @aws-cdk/aws-s3-assets export interface AssetOptions extends assets.CopyOptions, cdk.FileCopyOptions, cdk.AssetOptions { // ... ``` Then, when we enable stripping the deprecated elements using JSII on the V2 branch, this will be turned to: ```ts export interface AssetOptions extends cdk.FileCopyOptions, cdk.AssetOptions { // ... ``` Allowing us to deprecate the `@aws-cdk/assets` module, and not ship it with `aws-cdk-lib`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/assets/lib/fs/options.ts | 1 + .../aws-ecr-assets/lib/image-asset.ts | 19 +++++-- packages/@aws-cdk/aws-s3-assets/lib/asset.ts | 4 +- packages/@aws-cdk/core/lib/fs/options.ts | 51 ++++++++++++++----- 4 files changed, 56 insertions(+), 19 deletions(-) diff --git a/packages/@aws-cdk/assets/lib/fs/options.ts b/packages/@aws-cdk/assets/lib/fs/options.ts index 3ccc107d3700d..548fa4bda42ee 100644 --- a/packages/@aws-cdk/assets/lib/fs/options.ts +++ b/packages/@aws-cdk/assets/lib/fs/options.ts @@ -10,6 +10,7 @@ export interface CopyOptions { * A strategy for how to handle symlinks. * * @default Never + * @deprecated use `followSymlinks` instead */ readonly follow?: FollowMode; diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts index 2f6f5ff436baa..91d3f06b5f6a2 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts @@ -2,14 +2,16 @@ import * as fs from 'fs'; import * as path from 'path'; import * as assets from '@aws-cdk/assets'; import * as ecr from '@aws-cdk/aws-ecr'; -import { Annotations, Construct as CoreConstruct, FeatureFlags, IgnoreMode, Stack, Token } from '@aws-cdk/core'; +import { + Annotations, AssetStaging, Construct as CoreConstruct, FeatureFlags, FileFingerprintOptions, IgnoreMode, Stack, SymlinkFollowMode, Token, +} from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; /** * Options for DockerImageAsset */ -export interface DockerImageAssetOptions extends assets.FingerprintOptions { +export interface DockerImageAssetOptions extends assets.FingerprintOptions, FileFingerprintOptions { /** * ECR repository name * @@ -137,8 +139,9 @@ export class DockerImageAsset extends CoreConstruct implements assets.IAsset { // deletion of the ECR repository the app used). extraHash.version = '1.21.0'; - const staging = new assets.Staging(this, 'Staging', { + const staging = new AssetStaging(this, 'Staging', { ...props, + follow: props.followSymlinks ?? toSymlinkFollow(props.follow), exclude, ignoreMode, sourcePath: dir, @@ -181,3 +184,13 @@ function validateBuildArgs(buildArgs?: { [key: string]: string }) { } } } + +function toSymlinkFollow(follow?: assets.FollowMode): SymlinkFollowMode | undefined { + switch (follow) { + case undefined: return undefined; + case assets.FollowMode.NEVER: return SymlinkFollowMode.NEVER; + case assets.FollowMode.ALWAYS: return SymlinkFollowMode.ALWAYS; + case assets.FollowMode.BLOCK_EXTERNAL: return SymlinkFollowMode.BLOCK_EXTERNAL; + case assets.FollowMode.EXTERNAL: return SymlinkFollowMode.EXTERNAL; + } +} diff --git a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts index 938778d1381f4..d674d083b248b 100644 --- a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts +++ b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts @@ -15,7 +15,7 @@ import { Construct as CoreConstruct } from '@aws-cdk/core'; const ARCHIVE_EXTENSIONS = ['.zip', '.jar']; -export interface AssetOptions extends assets.CopyOptions, cdk.AssetOptions { +export interface AssetOptions extends assets.CopyOptions, cdk.FileCopyOptions, cdk.AssetOptions { /** * A list of principals that should be able to read this asset from S3. * You can use `asset.grantRead(principal)` to grant read permissions later. @@ -128,7 +128,7 @@ export class Asset extends CoreConstruct implements cdk.IAsset { const staging = new cdk.AssetStaging(this, 'Stage', { ...props, sourcePath: path.resolve(props.path), - follow: toSymlinkFollow(props.follow), + follow: props.followSymlinks ?? toSymlinkFollow(props.follow), assetHash: props.assetHash ?? props.sourceHash, }); diff --git a/packages/@aws-cdk/core/lib/fs/options.ts b/packages/@aws-cdk/core/lib/fs/options.ts index 3ea836a24e831..baf73bd7ffd30 100644 --- a/packages/@aws-cdk/core/lib/fs/options.ts +++ b/packages/@aws-cdk/core/lib/fs/options.ts @@ -56,19 +56,9 @@ export enum IgnoreMode { * context flag is set. */ DOCKER = 'docker' -}; - -/** - * Obtains applied when copying directories into the staging location. - */ -export interface CopyOptions { - /** - * A strategy for how to handle symlinks. - * - * @default SymlinkFollowMode.NEVER - */ - readonly follow?: SymlinkFollowMode; +} +interface FileOptions { /** * Glob patterns to exclude from the copy. * @@ -85,9 +75,30 @@ export interface CopyOptions { } /** - * Options related to calculating source hash. + * Options applied when copying directories + */ +export interface CopyOptions extends FileOptions { + /** + * A strategy for how to handle symlinks. + * + * @default SymlinkFollowMode.NEVER + */ + readonly follow?: SymlinkFollowMode; +} + +/** + * Options applied when copying directories into the staging location. */ -export interface FingerprintOptions extends CopyOptions { +export interface FileCopyOptions extends FileOptions { + /** + * A strategy for how to handle symlinks. + * + * @default SymlinkFollowMode.NEVER + */ + readonly followSymlinks?: SymlinkFollowMode; +} + +interface ExtraHashOptions { /** * Extra information to encode into the fingerprint (e.g. build instructions * and other inputs) @@ -96,3 +107,15 @@ export interface FingerprintOptions extends CopyOptions { */ readonly extraHash?: string; } + +/** + * Options related to calculating source hash. + */ +export interface FingerprintOptions extends CopyOptions, ExtraHashOptions { +} + +/** + * Options related to calculating source hash. + */ +export interface FileFingerprintOptions extends FileCopyOptions, ExtraHashOptions { +} From 84342943ad9f2ea8a83773f00816a0b8117c4d17 Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Sun, 31 Jan 2021 12:52:18 +0200 Subject: [PATCH 34/70] feat(ecr): Public Gallery authorization token (#12775) API for granting permissions to retrieve an authorization token for the [Public ECR Gallery](https://gallery.ecr.aws/), similarly to the [existing API](https://github.com/aws/aws-cdk/blob/master/packages/@aws-cdk/aws-ecr/lib/auth-token.ts) for private ECR registries. Also added a note in the README encouraging users to prefer authenticated pulls over anonymous ones to benefit from higher limits. ---- *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-ecr/README.md | 20 +++++++++++- packages/@aws-cdk/aws-ecr/lib/auth-token.ts | 28 +++++++++++++++- .../@aws-cdk/aws-ecr/test/test.auth-token.ts | 32 +++++++++++++++++-- 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-ecr/README.md b/packages/@aws-cdk/aws-ecr/README.md index 6278238b84eab..9bce0c80e132c 100644 --- a/packages/@aws-cdk/aws-ecr/README.md +++ b/packages/@aws-cdk/aws-ecr/README.md @@ -51,11 +51,29 @@ grants an IAM user access to call this API. ```ts import * as iam from '@aws-cdk/aws-iam'; +import * as ecr from '@aws-cdk/aws-ecr'; const user = new iam.User(this, 'User', { ... }); -iam.AuthorizationToken.grantRead(user); +ecr.AuthorizationToken.grantRead(user); ``` +If you access images in the [Public ECR Gallery](https://gallery.ecr.aws/) as well, it is recommended you authenticate to the regsitry to benefit from +higher rate and bandwidth limits. + +> See `Pricing` in https://aws.amazon.com/blogs/aws/amazon-ecr-public-a-new-public-container-registry/ and [Service quotas](https://docs.aws.amazon.com/AmazonECR/latest/public/public-service-quotas.html). + +The following code snippet grants an IAM user access to retrieve an authorization token for the public gallery. + +```ts +import * as iam from '@aws-cdk/aws-iam'; +import * as ecr from '@aws-cdk/aws-ecr'; + +const user = new iam.User(this, 'User', { ... }); +ecr.PublicGalleryAuthorizationToken.grantRead(user); +``` + +This user can then proceed to login to the registry using one of the [authentication methods](https://docs.aws.amazon.com/AmazonECR/latest/public/public-registries.html#public-registry-auth). + ## Automatically clean up repositories You can set life cycle rules to automatically clean up old images from your diff --git a/packages/@aws-cdk/aws-ecr/lib/auth-token.ts b/packages/@aws-cdk/aws-ecr/lib/auth-token.ts index 52c10cc513d0a..63484bbed0199 100644 --- a/packages/@aws-cdk/aws-ecr/lib/auth-token.ts +++ b/packages/@aws-cdk/aws-ecr/lib/auth-token.ts @@ -1,7 +1,9 @@ import * as iam from '@aws-cdk/aws-iam'; /** - * Authorization token to access ECR repositories via Docker CLI. + * Authorization token to access private ECR repositories in the current environment via Docker CLI. + * + * @see https://docs.aws.amazon.com/AmazonECR/latest/userguide/registry_auth.html */ export class AuthorizationToken { /** @@ -18,3 +20,27 @@ export class AuthorizationToken { private constructor() { } } + +/** + * Authorization token to access the global public ECR Gallery via Docker CLI. + * + * @see https://docs.aws.amazon.com/AmazonECR/latest/public/public-registries.html#public-registry-auth + */ +export class PublicGalleryAuthorizationToken { + + /** + * Grant access to retrieve an authorization token. + */ + public static grantRead(grantee: iam.IGrantable) { + grantee.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['ecr-public:GetAuthorizationToken', 'sts:GetServiceBearerToken'], + // GetAuthorizationToken only allows '*'. See https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonelasticcontainerregistry.html#amazonelasticcontainerregistry-actions-as-permissions + // GetServiceBearerToken only allows '*'. See https://docs.aws.amazon.com/service-authorization/latest/reference/list_awssecuritytokenservice.html#awssecuritytokenservice-actions-as-permissions + resources: ['*'], + })); + } + + private constructor() { + } + +} diff --git a/packages/@aws-cdk/aws-ecr/test/test.auth-token.ts b/packages/@aws-cdk/aws-ecr/test/test.auth-token.ts index 4e9e12e4fb078..bb1e13c5566b4 100644 --- a/packages/@aws-cdk/aws-ecr/test/test.auth-token.ts +++ b/packages/@aws-cdk/aws-ecr/test/test.auth-token.ts @@ -2,10 +2,10 @@ import { expect, haveResourceLike } from '@aws-cdk/assert'; import * as iam from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; -import { AuthorizationToken } from '../lib'; +import { AuthorizationToken, PublicGalleryAuthorizationToken } from '../lib'; export = { - 'grant()'(test: Test) { + 'AuthorizationToken.grantRead()'(test: Test) { // GIVEN const stack = new Stack(); const user = new iam.User(stack, 'User'); @@ -28,4 +28,32 @@ export = { test.done(); }, + + 'PublicGalleryAuthorizationToken.grantRead()'(test: Test) { + // GIVEN + const stack = new Stack(); + const user = new iam.User(stack, 'User'); + + // WHEN + PublicGalleryAuthorizationToken.grantRead(user); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ecr-public:GetAuthorizationToken', + 'sts:GetServiceBearerToken', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + }, + })); + + test.done(); + }, + }; \ No newline at end of file From 5060782b00e17bdf44e225f8f5ef03344be238c7 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Sun, 31 Jan 2021 06:56:30 -0800 Subject: [PATCH 35/70] fix(cfn-include): AWS::CloudFormation resources fail in monocdk (#12758) When we did the changes to fix cloudformation-include in #11595, we did not account for the fact that the `@aws-cdk/core` is not mapped to `uberpackage/core`, but instead just to the `uberpackage` root namespace. Special-case the `@aws-cdk/core` module in ubergen when transforming the `cfn-types-2-classes.json` file. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../uberpackage/cfn-include-app/example-template.json | 3 +++ tools/ubergen/bin/ubergen.ts | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk/test/integ/uberpackage/cfn-include-app/example-template.json b/packages/aws-cdk/test/integ/uberpackage/cfn-include-app/example-template.json index 0385b58961413..8ad9310b8fd4d 100644 --- a/packages/aws-cdk/test/integ/uberpackage/cfn-include-app/example-template.json +++ b/packages/aws-cdk/test/integ/uberpackage/cfn-include-app/example-template.json @@ -1,5 +1,8 @@ { "Resources": { + "NoopHandle": { + "Type": "AWS::CloudFormation::WaitConditionHandle" + }, "Bucket": { "Type": "AWS::S3::Bucket", "Properties": { diff --git a/tools/ubergen/bin/ubergen.ts b/tools/ubergen/bin/ubergen.ts index ea5c529f77f3e..9047fc28c371a 100644 --- a/tools/ubergen/bin/ubergen.ts +++ b/tools/ubergen/bin/ubergen.ts @@ -333,8 +333,11 @@ async function copyOrTransformFiles(from: string, to: string, libraries: readonl const cfnTypes2Classes: { [key: string]: string } = await fs.readJson(source); for (const cfnType of Object.keys(cfnTypes2Classes)) { const fqn = cfnTypes2Classes[cfnType]; - // replace @aws-cdk/aws- with /aws- - cfnTypes2Classes[cfnType] = fqn.replace('@aws-cdk', uberPackageJson.name); + // replace @aws-cdk/aws- with /aws-, + // except for @aws-cdk/core, which maps just to the name of the uberpackage + cfnTypes2Classes[cfnType] = fqn.startsWith('@aws-cdk/core.') + ? fqn.replace('@aws-cdk/core', uberPackageJson.name) + : fqn.replace('@aws-cdk', uberPackageJson.name); } await fs.writeJson(destination, cfnTypes2Classes, { spaces: 2 }); } else { From 889d6734c10174f2661e45057c345cd112a44187 Mon Sep 17 00:00:00 2001 From: Daisuke Yoshimoto Date: Mon, 1 Feb 2021 03:44:15 +0900 Subject: [PATCH 36/70] fix(efs): EFS fails to create when using a VPC with multiple subnets per availability zone (#12097) Fixes #10170 ### Fixes This PR has been modified so that it does not create a mount target if only VPC is specified in the EFS file system. ### Bug Currently, if a VPC is specified and no subnet is specified, the subnet search criteria are passed as undefined, all subnets in the VPC are retrieved, and mount targets are generated for all subnets in the VPC. I will. Since mount targets can only be created in one subnet for each Availability Zone, the above behavior will result in duplicate Availability Zones between mount targets, resulting in an error when creating the mount target. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-efs/lib/efs-file-system.ts | 2 +- .../aws-efs/test/efs-file-system.test.ts | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts index a93ea1b76d5a4..60af6fde51752 100644 --- a/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts +++ b/packages/@aws-cdk/aws-efs/lib/efs-file-system.ts @@ -272,7 +272,7 @@ export class FileSystem extends Resource implements IFileSystem { defaultPort: ec2.Port.tcp(FileSystem.DEFAULT_PORT), }); - const subnets = props.vpc.selectSubnets(props.vpcSubnets); + const subnets = props.vpc.selectSubnets(props.vpcSubnets ?? { onePerAz: true }); // We now have to create the mount target for each of the mentioned subnet let mountTargetCount = 0; diff --git a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts index 5b9f3c6539a45..d3868a841e0a5 100644 --- a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts +++ b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts @@ -1,4 +1,4 @@ -import { expect as expectCDK, haveResource, ResourcePart } from '@aws-cdk/assert'; +import { expect as expectCDK, haveResource, ResourcePart, countResources } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; import { RemovalPolicy, Size, Stack, Tags } from '@aws-cdk/core'; @@ -240,3 +240,17 @@ test('can specify backup policy', () => { }, })); }); + +test('can create when using a VPC with multiple subnets per availability zone', () => { + // create a vpc with two subnets in the same availability zone. + const oneAzVpc = new ec2.Vpc(stack, 'Vpc', { + maxAzs: 1, + subnetConfiguration: [{ name: 'One', subnetType: ec2.SubnetType.ISOLATED }, { name: 'Two', subnetType: ec2.SubnetType.ISOLATED }], + natGateways: 0, + }); + new FileSystem(stack, 'EfsFileSystem', { + vpc: oneAzVpc, + }); + // make sure only one mount target is created. + expectCDK(stack).to(countResources('AWS::EFS::MountTarget', 1)); +}); From 2c8a40913dd86a1fb464b57275492d260e4abd0f Mon Sep 17 00:00:00 2001 From: flemjame-at-amazon <57235867+flemjame-at-amazon@users.noreply.github.com> Date: Mon, 1 Feb 2021 08:03:53 -0500 Subject: [PATCH 37/70] docs(lambda): Example to guarantee new version creation (#12598) This is created to provide an example of a workaround for https://github.com/aws/aws-cdk/issues/10136 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index e2b317b8faba3..a59dc1a311936 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -143,6 +143,19 @@ of a new version resource. You can specify options for this version through the > of code providers (such as `lambda.Code.fromBucket`) require that you define a > `lambda.Version` resource directly since the CDK is unable to determine if > their contents had changed. +> +> An alternative to defining a `lambda.Version` is to set an environment variable +> which changes at least as often as your code does. This makes sure the function +> always has the latest code. +> +> ```ts +> const codeVersion = "stringOrMethodToGetCodeVersion"; +> const fn = new lambda.Function(this, 'MyFunction', { +> environment: { +> 'CodeVersionString': codeVersion +> } +> }); +> ``` The `version.addAlias()` method can be used to define an AWS Lambda alias that points to a specific version. From 415eb861c65829cc53eabbbb8706f83f08c74570 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 1 Feb 2021 14:40:16 +0100 Subject: [PATCH 38/70] feat(iam): Permissions Boundaries (#12777) Allow configuring Permissions Boundaries for an entire subtree using Aspects, add a sample policy which can be used to reduce future misconfiguration risk for untrusted CodeBuild projects as an example. Addresses one part of aws/aws-cdk-rfcs#5. Fixes #3242. ALSO IN THIS COMMIT: Fix a bug in the `assert` library, where `haveResource()` would *never* match any resource that didn't have a `Properties` block (even if we tested for no property in particular, or the absence of properties). This fix caused two ECS tests to fail, which were asserting the wrong thing anyway (both were asserting `notTo(haveResource(...))` where they actually meant to assert `to(haveResource())`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../assert/lib/assertions/have-resource.ts | 2 +- packages/@aws-cdk/aws-codebuild/lib/index.ts | 1 + .../lib/untrusted-code-boundary-policy.ts | 94 ++++++++++++++++ .../test/test.untrusted-code-boundary.ts | 56 ++++++++++ .../test/ec2/test.ec2-task-definition.ts | 2 +- .../aws-ecs/test/test.aws-log-driver.ts | 4 +- packages/@aws-cdk/aws-iam/README.md | 44 ++++++++ packages/@aws-cdk/aws-iam/lib/index.ts | 1 + .../aws-iam/lib/permissions-boundary.ts | 53 +++++++++ .../aws-iam/test/permissions-boundary.test.ts | 101 ++++++++++++++++++ 10 files changed, 354 insertions(+), 4 deletions(-) create mode 100644 packages/@aws-cdk/aws-codebuild/lib/untrusted-code-boundary-policy.ts create mode 100644 packages/@aws-cdk/aws-codebuild/test/test.untrusted-code-boundary.ts create mode 100644 packages/@aws-cdk/aws-iam/lib/permissions-boundary.ts create mode 100644 packages/@aws-cdk/aws-iam/test/permissions-boundary.test.ts diff --git a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts index 2f3352bee16ef..3e44013188b2c 100644 --- a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts +++ b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts @@ -66,7 +66,7 @@ export class HaveResourceAssertion extends JestFriendlyAssertion for (const logicalId of Object.keys(inspector.value.Resources || {})) { const resource = inspector.value.Resources[logicalId]; if (resource.Type === this.resourceType) { - const propsToCheck = this.part === ResourcePart.Properties ? resource.Properties : resource; + const propsToCheck = this.part === ResourcePart.Properties ? (resource.Properties ?? {}) : resource; // Pass inspection object as 2nd argument, initialize failure with default string, // to maintain backwards compatibility with old predicate API. diff --git a/packages/@aws-cdk/aws-codebuild/lib/index.ts b/packages/@aws-cdk/aws-codebuild/lib/index.ts index 96731b2130043..5c2de5f3119c2 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/index.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/index.ts @@ -10,6 +10,7 @@ export * from './cache'; export * from './build-spec'; export * from './file-location'; export * from './linux-gpu-build-image'; +export * from './untrusted-code-boundary-policy'; // AWS::CodeBuild CloudFormation Resources: export * from './codebuild.generated'; diff --git a/packages/@aws-cdk/aws-codebuild/lib/untrusted-code-boundary-policy.ts b/packages/@aws-cdk/aws-codebuild/lib/untrusted-code-boundary-policy.ts new file mode 100644 index 0000000000000..229cb547e7c1f --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/lib/untrusted-code-boundary-policy.ts @@ -0,0 +1,94 @@ +import * as iam from '@aws-cdk/aws-iam'; +import { Construct } from 'constructs'; + +/** + * Construction properties for UntrustedCodeBoundaryPolicy + */ +export interface UntrustedCodeBoundaryPolicyProps { + /** + * The name of the managed policy. + * + * @default - A name is automatically generated. + */ + readonly managedPolicyName?: string; + + /** + * Additional statements to add to the default set of statements + * + * @default - No additional statements + */ + readonly additionalStatements?: iam.PolicyStatement[]; +} + +/** + * Permissions Boundary for a CodeBuild Project running untrusted code + * + * This class is a Policy, intended to be used as a Permissions Boundary + * for a CodeBuild project. It allows most of the actions necessary to run + * the CodeBuild project, but disallows reading from Parameter Store + * and Secrets Manager. + * + * Use this when your CodeBuild project is running untrusted code (for + * example, if you are using one to automatically build Pull Requests + * that anyone can submit), and you want to prevent your future self + * from accidentally exposing Secrets to this build. + * + * (The reason you might want to do this is because otherwise anyone + * who can submit a Pull Request to your project can write a script + * to email those secrets to themselves). + * + * @example + * + * iam.PermissionsBoundary.of(project).apply(new UntrustedCodeBoundaryPolicy(this, 'Boundary')); + */ +export class UntrustedCodeBoundaryPolicy extends iam.ManagedPolicy { + constructor(scope: Construct, id: string, props: UntrustedCodeBoundaryPolicyProps = {}) { + super(scope, id, { + managedPolicyName: props.managedPolicyName, + description: 'Permissions Boundary Policy for CodeBuild Projects running untrusted code', + statements: [ + new iam.PolicyStatement({ + actions: [ + // For logging + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:PutLogEvents', + + // For test reports + 'codebuild:CreateReportGroup', + 'codebuild:CreateReport', + 'codebuild:UpdateReport', + 'codebuild:BatchPutTestCases', + 'codebuild:BatchPutCodeCoverages', + + // For batch builds + 'codebuild:StartBuild', + 'codebuild:StopBuild', + 'codebuild:RetryBuild', + + // For pulling ECR images + 'ecr:GetDownloadUrlForLayer', + 'ecr:BatchGetImage', + 'ecr:BatchCheckLayerAvailability', + + // For running in a VPC + 'ec2:CreateNetworkInterfacePermission', + 'ec2:CreateNetworkInterface', + 'ec2:DescribeNetworkInterfaces', + 'ec2:DeleteNetworkInterface', + 'ec2:DescribeSubnets', + 'ec2:DescribeSecurityGroups', + 'ec2:DescribeDhcpOptions', + 'ec2:DescribeVpcs', + + // NOTABLY MISSING: + // - Reading secrets + // - Reading parameterstore + ], + resources: ['*'], + }), + ...props.additionalStatements ?? [], + ], + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/test/test.untrusted-code-boundary.ts b/packages/@aws-cdk/aws-codebuild/test/test.untrusted-code-boundary.ts new file mode 100644 index 0000000000000..04196e631f5c8 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/test.untrusted-code-boundary.ts @@ -0,0 +1,56 @@ +import { expect, haveResourceLike, arrayWith } from '@aws-cdk/assert'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import * as codebuild from '../lib'; + +export = { + 'can attach permissions boundary to Project'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const project = new codebuild.Project(stack, 'Project', { + source: codebuild.Source.gitHub({ owner: 'a', repo: 'b' }), + }); + iam.PermissionsBoundary.of(project).apply(new codebuild.UntrustedCodeBoundaryPolicy(stack, 'Boundary')); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Role', { + PermissionsBoundary: { Ref: 'BoundaryEA298153' }, + })); + + test.done(); + }, + + 'can add additional statements Boundary'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const project = new codebuild.Project(stack, 'Project', { + source: codebuild.Source.gitHub({ owner: 'a', repo: 'b' }), + }); + iam.PermissionsBoundary.of(project).apply(new codebuild.UntrustedCodeBoundaryPolicy(stack, 'Boundary', { + additionalStatements: [ + new iam.PolicyStatement({ + actions: ['a:a'], + resources: ['b'], + }), + ], + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::ManagedPolicy', { + PolicyDocument: { + Statement: arrayWith({ + Effect: 'Allow', + Action: 'a:a', + Resource: 'b', + }), + }, + })); + + test.done(); + }, +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts index 8581738c6da51..9264c47e4550c 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts @@ -543,7 +543,7 @@ export = { }); // THEN - expect(stack).notTo(haveResource('AWS::ECR::Repository', {})); + expect(stack).to(haveResource('AWS::ECR::Repository', {})); test.done(); }, diff --git a/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts b/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts index 1ea5942386ad0..a4d5c9af9c53d 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts @@ -126,7 +126,7 @@ export = { test.done(); }, - 'without a defined log group'(test: Test) { + 'without a defined log group: creates one anyway'(test: Test) { // GIVEN td.addContainer('Container', { image, @@ -136,7 +136,7 @@ export = { }); // THEN - expect(stack).notTo(haveResource('AWS::Logs::LogGroup', {})); + expect(stack).to(haveResource('AWS::Logs::LogGroup', {})); test.done(); }, diff --git a/packages/@aws-cdk/aws-iam/README.md b/packages/@aws-cdk/aws-iam/README.md index d9488e7d081c8..f2b86ccff2f59 100644 --- a/packages/@aws-cdk/aws-iam/README.md +++ b/packages/@aws-cdk/aws-iam/README.md @@ -264,6 +264,50 @@ const newPolicy = new Policy(stack, 'MyNewPolicy', { }); ``` +## Permissions Boundaries + +[Permissions +Boundaries](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) +can be used as a mechanism to prevent privilege esclation by creating new +`Role`s. Permissions Boundaries are a Managed Policy, attached to Roles or +Users, that represent the *maximum* set of permissions they can have. The +effective set of permissions of a Role (or User) will be the intersection of +the Identity Policy and the Permissions Boundary attached to the Role (or +User). Permissions Boundaries are typically created by account +Administrators, and their use on newly created `Role`s will be enforced by +IAM policies. + +It is possible to attach Permissions Boundaries to all Roles created in a construct +tree all at once: + +```ts +// This imports an existing policy. +const boundary = iam.ManagedPolicy.fromManagedPolicyArn(this, 'Boundary', 'arn:aws:iam::123456789012:policy/boundary'); + +// This creates a new boundary +const boundary2 = new iam.ManagedPolicy(this, 'Boundary2', { + statements: [ + new iam.PolicyStatement({ + effect: iam.Effect.DENY, + actions: ['iam:*'], + resources: ['*'], + }), + ], +}); + +// Directly apply the boundary to a Role you create +iam.PermissionsBoundary.of(role).apply(boundary); + +// Apply the boundary to an Role that was implicitly created for you +iam.PermissionsBoundary.of(lambdaFunction).apply(boundary); + +// Apply the boundary to all Roles in a stack +iam.PermissionsBoundary.of(stack).apply(boundary); + +// Remove a Permissions Boundary that is inherited, for example from the Stack level +iam.PermissionsBoundary.of(customResource).clear(); +``` + ## OpenID Connect Providers OIDC identity providers are entities in IAM that describe an external identity diff --git a/packages/@aws-cdk/aws-iam/lib/index.ts b/packages/@aws-cdk/aws-iam/lib/index.ts index ba9250ca1e08e..19b8a156ba598 100644 --- a/packages/@aws-cdk/aws-iam/lib/index.ts +++ b/packages/@aws-cdk/aws-iam/lib/index.ts @@ -11,6 +11,7 @@ export * from './identity-base'; export * from './grant'; export * from './unknown-principal'; export * from './oidc-provider'; +export * from './permissions-boundary'; // AWS::IAM CloudFormation Resources: export * from './iam.generated'; diff --git a/packages/@aws-cdk/aws-iam/lib/permissions-boundary.ts b/packages/@aws-cdk/aws-iam/lib/permissions-boundary.ts new file mode 100644 index 0000000000000..7b4320462a1ac --- /dev/null +++ b/packages/@aws-cdk/aws-iam/lib/permissions-boundary.ts @@ -0,0 +1,53 @@ +import { Node, IConstruct } from 'constructs'; +import { CfnRole, CfnUser } from './iam.generated'; +import { IManagedPolicy } from './managed-policy'; + +/** + * Modify the Permissions Boundaries of Users and Roles in a construct tree + * + * @example + * + * const policy = ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess'); + * PermissionsBoundary.of(stack).apply(policy); + */ +export class PermissionsBoundary { + /** + * Access the Permissions Boundaries of a construct tree + */ + public static of(scope: IConstruct): PermissionsBoundary { + return new PermissionsBoundary(scope); + } + + private constructor(private readonly scope: IConstruct) { + } + + /** + * Apply the given policy as Permissions Boundary to all Roles in the scope + * + * Will override any Permissions Boundaries configured previously; in case + * a Permission Boundary is applied in multiple scopes, the Boundary applied + * closest to the Role wins. + */ + public apply(boundaryPolicy: IManagedPolicy) { + Node.of(this.scope).applyAspect({ + visit(node: IConstruct) { + if (node instanceof CfnRole || node instanceof CfnUser) { + node.permissionsBoundary = boundaryPolicy.managedPolicyArn; + } + }, + }); + } + + /** + * Remove previously applied Permissions Boundaries + */ + public clear() { + Node.of(this.scope).applyAspect({ + visit(node: IConstruct) { + if (node instanceof CfnRole || node instanceof CfnUser) { + node.permissionsBoundary = undefined; + } + }, + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/permissions-boundary.test.ts b/packages/@aws-cdk/aws-iam/test/permissions-boundary.test.ts new file mode 100644 index 0000000000000..99a14de55f2e1 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/test/permissions-boundary.test.ts @@ -0,0 +1,101 @@ +import { ABSENT } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import { App, Stack } from '@aws-cdk/core'; +import * as iam from '../lib'; + +let app: App; +let stack: Stack; +beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack'); +}); + +test('apply imported boundary to a role', () => { + // GIVEN + const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('service.amazonaws.com'), + }); + + // WHEN + iam.PermissionsBoundary.of(role).apply(iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Role', { + PermissionsBoundary: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::aws:policy/ReadOnlyAccess', + ]], + }, + }); +}); + +test('apply imported boundary to a user', () => { + // GIVEN + const user = new iam.User(stack, 'User'); + + // WHEN + iam.PermissionsBoundary.of(user).apply(iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')); + + // THEN + expect(stack).toHaveResource('AWS::IAM::User', { + PermissionsBoundary: { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::aws:policy/ReadOnlyAccess', + ]], + }, + }); +}); + +test('apply newly created boundary to a role', () => { + // GIVEN + const role = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('service.amazonaws.com'), + }); + + // WHEN + iam.PermissionsBoundary.of(role).apply(new iam.ManagedPolicy(stack, 'Policy', { + statements: [ + new iam.PolicyStatement({ + actions: ['*'], + resources: ['*'], + }), + ], + })); + + // THEN + expect(stack).toHaveResource('AWS::IAM::Role', { + PermissionsBoundary: { Ref: 'Policy23B91518' }, + }); +}); + +test('unapply inherited boundary from a user: order 1', () => { + // GIVEN + const user = new iam.User(stack, 'User'); + + // WHEN + iam.PermissionsBoundary.of(stack).apply(iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')); + iam.PermissionsBoundary.of(user).clear(); + + // THEN + expect(stack).toHaveResource('AWS::IAM::User', { + PermissionsBoundary: ABSENT, + }); +}); + +test('unapply inherited boundary from a user: order 2', () => { + // GIVEN + const user = new iam.User(stack, 'User'); + + // WHEN + iam.PermissionsBoundary.of(user).clear(); + iam.PermissionsBoundary.of(stack).apply(iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')); + + // THEN + expect(stack).toHaveResource('AWS::IAM::User', { + PermissionsBoundary: ABSENT, + }); +}); \ No newline at end of file From 3b66088010b6f2315a215e92505d5279680f16d4 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 1 Feb 2021 15:17:41 +0100 Subject: [PATCH 39/70] feat(core): `stack.exportValue()` can be used to solve "deadly embrace" (#12778) Deadly embrace (<3 who came up with this term) is an issue where a consumer stack depends on a producer stack via CloudFormation Exports, and you want to remove the use from the consumer. Removal of the resource sharing implicitly removes the CloudFormation Export, but now CloudFormation won't let you deploy that because the deployment order is always forced to be (1st) producer (2nd) consumer, and when the producer deploys and tries to remove the Export, the consumer is still using it. The best way to work around it is to manually ensure the CloudFormation Export exists while you remove the consuming relationship. @skinny85 has a [blog post] about this, but the mechanism can be more smooth. Add a method, `stack.exportValue(...)` which can be used to create the Export for the duration of the deployment that breaks the relationship, and add an explanation of how to use it. Genericize the method a bit so it also solves a long-standing issue about no L2 support for exports. Fixes #7602, fixes #2036. [blog post]: https://www.endoflineblog.com/cdk-tips-03-how-to-unblock-cross-stack-references ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/core/README.md | 59 +++++++++ packages/@aws-cdk/core/lib/private/refs.ts | 79 ++++-------- packages/@aws-cdk/core/lib/stack.ts | 143 +++++++++++++++++++-- packages/@aws-cdk/core/lib/token.ts | 26 ++++ packages/@aws-cdk/core/test/stack.test.ts | 66 +++++++++- 5 files changed, 310 insertions(+), 63 deletions(-) diff --git a/packages/@aws-cdk/core/README.md b/packages/@aws-cdk/core/README.md index 8c5ce270d8dc1..880aea79a55fc 100644 --- a/packages/@aws-cdk/core/README.md +++ b/packages/@aws-cdk/core/README.md @@ -92,6 +92,65 @@ nested stack and referenced using `Fn::GetAtt "Outputs.Xxx"` from the parent. Nested stacks also support the use of Docker image and file assets. +## Accessing resources in a different stack + +You can access resources in a different stack, as long as they are in the +same account and AWS Region. The following example defines the stack `stack1`, +which defines an Amazon S3 bucket. Then it defines a second stack, `stack2`, +which takes the bucket from stack1 as a constructor property. + +```ts +const prod = { account: '123456789012', region: 'us-east-1' }; + +const stack1 = new StackThatProvidesABucket(app, 'Stack1' , { env: prod }); + +// stack2 will take a property { bucket: IBucket } +const stack2 = new StackThatExpectsABucket(app, 'Stack2', { + bucket: stack1.bucket, + env: prod +}); +``` + +If the AWS CDK determines that the resource is in the same account and +Region, but in a different stack, it automatically synthesizes AWS +CloudFormation +[Exports](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-stack-exports.html) +in the producing stack and an +[Fn::ImportValue](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html) +in the consuming stack to transfer that information from one stack to the +other. + +### Removing automatic cross-stack references + +The automatic references created by CDK when you use resources across stacks +are convenient, but may block your deployments if you want to remove the +resources that are referenced in this way. You will see an error like: + +```text +Export Stack1:ExportsOutputFnGetAtt-****** cannot be deleted as it is in use by Stack1 +``` + +Let's say there is a Bucket in the `stack1`, and the `stack2` references its +`bucket.bucketName`. You now want to remove the bucket and run into the error above. + +It's not safe to remove `stack1.bucket` while `stack2` is still using it, so +unblocking yourself from this is a two-step process. This is how it works: + +DEPLOYMENT 1: break the relationship + +- Make sure `stack2` no longer references `bucket.bucketName` (maybe the consumer + stack now uses its own bucket, or it writes to an AWS DynamoDB table, or maybe you just + remove the Lambda Function altogether). +- In the `stack1` class, call `this.exportAttribute(this.bucket.bucketName)`. This + will make sure the CloudFormation Export continues to exist while the relationship + between the two stacks is being broken. +- Deploy (this will effectively only change the `stack2`, but it's safe to deploy both). + +DEPLOYMENT 2: remove the resource + +- You are now free to remove the `bucket` resource from `stack1`. +- Don't forget to remove the `exportAttribute()` call as well. +- Deploy again (this time only the `stack1` will be changed -- the bucket will be deleted). ## Durations diff --git a/packages/@aws-cdk/core/lib/private/refs.ts b/packages/@aws-cdk/core/lib/private/refs.ts index 46d44563b4a96..27618d6776f21 100644 --- a/packages/@aws-cdk/core/lib/private/refs.ts +++ b/packages/@aws-cdk/core/lib/private/refs.ts @@ -1,22 +1,19 @@ // ---------------------------------------------------- // CROSS REFERENCES // ---------------------------------------------------- -import * as cxapi from '@aws-cdk/cx-api'; import { CfnElement } from '../cfn-element'; import { CfnOutput } from '../cfn-output'; import { CfnParameter } from '../cfn-parameter'; -import { Construct, IConstruct } from '../construct-compat'; -import { FeatureFlags } from '../feature-flags'; +import { IConstruct } from '../construct-compat'; import { Names } from '../names'; import { Reference } from '../reference'; import { IResolvable } from '../resolvable'; import { Stack } from '../stack'; -import { Token } from '../token'; +import { Token, Tokenization } from '../token'; import { CfnReference } from './cfn-reference'; import { Intrinsic } from './intrinsic'; import { findTokens } from './resolve'; -import { makeUniqueId } from './uniqueid'; /** * This is called from the App level to resolve all references defined. Each @@ -167,55 +164,10 @@ function findAllReferences(root: IConstruct) { function createImportValue(reference: Reference): Intrinsic { const exportingStack = Stack.of(reference.target); - // Ensure a singleton "Exports" scoping Construct - // This mostly exists to trigger LogicalID munging, which would be - // disabled if we parented constructs directly under Stack. - // Also it nicely prevents likely construct name clashes - const exportsScope = getCreateExportsScope(exportingStack); + const importExpr = exportingStack.exportValue(reference); - // Ensure a singleton CfnOutput for this value - const resolved = exportingStack.resolve(reference); - const id = 'Output' + JSON.stringify(resolved); - const exportName = generateExportName(exportsScope, id); - - if (Token.isUnresolved(exportName)) { - throw new Error(`unresolved token in generated export name: ${JSON.stringify(exportingStack.resolve(exportName))}`); - } - - const output = exportsScope.node.tryFindChild(id) as CfnOutput; - if (!output) { - new CfnOutput(exportsScope, id, { value: Token.asString(reference), exportName }); - } - - // We want to return an actual FnImportValue Token here, but Fn.importValue() returns a 'string', - // so construct one in-place. - return new Intrinsic({ 'Fn::ImportValue': exportName }); -} - -function getCreateExportsScope(stack: Stack) { - const exportsName = 'Exports'; - let stackExports = stack.node.tryFindChild(exportsName) as Construct; - if (stackExports === undefined) { - stackExports = new Construct(stack, exportsName); - } - - return stackExports; -} - -function generateExportName(stackExports: Construct, id: string) { - const stackRelativeExports = FeatureFlags.of(stackExports).isEnabled(cxapi.STACK_RELATIVE_EXPORTS_CONTEXT); - const stack = Stack.of(stackExports); - - const components = [ - ...stackExports.node.scopes - .slice(stackRelativeExports ? stack.node.scopes.length : 2) - .map(c => c.node.id), - id, - ]; - const prefix = stack.stackName ? stack.stackName + ':' : ''; - const localPart = makeUniqueId(components); - const maxLength = 255; - return prefix + localPart.slice(Math.max(0, localPart.length - maxLength + prefix.length)); + // I happen to know this returns a Fn.importValue() which implements Intrinsic. + return Tokenization.reverseCompleteString(importExpr) as Intrinsic; } // ------------------------------------------------------------------------------------------------ @@ -262,6 +214,25 @@ function createNestedStackOutput(producer: Stack, reference: Reference): CfnRefe return producer.nestedStackResource.getAtt(`Outputs.${output.logicalId}`) as CfnReference; } +/** + * Translate a Reference into a nested stack into a value in the parent stack + * + * Will create Outputs along the chain of Nested Stacks, and return the final `{ Fn::GetAtt }`. + */ +export function referenceNestedStackValueInParent(reference: Reference, targetStack: Stack) { + let currentStack = Stack.of(reference.target); + if (currentStack !== targetStack && !isNested(currentStack, targetStack)) { + throw new Error(`Referenced resource must be in stack '${targetStack.node.path}', got '${reference.target.node.path}'`); + } + + while (currentStack !== targetStack) { + reference = createNestedStackOutput(Stack.of(reference.target), reference); + currentStack = Stack.of(reference.target); + } + + return reference; +} + /** * @returns true if this stack is a direct or indirect parent of the nested * stack `nested`. @@ -282,4 +253,4 @@ function isNested(nested: Stack, parent: Stack): boolean { // recurse with the child's direct parent return isNested(nested.nestedStackParent, parent); -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index c6a8a56916f2f..53e7adfdc206e 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -775,6 +775,93 @@ export class Stack extends CoreConstruct implements ITaggable { } } + /** + * Create a CloudFormation Export for a value + * + * Returns a string representing the corresponding `Fn.importValue()` + * expression for this Export. You can control the name for the export by + * passing the `name` option. + * + * If you don't supply a value for `name`, the value you're exporting must be + * a Resource attribute (for example: `bucket.bucketName`) and it will be + * given the same name as the automatic cross-stack reference that would be created + * if you used the attribute in another Stack. + * + * One of the uses for this method is to *remove* the relationship between + * two Stacks established by automatic cross-stack references. It will + * temporarily ensure that the CloudFormation Export still exists while you + * remove the reference from the consuming stack. After that, you can remove + * the resource and the manual export. + * + * ## Example + * + * Here is how the process works. Let's say there are two stacks, + * `producerStack` and `consumerStack`, and `producerStack` has a bucket + * called `bucket`, which is referenced by `consumerStack` (perhaps because + * an AWS Lambda Function writes into it, or something like that). + * + * It is not safe to remove `producerStack.bucket` because as the bucket is being + * deleted, `consumerStack` might still be using it. + * + * Instead, the process takes two deployments: + * + * ### Deployment 1: break the relationship + * + * - Make sure `consumerStack` no longer references `bucket.bucketName` (maybe the consumer + * stack now uses its own bucket, or it writes to an AWS DynamoDB table, or maybe you just + * remove the Lambda Function altogether). + * - In the `ProducerStack` class, call `this.exportValue(this.bucket.bucketName)`. This + * will make sure the CloudFormation Export continues to exist while the relationship + * between the two stacks is being broken. + * - Deploy (this will effectively only change the `consumerStack`, but it's safe to deploy both). + * + * ### Deployment 2: remove the bucket resource + * + * - You are now free to remove the `bucket` resource from `producerStack`. + * - Don't forget to remove the `exportValue()` call as well. + * - Deploy again (this time only the `producerStack` will be changed -- the bucket will be deleted). + */ + public exportValue(exportedValue: any, options: ExportValueOptions = {}) { + if (options.name) { + new CfnOutput(this, `Export${options.name}`, { + value: exportedValue, + exportName: options.name, + }); + return Fn.importValue(options.name); + } + + const resolvable = Tokenization.reverse(exportedValue); + if (!resolvable || !Reference.isReference(resolvable)) { + throw new Error('exportValue: either supply \'name\' or make sure to export a resource attribute (like \'bucket.bucketName\')'); + } + + // "teleport" the value here, in case it comes from a nested stack. This will also + // ensure the value is from our own scope. + const exportable = referenceNestedStackValueInParent(resolvable, this); + + // Ensure a singleton "Exports" scoping Construct + // This mostly exists to trigger LogicalID munging, which would be + // disabled if we parented constructs directly under Stack. + // Also it nicely prevents likely construct name clashes + const exportsScope = getCreateExportsScope(this); + + // Ensure a singleton CfnOutput for this value + const resolved = this.resolve(exportable); + const id = 'Output' + JSON.stringify(resolved); + const exportName = generateExportName(exportsScope, id); + + if (Token.isUnresolved(exportName)) { + throw new Error(`unresolved token in generated export name: ${JSON.stringify(this.resolve(exportName))}`); + } + + const output = exportsScope.node.tryFindChild(id) as CfnOutput; + if (!output) { + new CfnOutput(exportsScope, id, { value: Token.asString(exportable), exportName }); + } + + return Fn.importValue(exportName); + } + /** * Returns the naming scheme used to allocate logical IDs. By default, uses * the `HashedAddressingScheme` but this method can be overridden to customize @@ -1143,18 +1230,58 @@ function makeStackName(components: string[]) { return makeUniqueId(components); } +function getCreateExportsScope(stack: Stack) { + const exportsName = 'Exports'; + let stackExports = stack.node.tryFindChild(exportsName) as CoreConstruct; + if (stackExports === undefined) { + stackExports = new CoreConstruct(stack, exportsName); + } + + return stackExports; +} + +function generateExportName(stackExports: CoreConstruct, id: string) { + const stackRelativeExports = FeatureFlags.of(stackExports).isEnabled(cxapi.STACK_RELATIVE_EXPORTS_CONTEXT); + const stack = Stack.of(stackExports); + + const components = [ + ...stackExports.node.scopes + .slice(stackRelativeExports ? stack.node.scopes.length : 2) + .map(c => c.node.id), + id, + ]; + const prefix = stack.stackName ? stack.stackName + ':' : ''; + const localPart = makeUniqueId(components); + const maxLength = 255; + return prefix + localPart.slice(Math.max(0, localPart.length - maxLength + prefix.length)); +} + +interface StackDependency { + stack: Stack; + reasons: string[]; +} + +/** + * Options for the `stack.exportValue()` method + */ +export interface ExportValueOptions { + /** + * The name of the export to create + * + * @default - A name is automatically chosen + */ + readonly name?: string; +} + // These imports have to be at the end to prevent circular imports +import { CfnOutput } from './cfn-output'; import { addDependency } from './deps'; +import { FileSystem } from './fs'; +import { Names } from './names'; import { Reference } from './reference'; import { IResolvable } from './resolvable'; import { DefaultStackSynthesizer, IStackSynthesizer, LegacyStackSynthesizer } from './stack-synthesizers'; import { Stage } from './stage'; import { ITaggable, TagManager } from './tag-manager'; -import { Token } from './token'; -import { FileSystem } from './fs'; -import { Names } from './names'; - -interface StackDependency { - stack: Stack; - reasons: string[]; -} +import { Token, Tokenization } from './token'; +import { referenceNestedStackValueInParent } from './private/refs'; diff --git a/packages/@aws-cdk/core/lib/token.ts b/packages/@aws-cdk/core/lib/token.ts index 5f98db7a4f11f..4bbcdb454f9bd 100644 --- a/packages/@aws-cdk/core/lib/token.ts +++ b/packages/@aws-cdk/core/lib/token.ts @@ -132,6 +132,19 @@ export class Tokenization { return TokenMap.instance().splitString(s); } + /** + * Un-encode a string which is either a complete encoded token, or doesn't contain tokens at all + * + * It's illegal for the string to be a concatenation of an encoded token and something else. + */ + public static reverseCompleteString(s: string): IResolvable | undefined { + const fragments = Tokenization.reverseString(s); + if (fragments.length !== 1) { + throw new Error(`Tokenzation.reverseCompleteString: argument must not be a concatentation, got '${s}'`); + } + return fragments.firstToken; + } + /** * Un-encode a Tokenized value from a number */ @@ -146,6 +159,19 @@ export class Tokenization { return TokenMap.instance().lookupList(l); } + /** + * Reverse any value into a Resolvable, if possible + * + * In case of a string, the string must not be a concatenation. + */ + public static reverse(x: any): IResolvable | undefined { + if (Tokenization.isResolvable(x)) { return x; } + if (typeof x === 'string') { return Tokenization.reverseCompleteString(x); } + if (Array.isArray(x)) { return Tokenization.reverseList(x); } + if (typeof x === 'number') { return Tokenization.reverseNumber(x); } + return undefined; + } + /** * Resolves an object by evaluating all tokens and removing any undefined or empty objects or arrays. * Values can only be primitives, arrays or tokens. Other objects (i.e. with methods) will be rejected. diff --git a/packages/@aws-cdk/core/test/stack.test.ts b/packages/@aws-cdk/core/test/stack.test.ts index ceaf6a7c96e99..8891dbaa138c6 100644 --- a/packages/@aws-cdk/core/test/stack.test.ts +++ b/packages/@aws-cdk/core/test/stack.test.ts @@ -2,7 +2,9 @@ import * as cxapi from '@aws-cdk/cx-api'; import { testFutureBehavior, testLegacyBehavior } from 'cdk-build-tools/lib/feature-flag'; import { App, CfnCondition, CfnInclude, CfnOutput, CfnParameter, - CfnResource, Construct, Lazy, ScopedAws, Stack, validateString, ISynthesisSession, Tags, LegacyStackSynthesizer, DefaultStackSynthesizer, + CfnResource, Construct, Lazy, ScopedAws, Stack, validateString, + ISynthesisSession, Tags, LegacyStackSynthesizer, DefaultStackSynthesizer, + NestedStack, } from '../lib'; import { Intrinsic } from '../lib/private/intrinsic'; import { resolveReferences } from '../lib/private/refs'; @@ -535,7 +537,69 @@ describe('stack', () => { expect(assembly.getStackArtifact(child1.artifactId).dependencies.map((x: { id: any; }) => x.id)).toEqual([]); expect(assembly.getStackArtifact(child2.artifactId).dependencies.map((x: { id: any; }) => x.id)).toEqual(['ParentChild18FAEF419']); + }); + + test('automatic cross-stack references and manual exports look the same', () => { + // GIVEN: automatic + const appA = new App(); + const producerA = new Stack(appA, 'Producer'); + const consumerA = new Stack(appA, 'Consumer'); + const resourceA = new CfnResource(producerA, 'Resource', { type: 'AWS::Resource' }); + new CfnOutput(consumerA, 'SomeOutput', { value: `${resourceA.getAtt('Att')}` }); + + // GIVEN: manual + const appM = new App(); + const producerM = new Stack(appM, 'Producer'); + const resourceM = new CfnResource(producerM, 'Resource', { type: 'AWS::Resource' }); + producerM.exportValue(resourceM.getAtt('Att')); + + // THEN - producers are the same + const templateA = appA.synth().getStackByName(producerA.stackName).template; + const templateM = appM.synth().getStackByName(producerM.stackName).template; + + expect(templateA).toEqual(templateM); + }); + + test('automatic cross-stack references and manual exports look the same: nested stack edition', () => { + // GIVEN: automatic + const appA = new App(); + const producerA = new Stack(appA, 'Producer'); + const nestedA = new NestedStack(producerA, 'Nestor'); + const resourceA = new CfnResource(nestedA, 'Resource', { type: 'AWS::Resource' }); + + const consumerA = new Stack(appA, 'Consumer'); + new CfnOutput(consumerA, 'SomeOutput', { value: `${resourceA.getAtt('Att')}` }); + + // GIVEN: manual + const appM = new App(); + const producerM = new Stack(appM, 'Producer'); + const nestedM = new NestedStack(producerM, 'Nestor'); + const resourceM = new CfnResource(nestedM, 'Resource', { type: 'AWS::Resource' }); + producerM.exportValue(resourceM.getAtt('Att')); + + // THEN - producers are the same + const templateA = appA.synth().getStackByName(producerA.stackName).template; + const templateM = appM.synth().getStackByName(producerM.stackName).template; + + expect(templateA).toEqual(templateM); + }); + + test('manual exports require a name if not supplying a resource attribute', () => { + const app = new App(); + const stack = new Stack(app, 'Stack'); + + expect(() => { + stack.exportValue('someValue'); + }).toThrow(/or make sure to export a resource attribute/); + }); + + test('manual exports can also just be used to create an export of anything', () => { + const app = new App(); + const stack = new Stack(app, 'Stack'); + + const importV = stack.exportValue('someValue', { name: 'MyExport' }); + expect(stack.resolve(importV)).toEqual({ 'Fn::ImportValue': 'MyExport' }); }); test('CfnSynthesisError is ignored when preparing cross references', () => { From 898acfe16adc981cd4e660ca300bb728bfd6b51b Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 1 Feb 2021 07:33:04 -0800 Subject: [PATCH 40/70] docs(lambda): recommend NODEJS_12_X instead of NODEJS_10_X (#12713) This PR recommends NODEJS_12_X instead of NODEJS_10_X for deprecated versions of lambda.Runtime
Screenshot ![Screen Shot 2021-01-26 at 11 10 05 AM](https://user-images.githubusercontent.com/16024985/105892863-44109480-5fc7-11eb-9cb2-c91e8e13d9f3.png)
Reasons for recommending NODEJS_12_X: * Node.js 12.x went LTS in October 2019, and has been actively supported till October 2020. * Node.js 10.x is going end-of-life on April 30th, 2021 https://endoflife.date/nodejs ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda/lib/runtime.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/runtime.ts b/packages/@aws-cdk/aws-lambda/lib/runtime.ts index 1d98212628588..1f94401afa17f 100644 --- a/packages/@aws-cdk/aws-lambda/lib/runtime.ts +++ b/packages/@aws-cdk/aws-lambda/lib/runtime.ts @@ -43,28 +43,28 @@ export class Runtime { /** * The NodeJS runtime (nodejs) * - * @deprecated Use {@link NODEJS_10_X} + * @deprecated Use {@link NODEJS_12_X} */ public static readonly NODEJS = new Runtime('nodejs', RuntimeFamily.NODEJS, { supportsInlineCode: true }); /** * The NodeJS 4.3 runtime (nodejs4.3) * - * @deprecated Use {@link NODEJS_10_X} + * @deprecated Use {@link NODEJS_12_X} */ public static readonly NODEJS_4_3 = new Runtime('nodejs4.3', RuntimeFamily.NODEJS, { supportsInlineCode: true }); /** * The NodeJS 6.10 runtime (nodejs6.10) * - * @deprecated Use {@link NODEJS_10_X} + * @deprecated Use {@link NODEJS_12_X} */ public static readonly NODEJS_6_10 = new Runtime('nodejs6.10', RuntimeFamily.NODEJS, { supportsInlineCode: true }); /** * The NodeJS 8.10 runtime (nodejs8.10) * - * @deprecated Use {@link NODEJS_10_X} + * @deprecated Use {@link NODEJS_12_X} */ public static readonly NODEJS_8_10 = new Runtime('nodejs8.10', RuntimeFamily.NODEJS, { supportsInlineCode: true }); From 40b32bbda272b6e2f92fd5dd8de7ca5bf405ce52 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 1 Feb 2021 17:09:52 +0100 Subject: [PATCH 41/70] fix(cli, codepipeline): renamed bootstrap stack still not supported (#12771) Two mistakes in the previous attempt at fixing this (#12594): * There was a big fat `if (!bootstrapStack.found) { throw; }` line still in the middle of the code path. We had written an integ test to validate that the new situation would work, however the test was incorrect: it would create a non-default bootstrap stack, but if the account already happened to be default-bootstrapped before, the CLI would accidentally find that default bootstrap stack and use it, thereby never triggering the offending line. * The `BootsraplessSynthesizer` set `requiresBootstrapStackVersion`, which is pretty silly. This synthesizer was being used by CodePipeline's cross-region support stacks, so for cross-region deployments we would still require a bootstrap stack. Both of these are fixed and the test has been updated to force the CLI to look up a definitely nonexistent bootstrap stack. Fixes #12732. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-codepipeline/test/cross-env.test.ts | 212 +++++++++--------- .../bootstrapless-synthesizer.ts | 1 - .../lib/api/cloudformation-deployments.ts | 4 - packages/aws-cdk/lib/api/cxapp/exec.ts | 2 +- .../api/cloudformation-deployments.test.ts | 2 +- .../test/integ/cli/bootstrapping.integtest.ts | 5 +- 6 files changed, 114 insertions(+), 112 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts b/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts index 917f4c833858f..08fd0160b90f4 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/cross-env.test.ts @@ -6,130 +6,136 @@ import * as codepipeline from '../lib'; import { FakeBuildAction } from './fake-build-action'; import { FakeSourceAction } from './fake-source-action'; -let app: App; -let stack: Stack; -let sourceArtifact: codepipeline.Artifact; -let initialStages: codepipeline.StageProps[]; - -beforeEach(() => { - app = new App(); - stack = new Stack(app, 'PipelineStack', { env: { account: '2222', region: 'us-east-1' } }); - sourceArtifact = new codepipeline.Artifact(); - initialStages = [ - { - stageName: 'Source', - actions: [new FakeSourceAction({ - actionName: 'Source', - output: sourceArtifact, - })], - }, - { - stageName: 'Build', - actions: [new FakeBuildAction({ - actionName: 'Build', - input: sourceArtifact, - })], - }, - ]; -}); - -describe('crossAccountKeys=false', () => { - let pipeline: codepipeline.Pipeline; +describe.each(['legacy', 'modern'])('with %s synthesis', (synthesisStyle: string) => { + let app: App; + let stack: Stack; + let sourceArtifact: codepipeline.Artifact; + let initialStages: codepipeline.StageProps[]; + beforeEach(() => { - pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { - crossAccountKeys: false, - stages: initialStages, + app = new App({ + context: { + ...synthesisStyle === 'modern' ? { '@aws-cdk/core:newStyleStackSynthesis': true } : undefined, + }, }); + stack = new Stack(app, 'PipelineStack', { env: { account: '2222', region: 'us-east-1' } }); + sourceArtifact = new codepipeline.Artifact(); + initialStages = [ + { + stageName: 'Source', + actions: [new FakeSourceAction({ + actionName: 'Source', + output: sourceArtifact, + })], + }, + { + stageName: 'Build', + actions: [new FakeBuildAction({ + actionName: 'Build', + input: sourceArtifact, + })], + }, + ]; }); - test('creates a bucket but no keys', () => { - // THEN - expect(stack).not.toHaveResource('AWS::KMS::Key'); - expect(stack).toHaveResource('AWS::S3::Bucket'); - }); - - describe('prevents adding a cross-account action', () => { - const expectedError = 'crossAccountKeys: true'; - - let stage: codepipeline.IStage; + describe('crossAccountKeys=false', () => { + let pipeline: codepipeline.Pipeline; beforeEach(() => { - stage = pipeline.addStage({ stageName: 'Deploy' }); + pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { + crossAccountKeys: false, + stages: initialStages, + }); }); - test('by role', () => { - // WHEN - expect(() => { - stage.addAction(new FakeBuildAction({ - actionName: 'Deploy', - input: sourceArtifact, - role: iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::1111:role/some-role'), - })); - }).toThrow(expectedError); + test('creates a bucket but no keys', () => { + // THEN + expect(stack).not.toHaveResource('AWS::KMS::Key'); + expect(stack).toHaveResource('AWS::S3::Bucket'); }); - test('by resource', () => { - // WHEN - expect(() => { - stage.addAction(new FakeBuildAction({ - actionName: 'Deploy', - input: sourceArtifact, - resource: s3.Bucket.fromBucketAttributes(stack, 'Bucket', { - bucketName: 'foo', + describe('prevents adding a cross-account action', () => { + const expectedError = 'crossAccountKeys: true'; + + let stage: codepipeline.IStage; + beforeEach(() => { + stage = pipeline.addStage({ stageName: 'Deploy' }); + }); + + test('by role', () => { + // WHEN + expect(() => { + stage.addAction(new FakeBuildAction({ + actionName: 'Deploy', + input: sourceArtifact, + role: iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::1111:role/some-role'), + })); + }).toThrow(expectedError); + }); + + test('by resource', () => { + // WHEN + expect(() => { + stage.addAction(new FakeBuildAction({ + actionName: 'Deploy', + input: sourceArtifact, + resource: s3.Bucket.fromBucketAttributes(stack, 'Bucket', { + bucketName: 'foo', + account: '1111', + }), + })); + }).toThrow(expectedError); + }); + + test('by declared account', () => { + // WHEN + expect(() => { + stage.addAction(new FakeBuildAction({ + actionName: 'Deploy', + input: sourceArtifact, account: '1111', - }), - })); - }).toThrow(expectedError); + })); + }).toThrow(expectedError); + }); }); - test('by declared account', () => { - // WHEN - expect(() => { + describe('also affects cross-region support stacks', () => { + let stage: codepipeline.IStage; + beforeEach(() => { + stage = pipeline.addStage({ stageName: 'Deploy' }); + }); + + test('when making a support stack', () => { + // WHEN stage.addAction(new FakeBuildAction({ actionName: 'Deploy', input: sourceArtifact, - account: '1111', + // No resource to grab onto forces creating a fresh support stack + region: 'eu-west-1', })); - }).toThrow(expectedError); - }); - }); - describe('also affects cross-region support stacks', () => { - let stage: codepipeline.IStage; - beforeEach(() => { - stage = pipeline.addStage({ stageName: 'Deploy' }); - }); - - test('when making a support stack', () => { - // WHEN - stage.addAction(new FakeBuildAction({ - actionName: 'Deploy', - input: sourceArtifact, - // No resource to grab onto forces creating a fresh support stack - region: 'eu-west-1', - })); - - // THEN - const asm = app.synth(); - const supportStack = asm.getStack(`${stack.stackName}-support-eu-west-1`); + // THEN + const asm = app.synth(); + const supportStack = asm.getStack(`${stack.stackName}-support-eu-west-1`); - // THEN - expect(supportStack).not.toHaveResource('AWS::KMS::Key'); - expect(supportStack).toHaveResource('AWS::S3::Bucket'); - }); + // THEN + expect(supportStack).not.toHaveResource('AWS::KMS::Key'); + expect(supportStack).toHaveResource('AWS::S3::Bucket'); + }); - test('when twiddling another stack', () => { - const stack2 = new Stack(app, 'Stack2', { env: { account: '2222', region: 'eu-west-1' } }); + test('when twiddling another stack', () => { + const stack2 = new Stack(app, 'Stack2', { env: { account: '2222', region: 'eu-west-1' } }); - // WHEN - stage.addAction(new FakeBuildAction({ - actionName: 'Deploy', - input: sourceArtifact, - resource: new iam.User(stack2, 'DoesntMatterWhatThisIs'), - })); + // WHEN + stage.addAction(new FakeBuildAction({ + actionName: 'Deploy', + input: sourceArtifact, + resource: new iam.User(stack2, 'DoesntMatterWhatThisIs'), + })); - // THEN - expect(stack2).not.toHaveResource('AWS::KMS::Key'); - expect(stack2).toHaveResource('AWS::S3::Bucket'); + // THEN + expect(stack2).not.toHaveResource('AWS::KMS::Key'); + expect(stack2).toHaveResource('AWS::S3::Bucket'); + }); }); }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/bootstrapless-synthesizer.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/bootstrapless-synthesizer.ts index 16ea69c1b2302..1a9a2ab8ee0cc 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/bootstrapless-synthesizer.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/bootstrapless-synthesizer.ts @@ -57,7 +57,6 @@ export class BootstraplessSynthesizer extends DefaultStackSynthesizer { this.emitStackArtifact(this.stack, session, { assumeRoleArn: this.deployRoleArn, cloudFormationExecutionRoleArn: this.cloudFormationExecutionRoleArn, - requiresBootstrapStackVersion: 1, }); } } diff --git a/packages/aws-cdk/lib/api/cloudformation-deployments.ts b/packages/aws-cdk/lib/api/cloudformation-deployments.ts index 2eec90afdb790..3fe5fed118a76 100644 --- a/packages/aws-cdk/lib/api/cloudformation-deployments.ts +++ b/packages/aws-cdk/lib/api/cloudformation-deployments.ts @@ -282,10 +282,6 @@ export class CloudFormationDeployments { if (requiresBootstrapStackVersion === undefined) { return; } - if (!toolkitInfo.found) { - throw new Error(`${stackName}: publishing assets requires bootstrap stack version '${requiresBootstrapStackVersion}', no bootstrap stack found. Please run 'cdk bootstrap'.`); - } - try { await toolkitInfo.validateVersion(requiresBootstrapStackVersion, bootstrapStackVersionSsmParameter); } catch (e) { diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index 376ee542f4580..facaf24a3bfe0 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -92,7 +92,7 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom } async function exec() { - return new Promise((ok, fail) => { + return new Promise((ok, fail) => { // We use a slightly lower-level interface to: // // - Pass arguments in an array instead of a string, to get around a diff --git a/packages/aws-cdk/test/api/cloudformation-deployments.test.ts b/packages/aws-cdk/test/api/cloudformation-deployments.test.ts index 7afea33be6a0b..7a4e29628564c 100644 --- a/packages/aws-cdk/test/api/cloudformation-deployments.test.ts +++ b/packages/aws-cdk/test/api/cloudformation-deployments.test.ts @@ -76,7 +76,7 @@ test('deployment fails if bootstrap stack is missing', async () => { requiresBootstrapStackVersion: 99, }, }), - })).rejects.toThrow(/no bootstrap stack found/); + })).rejects.toThrow(/requires a bootstrap stack/); }); test('deployment fails if bootstrap stack is too old', async () => { diff --git a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts index e11e3b48bfcc0..c6ba2e53ec2d8 100644 --- a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts @@ -259,8 +259,9 @@ integTest('can deploy modern-synthesized stack even if bootstrap stack name is u // Deploy stack that uses file assets await fixture.cdkDeploy('lambda', { options: [ - // Next line explicitly commented to show that we don't pass it! - // '--toolkit-stack-name', bootstrapStackName, + // Explicity pass a name that's sure to not exist, otherwise the CLI might accidentally find a + // default bootstracp stack if that happens to be in the account already. + '--toolkit-stack-name', 'DefinitelyDoesNotExist', '--context', `@aws-cdk/core:bootstrapQualifier=${fixture.qualifier}`, '--context', '@aws-cdk/core:newStyleStackSynthesis=1', ], From 3264a7b9cb4df454b69b5ee60e409cdfbfe8817a Mon Sep 17 00:00:00 2001 From: Clarence Date: Tue, 2 Feb 2021 02:35:15 +0800 Subject: [PATCH 42/70] docs(route53): route53 fromHostedZoneId doc error (#12805) `fromHostedZoneId` is use parameter not object https://github.com/aws/aws-cdk/blob/3b66088010b6f2315a215e92505d5279680f16d4/packages/%40aws-cdk/aws-route53/lib/hosted-zone.ts#L70 So I fix doc ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-route53/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-route53/README.md b/packages/@aws-cdk/aws-route53/README.md index 49947e4791092..b9f75fb1b9d4e 100644 --- a/packages/@aws-cdk/aws-route53/README.md +++ b/packages/@aws-cdk/aws-route53/README.md @@ -169,9 +169,7 @@ Alternatively, use the `HostedZone.fromHostedZoneId` to import hosted zones if you know the ID and the retrieval for the `zoneName` is undesirable. ```ts -const zone = HostedZone.fromHostedZoneId(this, 'MyZone', { - hostedZoneId: 'ZOJJZC49E0EPZ', -}); +const zone = HostedZone.fromHostedZoneId(this, 'MyZone', 'ZOJJZC49E0EPZ'); ``` ## VPC Endpoint Service Private DNS From 9a81faaafb46512acae917b3374ed1ffb12ba744 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 2 Feb 2021 06:24:44 +0000 Subject: [PATCH 43/70] chore(eslint-plugin-cdk): tests for rule no-qualified-construct (#12809) Add missing tests ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../eslint-plugin-cdk/test/rules/eslintrc.js | 12 ---- .../test/rules/fixtures.test.ts | 63 +++++++++++++++++++ .../qualified-heritage.expected.ts | 8 +++ .../qualified-heritage.ts | 4 ++ .../qualified-usage.expected.ts | 4 ++ .../no-qualified-construct/qualified-usage.ts | 3 + .../test/rules/no-core-construct.test.ts | 44 ------------- 7 files changed, 82 insertions(+), 56 deletions(-) delete mode 100644 tools/eslint-plugin-cdk/test/rules/eslintrc.js create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures.test.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-heritage.expected.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-heritage.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.expected.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.ts delete mode 100644 tools/eslint-plugin-cdk/test/rules/no-core-construct.test.ts diff --git a/tools/eslint-plugin-cdk/test/rules/eslintrc.js b/tools/eslint-plugin-cdk/test/rules/eslintrc.js deleted file mode 100644 index c68b2066acce3..0000000000000 --- a/tools/eslint-plugin-cdk/test/rules/eslintrc.js +++ /dev/null @@ -1,12 +0,0 @@ -const path = require('path'); -const rulesDirPlugin = require('eslint-plugin-rulesdir'); -rulesDirPlugin.RULES_DIR = path.join(__dirname, '../../lib/rules'); - -module.exports = { - parser: '@typescript-eslint/parser', - plugins: ['rulesdir'], - rules: { - quotes: [ 'error', 'single', { avoidEscape: true }], - 'rulesdir/no-core-construct': [ 'error' ], - } -} diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures.test.ts b/tools/eslint-plugin-cdk/test/rules/fixtures.test.ts new file mode 100644 index 0000000000000..89f0568048fbd --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures.test.ts @@ -0,0 +1,63 @@ +import { ESLint } from 'eslint'; +import * as fs from 'fs-extra'; +import * as path from 'path'; + +const rulesDirPlugin = require('eslint-plugin-rulesdir'); +rulesDirPlugin.RULES_DIR = path.join(__dirname, '../../lib/rules'); + +const linter = new ESLint({ + baseConfig: { + parser: '@typescript-eslint/parser', + plugins: ['rulesdir'], + rules: { + quotes: [ 'error', 'single', { avoidEscape: true }], + 'rulesdir/no-core-construct': [ 'error' ], + 'rulesdir/no-qualified-construct': [ 'error' ], + } + }, + rulePaths: [ + path.join(__dirname, '../../lib/rules'), + ], + fix: true, +}); + +const outputRoot = path.join(process.cwd(), '.test-output'); +fs.mkdirpSync(outputRoot); + +const fixturesRoot = path.join(__dirname, 'fixtures'); + +fs.readdirSync(fixturesRoot).filter(f => fs.lstatSync(path.join(fixturesRoot, f)).isDirectory()).forEach(d => { + describe(d, () => { + const outputDir = path.join(outputRoot, d); + fs.mkdirpSync(outputDir); + + const fixturesDir = path.join(fixturesRoot, d); + const fixtureFiles = fs.readdirSync(fixturesDir).filter(f => f.endsWith('.ts') && !f.endsWith('.expected.ts')); + + fixtureFiles.forEach(f => { + test(f, async (done) => { + const actualFile = await lintAndFix(path.join(fixturesDir, f), outputDir); + const expectedFile = path.join(fixturesDir, `${path.basename(f, '.ts')}.expected.ts`); + if (!fs.existsSync(expectedFile)) { + done.fail(`Expected file not found. Generated output at ${actualFile}`); + } + const actual = await fs.readFile(actualFile, { encoding: 'utf8' }); + const expected = await fs.readFile(expectedFile, { encoding: 'utf8' }); + if (actual !== expected) { + done.fail(`Linted file did not match expectations. Expected: ${expectedFile}. Actual: ${actualFile}`); + } + done(); + }); + }); + }); +}); + +async function lintAndFix(file: string, outputDir: string) { + const newPath = path.join(outputDir, path.basename(file)) + let result = await linter.lintFiles(file); + await ESLint.outputFixes(result.map(r => { + r.filePath = newPath; + return r; + })); + return newPath; +} diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-heritage.expected.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-heritage.expected.ts new file mode 100644 index 0000000000000..6510d7dd5542f --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-heritage.expected.ts @@ -0,0 +1,8 @@ +import * as cdk from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + +class MyConstruct extends Construct { +} \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-heritage.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-heritage.ts new file mode 100644 index 0000000000000..3f8b877e32c2e --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-heritage.ts @@ -0,0 +1,4 @@ +import * as cdk from '@aws-cdk/core'; + +class MyConstruct extends cdk.Construct { +} \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.expected.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.expected.ts new file mode 100644 index 0000000000000..af7c0f393f307 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.expected.ts @@ -0,0 +1,4 @@ +import * as cdk from '@aws-cdk/core'; +import * as constructs from 'constructs'; + +let x: constructs.Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.ts new file mode 100644 index 0000000000000..d2ebc12dc01ff --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.ts @@ -0,0 +1,3 @@ +import * as cdk from '@aws-cdk/core'; + +let x: cdk.Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/no-core-construct.test.ts b/tools/eslint-plugin-cdk/test/rules/no-core-construct.test.ts deleted file mode 100644 index c2272cfd39353..0000000000000 --- a/tools/eslint-plugin-cdk/test/rules/no-core-construct.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ESLint } from 'eslint'; -import * as fs from 'fs-extra'; -import * as path from 'path'; - -const linter = new ESLint({ - overrideConfigFile: path.join(__dirname, 'eslintrc.js'), - rulePaths: [ - path.join(__dirname, '../../lib/rules'), - ], - fix: true, -}); - -const outputDir = path.join(process.cwd(), '.test-output'); -fs.mkdirpSync(outputDir); -const fixturesDir = path.join(__dirname, 'fixtures', 'no-core-construct'); - -describe('no-core-construct', () => { - const fixtureFiles = fs.readdirSync(fixturesDir).filter(f => f.endsWith('.ts') && !f.endsWith('.expected.ts')); - fixtureFiles.forEach(f => { - test(f, async (done) => { - const actualFile = await lintAndFix(path.join(fixturesDir, f)); - const expectedFile = path.join(fixturesDir, `${path.basename(f, '.ts')}.expected.ts`); - if (!fs.existsSync(expectedFile)) { - done.fail(`Expected file not found. Generated output at ${actualFile}`); - } - const actual = await fs.readFile(actualFile, { encoding: 'utf8' }); - const expected = await fs.readFile(expectedFile, { encoding: 'utf8' }); - if (actual !== expected) { - done.fail(`Linted file did not match expectations. Expected: ${expectedFile}. Actual: ${actualFile}`); - } - done(); - }); - }); -}); - -async function lintAndFix(file: string) { - const newPath = path.join(outputDir, path.basename(file)) - let result = await linter.lintFiles(file); - await ESLint.outputFixes(result.map(r => { - r.filePath = newPath; - return r; - })); - return newPath; -} From 721344b592c93094a278ed5525be8cba2a549762 Mon Sep 17 00:00:00 2001 From: Christoph Gysin Date: Tue, 2 Feb 2021 19:05:18 +0200 Subject: [PATCH 44/70] chore(stepfunctions): Fix examples (#12824) fixes #12823 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-stepfunctions/README.md | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 1fb164cc6e8e2..ae6635358d0ec 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -84,9 +84,9 @@ definition. The definition is specified by its start state, and encompasses all states reachable from the start state: ```ts -const startState = new stepfunctions.Pass(this, 'StartState'); +const startState = new sfn.Pass(this, 'StartState'); -new stepfunctions.StateMachine(this, 'StateMachine', { +new sfn.StateMachine(this, 'StateMachine', { definition: startState }); ``` @@ -138,8 +138,8 @@ will be passed as the state's output. ```ts // Makes the current JSON state { ..., "subObject": { "hello": "world" } } -const pass = new stepfunctions.Pass(this, 'Add Hello World', { - result: stepfunctions.Result.fromObject({ hello: 'world' }), +const pass = new sfn.Pass(this, 'Add Hello World', { + result: sfn.Result.fromObject({ hello: 'world' }), resultPath: '$.subObject', }); @@ -154,9 +154,9 @@ The following example filters the `greeting` field from the state input and also injects a field called `otherData`. ```ts -const pass = new stepfunctions.Pass(this, 'Filter input and inject data', { +const pass = new sfn.Pass(this, 'Filter input and inject data', { parameters: { // input to the pass state - input: stepfunctions.JsonPath.stringAt('$.input.greeting'), + input: sfn.JsonPath.stringAt('$.input.greeting'), otherData: 'some-extra-stuff' }, }); @@ -177,8 +177,8 @@ state. ```ts // Wait until it's the time mentioned in the the state object's "triggerTime" // field. -const wait = new stepfunctions.Wait(this, 'Wait For Trigger Time', { - time: stepfunctions.WaitTime.timestampPath('$.triggerTime'), +const wait = new sfn.Wait(this, 'Wait For Trigger Time', { + time: sfn.WaitTime.timestampPath('$.triggerTime'), }); // Set the next state @@ -191,11 +191,11 @@ A `Choice` state can take a different path through the workflow based on the values in the execution's JSON state: ```ts -const choice = new stepfunctions.Choice(this, 'Did it work?'); +const choice = new sfn.Choice(this, 'Did it work?'); // Add conditions with .when() -choice.when(stepfunctions.Condition.stringEqual('$.status', 'SUCCESS'), successState); -choice.when(stepfunctions.Condition.numberGreaterThan('$.attempts', 5), failureState); +choice.when(sfn.Condition.stringEquals('$.status', 'SUCCESS'), successState); +choice.when(sfn.Condition.numberGreaterThan('$.attempts', 5), failureState); // Use .otherwise() to indicate what should be done if none of the conditions match choice.otherwise(tryAgainState); @@ -206,9 +206,9 @@ all branches come together and continuing as one (similar to how an `if ... then ... else` works in a programming language), use the `.afterwards()` method: ```ts -const choice = new stepfunctions.Choice(this, 'What color is it?'); -choice.when(stepfunctions.Condition.stringEqual('$.color', 'BLUE'), handleBlueItem); -choice.when(stepfunctions.Condition.stringEqual('$.color', 'RED'), handleRedItem); +const choice = new sfn.Choice(this, 'What color is it?'); +choice.when(sfn.Condition.stringEquals('$.color', 'BLUE'), handleBlueItem); +choice.when(sfn.Condition.stringEquals('$.color', 'RED'), handleRedItem); choice.otherwise(handleOtherItemColor); // Use .afterwards() to join all possible paths back together and continue @@ -275,7 +275,7 @@ A `Parallel` state executes one or more subworkflows in parallel. It can also be used to catch and recover from errors in subworkflows. ```ts -const parallel = new stepfunctions.Parallel(this, 'Do the work in parallel'); +const parallel = new sfn.Parallel(this, 'Do the work in parallel'); // Add branches to be executed in parallel parallel.branch(shipItem); @@ -298,7 +298,7 @@ Reaching a `Succeed` state terminates the state machine execution with a succesful status. ```ts -const success = new stepfunctions.Succeed(this, 'We did it!'); +const success = new sfn.Succeed(this, 'We did it!'); ``` ### Fail @@ -308,7 +308,7 @@ failure status. The fail state should report the reason for the failure. Failures can be caught by encompassing `Parallel` states. ```ts -const success = new stepfunctions.Fail(this, 'Fail', { +const success = new sfn.Fail(this, 'Fail', { error: 'WorkflowFailure', cause: "Something went wrong" }); @@ -323,11 +323,11 @@ While the `Parallel` state executes multiple branches of steps using the same in execute the same steps for multiple entries of an array in the state input. ```ts -const map = new stepfunctions.Map(this, 'Map State', { +const map = new sfn.Map(this, 'Map State', { maxConcurrency: 1, - itemsPath: stepfunctions.JsonPath.stringAt('$.inputForMap') + itemsPath: sfn.JsonPath.stringAt('$.inputForMap') }); -map.iterator(new stepfunctions.Pass(this, 'Pass State')); +map.iterator(new sfn.Pass(this, 'Pass State')); ``` ### Custom State @@ -420,7 +420,7 @@ const definition = step1 .branch(step9.next(step10))) .next(finish); -new stepfunctions.StateMachine(this, 'StateMachine', { +new sfn.StateMachine(this, 'StateMachine', { definition, }); ``` @@ -429,14 +429,13 @@ If you don't like the visual look of starting a chain directly off the first step, you can use `Chain.start`: ```ts -const definition = stepfunctions.Chain +const definition = sfn.Chain .start(step1) .next(step2) .next(step3) // ... ``` - ## State Machine Fragments It is possible to define reusable (or abstracted) mini-state machines by @@ -461,16 +460,16 @@ interface MyJobProps { jobFlavor: string; } -class MyJob extends stepfunctions.StateMachineFragment { - public readonly startState: State; - public readonly endStates: INextable[]; +class MyJob extends sfn.StateMachineFragment { + public readonly startState: sfn.State; + public readonly endStates: sfn.INextable[]; constructor(parent: cdk.Construct, id: string, props: MyJobProps) { super(parent, id); - const first = new stepfunctions.Task(this, 'First', { ... }); + const first = new sfn.Task(this, 'First', { ... }); // ... - const last = new stepfunctions.Task(this, 'Last', { ... }); + const last = new sfn.Task(this, 'Last', { ... }); this.startState = first; this.endStates = [last]; @@ -478,7 +477,7 @@ class MyJob extends stepfunctions.StateMachineFragment { } // Do 3 different variants of MyJob in parallel -new stepfunctions.Parallel(this, 'All jobs') +new sfn.Parallel(this, 'All jobs') .branch(new MyJob(this, 'Quick', { jobFlavor: 'quick' }).prefixStates()) .branch(new MyJob(this, 'Medium', { jobFlavor: 'medium' }).prefixStates()) .branch(new MyJob(this, 'Slow', { jobFlavor: 'slow' }).prefixStates()); @@ -500,7 +499,7 @@ You need the ARN to do so, so if you use Activities be sure to pass the Activity ARN into your worker pool: ```ts -const activity = new stepfunctions.Activity(this, 'Activity'); +const activity = new sfn.Activity(this, 'Activity'); // Read this CloudFormation Output from your application and use it to poll for work on // the activity. @@ -512,7 +511,7 @@ new cdk.CfnOutput(this, 'ActivityArn', { value: activity.activityArn }); Granting IAM permissions to an activity can be achieved by calling the `grant(principal, actions)` API: ```ts -const activity = new stepfunctions.Activity(this, 'Activity'); +const activity = new sfn.Activity(this, 'Activity'); const role = new iam.Role(stack, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), @@ -564,11 +563,11 @@ destination LogGroup: ```ts const logGroup = new logs.LogGroup(stack, 'MyLogGroup'); -new stepfunctions.StateMachine(stack, 'MyStateMachine', { - definition: stepfunctions.Chain.start(new stepfunctions.Pass(stack, 'Pass')), +new sfn.StateMachine(stack, 'MyStateMachine', { + definition: sfn.Chain.start(new sfn.Pass(stack, 'Pass')), logs: { destination: logGroup, - level: stepfunctions.LogLevel.ALL, + level: sfn.LogLevel.ALL, } }); ``` @@ -580,8 +579,8 @@ Enable X-Ray tracing for StateMachine: ```ts const logGroup = new logs.LogGroup(stack, 'MyLogGroup'); -new stepfunctions.StateMachine(stack, 'MyStateMachine', { - definition: stepfunctions.Chain.start(new stepfunctions.Pass(stack, 'Pass')), +new sfn.StateMachine(stack, 'MyStateMachine', { + definition: sfn.Chain.start(new sfn.Pass(stack, 'Pass')), tracingEnabled: true }); ``` From 06b6d820b4ad7f913b8538bab63ce3a42af4be8f Mon Sep 17 00:00:00 2001 From: Christoph Gysin Date: Tue, 2 Feb 2021 20:43:45 +0200 Subject: [PATCH 45/70] chore(s3): Remove unused KMS key in example (#12826) fixes #12825 ---- *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-s3/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/@aws-cdk/aws-s3/README.md b/packages/@aws-cdk/aws-s3/README.md index 1ce00f56ba0a9..12ae88bb02d2e 100644 --- a/packages/@aws-cdk/aws-s3/README.md +++ b/packages/@aws-cdk/aws-s3/README.md @@ -67,8 +67,6 @@ assert(bucket.encryptionKey === myKmsKey); Enable KMS-SSE encryption via [S3 Bucket Keys](https://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-key.html): ```ts -const myKmsKey = new kms.Key(this, 'MyKey'); - const bucket = new Bucket(this, 'MyEncryptedBucket', { encryption: BucketEncryption.KMS, bucketKeyEnabled: true From 0963f786e49fc3cef9cc5c4cde3ecba5540a2749 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Tue, 2 Feb 2021 12:06:21 -0800 Subject: [PATCH 46/70] revert: revert "chore: add new interfaces for Assets (#12700)" (#12832) Needs reverting because of https://github.com/aws/jsii/issues/2256 . This reverts commit 1a9f2a81. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/assets/lib/fs/options.ts | 1 - .../aws-ecr-assets/lib/image-asset.ts | 19 ++----- packages/@aws-cdk/aws-s3-assets/lib/asset.ts | 4 +- packages/@aws-cdk/core/lib/fs/options.ts | 51 +++++-------------- 4 files changed, 19 insertions(+), 56 deletions(-) diff --git a/packages/@aws-cdk/assets/lib/fs/options.ts b/packages/@aws-cdk/assets/lib/fs/options.ts index 548fa4bda42ee..3ccc107d3700d 100644 --- a/packages/@aws-cdk/assets/lib/fs/options.ts +++ b/packages/@aws-cdk/assets/lib/fs/options.ts @@ -10,7 +10,6 @@ export interface CopyOptions { * A strategy for how to handle symlinks. * * @default Never - * @deprecated use `followSymlinks` instead */ readonly follow?: FollowMode; diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts index 91d3f06b5f6a2..2f6f5ff436baa 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts @@ -2,16 +2,14 @@ import * as fs from 'fs'; import * as path from 'path'; import * as assets from '@aws-cdk/assets'; import * as ecr from '@aws-cdk/aws-ecr'; -import { - Annotations, AssetStaging, Construct as CoreConstruct, FeatureFlags, FileFingerprintOptions, IgnoreMode, Stack, SymlinkFollowMode, Token, -} from '@aws-cdk/core'; +import { Annotations, Construct as CoreConstruct, FeatureFlags, IgnoreMode, Stack, Token } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; /** * Options for DockerImageAsset */ -export interface DockerImageAssetOptions extends assets.FingerprintOptions, FileFingerprintOptions { +export interface DockerImageAssetOptions extends assets.FingerprintOptions { /** * ECR repository name * @@ -139,9 +137,8 @@ export class DockerImageAsset extends CoreConstruct implements assets.IAsset { // deletion of the ECR repository the app used). extraHash.version = '1.21.0'; - const staging = new AssetStaging(this, 'Staging', { + const staging = new assets.Staging(this, 'Staging', { ...props, - follow: props.followSymlinks ?? toSymlinkFollow(props.follow), exclude, ignoreMode, sourcePath: dir, @@ -184,13 +181,3 @@ function validateBuildArgs(buildArgs?: { [key: string]: string }) { } } } - -function toSymlinkFollow(follow?: assets.FollowMode): SymlinkFollowMode | undefined { - switch (follow) { - case undefined: return undefined; - case assets.FollowMode.NEVER: return SymlinkFollowMode.NEVER; - case assets.FollowMode.ALWAYS: return SymlinkFollowMode.ALWAYS; - case assets.FollowMode.BLOCK_EXTERNAL: return SymlinkFollowMode.BLOCK_EXTERNAL; - case assets.FollowMode.EXTERNAL: return SymlinkFollowMode.EXTERNAL; - } -} diff --git a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts index d674d083b248b..938778d1381f4 100644 --- a/packages/@aws-cdk/aws-s3-assets/lib/asset.ts +++ b/packages/@aws-cdk/aws-s3-assets/lib/asset.ts @@ -15,7 +15,7 @@ import { Construct as CoreConstruct } from '@aws-cdk/core'; const ARCHIVE_EXTENSIONS = ['.zip', '.jar']; -export interface AssetOptions extends assets.CopyOptions, cdk.FileCopyOptions, cdk.AssetOptions { +export interface AssetOptions extends assets.CopyOptions, cdk.AssetOptions { /** * A list of principals that should be able to read this asset from S3. * You can use `asset.grantRead(principal)` to grant read permissions later. @@ -128,7 +128,7 @@ export class Asset extends CoreConstruct implements cdk.IAsset { const staging = new cdk.AssetStaging(this, 'Stage', { ...props, sourcePath: path.resolve(props.path), - follow: props.followSymlinks ?? toSymlinkFollow(props.follow), + follow: toSymlinkFollow(props.follow), assetHash: props.assetHash ?? props.sourceHash, }); diff --git a/packages/@aws-cdk/core/lib/fs/options.ts b/packages/@aws-cdk/core/lib/fs/options.ts index baf73bd7ffd30..3ea836a24e831 100644 --- a/packages/@aws-cdk/core/lib/fs/options.ts +++ b/packages/@aws-cdk/core/lib/fs/options.ts @@ -56,9 +56,19 @@ export enum IgnoreMode { * context flag is set. */ DOCKER = 'docker' -} +}; + +/** + * Obtains applied when copying directories into the staging location. + */ +export interface CopyOptions { + /** + * A strategy for how to handle symlinks. + * + * @default SymlinkFollowMode.NEVER + */ + readonly follow?: SymlinkFollowMode; -interface FileOptions { /** * Glob patterns to exclude from the copy. * @@ -75,30 +85,9 @@ interface FileOptions { } /** - * Options applied when copying directories - */ -export interface CopyOptions extends FileOptions { - /** - * A strategy for how to handle symlinks. - * - * @default SymlinkFollowMode.NEVER - */ - readonly follow?: SymlinkFollowMode; -} - -/** - * Options applied when copying directories into the staging location. + * Options related to calculating source hash. */ -export interface FileCopyOptions extends FileOptions { - /** - * A strategy for how to handle symlinks. - * - * @default SymlinkFollowMode.NEVER - */ - readonly followSymlinks?: SymlinkFollowMode; -} - -interface ExtraHashOptions { +export interface FingerprintOptions extends CopyOptions { /** * Extra information to encode into the fingerprint (e.g. build instructions * and other inputs) @@ -107,15 +96,3 @@ interface ExtraHashOptions { */ readonly extraHash?: string; } - -/** - * Options related to calculating source hash. - */ -export interface FingerprintOptions extends CopyOptions, ExtraHashOptions { -} - -/** - * Options related to calculating source hash. - */ -export interface FileFingerprintOptions extends FileCopyOptions, ExtraHashOptions { -} From 59cb6d032a55515ec5e9903f899de588d18d4cb5 Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Wed, 3 Feb 2021 03:02:18 -0700 Subject: [PATCH 47/70] feat(cloudfront): add PublicKey and KeyGroup L2 constructs (#12743) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @njlynch This is my humble start on creating L2 constructs for `PublicKey` and `KeyGroup` for CloudFront module. I'm going to need some guidance/mentorship as this is my first L2 construct from the scratch. I'll convert this PR to draft and I'll post some of my thoughts and ideas around this feature tomorrow. I'm trying to address feature requests in https://github.com/aws/aws-cdk/issues/11791. I've decided to lump `PublicKey` and `KeyGroup` features together as they seem to depend on each other. All in the good spirits of learning how to extend CDK 🍻 . Any ideas and/or constructive criticism is more than welcome... that's the best way to learn.✌️ ---- *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-cloudfront/README.md | 37 ++++ packages/@aws-cdk/aws-cloudfront/lib/index.ts | 2 + .../@aws-cdk/aws-cloudfront/lib/key-group.ts | 75 +++++++ .../@aws-cdk/aws-cloudfront/lib/public-key.ts | 83 ++++++++ packages/@aws-cdk/aws-cloudfront/package.json | 4 +- .../integ.cloudfront-key-group.expected.json | 27 +++ .../test/integ.cloudfront-key-group.ts | 25 +++ .../aws-cloudfront/test/key-group.test.ts | 187 ++++++++++++++++++ .../aws-cloudfront/test/public-key.test.ts | 85 ++++++++ 9 files changed, 524 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk/aws-cloudfront/lib/key-group.ts create mode 100644 packages/@aws-cdk/aws-cloudfront/lib/public-key.ts create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-key-group.expected.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-key-group.ts create mode 100644 packages/@aws-cdk/aws-cloudfront/test/key-group.test.ts create mode 100644 packages/@aws-cdk/aws-cloudfront/test/public-key.test.ts diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index f42d6a15f7abc..1355d7a64d31d 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -520,3 +520,40 @@ new CloudFrontWebDistribution(stack, 'ADistribution', { ], }); ``` + +## KeyGroup & PublicKey API + +Now you can create a key group to use with CloudFront signed URLs and signed cookies. You can add public keys to use with CloudFront features such as signed URLs, signed cookies, and field-level encryption. + +The following example command uses OpenSSL to generate an RSA key pair with a length of 2048 bits and save to the file named `private_key.pem`. + +```bash +openssl genrsa -out private_key.pem 2048 +``` + +The resulting file contains both the public and the private key. The following example command extracts the public key from the file named `private_key.pem` and stores it in `public_key.pem`. + +```bash +openssl rsa -pubout -in private_key.pem -out public_key.pem +``` + +Note: Don't forget to copy/paste the contents of `public_key.pem` file including `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` lines into `encodedKey` parameter when creating a `PublicKey`. + +Example: + +```ts + new cloudfront.KeyGroup(stack, 'MyKeyGroup', { + items: [ + new cloudfront.PublicKey(stack, 'MyPublicKey', { + encodedKey: '...', // contents of public_key.pem file + // comment: 'Key is expiring on ...', + }), + ], + // comment: 'Key group containing public keys ...', + }); +``` + +See: + +* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html +* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html diff --git a/packages/@aws-cdk/aws-cloudfront/lib/index.ts b/packages/@aws-cdk/aws-cloudfront/lib/index.ts index 726a1d1d01948..7de2aa62b4412 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/index.ts @@ -1,9 +1,11 @@ export * from './cache-policy'; export * from './distribution'; export * from './geo-restriction'; +export * from './key-group'; export * from './origin'; export * from './origin-access-identity'; export * from './origin-request-policy'; +export * from './public-key'; export * from './web-distribution'; export * as experimental from './experimental'; diff --git a/packages/@aws-cdk/aws-cloudfront/lib/key-group.ts b/packages/@aws-cdk/aws-cloudfront/lib/key-group.ts new file mode 100644 index 0000000000000..aea7bf451f305 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/lib/key-group.ts @@ -0,0 +1,75 @@ +import { IResource, Names, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnKeyGroup } from './cloudfront.generated'; +import { IPublicKey } from './public-key'; + +/** + * Represents a Key Group + */ +export interface IKeyGroup extends IResource { + /** + * The ID of the key group. + * @attribute + */ + readonly keyGroupId: string; +} + +/** + * Properties for creating a Public Key + */ +export interface KeyGroupProps { + /** + * A name to identify the key group. + * @default - generated from the `id` + */ + readonly keyGroupName?: string; + + /** + * A comment to describe the key group. + * @default - no comment + */ + readonly comment?: string; + + /** + * A list of public keys to add to the key group. + */ + readonly items: IPublicKey[]; +} + +/** + * A Key Group configuration + * + * @resource AWS::CloudFront::KeyGroup + */ +export class KeyGroup extends Resource implements IKeyGroup { + + /** Imports a Key Group from its id. */ + public static fromKeyGroupId(scope: Construct, id: string, keyGroupId: string): IKeyGroup { + return new class extends Resource implements IKeyGroup { + public readonly keyGroupId = keyGroupId; + }(scope, id); + } + public readonly keyGroupId: string; + + constructor(scope: Construct, id: string, props: KeyGroupProps) { + super(scope, id); + + const resource = new CfnKeyGroup(this, 'Resource', { + keyGroupConfig: { + name: props.keyGroupName ?? this.generateName(), + comment: props.comment, + items: props.items.map(key => key.publicKeyId), + }, + }); + + this.keyGroupId = resource.ref; + } + + private generateName(): string { + const name = Names.uniqueId(this); + if (name.length > 80) { + return name.substring(0, 40) + name.substring(name.length - 40); + } + return name; + } +} diff --git a/packages/@aws-cdk/aws-cloudfront/lib/public-key.ts b/packages/@aws-cdk/aws-cloudfront/lib/public-key.ts new file mode 100644 index 0000000000000..e2c2b6e044cdb --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/lib/public-key.ts @@ -0,0 +1,83 @@ +import { IResource, Names, Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnPublicKey } from './cloudfront.generated'; + +/** + * Represents a Public Key + */ +export interface IPublicKey extends IResource { + /** + * The ID of the key group. + * @attribute + */ + readonly publicKeyId: string; +} + +/** + * Properties for creating a Public Key + */ +export interface PublicKeyProps { + /** + * A name to identify the public key. + * @default - generated from the `id` + */ + readonly publicKeyName?: string; + + /** + * A comment to describe the public key. + * @default - no comment + */ + readonly comment?: string; + + /** + * The public key that you can use with signed URLs and signed cookies, or with field-level encryption. + * The `encodedKey` parameter must include `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` lines. + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/field-level-encryption.html + */ + readonly encodedKey: string; +} + +/** + * A Public Key Configuration + * + * @resource AWS::CloudFront::PublicKey + */ +export class PublicKey extends Resource implements IPublicKey { + + /** Imports a Public Key from its id. */ + public static fromPublicKeyId(scope: Construct, id: string, publicKeyId: string): IPublicKey { + return new class extends Resource implements IPublicKey { + public readonly publicKeyId = publicKeyId; + }(scope, id); + } + + public readonly publicKeyId: string; + + constructor(scope: Construct, id: string, props: PublicKeyProps) { + super(scope, id); + + if (!Token.isUnresolved(props.encodedKey) && !/^-----BEGIN PUBLIC KEY-----/.test(props.encodedKey)) { + throw new Error(`Public key must be in PEM format (with the BEGIN/END PUBLIC KEY lines); got ${props.encodedKey}`); + } + + const resource = new CfnPublicKey(this, 'Resource', { + publicKeyConfig: { + name: props.publicKeyName ?? this.generateName(), + callerReference: this.node.addr, + encodedKey: props.encodedKey, + comment: props.comment, + }, + }); + + this.publicKeyId = resource.ref; + } + + private generateName(): string { + const name = Names.uniqueId(this); + if (name.length > 80) { + return name.substring(0, 40) + name.substring(name.length - 40); + } + return name; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 651d22e95e668..c781a065bbc0a 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -153,7 +153,9 @@ "resource-attribute:@aws-cdk/aws-cloudfront.CachePolicy.cachePolicyLastModifiedTime", "construct-interface-extends-iconstruct:@aws-cdk/aws-cloudfront.IOriginRequestPolicy", "resource-interface-extends-resource:@aws-cdk/aws-cloudfront.IOriginRequestPolicy", - "resource-attribute:@aws-cdk/aws-cloudfront.OriginRequestPolicy.originRequestPolicyLastModifiedTime" + "resource-attribute:@aws-cdk/aws-cloudfront.OriginRequestPolicy.originRequestPolicyLastModifiedTime", + "resource-attribute:@aws-cdk/aws-cloudfront.KeyGroup.keyGroupLastModifiedTime", + "resource-attribute:@aws-cdk/aws-cloudfront.PublicKey.publicKeyCreatedTime" ] }, "awscdkio": { diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-key-group.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-key-group.expected.json new file mode 100644 index 0000000000000..45191bad86cff --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-key-group.expected.json @@ -0,0 +1,27 @@ +{ + "Resources": { + "AwesomePublicKeyED3E7F55": { + "Type": "AWS::CloudFront::PublicKey", + "Properties": { + "PublicKeyConfig": { + "CallerReference": "c88e460888c5762c9c47ac0cdc669370d787fb2d9f", + "EncodedKey": "-----BEGIN PUBLIC KEY-----\n MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS\n JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa\n dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj\n 6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e\n 0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD\n /3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx\n NQIDAQAB\n -----END PUBLIC KEY-----\n ", + "Name": "awscdkcloudfrontcustomAwesomePublicKey0E83393B" + } + } + }, + "AwesomeKeyGroup3EF8348B": { + "Type": "AWS::CloudFront::KeyGroup", + "Properties": { + "KeyGroupConfig": { + "Items": [ + { + "Ref": "AwesomePublicKeyED3E7F55" + } + ], + "Name": "awscdkcloudfrontcustomAwesomeKeyGroup73FD4DCA" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-key-group.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-key-group.ts new file mode 100644 index 0000000000000..7bfdbbe645446 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-key-group.ts @@ -0,0 +1,25 @@ +import * as cdk from '@aws-cdk/core'; +import * as cloudfront from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-cloudfront-custom'); + +new cloudfront.KeyGroup(stack, 'AwesomeKeyGroup', { + items: [ + new cloudfront.PublicKey(stack, 'AwesomePublicKey', { + encodedKey: `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS + JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa + dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj + 6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e + 0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD + /3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx + NQIDAQAB + -----END PUBLIC KEY----- + `, + }), + ], +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront/test/key-group.test.ts b/packages/@aws-cdk/aws-cloudfront/test/key-group.test.ts new file mode 100644 index 0000000000000..f5a0ae43c0855 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/key-group.test.ts @@ -0,0 +1,187 @@ +import '@aws-cdk/assert/jest'; +import { expect as expectStack } from '@aws-cdk/assert'; +import { App, Stack } from '@aws-cdk/core'; +import { KeyGroup, PublicKey } from '../lib'; + +const publicKey1 = `-----BEGIN PUBLIC KEY----- +FIRST_KEYgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS +JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa +dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj +6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e +0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD +/3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx +NQIDAQAB +-----END PUBLIC KEY-----`; + +const publicKey2 = `-----BEGIN PUBLIC KEY----- +SECOND_KEYkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS +JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa +dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj +6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e +0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD +/3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx +NQIDAQAB +-----END PUBLIC KEY-----`; + +describe('KeyGroup', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack', { + env: { account: '123456789012', region: 'testregion' }, + }); + }); + + test('import existing key group by id', () => { + const keyGroupId = '344f6fe5-7ce5-4df0-a470-3f14177c549c'; + const keyGroup = KeyGroup.fromKeyGroupId(stack, 'MyKeyGroup', keyGroupId); + expect(keyGroup.keyGroupId).toEqual(keyGroupId); + }); + + test('minimal example', () => { + new KeyGroup(stack, 'MyKeyGroup', { + items: [ + new PublicKey(stack, 'MyPublicKey', { + encodedKey: publicKey1, + }), + ], + }); + + expectStack(stack).toMatch({ + Resources: { + MyPublicKey78071F3D: { + Type: 'AWS::CloudFront::PublicKey', + Properties: { + PublicKeyConfig: { + CallerReference: 'c872d91ae0d2943aad25d4b31f1304d0a62c658ace', + EncodedKey: publicKey1, + Name: 'StackMyPublicKey36EDA6AB', + }, + }, + }, + MyKeyGroupAF22FD35: { + Type: 'AWS::CloudFront::KeyGroup', + Properties: { + KeyGroupConfig: { + Items: [ + { + Ref: 'MyPublicKey78071F3D', + }, + ], + Name: 'StackMyKeyGroupC9D82374', + }, + }, + }, + }, + }); + }); + + test('maximum example', () => { + new KeyGroup(stack, 'MyKeyGroup', { + keyGroupName: 'AcmeKeyGroup', + comment: 'Key group created on 1/1/1984', + items: [ + new PublicKey(stack, 'MyPublicKey', { + publicKeyName: 'pub-key', + encodedKey: publicKey1, + comment: 'Key expiring on 1/1/1984', + }), + ], + }); + + expectStack(stack).toMatch({ + Resources: { + MyPublicKey78071F3D: { + Type: 'AWS::CloudFront::PublicKey', + Properties: { + PublicKeyConfig: { + CallerReference: 'c872d91ae0d2943aad25d4b31f1304d0a62c658ace', + EncodedKey: publicKey1, + Name: 'pub-key', + Comment: 'Key expiring on 1/1/1984', + }, + }, + }, + MyKeyGroupAF22FD35: { + Type: 'AWS::CloudFront::KeyGroup', + Properties: { + KeyGroupConfig: { + Items: [ + { + Ref: 'MyPublicKey78071F3D', + }, + ], + Name: 'AcmeKeyGroup', + Comment: 'Key group created on 1/1/1984', + }, + }, + }, + }, + }); + }); + + test('multiple keys example', () => { + new KeyGroup(stack, 'MyKeyGroup', { + keyGroupName: 'AcmeKeyGroup', + comment: 'Key group created on 1/1/1984', + items: [ + new PublicKey(stack, 'BingoKey', { + publicKeyName: 'Bingo-Key', + encodedKey: publicKey1, + comment: 'Key expiring on 1/1/1984', + }), + new PublicKey(stack, 'RollyKey', { + publicKeyName: 'Rolly-Key', + encodedKey: publicKey2, + comment: 'Key expiring on 1/1/1984', + }), + ], + }); + + expectStack(stack).toMatch({ + Resources: { + BingoKeyCBEC786C: { + Type: 'AWS::CloudFront::PublicKey', + Properties: { + PublicKeyConfig: { + CallerReference: 'c847cb3dc23f619c0a1e400a44afaf1060d27a1d1a', + EncodedKey: publicKey1, + Name: 'Bingo-Key', + Comment: 'Key expiring on 1/1/1984', + }, + }, + }, + RollyKey83F8BC5B: { + Type: 'AWS::CloudFront::PublicKey', + Properties: { + PublicKeyConfig: { + CallerReference: 'c83a16945c386bf6cd88a3aaa1aa603eeb4b6c6c57', + EncodedKey: publicKey2, + Name: 'Rolly-Key', + Comment: 'Key expiring on 1/1/1984', + }, + }, + }, + MyKeyGroupAF22FD35: { + Type: 'AWS::CloudFront::KeyGroup', + Properties: { + KeyGroupConfig: { + Items: [ + { + Ref: 'BingoKeyCBEC786C', + }, + { + Ref: 'RollyKey83F8BC5B', + }, + ], + Name: 'AcmeKeyGroup', + Comment: 'Key group created on 1/1/1984', + }, + }, + }, + }, + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/public-key.test.ts b/packages/@aws-cdk/aws-cloudfront/test/public-key.test.ts new file mode 100644 index 0000000000000..934b1a9dc8107 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/public-key.test.ts @@ -0,0 +1,85 @@ +import '@aws-cdk/assert/jest'; +import { expect as expectStack } from '@aws-cdk/assert'; +import { App, Stack } from '@aws-cdk/core'; +import { PublicKey } from '../lib'; + +const publicKey = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS +JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa +dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj +6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e +0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD +/3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx +NQIDAQAB +-----END PUBLIC KEY-----`; + +describe('PublicKey', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app, 'Stack', { + env: { account: '123456789012', region: 'testregion' }, + }); + }); + + test('import existing key group by id', () => { + const publicKeyId = 'K36X4X2EO997HM'; + const pubKey = PublicKey.fromPublicKeyId(stack, 'MyPublicKey', publicKeyId); + expect(pubKey.publicKeyId).toEqual(publicKeyId); + }); + + test('minimal example', () => { + new PublicKey(stack, 'MyPublicKey', { + encodedKey: publicKey, + }); + + expectStack(stack).toMatch({ + Resources: { + MyPublicKey78071F3D: { + Type: 'AWS::CloudFront::PublicKey', + Properties: { + PublicKeyConfig: { + CallerReference: 'c872d91ae0d2943aad25d4b31f1304d0a62c658ace', + EncodedKey: publicKey, + Name: 'StackMyPublicKey36EDA6AB', + }, + }, + }, + }, + }); + }); + + test('maximum example', () => { + new PublicKey(stack, 'MyPublicKey', { + publicKeyName: 'pub-key', + encodedKey: publicKey, + comment: 'Key expiring on 1/1/1984', + }); + + expectStack(stack).toMatch({ + Resources: { + MyPublicKey78071F3D: { + Type: 'AWS::CloudFront::PublicKey', + Properties: { + PublicKeyConfig: { + CallerReference: 'c872d91ae0d2943aad25d4b31f1304d0a62c658ace', + Comment: 'Key expiring on 1/1/1984', + EncodedKey: publicKey, + Name: 'pub-key', + }, + }, + }, + }, + }); + }); + + test('bad key example', () => { + expect(() => new PublicKey(stack, 'MyPublicKey', { + publicKeyName: 'pub-key', + encodedKey: 'bad-key', + comment: 'Key expiring on 1/1/1984', + })).toThrow(/Public key must be in PEM format [(]with the BEGIN\/END PUBLIC KEY lines[)]; got (.*?)/); + }); +}); \ No newline at end of file From 8a1a9b82a36e681334fd45be595f6ecdf904ad34 Mon Sep 17 00:00:00 2001 From: Ayush Goyal Date: Wed, 3 Feb 2021 17:04:11 +0530 Subject: [PATCH 48/70] feat(apigateway): import an existing Resource (#12785) feat(apigateway): add fromResourceAttribute helper for importing Resource closes #4432 NOTE: No change in Readme is done since I was not able to find a good place for it in the Readme. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-apigateway/lib/resource.ts | 44 +++++++++++++++++++ .../aws-apigateway/test/resource.test.ts | 21 +++++++++ 2 files changed, 65 insertions(+) diff --git a/packages/@aws-cdk/aws-apigateway/lib/resource.ts b/packages/@aws-cdk/aws-apigateway/lib/resource.ts index 04d9598303ad8..33ec6f1d5f7fd 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/resource.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/resource.ts @@ -373,7 +373,51 @@ export abstract class ResourceBase extends ResourceConstruct implements IResourc } } +/** + * Attributes that can be specified when importing a Resource + */ +export interface ResourceAttributes { + /** + * The ID of the resource. + */ + readonly resourceId: string; + + /** + * The rest API that this resource is part of. + */ + readonly restApi: IRestApi; + + /** + * The full path of this resource. + */ + readonly path: string; +} + export class Resource extends ResourceBase { + /** + * Import an existing resource + */ + public static fromResourceAttributes(scope: Construct, id: string, attrs: ResourceAttributes): IResource { + class Import extends ResourceBase { + public readonly api = attrs.restApi; + public readonly resourceId = attrs.resourceId; + public readonly path = attrs.path; + public readonly defaultIntegration?: Integration = undefined; + public readonly defaultMethodOptions?: MethodOptions = undefined; + public readonly defaultCorsPreflightOptions?: CorsOptions = undefined; + + public get parentResource(): IResource { + throw new Error('parentResource is not configured for imported resource.'); + } + + public get restApi(): RestApi { + throw new Error('restApi is not configured for imported resource.'); + } + } + + return new Import(scope, id); + } + public readonly parentResource?: IResource; public readonly api: IRestApi; public readonly resourceId: string; diff --git a/packages/@aws-cdk/aws-apigateway/test/resource.test.ts b/packages/@aws-cdk/aws-apigateway/test/resource.test.ts index ecad61cb1905c..0a20483f4261c 100644 --- a/packages/@aws-cdk/aws-apigateway/test/resource.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/resource.test.ts @@ -236,6 +236,27 @@ describe('resource', () => { }); + test('fromResourceAttributes()', () => { + // GIVEN + const stack = new Stack(); + const resourceId = 'resource-id'; + const api = new apigw.RestApi(stack, 'MyRestApi'); + + // WHEN + const imported = apigw.Resource.fromResourceAttributes(stack, 'imported-resource', { + resourceId, + restApi: api, + path: 'some-path', + }); + imported.addMethod('GET'); + + // THEN + expect(stack).toHaveResource('AWS::ApiGateway::Method', { + HttpMethod: 'GET', + ResourceId: resourceId, + }); + }); + describe('getResource', () => { describe('root resource', () => { From 8d3aabaffe436e6a3eebc0a58fe361c5b4b93f08 Mon Sep 17 00:00:00 2001 From: Christoph Gysin Date: Wed, 3 Feb 2021 14:13:05 +0200 Subject: [PATCH 49/70] feat(lambda): inline code for Python 3.8 (#12788) fixes #6503 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda/lib/runtime.ts | 1 + .../integ.runtime.inlinecode.expected.json | 65 +++++++++++++++++-- .../test/integ.runtime.inlinecode.ts | 9 ++- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/runtime.ts b/packages/@aws-cdk/aws-lambda/lib/runtime.ts index 1f94401afa17f..05d4996ff5e4f 100644 --- a/packages/@aws-cdk/aws-lambda/lib/runtime.ts +++ b/packages/@aws-cdk/aws-lambda/lib/runtime.ts @@ -103,6 +103,7 @@ export class Runtime { * The Python 3.8 runtime (python3.8) */ public static readonly PYTHON_3_8 = new Runtime('python3.8', RuntimeFamily.PYTHON, { + supportsInlineCode: true, supportsCodeGuruProfiling: true, }); diff --git a/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.expected.json index 8bbe8cdef572a..30d39828cc39d 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.expected.json @@ -37,13 +37,13 @@ "Code": { "ZipFile": "exports.handler = async function(event) { return \"success\" }" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "NODEJS10XServiceRole2FD24B65", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs10.x" }, "DependsOn": [ @@ -87,13 +87,13 @@ "Code": { "ZipFile": "exports.handler = async function(event) { return \"success\" }" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "NODEJS12XServiceRole59E71436", "Arn" ] }, + "Handler": "index.handler", "Runtime": "nodejs12.x" }, "DependsOn": [ @@ -137,13 +137,13 @@ "Code": { "ZipFile": "def handler(event, context):\n return \"success\"" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "PYTHON27ServiceRoleF484A17D", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python2.7" }, "DependsOn": [ @@ -187,13 +187,13 @@ "Code": { "ZipFile": "def handler(event, context):\n return \"success\"" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "PYTHON36ServiceRole814B3AD9", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python3.6" }, "DependsOn": [ @@ -237,18 +237,68 @@ "Code": { "ZipFile": "def handler(event, context):\n return \"success\"" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "PYTHON37ServiceRoleDE7E561E", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python3.7" }, "DependsOn": [ "PYTHON37ServiceRoleDE7E561E" ] + }, + "PYTHON38ServiceRole3EA86BBE": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "PYTHON38A180AE47": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "def handler(event, context):\n return \"success\"" + }, + "Role": { + "Fn::GetAtt": [ + "PYTHON38ServiceRole3EA86BBE", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "python3.8" + }, + "DependsOn": [ + "PYTHON38ServiceRole3EA86BBE" + ] } }, "Outputs": { @@ -276,6 +326,11 @@ "Value": { "Ref": "PYTHON37D3A10E04" } + }, + "PYTHON38functionName": { + "Value": { + "Ref": "PYTHON38A180AE47" + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.ts b/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.ts index aa4ef06e6a5e1..56f5bd27f7746 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.runtime.inlinecode.ts @@ -50,4 +50,11 @@ const python37 = new Function(stack, 'PYTHON_3_7', { }); new CfnOutput(stack, 'PYTHON_3_7-functionName', { value: python37.functionName }); -app.synth(); \ No newline at end of file +const python38 = new Function(stack, 'PYTHON_3_8', { + code: new InlineCode('def handler(event, context):\n return "success"'), + handler: 'index.handler', + runtime: Runtime.PYTHON_3_8, +}); +new CfnOutput(stack, 'PYTHON_3_8-functionName', { value: python38.functionName }); + +app.synth(); From 2aba609929de3b4517795aa06129b2fe31bf11b6 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 3 Feb 2021 12:50:28 +0000 Subject: [PATCH 50/70] chore(eslint-plugin-cdk): fix tests and expectations (#12831) The previous change - 9a81faaa - to add test cases to this package had a bug. Two different eslint rules were being applied simultaneously creating corrupt expectation. Fixing the test so it only runs the specific eslint rule for that fixture, and fixing the expectation. At the same time, added a test case that was previously missed. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../test/rules/fixtures.test.ts | 32 +++++++++---------- .../fixtures/no-core-construct/eslintrc.js | 6 ++++ .../both-constructs.expected.ts | 9 ++++++ .../no-qualified-construct/both-constructs.ts | 5 +++ .../no-qualified-construct/eslintrc.js | 6 ++++ .../qualified-usage.expected.ts | 7 ++-- 6 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/no-core-construct/eslintrc.js create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/both-constructs.expected.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/both-constructs.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/eslintrc.js diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures.test.ts b/tools/eslint-plugin-cdk/test/rules/fixtures.test.ts index 89f0568048fbd..b65670f43b429 100644 --- a/tools/eslint-plugin-cdk/test/rules/fixtures.test.ts +++ b/tools/eslint-plugin-cdk/test/rules/fixtures.test.ts @@ -5,21 +5,7 @@ import * as path from 'path'; const rulesDirPlugin = require('eslint-plugin-rulesdir'); rulesDirPlugin.RULES_DIR = path.join(__dirname, '../../lib/rules'); -const linter = new ESLint({ - baseConfig: { - parser: '@typescript-eslint/parser', - plugins: ['rulesdir'], - rules: { - quotes: [ 'error', 'single', { avoidEscape: true }], - 'rulesdir/no-core-construct': [ 'error' ], - 'rulesdir/no-qualified-construct': [ 'error' ], - } - }, - rulePaths: [ - path.join(__dirname, '../../lib/rules'), - ], - fix: true, -}); +let linter: ESLint; const outputRoot = path.join(process.cwd(), '.test-output'); fs.mkdirpSync(outputRoot); @@ -28,10 +14,24 @@ const fixturesRoot = path.join(__dirname, 'fixtures'); fs.readdirSync(fixturesRoot).filter(f => fs.lstatSync(path.join(fixturesRoot, f)).isDirectory()).forEach(d => { describe(d, () => { + const fixturesDir = path.join(fixturesRoot, d); + + beforeAll(() => { + linter = new ESLint({ + baseConfig: { + parser: '@typescript-eslint/parser', + }, + overrideConfigFile: path.join(fixturesDir, 'eslintrc.js'), + rulePaths: [ + path.join(__dirname, '../../lib/rules'), + ], + fix: true, + }); + }); + const outputDir = path.join(outputRoot, d); fs.mkdirpSync(outputDir); - const fixturesDir = path.join(fixturesRoot, d); const fixtureFiles = fs.readdirSync(fixturesDir).filter(f => f.endsWith('.ts') && !f.endsWith('.expected.ts')); fixtureFiles.forEach(f => { diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/no-core-construct/eslintrc.js b/tools/eslint-plugin-cdk/test/rules/fixtures/no-core-construct/eslintrc.js new file mode 100644 index 0000000000000..3bd78e797f728 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/no-core-construct/eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: ['rulesdir'], + rules: { + 'rulesdir/no-core-construct': [ 'error' ], + } +} \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/both-constructs.expected.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/both-constructs.expected.ts new file mode 100644 index 0000000000000..20caf8244cd1b --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/both-constructs.expected.ts @@ -0,0 +1,9 @@ +import { Construct } from 'constructs' +import * as cdk from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + +let x: CoreConstruct; +let y: Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/both-constructs.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/both-constructs.ts new file mode 100644 index 0000000000000..bd92a909af763 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/both-constructs.ts @@ -0,0 +1,5 @@ +import { Construct } from 'constructs' +import * as cdk from '@aws-cdk/core'; + +let x: cdk.Construct; +let y: Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/eslintrc.js b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/eslintrc.js new file mode 100644 index 0000000000000..30de0b87a63df --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: ['rulesdir'], + rules: { + 'rulesdir/no-qualified-construct': [ 'error' ], + } +} \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.expected.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.expected.ts index af7c0f393f307..bba5c3ae8aa50 100644 --- a/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.expected.ts +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/no-qualified-construct/qualified-usage.expected.ts @@ -1,4 +1,7 @@ import * as cdk from '@aws-cdk/core'; -import * as constructs from 'constructs'; -let x: constructs.Construct; \ No newline at end of file +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + +let x: Construct; \ No newline at end of file From ff1e5b3c580119c107fe26c67fe3cc220f9ee7c9 Mon Sep 17 00:00:00 2001 From: Ayush Goyal Date: Wed, 3 Feb 2021 19:38:17 +0530 Subject: [PATCH 51/70] feat(apigateway): cognito user pool authorizer (#12786) feat(apigateway): add support for cognito user pool authorizer closes #5618 ---- *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-apigateway/README.md | 20 ++ .../aws-apigateway/lib/authorizers/cognito.ts | 115 +++++++++++ .../aws-apigateway/lib/authorizers/index.ts | 1 + packages/@aws-cdk/aws-apigateway/package.json | 2 + .../test/authorizers/cognito.test.ts | 66 ++++++ .../integ.cognito-authorizer.expected.json | 191 ++++++++++++++++++ .../authorizers/integ.cognito-authorizer.ts | 43 ++++ 7 files changed, 438 insertions(+) create mode 100644 packages/@aws-cdk/aws-apigateway/lib/authorizers/cognito.ts create mode 100644 packages/@aws-cdk/aws-apigateway/test/authorizers/cognito.test.ts create mode 100644 packages/@aws-cdk/aws-apigateway/test/authorizers/integ.cognito-authorizer.expected.json create mode 100644 packages/@aws-cdk/aws-apigateway/test/authorizers/integ.cognito-authorizer.ts diff --git a/packages/@aws-cdk/aws-apigateway/README.md b/packages/@aws-cdk/aws-apigateway/README.md index 60d2cdcfa3654..4d4d1a79eb797 100644 --- a/packages/@aws-cdk/aws-apigateway/README.md +++ b/packages/@aws-cdk/aws-apigateway/README.md @@ -32,6 +32,7 @@ running on AWS Lambda, or any web application. - [IAM-based authorizer](#iam-based-authorizer) - [Lambda-based token authorizer](#lambda-based-token-authorizer) - [Lambda-based request authorizer](#lambda-based-request-authorizer) + - [Cognito User Pools authorizer](#cognito-user-pools-authorizer) - [Mutual TLS](#mutal-tls-mtls) - [Deployments](#deployments) - [Deep dive: Invalidation of deployments](#deep-dive-invalidation-of-deployments) @@ -580,6 +581,25 @@ Authorizers can also be passed via the `defaultMethodOptions` property within th explicitly overridden, the specified defaults will be applied across all `Method`s across the `RestApi` or across all `Resource`s, depending on where the defaults were specified. +### Cognito User Pools authorizer + +API Gateway also allows [Amazon Cognito user pools as authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html) + +The following snippet configures a Cognito user pool as an authorizer: + +```ts +const userPool = new cognito.UserPool(stack, 'UserPool'); + +const auth = new apigateway.CognitoUserPoolsAuthorizer(this, 'booksAuthorizer', { + cognitoUserPools: [userPool] +}); + +books.addMethod('GET', new apigateway.HttpIntegration('http://amazon.com'), { + authorizer: auth, + authorizationType: apigateway.AuthorizationType.COGNITO, +}); +``` + ## Mutual TLS (mTLS) Mutual TLS can be configured to limit access to your API based by using client certificates instead of (or as an extension of) using authorization headers. diff --git a/packages/@aws-cdk/aws-apigateway/lib/authorizers/cognito.ts b/packages/@aws-cdk/aws-apigateway/lib/authorizers/cognito.ts new file mode 100644 index 0000000000000..a1d000189354c --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/lib/authorizers/cognito.ts @@ -0,0 +1,115 @@ +import * as cognito from '@aws-cdk/aws-cognito'; +import { Duration, Lazy, Names, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnAuthorizer } from '../apigateway.generated'; +import { Authorizer, IAuthorizer } from '../authorizer'; +import { AuthorizationType } from '../method'; +import { IRestApi } from '../restapi'; + +/** + * Properties for CognitoUserPoolsAuthorizer + */ +export interface CognitoUserPoolsAuthorizerProps { + /** + * An optional human friendly name for the authorizer. Note that, this is not the primary identifier of the authorizer. + * + * @default - the unique construct ID + */ + readonly authorizerName?: string; + + /** + * The user pools to associate with this authorizer. + */ + readonly cognitoUserPools: cognito.IUserPool[]; + + /** + * How long APIGateway should cache the results. Max 1 hour. + * Disable caching by setting this to 0. + * + * @default Duration.minutes(5) + */ + readonly resultsCacheTtl?: Duration; + + /** + * The request header mapping expression for the bearer token. This is typically passed as part of the header, in which case + * this should be `method.request.header.Authorizer` where Authorizer is the header containing the bearer token. + * @see https://docs.aws.amazon.com/apigateway/api-reference/link-relation/authorizer-create/#identitySource + * @default `IdentitySource.header('Authorization')` + */ + readonly identitySource?: string; +} + +/** + * Cognito user pools based custom authorizer + * + * @resource AWS::ApiGateway::Authorizer + */ +export class CognitoUserPoolsAuthorizer extends Authorizer implements IAuthorizer { + /** + * The id of the authorizer. + * @attribute + */ + public readonly authorizerId: string; + + /** + * The ARN of the authorizer to be used in permission policies, such as IAM and resource-based grants. + * @attribute + */ + public readonly authorizerArn: string; + + /** + * The authorization type of this authorizer. + */ + public readonly authorizationType?: AuthorizationType; + + private restApiId?: string; + + constructor(scope: Construct, id: string, props: CognitoUserPoolsAuthorizerProps) { + super(scope, id); + + const restApiId = this.lazyRestApiId(); + const resource = new CfnAuthorizer(this, 'Resource', { + name: props.authorizerName ?? Names.uniqueId(this), + restApiId, + type: 'COGNITO_USER_POOLS', + providerArns: props.cognitoUserPools.map(userPool => userPool.userPoolArn), + authorizerResultTtlInSeconds: props.resultsCacheTtl?.toSeconds(), + identitySource: props.identitySource || 'method.request.header.Authorization', + }); + + this.authorizerId = resource.ref; + this.authorizerArn = Stack.of(this).formatArn({ + service: 'execute-api', + resource: restApiId, + resourceName: `authorizers/${this.authorizerId}`, + }); + this.authorizationType = AuthorizationType.COGNITO; + } + + /** + * Attaches this authorizer to a specific REST API. + * @internal + */ + public _attachToApi(restApi: IRestApi): void { + if (this.restApiId && this.restApiId !== restApi.restApiId) { + throw new Error('Cannot attach authorizer to two different rest APIs'); + } + + this.restApiId = restApi.restApiId; + } + + /** + * Returns a token that resolves to the Rest Api Id at the time of synthesis. + * Throws an error, during token resolution, if no RestApi is attached to this authorizer. + */ + private lazyRestApiId() { + return Lazy.string({ + produce: () => { + if (!this.restApiId) { + throw new Error(`Authorizer (${this.node.path}) must be attached to a RestApi`); + } + return this.restApiId; + }, + }); + } +} diff --git a/packages/@aws-cdk/aws-apigateway/lib/authorizers/index.ts b/packages/@aws-cdk/aws-apigateway/lib/authorizers/index.ts index 57289c931f760..fd93db036fefe 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/authorizers/index.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/authorizers/index.ts @@ -1,2 +1,3 @@ export * from './lambda'; export * from './identity-source'; +export * from './cognito'; diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index c9650deeb9a4b..3a0641e8b1162 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -80,6 +80,7 @@ "dependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", + "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", @@ -95,6 +96,7 @@ "peerDependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", + "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/cognito.test.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/cognito.test.ts new file mode 100644 index 0000000000000..e59339177d5d4 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/cognito.test.ts @@ -0,0 +1,66 @@ +import '@aws-cdk/assert/jest'; +import * as cognito from '@aws-cdk/aws-cognito'; +import { Duration, Stack } from '@aws-cdk/core'; +import { AuthorizationType, CognitoUserPoolsAuthorizer, RestApi } from '../../lib'; + +describe('Cognito Authorizer', () => { + test('default cognito authorizer', () => { + // GIVEN + const stack = new Stack(); + const userPool = new cognito.UserPool(stack, 'UserPool'); + + // WHEN + const authorizer = new CognitoUserPoolsAuthorizer(stack, 'myauthorizer', { + cognitoUserPools: [userPool], + }); + + const restApi = new RestApi(stack, 'myrestapi'); + restApi.root.addMethod('ANY', undefined, { + authorizer, + authorizationType: AuthorizationType.COGNITO, + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGateway::Authorizer', { + Type: 'COGNITO_USER_POOLS', + RestApiId: stack.resolve(restApi.restApiId), + IdentitySource: 'method.request.header.Authorization', + ProviderARNs: [stack.resolve(userPool.userPoolArn)], + }); + + expect(authorizer.authorizerArn.endsWith(`/authorizers/${authorizer.authorizerId}`)).toBeTruthy(); + }); + + test('cognito authorizer with all parameters specified', () => { + // GIVEN + const stack = new Stack(); + const userPool1 = new cognito.UserPool(stack, 'UserPool1'); + const userPool2 = new cognito.UserPool(stack, 'UserPool2'); + + // WHEN + const authorizer = new CognitoUserPoolsAuthorizer(stack, 'myauthorizer', { + cognitoUserPools: [userPool1, userPool2], + identitySource: 'method.request.header.whoami', + authorizerName: 'myauthorizer', + resultsCacheTtl: Duration.minutes(1), + }); + + const restApi = new RestApi(stack, 'myrestapi'); + restApi.root.addMethod('ANY', undefined, { + authorizer, + authorizationType: AuthorizationType.COGNITO, + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGateway::Authorizer', { + Type: 'COGNITO_USER_POOLS', + Name: 'myauthorizer', + RestApiId: stack.resolve(restApi.restApiId), + IdentitySource: 'method.request.header.whoami', + AuthorizerResultTtlInSeconds: 60, + ProviderARNs: [stack.resolve(userPool1.userPoolArn), stack.resolve(userPool2.userPoolArn)], + }); + + expect(authorizer.authorizerArn.endsWith(`/authorizers/${authorizer.authorizerId}`)).toBeTruthy(); + }); +}); diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.cognito-authorizer.expected.json b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.cognito-authorizer.expected.json new file mode 100644 index 0000000000000..990619cb495d4 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.cognito-authorizer.expected.json @@ -0,0 +1,191 @@ +{ + "Resources": { + "UserPool6BA7E5F2": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "The verification code to your new account is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsVerificationMessage": "The verification code to your new account is {####}", + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "The verification code to your new account is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + } + }, + "myauthorizer23CB99DD": { + "Type": "AWS::ApiGateway::Authorizer", + "Properties": { + "RestApiId": { + "Ref": "myrestapi551C8392" + }, + "Type": "COGNITO_USER_POOLS", + "IdentitySource": "method.request.header.Authorization", + "Name": "CognitoUserPoolsAuthorizerIntegmyauthorizer10C804C1", + "ProviderARNs": [ + { + "Fn::GetAtt": [ + "UserPool6BA7E5F2", + "Arn" + ] + } + ] + } + }, + "myrestapi551C8392": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Name": "myrestapi" + } + }, + "myrestapiCloudWatchRoleC48DA1DD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + ] + } + ] + } + }, + "myrestapiAccountA49A05BE": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "myrestapiCloudWatchRoleC48DA1DD", + "Arn" + ] + } + }, + "DependsOn": [ + "myrestapi551C8392" + ] + }, + "myrestapiDeployment419B1464b903292b53d7532ca4296973bcb95b1a": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "myrestapi551C8392" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "myrestapiANY94B0497F" + ] + }, + "myrestapiDeploymentStageprodA9250EA4": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "myrestapi551C8392" + }, + "DeploymentId": { + "Ref": "myrestapiDeployment419B1464b903292b53d7532ca4296973bcb95b1a" + }, + "StageName": "prod" + } + }, + "myrestapiANY94B0497F": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Fn::GetAtt": [ + "myrestapi551C8392", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "myrestapi551C8392" + }, + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "myauthorizer23CB99DD" + }, + "Integration": { + "IntegrationResponses": [ + { + "StatusCode": "200" + } + ], + "PassthroughBehavior": "NEVER", + "RequestTemplates": { + "application/json": "{ \"statusCode\": 200 }" + }, + "Type": "MOCK" + }, + "MethodResponses": [ + { + "StatusCode": "200" + } + ] + } + } + }, + "Outputs": { + "myrestapiEndpointE06F9D98": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "myrestapi551C8392" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "myrestapiDeploymentStageprodA9250EA4" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.cognito-authorizer.ts b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.cognito-authorizer.ts new file mode 100644 index 0000000000000..4830dc83ae29f --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/authorizers/integ.cognito-authorizer.ts @@ -0,0 +1,43 @@ +import * as cognito from '@aws-cdk/aws-cognito'; +import { App, Stack } from '@aws-cdk/core'; +import { AuthorizationType, CognitoUserPoolsAuthorizer, MockIntegration, PassthroughBehavior, RestApi } from '../../lib'; + +/* + * Stack verification steps: + * * 1. Get the IdToken for the created pool by adding user/app-client and using aws cognito-idp: + * * a. aws cognito-idp create-user-pool-client --user-pool-id --client-name --no-generate-secret + * * b. aws cognito-idp admin-create-user --user-pool-id --username --temporary-password + * * c. aws cognito-idp initiate-auth --client-id --auth-flow USER_PASSWORD_AUTH --auth-parameters USERNAME=,PASSWORD= + * * d. aws cognito-idp respond-to-auth-challenge --client-id --challenge-name --session + * * + * * 2. Verify the stack using above obtained token: + * * a. `curl -s -o /dev/null -w "%{http_code}" ` should return 401 + * * b. `curl -s -o /dev/null -w "%{http_code}" -H 'Authorization: ' ` should return 403 + * * c. `curl -s -o /dev/null -w "%{http_code}" -H 'Authorization: ' ` should return 200 + */ + +const app = new App(); +const stack = new Stack(app, 'CognitoUserPoolsAuthorizerInteg'); + +const userPool = new cognito.UserPool(stack, 'UserPool'); + +const authorizer = new CognitoUserPoolsAuthorizer(stack, 'myauthorizer', { + cognitoUserPools: [userPool], +}); + +const restApi = new RestApi(stack, 'myrestapi'); +restApi.root.addMethod('ANY', new MockIntegration({ + integrationResponses: [ + { statusCode: '200' }, + ], + passthroughBehavior: PassthroughBehavior.NEVER, + requestTemplates: { + 'application/json': '{ "statusCode": 200 }', + }, +}), { + methodResponses: [ + { statusCode: '200' }, + ], + authorizer, + authorizationType: AuthorizationType.COGNITO, +}); From 208a7c1904d2e58a255a32133acec5238d23affc Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Wed, 3 Feb 2021 22:16:20 +0000 Subject: [PATCH 52/70] chore(release): 1.88.0 --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ version.v1.json | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d66ae0944d6..55ff1bfa44ad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,43 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.88.0](https://github.com/aws/aws-cdk/compare/v1.87.1...v1.88.0) (2021-02-03) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **appmesh:** the properties virtualRouter and virtualNode of VirtualServiceProps have been replaced with the union-like class VirtualServiceProvider +* **appmesh**: the method `addVirtualService` has been removed from `IMesh` +* **cloudfront:** experimental EdgeFunction stack names have changed from 'edge-lambda-stack-${region}' to 'edge-lambda-stack-${stackid}' to support multiple independent CloudFront distributions with EdgeFunctions. + +### Features + +* **apigateway:** cognito user pool authorizer ([#12786](https://github.com/aws/aws-cdk/issues/12786)) ([ff1e5b3](https://github.com/aws/aws-cdk/commit/ff1e5b3c580119c107fe26c67fe3cc220f9ee7c9)), closes [#5618](https://github.com/aws/aws-cdk/issues/5618) +* **apigateway:** import an existing Resource ([#12785](https://github.com/aws/aws-cdk/issues/12785)) ([8a1a9b8](https://github.com/aws/aws-cdk/commit/8a1a9b82a36e681334fd45be595f6ecdf904ad34)), closes [#4432](https://github.com/aws/aws-cdk/issues/4432) +* **appmesh:** change VirtualService provider to a union-like class ([#11978](https://github.com/aws/aws-cdk/issues/11978)) ([dfc765a](https://github.com/aws/aws-cdk/commit/dfc765af44c755f10be8f6c1c2eae55f62e2aa08)), closes [#9490](https://github.com/aws/aws-cdk/issues/9490) +* **aws-route53:** cross account DNS delegations ([#12680](https://github.com/aws/aws-cdk/issues/12680)) ([126a693](https://github.com/aws/aws-cdk/commit/126a6935cacc1f68b1d1155e484912d4ed6978f2)), closes [#8776](https://github.com/aws/aws-cdk/issues/8776) +* **cloudfront:** add PublicKey and KeyGroup L2 constructs ([#12743](https://github.com/aws/aws-cdk/issues/12743)) ([59cb6d0](https://github.com/aws/aws-cdk/commit/59cb6d032a55515ec5e9903f899de588d18d4cb5)) +* **core:** `stack.exportValue()` can be used to solve "deadly embrace" ([#12778](https://github.com/aws/aws-cdk/issues/12778)) ([3b66088](https://github.com/aws/aws-cdk/commit/3b66088010b6f2315a215e92505d5279680f16d4)), closes [#7602](https://github.com/aws/aws-cdk/issues/7602) [#2036](https://github.com/aws/aws-cdk/issues/2036) +* **ecr:** Public Gallery authorization token ([#12775](https://github.com/aws/aws-cdk/issues/12775)) ([8434294](https://github.com/aws/aws-cdk/commit/84342943ad9f2ea8a83773f00816a0b8117c4d17)) +* **ecs-patterns:** Add PlatformVersion option to ScheduledFargateTask props ([#12676](https://github.com/aws/aws-cdk/issues/12676)) ([3cbf38b](https://github.com/aws/aws-cdk/commit/3cbf38b09a9e66a6c009f833481fb25b8c5fc26c)), closes [#12623](https://github.com/aws/aws-cdk/issues/12623) +* **elbv2:** support for 2020 SSL policy ([#12710](https://github.com/aws/aws-cdk/issues/12710)) ([1dd3d05](https://github.com/aws/aws-cdk/commit/1dd3d0518dc2a70c725f87dd5d4377338389125c)), closes [#12595](https://github.com/aws/aws-cdk/issues/12595) +* **iam:** Permissions Boundaries ([#12777](https://github.com/aws/aws-cdk/issues/12777)) ([415eb86](https://github.com/aws/aws-cdk/commit/415eb861c65829cc53eabbbb8706f83f08c74570)), closes [aws/aws-cdk-rfcs#5](https://github.com/aws/aws-cdk-rfcs/issues/5) [#3242](https://github.com/aws/aws-cdk/issues/3242) +* **lambda:** inline code for Python 3.8 ([#12788](https://github.com/aws/aws-cdk/issues/12788)) ([8d3aaba](https://github.com/aws/aws-cdk/commit/8d3aabaffe436e6a3eebc0a58fe361c5b4b93f08)), closes [#6503](https://github.com/aws/aws-cdk/issues/6503) + + +### Bug Fixes + +* **apigateway:** stack update fails to replace api key ([#12745](https://github.com/aws/aws-cdk/issues/12745)) ([ffe7e42](https://github.com/aws/aws-cdk/commit/ffe7e425e605144a465cea9befa68d4fe19f9d8c)), closes [#12698](https://github.com/aws/aws-cdk/issues/12698) +* **cfn-include:** AWS::CloudFormation resources fail in monocdk ([#12758](https://github.com/aws/aws-cdk/issues/12758)) ([5060782](https://github.com/aws/aws-cdk/commit/5060782b00e17bdf44e225f8f5ef03344be238c7)), closes [#11595](https://github.com/aws/aws-cdk/issues/11595) +* **cli, codepipeline:** renamed bootstrap stack still not supported ([#12771](https://github.com/aws/aws-cdk/issues/12771)) ([40b32bb](https://github.com/aws/aws-cdk/commit/40b32bbda272b6e2f92fd5dd8de7ca5bf405ce52)), closes [#12594](https://github.com/aws/aws-cdk/issues/12594) [#12732](https://github.com/aws/aws-cdk/issues/12732) +* **cloudfront:** use node addr for edgeStackId name ([#12702](https://github.com/aws/aws-cdk/issues/12702)) ([c429bb7](https://github.com/aws/aws-cdk/commit/c429bb7df2406346426dce22d716cabc484ec7e6)), closes [#12323](https://github.com/aws/aws-cdk/issues/12323) +* **codedeploy:** wrong syntax on Windows 'installAgent' flag ([#12736](https://github.com/aws/aws-cdk/issues/12736)) ([238742e](https://github.com/aws/aws-cdk/commit/238742e4323310ce850d8edc70abe4b0e9f53186)), closes [#12734](https://github.com/aws/aws-cdk/issues/12734) +* **codepipeline:** permission denied for Action-level environment variables ([#12761](https://github.com/aws/aws-cdk/issues/12761)) ([99fd074](https://github.com/aws/aws-cdk/commit/99fd074a07ead624f64d3fe64685ba67c798976e)), closes [#12742](https://github.com/aws/aws-cdk/issues/12742) +* **ec2:** ARM-backed bastion hosts try to run x86-based Amazon Linux AMI ([#12280](https://github.com/aws/aws-cdk/issues/12280)) ([1a73d76](https://github.com/aws/aws-cdk/commit/1a73d761ad2363842567a1b6e0488ceb093e70b2)), closes [#12279](https://github.com/aws/aws-cdk/issues/12279) +* **efs:** EFS fails to create when using a VPC with multiple subnets per availability zone ([#12097](https://github.com/aws/aws-cdk/issues/12097)) ([889d673](https://github.com/aws/aws-cdk/commit/889d6734c10174f2661e45057c345cd112a44187)), closes [#10170](https://github.com/aws/aws-cdk/issues/10170) +* **iam:** cannot use the same Role for multiple Config Rules ([#12724](https://github.com/aws/aws-cdk/issues/12724)) ([2f6521a](https://github.com/aws/aws-cdk/commit/2f6521a1d8670b2653f7dee281309351181cf918)), closes [#12714](https://github.com/aws/aws-cdk/issues/12714) +* **lambda:** codeguru profiler not set up for Node runtime ([#12712](https://github.com/aws/aws-cdk/issues/12712)) ([59db763](https://github.com/aws/aws-cdk/commit/59db763e7d05d68fd85b6fd37246d69d4670d7d5)), closes [#12624](https://github.com/aws/aws-cdk/issues/12624) + ## [1.87.1](https://github.com/aws/aws-cdk/compare/v1.87.0...v1.87.1) (2021-01-28) diff --git a/version.v1.json b/version.v1.json index 816b141bbbf4a..f804856ae4ccf 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.87.1" + "version": "1.88.0" } From 5664480a97958263ee7cb903c2aff0276e738dc3 Mon Sep 17 00:00:00 2001 From: Steven Swartz Date: Thu, 4 Feb 2021 04:51:07 -0500 Subject: [PATCH 53/70] feat(lambda): layer version removal policy (#12792) closes #12718 Allows providing a removal policy for the `Lambda::LayerVersion` L2 construct to retain previous layer versions when the layer is updated. When a value is not provided, the default CloudFormation behavior is used, which is to delete the previous version defined in the stack. --- packages/@aws-cdk/aws-lambda/README.md | 11 +++++++++++ packages/@aws-cdk/aws-lambda/lib/layers.ts | 14 +++++++++++++- .../@aws-cdk/aws-lambda/test/layers.test.ts | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index a59dc1a311936..de6940cd249e8 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -189,6 +189,17 @@ granting permissions to other AWS accounts or organizations. [Example of Lambda Layer usage](test/integ.layer-version.lit.ts) +By default, updating a layer creates a new layer version, and CloudFormation will delete the old version as part of the stack update. + +Alternatively, a removal policy can be used to retain the old version: + +```ts +import { LayerVersion } from '@aws-cdk/aws-lambda'; +new LayerVersion(this, 'MyLayer', { + removalPolicy: RemovalPolicy.RETAIN +}); +``` + ## Event Rule Target You can use an AWS Lambda function as a target for an Amazon CloudWatch event diff --git a/packages/@aws-cdk/aws-lambda/lib/layers.ts b/packages/@aws-cdk/aws-lambda/lib/layers.ts index 92de56f57e7a8..babf91079b8b6 100644 --- a/packages/@aws-cdk/aws-lambda/lib/layers.ts +++ b/packages/@aws-cdk/aws-lambda/lib/layers.ts @@ -1,4 +1,4 @@ -import { IResource, Resource } from '@aws-cdk/core'; +import { IResource, RemovalPolicy, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Code } from './code'; import { CfnLayerVersion, CfnLayerVersionPermission } from './lambda.generated'; @@ -28,6 +28,14 @@ export interface LayerVersionOptions { * @default - A name will be generated. */ readonly layerVersionName?: string; + + /** + * Whether to retain this version of the layer when a new version is added + * or when the stack is deleted. + * + * @default RemovalPolicy.DESTROY + */ + readonly removalPolicy?: RemovalPolicy; } export interface LayerVersionProps extends LayerVersionOptions { @@ -198,6 +206,10 @@ export class LayerVersion extends LayerVersionBase { licenseInfo: props.license, }); + if (props.removalPolicy) { + resource.applyRemovalPolicy(props.removalPolicy); + } + props.code.bindToResource(resource, { resourceProperty: 'Content', }); diff --git a/packages/@aws-cdk/aws-lambda/test/layers.test.ts b/packages/@aws-cdk/aws-lambda/test/layers.test.ts index 0806f6d823dba..4a8f0e94ed6cb 100644 --- a/packages/@aws-cdk/aws-lambda/test/layers.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/layers.test.ts @@ -86,4 +86,21 @@ describe('layers', () => { }, }, ResourcePart.CompleteDefinition); }); + + test('creating a layer with a removal policy', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new lambda.LayerVersion(stack, 'layer', { + code: lambda.Code.fromAsset(path.join(__dirname, 'layer-code')), + removalPolicy: cdk.RemovalPolicy.RETAIN, + }); + + // THEN + expect(canonicalizeTemplate(SynthUtils.toCloudFormation(stack))).toHaveResource('AWS::Lambda::LayerVersion', { + UpdateReplacePolicy: 'Retain', + DeletionPolicy: 'Retain', + }, ResourcePart.CompleteDefinition); + }); }); From 404b5569bb76e3f579dd3005dcf06eeeae9df7f6 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 4 Feb 2021 10:30:28 +0000 Subject: [PATCH 54/70] chore: isolate CoreConstruct imports to avoid merge conflicts (#12850) As part of CDKv2, the construct layer in '@aws-cdk/core' is being replaced with the constructs API in the 'constructs' module. During the transition period between v1 and v2, both exist. On the `master` branch, code will include importing `Construct` from the '@aws-cdk/core' module but this will not be possible in the `v2-main` branch where this API does not exist. Any change to the same line as the import of the `Construct` symbol will cause merge conflicts. Hence, move the import into a separate line and section to avoid these conflicts. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/assets/lib/staging.ts | 6 +- .../@aws-cdk/aws-apigateway/lib/deployment.ts | 6 +- .../lib/lambda-hook.ts | 6 +- .../lib/lifecycle-hook-target.ts | 6 +- .../lib/step-scaling-action.ts | 6 +- .../lib/step-scaling-policy.ts | 6 +- .../lib/target-tracking-scaling-policy.ts | 6 +- .../test/integ.core-custom-resources.ts | 6 +- .../test/integ.nested-stack.ts | 6 +- .../test/integ.nested-stacks-assets.ts | 6 +- .../test/integ.nested-stacks-multi.ts | 6 +- .../test/integ.nested-stacks-refs1.ts | 6 +- .../test/integ.nested-stacks-refs2.ts | 6 +- .../test/integ.nested-stacks-refs3.ts | 6 +- .../test/test.nested-stack.ts | 6 +- .../@aws-cdk/aws-cloudfront/lib/origin.ts | 6 +- .../aws-cloudwatch/lib/alarm-action.ts | 6 +- .../aws-codepipeline-actions/lib/action.ts | 6 +- .../lib/alexa-ask/deploy-action.ts | 6 +- .../lib/bitbucket/source-action.ts | 6 +- .../lib/codecommit/source-action.ts | 6 +- .../lib/codedeploy/ecs-deploy-action.ts | 6 +- .../lib/codedeploy/server-deploy-action.ts | 6 +- .../lib/ecr/source-action.ts | 6 +- .../lib/ecs/deploy-action.ts | 6 +- .../lib/github/source-action.ts | 6 +- .../lib/jenkins/jenkins-action.ts | 6 +- .../lib/lambda/invoke-action.ts | 6 +- .../lib/s3/deploy-action.ts | 6 +- .../lib/s3/source-action.ts | 6 +- .../lib/servicecatalog/deploy-action.ts | 6 +- .../@aws-cdk/aws-codepipeline/lib/action.ts | 6 +- .../@aws-cdk/aws-codepipeline/lib/pipeline.ts | 6 +- .../lib/private/rich-action.ts | 6 +- .../aws-dynamodb/lib/replica-provider.ts | 6 +- packages/@aws-cdk/aws-dynamodb/lib/table.ts | 6 +- packages/@aws-cdk/aws-ec2/lib/cfn-init.ts | 6 +- .../@aws-cdk/aws-ec2/lib/machine-image.ts | 6 +- .../aws-ecr-assets/lib/image-asset.ts | 6 +- .../@aws-cdk/aws-eks-legacy/lib/aws-auth.ts | 6 +- .../aws-eks-legacy/lib/cluster-resource.ts | 6 +- .../@aws-cdk/aws-eks-legacy/lib/cluster.ts | 6 +- .../@aws-cdk/aws-eks-legacy/lib/helm-chart.ts | 6 +- .../aws-eks-legacy/lib/k8s-resource.ts | 6 +- .../aws-eks-legacy/lib/kubectl-layer.ts | 6 +- .../lib/alb/application-listener-action.ts | 6 +- .../lib/alb/application-target-group.ts | 6 +- .../lib/nlb/network-listener-action.ts | 6 +- .../@aws-cdk/aws-events-targets/lib/util.ts | 6 +- .../lib/accelerator-security-group.ts | 6 +- .../aws-iam/test/immutable-role.test.ts | 6 +- packages/@aws-cdk/aws-kms/test/alias.test.ts | 6 +- .../lib/event-bridge.ts | 6 +- .../aws-lambda-destinations/lib/lambda.ts | 6 +- .../@aws-cdk/aws-lambda/lib/destination.ts | 6 +- .../private/scalable-function-attribute.ts | 6 +- packages/@aws-cdk/aws-rds/lib/private/util.ts | 6 +- packages/@aws-cdk/aws-route53/lib/util.ts | 6 +- .../aws-s3-notifications/lib/lambda.ts | 6 +- .../aws-sns-subscriptions/lib/lambda.ts | 6 +- .../@aws-cdk/aws-sns-subscriptions/lib/sqs.ts | 6 +- packages/@aws-cdk/aws-sns/lib/subscriber.ts | 6 +- packages/@aws-cdk/aws-sns/lib/topic-base.ts | 6 +- .../lib/provider-framework/provider.ts | 6 +- .../waiter-state-machine.ts | 6 +- .../lib/synths/simple-synth-action.ts | 6 +- .../lib/validation/shell-script-action.ts | 6 +- tools/cdk-build-tools/config/eslintrc.js | 1 + tools/eslint-plugin-cdk/lib/index.ts | 1 + .../lib/rules/construct-import-order.ts | 109 ++++++++++++++++++ .../construct-class-usage.expected.ts | 9 ++ .../construct-class-usage.ts | 5 + .../construct-multiple-specifiers.expected.ts | 8 ++ .../construct-multiple-specifiers.ts | 4 + .../construct-nonfinal.expected.ts | 9 ++ .../construct-nonfinal.ts | 5 + .../coreconstruct-class-usage.expected.ts | 9 ++ .../coreconstruct-class-usage.ts | 5 + ...econstruct-multiple-specifiers.expected.ts | 9 ++ .../coreconstruct-multiple-specifiers.ts | 5 + .../coreconstruct-nonfinal.expected.ts | 9 ++ .../coreconstruct-nonfinal.ts | 5 + .../construct-import-order/eslintrc.js | 6 + 83 files changed, 534 insertions(+), 67 deletions(-) create mode 100644 tools/eslint-plugin-cdk/lib/rules/construct-import-order.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-class-usage.expected.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-class-usage.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-multiple-specifiers.expected.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-multiple-specifiers.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-nonfinal.expected.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-nonfinal.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-class-usage.expected.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-class-usage.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-multiple-specifiers.expected.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-multiple-specifiers.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-nonfinal.expected.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-nonfinal.ts create mode 100644 tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/eslintrc.js diff --git a/packages/@aws-cdk/assets/lib/staging.ts b/packages/@aws-cdk/assets/lib/staging.ts index 3a441a0219f55..fe573ce442fdc 100644 --- a/packages/@aws-cdk/assets/lib/staging.ts +++ b/packages/@aws-cdk/assets/lib/staging.ts @@ -1,7 +1,11 @@ -import { AssetStaging, Construct } from '@aws-cdk/core'; +import { AssetStaging } from '@aws-cdk/core'; import { toSymlinkFollow } from './compat'; import { FingerprintOptions } from './fs/options'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Deprecated * @deprecated use `core.AssetStagingProps` diff --git a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts index a286e978fdafb..d44b2ab223f4a 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts @@ -1,10 +1,14 @@ import * as crypto from 'crypto'; -import { Construct as CoreConstruct, Lazy, RemovalPolicy, Resource, CfnResource } from '@aws-cdk/core'; +import { Lazy, RemovalPolicy, Resource, CfnResource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDeployment } from './apigateway.generated'; import { Method } from './method'; import { IRestApi, RestApi, SpecRestApi, RestApiBase } from './restapi'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + export interface DeploymentProps { /** * The Rest API to deploy. diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/lambda-hook.ts b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/lambda-hook.ts index 6c3c9cd4caae2..dbe170438320e 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/lambda-hook.ts +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/lib/lambda-hook.ts @@ -3,9 +3,13 @@ import * as kms from '@aws-cdk/aws-kms'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import * as subs from '@aws-cdk/aws-sns-subscriptions'; -import { Construct } from '@aws-cdk/core'; + import { TopicHook } from './topic-hook'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Use a Lambda Function as a hook target * diff --git a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook-target.ts b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook-target.ts index b72a3245eb4d5..e15ae3ef081b5 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook-target.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook-target.ts @@ -1,6 +1,10 @@ -import { Construct } from '@aws-cdk/core'; + import { ILifecycleHook } from './lifecycle-hook'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Interface for autoscaling lifecycle hook targets */ diff --git a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts index 9b9939d740d16..35b397e81dd04 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts @@ -1,8 +1,12 @@ -import { Construct as CoreConstruct, Duration, Lazy } from '@aws-cdk/core'; +import { Duration, Lazy } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IAutoScalingGroup } from './auto-scaling-group'; import { CfnScalingPolicy } from './autoscaling.generated'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + /** * Properties for a scaling policy */ diff --git a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts index a3a417bd126d5..c3b51a892c222 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts @@ -1,10 +1,14 @@ import { findAlarmThresholds, normalizeIntervals } from '@aws-cdk/aws-autoscaling-common'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; -import { Construct as CoreConstruct, Duration } from '@aws-cdk/core'; +import { Duration } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IAutoScalingGroup } from './auto-scaling-group'; import { AdjustmentType, MetricAggregationType, StepScalingAction } from './step-scaling-action'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + export interface BasicStepScalingPolicyProps { /** * Metric to scale on. diff --git a/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts b/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts index 5631016066761..9a89faef3b045 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts @@ -1,9 +1,13 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; -import { Construct as CoreConstruct, Duration } from '@aws-cdk/core'; +import { Duration } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IAutoScalingGroup } from './auto-scaling-group'; import { CfnScalingPolicy } from './autoscaling.generated'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + /** * Base interface for target tracking props * diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.core-custom-resources.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.core-custom-resources.ts index 41029448ec1dc..1743444c1ad57 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.core-custom-resources.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.core-custom-resources.ts @@ -7,7 +7,11 @@ * - GetAtt.Attribute1: "foo" * - GetAtt.Attribute2: 1234 */ -import { App, CfnOutput, Construct, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime, Stack, Token } from '@aws-cdk/core'; +import { App, CfnOutput, CustomResource, CustomResourceProvider, CustomResourceProviderRuntime, Stack, Token } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; /* eslint-disable cdk/no-core-construct */ diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts index 650609c0e6d40..9bb2ba7e5f852 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts @@ -2,9 +2,13 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; import * as sns_subscriptions from '@aws-cdk/aws-sns-subscriptions'; import * as sqs from '@aws-cdk/aws-sqs'; -import { App, CfnParameter, Construct, Stack } from '@aws-cdk/core'; +import { App, CfnParameter, Stack } from '@aws-cdk/core'; import * as cfn from '../lib'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /* eslint-disable cdk/no-core-construct */ interface MyNestedStackProps { diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts index 4a6599c289928..ad7ed4e85d294 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-assets.ts @@ -1,9 +1,13 @@ /// !cdk-integ pragma:ignore-assets import * as path from 'path'; import * as lambda from '@aws-cdk/aws-lambda'; -import { App, Construct, Stack } from '@aws-cdk/core'; +import { App, Stack } from '@aws-cdk/core'; import * as cfn from '../lib'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /* eslint-disable cdk/no-core-construct */ class NestedStack extends cfn.NestedStack { diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts index 109b151138b19..67f11d0f31878 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts @@ -1,8 +1,12 @@ /// !cdk-integ pragma:ignore-assets import * as sns from '@aws-cdk/aws-sns'; -import { App, Construct, Stack } from '@aws-cdk/core'; +import { App, Stack } from '@aws-cdk/core'; import * as cfn from '../lib'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /* eslint-disable cdk/no-core-construct */ class YourNestedStack extends cfn.NestedStack { diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts index b6ca8b087f7ad..1240d8d39e5b3 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs1.ts @@ -5,9 +5,13 @@ /* eslint-disable cdk/no-core-construct */ import * as sns from '@aws-cdk/aws-sns'; -import { App, Construct, Stack } from '@aws-cdk/core'; +import { App, Stack } from '@aws-cdk/core'; import * as cfn from '../lib'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + class ConsumerNestedStack extends cfn.NestedStack { constructor(scope: Construct, id: string, topic: sns.Topic) { super(scope, id); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts index 5b884b209786b..712de493fa9a3 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs2.ts @@ -1,8 +1,12 @@ /// !cdk-integ * import * as sns from '@aws-cdk/aws-sns'; -import { App, Construct, Fn, Stack } from '@aws-cdk/core'; +import { App, Fn, Stack } from '@aws-cdk/core'; import * as cfn from '../lib'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + // non-nested non-parent stack consumes a resource from a nested stack /* eslint-disable cdk/no-core-construct */ diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts index ab1f51be91330..f44eb1b1f731a 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-refs3.ts @@ -1,8 +1,12 @@ /// !cdk-integ * import * as sns from '@aws-cdk/aws-sns'; -import { App, Construct, Fn, Stack } from '@aws-cdk/core'; +import { App, Fn, Stack } from '@aws-cdk/core'; import * as cfn from '../lib'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + // references between siblings /* eslint-disable cdk/no-core-construct */ diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts b/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts index 056849c1dc12b..64656faf0fca3 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts @@ -3,10 +3,14 @@ import * as path from 'path'; import { expect, haveResource, matchTemplate, SynthUtils } from '@aws-cdk/assert'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import * as sns from '@aws-cdk/aws-sns'; -import { App, CfnParameter, CfnResource, Construct, ContextProvider, LegacyStackSynthesizer, Names, Stack } from '@aws-cdk/core'; +import { App, CfnParameter, CfnResource, ContextProvider, LegacyStackSynthesizer, Names, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { NestedStack } from '../lib/nested-stack'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /* eslint-disable cdk/no-core-construct */ /* eslint-disable max-len */ diff --git a/packages/@aws-cdk/aws-cloudfront/lib/origin.ts b/packages/@aws-cdk/aws-cloudfront/lib/origin.ts index 0722dff17099d..6f5a42e407e01 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/origin.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/origin.ts @@ -1,6 +1,10 @@ -import { Construct, Duration, Token } from '@aws-cdk/core'; +import { Duration, Token } from '@aws-cdk/core'; import { CfnDistribution } from './cloudfront.generated'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * The failover configuration used for Origin Groups, * returned in {@link OriginBindConfig.failoverConfig}. diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm-action.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm-action.ts index 7b48d0f055873..fb7afe98d7725 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm-action.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm-action.ts @@ -1,6 +1,10 @@ -import { Construct } from '@aws-cdk/core'; + import { IAlarm } from './alarm-base'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Interface for objects that can be the targets of CloudWatch alarm actions */ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/action.ts index b3ce90e2aa793..c2f7ca5ca5305 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/action.ts @@ -1,6 +1,10 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as events from '@aws-cdk/aws-events'; -import { Construct, Lazy } from '@aws-cdk/core'; +import { Lazy } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; /** * Low-level class for generic CodePipeline Actions. diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/alexa-ask/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/alexa-ask/deploy-action.ts index ebbe9fd060168..f5eb7c4e64579 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/alexa-ask/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/alexa-ask/deploy-action.ts @@ -1,7 +1,11 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; -import { Construct, SecretValue } from '@aws-cdk/core'; +import { SecretValue } from '@aws-cdk/core'; import { Action } from '../action'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Construction properties of the {@link AlexaSkillDeployAction Alexa deploy Action}. */ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts index 75f34777471e5..7d7625ab4fca4 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/bitbucket/source-action.ts @@ -1,9 +1,13 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct } from '@aws-cdk/core'; + import { Action } from '../action'; import { sourceArtifactBounds } from '../common'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Construction properties for {@link BitBucketSourceAction}. * diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts index 2b18bb9db6071..602a168487830 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts @@ -2,10 +2,14 @@ import * as codecommit from '@aws-cdk/aws-codecommit'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as targets from '@aws-cdk/aws-events-targets'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct, Names, Token } from '@aws-cdk/core'; +import { Names, Token } from '@aws-cdk/core'; import { Action } from '../action'; import { sourceArtifactBounds } from '../common'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * How should the CodeCommit Action detect changes. * This is the type of the {@link CodeCommitSourceAction.trigger} property. diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/ecs-deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/ecs-deploy-action.ts index 33bc07c148b1e..dd9ff5247e828 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/ecs-deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/ecs-deploy-action.ts @@ -1,9 +1,13 @@ import * as codedeploy from '@aws-cdk/aws-codedeploy'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct, Lazy } from '@aws-cdk/core'; +import { Lazy } from '@aws-cdk/core'; import { Action } from '../action'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Configuration for replacing a placeholder string in the ECS task * definition template file with an image URI. diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts index 07c0662824481..519a47708b74a 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts @@ -1,10 +1,14 @@ import * as codedeploy from '@aws-cdk/aws-codedeploy'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct } from '@aws-cdk/core'; + import { Action } from '../action'; import { deployArtifactBounds } from '../common'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Construction properties of the {@link CodeDeployServerDeployAction CodeDeploy server deploy CodePipeline Action}. */ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts index f788c60d6aeea..6042d701017a5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts @@ -2,10 +2,14 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as ecr from '@aws-cdk/aws-ecr'; import * as targets from '@aws-cdk/aws-events-targets'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct, Names } from '@aws-cdk/core'; +import { Names } from '@aws-cdk/core'; import { Action } from '../action'; import { sourceArtifactBounds } from '../common'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * The CodePipeline variables emitted by the ECR source Action. */ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts index ec5dfb7ad2999..22c9988ac3ff5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts @@ -1,10 +1,14 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct, Duration } from '@aws-cdk/core'; +import { Duration } from '@aws-cdk/core'; import { Action } from '../action'; import { deployArtifactBounds } from '../common'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Construction properties of {@link EcsDeployAction}. */ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts index 82ca0b033120c..96b90a0ecb19b 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts @@ -1,8 +1,12 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; -import { Construct, SecretValue } from '@aws-cdk/core'; +import { SecretValue } from '@aws-cdk/core'; import { Action } from '../action'; import { sourceArtifactBounds } from '../common'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * If and how the GitHub source action should be triggered */ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-action.ts index 5ebb614ea4ee7..81da65206b12a 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-action.ts @@ -1,8 +1,12 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; -import { Construct } from '@aws-cdk/core'; + import { Action } from '../action'; import { IJenkinsProvider, jenkinsArtifactsBounds } from './jenkins-provider'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * The type of the Jenkins Action that determines its CodePipeline Category - * Build, or Test. diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts index 987968728cb4d..f8a49d977f6b4 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts @@ -1,9 +1,13 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, Stack } from '@aws-cdk/core'; +import { Stack } from '@aws-cdk/core'; import { Action } from '../action'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Construction properties of the {@link LambdaInvokeAction Lambda invoke CodePipeline Action}. */ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts index aebc8ba2548c8..75998456f4a39 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts @@ -1,10 +1,14 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as s3 from '@aws-cdk/aws-s3'; -import { Construct, Duration } from '@aws-cdk/core'; +import { Duration } from '@aws-cdk/core'; import { kebab as toKebabCase } from 'case'; import { Action } from '../action'; import { deployArtifactBounds } from '../common'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + // Class copied verbatim from the aws-s3-deployment module. // Yes, it sucks that we didn't abstract this properly in a common class, // but having 2 different CacheControl classes that behave differently would be worse I think. diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts index 2470ea6cca25f..cb86bd2591c6a 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts @@ -1,10 +1,14 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as targets from '@aws-cdk/aws-events-targets'; import * as s3 from '@aws-cdk/aws-s3'; -import { Construct, Names, Token } from '@aws-cdk/core'; +import { Names, Token } from '@aws-cdk/core'; import { Action } from '../action'; import { sourceArtifactBounds } from '../common'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * How should the S3 Action detect changes. * This is the type of the {@link S3SourceAction.trigger} property. diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts index f55766315c070..9059af1ac0ba1 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts @@ -1,8 +1,12 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct } from '@aws-cdk/core'; + import { Action } from '../action'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Construction properties of the {@link ServiceCatalogDeployAction ServiceCatalog deploy CodePipeline Action}. * diff --git a/packages/@aws-cdk/aws-codepipeline/lib/action.ts b/packages/@aws-cdk/aws-codepipeline/lib/action.ts index b2b6e79154699..6b28bcc8a3749 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/action.ts @@ -1,9 +1,13 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; -import { Construct, IResource } from '@aws-cdk/core'; +import { IResource } from '@aws-cdk/core'; import { Artifact } from './artifact'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + export enum ActionCategory { SOURCE = 'Source', BUILD = 'Build', diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index ba7fd8d87233d..760e049c6ed73 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -3,7 +3,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import { - App, BootstraplessSynthesizer, Construct as CoreConstruct, DefaultStackSynthesizer, + App, BootstraplessSynthesizer, DefaultStackSynthesizer, IStackSynthesizer, Lazy, Names, PhysicalName, RemovalPolicy, Resource, Stack, Token, } from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -15,6 +15,10 @@ import { RichAction } from './private/rich-action'; import { Stage } from './private/stage'; import { validateName, validateNamespaceName, validateSourceAction } from './private/validation'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + /** * Allows you to control where to place a new Stage when it's added to the Pipeline. * Note that you can provide only one of the below properties - diff --git a/packages/@aws-cdk/aws-codepipeline/lib/private/rich-action.ts b/packages/@aws-cdk/aws-codepipeline/lib/private/rich-action.ts index f218344da61fa..e2cde045f88a3 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/private/rich-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/private/rich-action.ts @@ -1,7 +1,11 @@ import * as events from '@aws-cdk/aws-events'; -import { Construct, ResourceEnvironment, Stack, Token, TokenComparison } from '@aws-cdk/core'; +import { ResourceEnvironment, Stack, Token, TokenComparison } from '@aws-cdk/core'; import { ActionBindOptions, ActionConfig, ActionProperties, IAction, IPipeline, IStage } from '../action'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Helper routines to work with Actions * diff --git a/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts b/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts index 45182672e45a0..718c0e693e454 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts @@ -1,10 +1,14 @@ import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct as CoreConstruct, Duration, NestedStack, Stack } from '@aws-cdk/core'; +import { Duration, NestedStack, Stack } from '@aws-cdk/core'; import * as cr from '@aws-cdk/custom-resources'; import { Construct } from 'constructs'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + export class ReplicaProvider extends NestedStack { /** * Creates a stack-singleton resource provider nested stack. diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index 792f68f4cca3e..b1c7c4f339ccd 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -3,7 +3,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import { - Aws, CfnCondition, CfnCustomResource, Construct as CoreConstruct, CustomResource, Fn, + Aws, CfnCondition, CfnCustomResource, CustomResource, Fn, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token, } from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -14,6 +14,10 @@ import { ReplicaProvider } from './replica-provider'; import { EnableScalingProps, IScalableTableAttribute } from './scalable-attribute-api'; import { ScalableTableAttribute } from './scalable-table-attribute'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + const HASH_KEY_TYPE = 'HASH'; const RANGE_KEY_TYPE = 'RANGE'; diff --git a/packages/@aws-cdk/aws-ec2/lib/cfn-init.ts b/packages/@aws-cdk/aws-ec2/lib/cfn-init.ts index 324847845849b..2388c80736df4 100644 --- a/packages/@aws-cdk/aws-ec2/lib/cfn-init.ts +++ b/packages/@aws-cdk/aws-ec2/lib/cfn-init.ts @@ -1,11 +1,15 @@ import * as crypto from 'crypto'; import * as iam from '@aws-cdk/aws-iam'; -import { Aws, CfnResource, Construct } from '@aws-cdk/core'; +import { Aws, CfnResource } from '@aws-cdk/core'; import { InitElement } from './cfn-init-elements'; import { OperatingSystemType } from './machine-image'; import { InitBindOptions, InitElementConfig, InitElementType, InitPlatform } from './private/cfn-init-internal'; import { UserData } from './user-data'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * A CloudFormation-init configuration */ diff --git a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts index 34405181774a1..1871fc1e75ac5 100644 --- a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts +++ b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts @@ -1,10 +1,14 @@ import * as ssm from '@aws-cdk/aws-ssm'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { Construct, ContextProvider, Stack, Token } from '@aws-cdk/core'; +import { ContextProvider, Stack, Token } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { UserData } from './user-data'; import { WindowsVersion } from './windows-versions'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Interface for classes that can select an appropriate machine image to use */ diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts index 2f6f5ff436baa..26a3a40f35335 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts @@ -2,10 +2,14 @@ import * as fs from 'fs'; import * as path from 'path'; import * as assets from '@aws-cdk/assets'; import * as ecr from '@aws-cdk/aws-ecr'; -import { Annotations, Construct as CoreConstruct, FeatureFlags, IgnoreMode, Stack, Token } from '@aws-cdk/core'; +import { Annotations, FeatureFlags, IgnoreMode, Stack, Token } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + /** * Options for DockerImageAsset */ diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/aws-auth.ts b/packages/@aws-cdk/aws-eks-legacy/lib/aws-auth.ts index 276937847d558..933ccbb144ea1 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/aws-auth.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/aws-auth.ts @@ -1,9 +1,13 @@ import * as iam from '@aws-cdk/aws-iam'; -import { Construct, Lazy, Stack } from '@aws-cdk/core'; +import { Lazy, Stack } from '@aws-cdk/core'; import { Mapping } from './aws-auth-mapping'; import { Cluster } from './cluster'; import { KubernetesResource } from './k8s-resource'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + export interface AwsAuthProps { /** * The EKS cluster to apply this configuration to. diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/cluster-resource.ts b/packages/@aws-cdk/aws-eks-legacy/lib/cluster-resource.ts index 8c7600d3a1d34..81c04ef56653a 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/cluster-resource.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/cluster-resource.ts @@ -2,10 +2,14 @@ import * as path from 'path'; import * as cfn from '@aws-cdk/aws-cloudformation'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, Duration, Token } from '@aws-cdk/core'; +import { Duration, Token } from '@aws-cdk/core'; import { CfnClusterProps } from './eks.generated'; import { KubectlLayer } from './kubectl-layer'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * A low-level CFN resource Amazon EKS cluster implemented through a custom * resource. diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts index 61f3ccc01fe3d..a00358d7395ce 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts @@ -4,7 +4,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as ssm from '@aws-cdk/aws-ssm'; -import { Annotations, CfnOutput, Construct, Duration, IResource, Resource, Stack, Token, Tags } from '@aws-cdk/core'; +import { Annotations, CfnOutput, Duration, IResource, Resource, Stack, Token, Tags } from '@aws-cdk/core'; import { AwsAuth } from './aws-auth'; import { ClusterResource } from './cluster-resource'; import { CfnCluster, CfnClusterProps } from './eks.generated'; @@ -14,6 +14,10 @@ import { KubectlLayer } from './kubectl-layer'; import { spotInterruptHandler } from './spot-interrupt-handler'; import { renderUserData } from './user-data'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + // defaults are based on https://eksctl.io const DEFAULT_CAPACITY_COUNT = 2; const DEFAULT_CAPACITY_TYPE = ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE); diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts index 9be42e435123c..a427353e9f6c7 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts @@ -1,10 +1,14 @@ import * as path from 'path'; import { CustomResource, CustomResourceProvider } from '@aws-cdk/aws-cloudformation'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, Duration, Names, Stack } from '@aws-cdk/core'; +import { Duration, Names, Stack } from '@aws-cdk/core'; import { Cluster } from './cluster'; import { KubectlLayer } from './kubectl-layer'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Helm Chart options. */ diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts b/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts index d5fd29d272903..14aa566d50406 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/k8s-resource.ts @@ -1,7 +1,11 @@ import * as cfn from '@aws-cdk/aws-cloudformation'; -import { Construct, Stack } from '@aws-cdk/core'; +import { Stack } from '@aws-cdk/core'; import { Cluster } from './cluster'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + export interface KubernetesResourceProps { /** * The EKS cluster to apply this configuration to. diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts b/packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts index 33aebc224d6ce..7121b34a277d6 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/kubectl-layer.ts @@ -1,6 +1,10 @@ import * as crypto from 'crypto'; import * as lambda from '@aws-cdk/aws-lambda'; -import { CfnResource, Construct, Resource, Stack, Token } from '@aws-cdk/core'; +import { CfnResource, Resource, Stack, Token } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; const KUBECTL_APP_ARN = 'arn:aws:serverlessrepo:us-east-1:903779448426:applications/lambda-layer-kubectl'; const KUBECTL_APP_VERSION = '1.13.7'; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-action.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-action.ts index 09ae095c46a92..91d839a605d74 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-action.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-action.ts @@ -1,9 +1,13 @@ -import { Construct, Duration, IConstruct, SecretValue, Tokenization } from '@aws-cdk/core'; +import { Duration, IConstruct, SecretValue, Tokenization } from '@aws-cdk/core'; import { CfnListener } from '../elasticloadbalancingv2.generated'; import { IListenerAction } from '../shared/listener-action'; import { IApplicationListener } from './application-listener'; import { IApplicationTargetGroup } from './application-target-group'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * What to do when a client makes a request to a listener * diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index a457878c63bb9..a1d3de25bf82d 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -1,6 +1,6 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; -import { Annotations, Construct as CoreConstruct, Duration } from '@aws-cdk/core'; +import { Annotations, Duration } from '@aws-cdk/core'; import { IConstruct, Construct } from 'constructs'; import { ApplicationELBMetrics } from '../elasticloadbalancingv2-canned-metrics.generated'; import { @@ -13,6 +13,10 @@ import { determineProtocolAndPort } from '../shared/util'; import { IApplicationListener } from './application-listener'; import { HttpCodeTarget } from './application-load-balancer'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + /** * Properties for defining an Application Target Group */ diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener-action.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener-action.ts index 81b45a5e3b146..cc86ca0458ef7 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener-action.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener-action.ts @@ -1,9 +1,13 @@ -import { Construct, Duration } from '@aws-cdk/core'; +import { Duration } from '@aws-cdk/core'; import { CfnListener } from '../elasticloadbalancingv2.generated'; import { IListenerAction } from '../shared/listener-action'; import { INetworkListener } from './network-listener'; import { INetworkTargetGroup } from './network-target-group'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * What to do when a client makes a request to a listener * diff --git a/packages/@aws-cdk/aws-events-targets/lib/util.ts b/packages/@aws-cdk/aws-events-targets/lib/util.ts index fe41154b6037c..74465558bb3f9 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/util.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/util.ts @@ -1,7 +1,11 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, ConstructNode, IConstruct, Names } from '@aws-cdk/core'; +import { ConstructNode, IConstruct, Names } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; /** * Obtain the Role for the EventBridge event diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator-security-group.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator-security-group.ts index 793ae6b3aceca..9197613d69b61 100644 --- a/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator-security-group.ts +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/accelerator-security-group.ts @@ -1,8 +1,12 @@ import { ISecurityGroup, SecurityGroup, IVpc } from '@aws-cdk/aws-ec2'; -import { Construct } from '@aws-cdk/core'; + import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '@aws-cdk/custom-resources'; import { EndpointGroup } from '../lib'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * The security group used by a Global Accelerator to send traffic to resources in a VPC. */ diff --git a/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts b/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts index 0a14afee947a5..343c437c494e4 100644 --- a/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts +++ b/packages/@aws-cdk/aws-iam/test/immutable-role.test.ts @@ -1,7 +1,11 @@ import '@aws-cdk/assert/jest'; -import { Construct, Stack } from '@aws-cdk/core'; +import { Stack } from '@aws-cdk/core'; import * as iam from '../lib'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /* eslint-disable quote-props */ describe('ImmutableRole', () => { diff --git a/packages/@aws-cdk/aws-kms/test/alias.test.ts b/packages/@aws-cdk/aws-kms/test/alias.test.ts index b39ea8bf5b232..6c4356a7e8f36 100644 --- a/packages/@aws-cdk/aws-kms/test/alias.test.ts +++ b/packages/@aws-cdk/aws-kms/test/alias.test.ts @@ -1,9 +1,13 @@ import '@aws-cdk/assert/jest'; import { ArnPrincipal, PolicyStatement } from '@aws-cdk/aws-iam'; -import { App, CfnOutput, Construct, Stack } from '@aws-cdk/core'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; import { Alias } from '../lib/alias'; import { IKey, Key } from '../lib/key'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + test('default alias', () => { const app = new App(); const stack = new Stack(app, 'Test'); diff --git a/packages/@aws-cdk/aws-lambda-destinations/lib/event-bridge.ts b/packages/@aws-cdk/aws-lambda-destinations/lib/event-bridge.ts index 893a0096f92fd..f61d8409da7bd 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/lib/event-bridge.ts +++ b/packages/@aws-cdk/aws-lambda-destinations/lib/event-bridge.ts @@ -1,6 +1,10 @@ import * as events from '@aws-cdk/aws-events'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, Stack } from '@aws-cdk/core'; +import { Stack } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; /** * Use an Event Bridge event bus as a Lambda destination. diff --git a/packages/@aws-cdk/aws-lambda-destinations/lib/lambda.ts b/packages/@aws-cdk/aws-lambda-destinations/lib/lambda.ts index eaa6d020de3e7..319546471473d 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/lib/lambda.ts +++ b/packages/@aws-cdk/aws-lambda-destinations/lib/lambda.ts @@ -1,9 +1,13 @@ import * as events from '@aws-cdk/aws-events'; import * as targets from '@aws-cdk/aws-events-targets'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct } from '@aws-cdk/core'; + import { EventBridgeDestination } from './event-bridge'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Options for a Lambda destination */ diff --git a/packages/@aws-cdk/aws-lambda/lib/destination.ts b/packages/@aws-cdk/aws-lambda/lib/destination.ts index 8fb6ab956db6d..8e2917ab827fc 100644 --- a/packages/@aws-cdk/aws-lambda/lib/destination.ts +++ b/packages/@aws-cdk/aws-lambda/lib/destination.ts @@ -1,6 +1,10 @@ -import { Construct } from '@aws-cdk/core'; + import { IFunction } from './function-base'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * A destination configuration */ diff --git a/packages/@aws-cdk/aws-lambda/lib/private/scalable-function-attribute.ts b/packages/@aws-cdk/aws-lambda/lib/private/scalable-function-attribute.ts index 3e1c2e634e0b1..34ceb28ad861f 100644 --- a/packages/@aws-cdk/aws-lambda/lib/private/scalable-function-attribute.ts +++ b/packages/@aws-cdk/aws-lambda/lib/private/scalable-function-attribute.ts @@ -1,7 +1,11 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; -import { Construct, Token } from '@aws-cdk/core'; +import { Token } from '@aws-cdk/core'; import { IScalableFunctionAttribute, UtilizationScalingOptions } from '../scalable-attribute-api'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * A scalable lambda alias attribute */ diff --git a/packages/@aws-cdk/aws-rds/lib/private/util.ts b/packages/@aws-cdk/aws-rds/lib/private/util.ts index 8cba1e4a1ee1e..ca5f16d21e038 100644 --- a/packages/@aws-cdk/aws-rds/lib/private/util.ts +++ b/packages/@aws-cdk/aws-rds/lib/private/util.ts @@ -1,10 +1,14 @@ import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; -import { Construct, CfnDeletionPolicy, CfnResource, RemovalPolicy } from '@aws-cdk/core'; +import { CfnDeletionPolicy, CfnResource, RemovalPolicy } from '@aws-cdk/core'; import { DatabaseSecret } from '../database-secret'; import { IEngine } from '../engine'; import { Credentials } from '../props'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * The default set of characters we exclude from generated passwords for database users. * It's a combination of characters that have a tendency to cause problems in shell scripts, diff --git a/packages/@aws-cdk/aws-route53/lib/util.ts b/packages/@aws-cdk/aws-route53/lib/util.ts index a316e2ecd59f9..d703c77348538 100644 --- a/packages/@aws-cdk/aws-route53/lib/util.ts +++ b/packages/@aws-cdk/aws-route53/lib/util.ts @@ -1,6 +1,10 @@ -import { Construct, Stack } from '@aws-cdk/core'; +import { Stack } from '@aws-cdk/core'; import { IHostedZone } from './hosted-zone-ref'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Validates a zone name is valid by Route53 specifc naming rules, * and that there is no trailing dot in the name. diff --git a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts index 36ad917ec4ec4..183512a996e26 100644 --- a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts +++ b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts @@ -1,7 +1,11 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; -import { CfnResource, Construct, Names, Stack } from '@aws-cdk/core'; +import { CfnResource, Names, Stack } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; /** * Use a Lambda function as a bucket notification destination diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts index 3ecab463d2c2c..aa7581653d5ba 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts @@ -1,9 +1,13 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; -import { Construct, Names, Stack } from '@aws-cdk/core'; +import { Names, Stack } from '@aws-cdk/core'; import { SubscriptionProps } from './subscription'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Properties for a Lambda subscription */ diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts index bac6a13859d9c..4a41c5e43feb2 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts @@ -1,9 +1,13 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Construct, Names, Stack } from '@aws-cdk/core'; +import { Names, Stack } from '@aws-cdk/core'; import { SubscriptionProps } from './subscription'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Properties for an SQS subscription */ diff --git a/packages/@aws-cdk/aws-sns/lib/subscriber.ts b/packages/@aws-cdk/aws-sns/lib/subscriber.ts index edfd3632d415a..d63667dde46e5 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscriber.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscriber.ts @@ -1,7 +1,11 @@ -import { Construct } from '@aws-cdk/core'; + import { SubscriptionOptions } from './subscription'; import { ITopic } from './topic-base'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Subscription configuration */ diff --git a/packages/@aws-cdk/aws-sns/lib/topic-base.ts b/packages/@aws-cdk/aws-sns/lib/topic-base.ts index bddfde6288748..db262ef87e068 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic-base.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic-base.ts @@ -1,9 +1,13 @@ import * as iam from '@aws-cdk/aws-iam'; -import { Construct, IResource, Resource, Token } from '@aws-cdk/core'; +import { IResource, Resource, Token } from '@aws-cdk/core'; import { TopicPolicy } from './policy'; import { ITopicSubscription } from './subscriber'; import { Subscription } from './subscription'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Represents an SNS topic */ diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts index 3ae10fdf2e560..9da696f268b92 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts @@ -3,12 +3,16 @@ import * as cfn from '@aws-cdk/aws-cloudformation'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; -import { Construct as CoreConstruct, Duration } from '@aws-cdk/core'; +import { Duration } from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as consts from './runtime/consts'; import { calculateRetryPolicy } from './util'; import { WaiterStateMachine } from './waiter-state-machine'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + const RUNTIME_HANDLER_PATH = path.join(__dirname, 'runtime'); const FRAMEWORK_HANDLER_TIMEOUT = Duration.minutes(15); // keep it simple for now diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/waiter-state-machine.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/waiter-state-machine.ts index 513e18d7f9c8b..6799fb3178123 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/waiter-state-machine.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/waiter-state-machine.ts @@ -1,6 +1,10 @@ import { Grant, IGrantable, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; -import { CfnResource, Construct, Duration, Stack } from '@aws-cdk/core'; +import { CfnResource, Duration, Stack } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; export interface WaiterStateMachineProps { /** diff --git a/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts index 0a67af8a5609d..415f49f703e26 100644 --- a/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts +++ b/packages/@aws-cdk/pipelines/lib/synths/simple-synth-action.ts @@ -6,11 +6,15 @@ import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct, Stack } from '@aws-cdk/core'; +import { Stack } from '@aws-cdk/core'; import { cloudAssemblyBuildSpecDir } from '../private/construct-internals'; import { toPosixPath } from '../private/fs'; import { copyEnvironmentVariables, filterEmpty } from './_util'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Configuration options for a SimpleSynth */ diff --git a/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts b/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts index f70aa7897c1be..59f64555a76b5 100644 --- a/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts +++ b/packages/@aws-cdk/pipelines/lib/validation/shell-script-action.ts @@ -4,9 +4,13 @@ import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct } from '@aws-cdk/core'; + import { StackOutput } from '../stage'; +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + /** * Properties for ShellScriptAction */ diff --git a/tools/cdk-build-tools/config/eslintrc.js b/tools/cdk-build-tools/config/eslintrc.js index 446af2c2e2ff4..8fda93acca482 100644 --- a/tools/cdk-build-tools/config/eslintrc.js +++ b/tools/cdk-build-tools/config/eslintrc.js @@ -41,6 +41,7 @@ module.exports = { }, ignorePatterns: ['*.js', '*.d.ts', 'node_modules/', '*.generated.ts'], rules: { + 'cdk/construct-import-order': [ 'error' ], 'cdk/no-core-construct': [ 'error' ], 'cdk/no-qualified-construct': [ 'error' ], // Require use of the `import { foo } from 'bar';` form instead of `import foo = require('bar');` diff --git a/tools/eslint-plugin-cdk/lib/index.ts b/tools/eslint-plugin-cdk/lib/index.ts index 94eef4dd8f57f..a9eb84b77ef0f 100644 --- a/tools/eslint-plugin-cdk/lib/index.ts +++ b/tools/eslint-plugin-cdk/lib/index.ts @@ -1,4 +1,5 @@ export const rules = { 'no-core-construct': require('./rules/no-core-construct'), 'no-qualified-construct': require('./rules/no-qualified-construct'), + 'construct-import-order': require('./rules/construct-import-order'), }; diff --git a/tools/eslint-plugin-cdk/lib/rules/construct-import-order.ts b/tools/eslint-plugin-cdk/lib/rules/construct-import-order.ts new file mode 100644 index 0000000000000..bc6d5a0e160bf --- /dev/null +++ b/tools/eslint-plugin-cdk/lib/rules/construct-import-order.ts @@ -0,0 +1,109 @@ +// +// This rule ensures that the `@aws-cdk/core.Construct` class is always +// imported at the end, and in a separate section. In the `v2-main` branch, +// this class is removed and so is the import. Keeping it in a separate line +// and section ensures that any other adjustments to the import do not cause +// conflicts on forward merges. +// + +import { ImportDeclaration } from 'estree'; +import { Rule } from 'eslint'; + +interface ImportOrderViolation { + node: ImportDeclaration; + localName: string; + range: [number, number]; +} + +let importOrderViolation: ImportOrderViolation | undefined; +let coreConstructImportLine: ImportDeclaration | undefined; +let lastImport: Rule.Node | undefined; + +export function create(context: Rule.RuleContext): Rule.NodeListener { + // skip core + if (context.getFilename().includes('@aws-cdk/core')) { + return {}; + } + + return { + Program: _ => { + // reset for every file + importOrderViolation = undefined; + coreConstructImportLine = undefined; + lastImport = undefined; + }, + + // collect all "import" statements. we will later use them to determine + // exactly how to import `core.Construct`. + ImportDeclaration: node => { + lastImport = node; + + if (coreConstructImportLine && coreConstructImportLine.range) { + // If CoreConstruct import was previously seen, this import line should not succeed it. + + importOrderViolation = { + node: coreConstructImportLine, + range: coreConstructImportLine.range, + localName: coreConstructImportLine.specifiers[0].local.name, + }; + } + + for (const [i, s] of node.specifiers.entries()) { + const isConstruct = (s.local.name === 'CoreConstruct' || s.local.name === 'Construct') && node.source.value === '@aws-cdk/core'; + if (isConstruct && s.range) { + if (node.specifiers.length > 1) { + // if there is more than one specifier on the line that also imports CoreConstruct, i.e., + // `import { Resource, Construct as CoreConstruct, Token } from '@aws-cdk/core'` + + // If this is the last specifier, delete just that. If not, delete until the beginning of the next specifier. + const range: [number, number] = (i === node.specifiers.length - 1) ? s.range : [s.range[0], node.specifiers[i + 1].range![0]]; + importOrderViolation = { node, range, localName: s.local.name }; + } else { + // This means that CoreConstruct is the only import within this line, + // so record the node so the whole line can be removed if there are imports that follow + + coreConstructImportLine = node; + } + } + } + }, + + Identifier: node => { + if ( + node.parent.type !== 'ImportSpecifier' && + (node.name === 'CoreConstruct' || node.name === 'Construct') && + importOrderViolation + ) { + reportImportOrderViolations(context); + } + }, + } +} + +function reportImportOrderViolations(context: Rule.RuleContext) { + if (importOrderViolation && lastImport) { + const violation = importOrderViolation; + const _lastImport = lastImport; + context.report({ + message: 'To avoid merge conflicts with the v2 branch, import of "@aws-cdk/core.Construct" must be in its own line, ' + + 'and as the very last import.', + node: violation.node, + fix: fixer => { + const fixes: Rule.Fix[] = []; + fixes.push(fixer.removeRange(violation.range)); + const sym = violation.localName === 'Construct' ? 'Construct' : 'Construct as CoreConstruct' + const addImport = `import { ${sym} } from '@aws-cdk/core';`; + fixes.push(fixer.insertTextAfter(_lastImport, [ + "", + "", + "// keep this import separate from other imports to reduce chance for merge conflicts with v2-main", + "// eslint-disable-next-line no-duplicate-imports, import/order", + addImport, + ].join('\n'))); + return fixes; + } + }); + // reset, so that this is reported only once + importOrderViolation = undefined; + } +} \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-class-usage.expected.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-class-usage.expected.ts new file mode 100644 index 0000000000000..a6c9ecede29f0 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-class-usage.expected.ts @@ -0,0 +1,9 @@ + +import { Something } from 'Somewhere'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + +class MyConstruct extends Construct { +} \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-class-usage.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-class-usage.ts new file mode 100644 index 0000000000000..8689366792602 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-class-usage.ts @@ -0,0 +1,5 @@ +import { Construct } from '@aws-cdk/core'; +import { Something } from 'Somewhere'; + +class MyConstruct extends Construct { +} \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-multiple-specifiers.expected.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-multiple-specifiers.expected.ts new file mode 100644 index 0000000000000..b0458a380f220 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-multiple-specifiers.expected.ts @@ -0,0 +1,8 @@ +import { Resource } from '@aws-cdk/core'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + +let x: Resource; +let y: Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-multiple-specifiers.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-multiple-specifiers.ts new file mode 100644 index 0000000000000..c417c728ab86c --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-multiple-specifiers.ts @@ -0,0 +1,4 @@ +import { Construct, Resource } from '@aws-cdk/core'; + +let x: Resource; +let y: Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-nonfinal.expected.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-nonfinal.expected.ts new file mode 100644 index 0000000000000..a9e29b0d3a912 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-nonfinal.expected.ts @@ -0,0 +1,9 @@ + +import { Something } from 'Somewhere'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + +let x: Something; +let y: Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-nonfinal.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-nonfinal.ts new file mode 100644 index 0000000000000..d7716d82c3d12 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/construct-nonfinal.ts @@ -0,0 +1,5 @@ +import { Construct } from '@aws-cdk/core'; +import { Something } from 'Somewhere'; + +let x: Something; +let y: Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-class-usage.expected.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-class-usage.expected.ts new file mode 100644 index 0000000000000..eccef051ab56c --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-class-usage.expected.ts @@ -0,0 +1,9 @@ + +import { Something } from 'Somewhere'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + +class MyConstruct extends CoreConstruct { +} \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-class-usage.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-class-usage.ts new file mode 100644 index 0000000000000..5cf72993ecbe9 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-class-usage.ts @@ -0,0 +1,5 @@ +import { Construct as CoreConstruct } from '@aws-cdk/core'; +import { Something } from 'Somewhere'; + +class MyConstruct extends CoreConstruct { +} \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-multiple-specifiers.expected.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-multiple-specifiers.expected.ts new file mode 100644 index 0000000000000..8a8b9c6efe4b4 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-multiple-specifiers.expected.ts @@ -0,0 +1,9 @@ +import { Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs' + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + +let x: CoreConstruct; +let y: Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-multiple-specifiers.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-multiple-specifiers.ts new file mode 100644 index 0000000000000..403f5166a31bc --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-multiple-specifiers.ts @@ -0,0 +1,5 @@ +import { Construct as CoreConstruct, Resource, Token } from '@aws-cdk/core'; +import { Construct } from 'constructs' + +let x: CoreConstruct; +let y: Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-nonfinal.expected.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-nonfinal.expected.ts new file mode 100644 index 0000000000000..a0430db275df2 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-nonfinal.expected.ts @@ -0,0 +1,9 @@ + +import { Construct } from 'constructs'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct as CoreConstruct } from '@aws-cdk/core'; + +let x: CoreConstruct; +let y: Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-nonfinal.ts b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-nonfinal.ts new file mode 100644 index 0000000000000..98baab61bb84c --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/coreconstruct-nonfinal.ts @@ -0,0 +1,5 @@ +import { Construct as CoreConstruct } from '@aws-cdk/core'; +import { Construct } from 'constructs'; + +let x: CoreConstruct; +let y: Construct; \ No newline at end of file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/eslintrc.js b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/eslintrc.js new file mode 100644 index 0000000000000..3082e03d4ed79 --- /dev/null +++ b/tools/eslint-plugin-cdk/test/rules/fixtures/construct-import-order/eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: ['rulesdir'], + rules: { + 'rulesdir/construct-import-order': [ 'error' ], + } +} \ No newline at end of file From be7202fa229435607e81d480726e9ce7f625b85a Mon Sep 17 00:00:00 2001 From: Chris McKnight Date: Thu, 4 Feb 2021 06:03:42 -0600 Subject: [PATCH 55/70] fix(core): incorrect GetParameter permissions in nonstandard partitions (#12813) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml index ee30856453ff9..b541401f930e7 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml @@ -358,7 +358,7 @@ Resources: Action: - ssm:GetParameter Resource: - - Fn::Sub: "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${CdkBootstrapVersion}" + - Fn::Sub: "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter${CdkBootstrapVersion}" Version: '2012-10-17' PolicyName: default RoleName: From a53032976ee4119375a46861bbaa9907725f9745 Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Thu, 4 Feb 2021 05:43:44 -0700 Subject: [PATCH 56/70] chore(core): minor typo in Names.ts in core (#12738) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/core/lib/names.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/core/lib/names.ts b/packages/@aws-cdk/core/lib/names.ts index 03998fcebe902..2d204c298d9fe 100644 --- a/packages/@aws-cdk/core/lib/names.ts +++ b/packages/@aws-cdk/core/lib/names.ts @@ -9,7 +9,7 @@ import { makeUniqueId } from './private/uniqueid'; export class Names { /** * Returns a CloudFormation-compatible unique identifier for a construct based - * on its path. The identifier includes a human readable porition rendered + * on its path. The identifier includes a human readable portion rendered * from the path components and a hash suffix. * * @param construct The construct @@ -23,7 +23,7 @@ export class Names { /** * Returns a CloudFormation-compatible unique identifier for a construct based - * on its path. The identifier includes a human readable porition rendered + * on its path. The identifier includes a human readable portion rendered * from the path components and a hash suffix. * * TODO (v2): replace with API to use `constructs.Node`. From b16ea9fe55892637c5352d73d44a90cd7aae1789 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 4 Feb 2021 13:21:38 +0000 Subject: [PATCH 57/70] chore: update error message with the correct property name (#12863) A previous commit - 715a0300 - introduced the `sameEnvironment` property but incorrectly stated this as `allowPermissions` property in the error message. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda/lib/function-base.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 8b8dd585c21c2..92ca396cafbb2 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -326,7 +326,7 @@ export abstract class FunctionBase extends Resource implements IFunction { const permissionNode = this._functionNode().tryFindChild(identifier); if (!permissionNode) { throw new Error('Cannot modify permission to lambda function. Function is either imported or $LATEST version. ' - + 'If the function is imported from the same account use `fromFunctionAttributes()` API with the `allowPermissions` flag.'); + + 'If the function is imported from the same account use `fromFunctionAttributes()` API with the `sameEnvironment` flag.'); } return { statementAdded: true, policyDependable: permissionNode }; }, From f959b3a2eeb5a9a9e44ea3f88622f77f7667bfa4 Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Thu, 4 Feb 2021 16:37:35 +0200 Subject: [PATCH 58/70] feat(cfnspec): cloudformation spec v26.0.0 (#12841) Co-authored-by: AWS CDK Team Co-authored-by: Eli Polonsky Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../@aws-cdk/aws-lookoutvision/.eslintrc.js | 3 + .../@aws-cdk/aws-lookoutvision/.gitignore | 19 + .../@aws-cdk/aws-lookoutvision/.npmignore | 28 + packages/@aws-cdk/aws-lookoutvision/LICENSE | 201 +++++ packages/@aws-cdk/aws-lookoutvision/NOTICE | 2 + packages/@aws-cdk/aws-lookoutvision/README.md | 20 + .../@aws-cdk/aws-lookoutvision/jest.config.js | 2 + .../@aws-cdk/aws-lookoutvision/lib/index.ts | 2 + .../@aws-cdk/aws-lookoutvision/package.json | 97 +++ .../test/lookoutvision.test.ts | 6 + packages/@aws-cdk/cfnspec/CHANGELOG.md | 84 +++ packages/@aws-cdk/cfnspec/cfn.version | 2 +- ...0_CloudFormationResourceSpecification.json | 710 +++++++++++++++++- .../cloudformation-include/package.json | 2 + packages/aws-cdk-lib/package.json | 1 + packages/decdk/package.json | 1 + packages/monocdk/package.json | 1 + 17 files changed, 1146 insertions(+), 35 deletions(-) create mode 100644 packages/@aws-cdk/aws-lookoutvision/.eslintrc.js create mode 100644 packages/@aws-cdk/aws-lookoutvision/.gitignore create mode 100644 packages/@aws-cdk/aws-lookoutvision/.npmignore create mode 100644 packages/@aws-cdk/aws-lookoutvision/LICENSE create mode 100644 packages/@aws-cdk/aws-lookoutvision/NOTICE create mode 100644 packages/@aws-cdk/aws-lookoutvision/README.md create mode 100644 packages/@aws-cdk/aws-lookoutvision/jest.config.js create mode 100644 packages/@aws-cdk/aws-lookoutvision/lib/index.ts create mode 100644 packages/@aws-cdk/aws-lookoutvision/package.json create mode 100644 packages/@aws-cdk/aws-lookoutvision/test/lookoutvision.test.ts diff --git a/packages/@aws-cdk/aws-lookoutvision/.eslintrc.js b/packages/@aws-cdk/aws-lookoutvision/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/@aws-cdk/aws-lookoutvision/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lookoutvision/.gitignore b/packages/@aws-cdk/aws-lookoutvision/.gitignore new file mode 100644 index 0000000000000..62ebc95d75ce6 --- /dev/null +++ b/packages/@aws-cdk/aws-lookoutvision/.gitignore @@ -0,0 +1,19 @@ +*.js +*.js.map +*.d.ts +tsconfig.json +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk +nyc.config.js +!.eslintrc.js +!jest.config.js +junit.xml diff --git a/packages/@aws-cdk/aws-lookoutvision/.npmignore b/packages/@aws-cdk/aws-lookoutvision/.npmignore new file mode 100644 index 0000000000000..e4486030fcb17 --- /dev/null +++ b/packages/@aws-cdk/aws-lookoutvision/.npmignore @@ -0,0 +1,28 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo + +tsconfig.json + +.eslintrc.js +jest.config.js + +# exclude cdk artifacts +**/cdk.out +junit.xml +test/ diff --git a/packages/@aws-cdk/aws-lookoutvision/LICENSE b/packages/@aws-cdk/aws-lookoutvision/LICENSE new file mode 100644 index 0000000000000..28e4bdcec77ec --- /dev/null +++ b/packages/@aws-cdk/aws-lookoutvision/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-lookoutvision/NOTICE b/packages/@aws-cdk/aws-lookoutvision/NOTICE new file mode 100644 index 0000000000000..5fc3826926b5b --- /dev/null +++ b/packages/@aws-cdk/aws-lookoutvision/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-lookoutvision/README.md b/packages/@aws-cdk/aws-lookoutvision/README.md new file mode 100644 index 0000000000000..b0082462251d2 --- /dev/null +++ b/packages/@aws-cdk/aws-lookoutvision/README.md @@ -0,0 +1,20 @@ +# AWS::LookoutVision Construct Library + + +--- + +![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) + +> All classes with the `Cfn` prefix in this module ([CFN Resources]) are always stable and safe to use. +> +> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib + +--- + + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +```ts +import lookoutvision = require('@aws-cdk/aws-lookoutvision'); +``` diff --git a/packages/@aws-cdk/aws-lookoutvision/jest.config.js b/packages/@aws-cdk/aws-lookoutvision/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-lookoutvision/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-lookoutvision/lib/index.ts b/packages/@aws-cdk/aws-lookoutvision/lib/index.ts new file mode 100644 index 0000000000000..1be824f5b354f --- /dev/null +++ b/packages/@aws-cdk/aws-lookoutvision/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::LookoutVision CloudFormation Resources: +export * from './lookoutvision.generated'; diff --git a/packages/@aws-cdk/aws-lookoutvision/package.json b/packages/@aws-cdk/aws-lookoutvision/package.json new file mode 100644 index 0000000000000..9f7d59fd4c321 --- /dev/null +++ b/packages/@aws-cdk/aws-lookoutvision/package.json @@ -0,0 +1,97 @@ +{ + "name": "@aws-cdk/aws-lookoutvision", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::LookoutVision", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "projectReferences": true, + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.LookoutVision", + "packageId": "Amazon.CDK.AWS.LookoutVision", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "java": { + "package": "software.amazon.awscdk.services.lookoutvision", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "lookoutvision" + } + }, + "python": { + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ], + "distName": "aws-cdk.aws-lookoutvision", + "module": "aws_cdk.aws_lookoutvision" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-lookoutvision" + }, + "homepage": "https://github.com/aws/aws-cdk", + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts", + "build+test": "yarn build && yarn test", + "build+test+package": "yarn build+test && yarn package", + "compat": "cdk-compat", + "gen": "cfn2ts", + "rosetta:extract": "yarn --silent jsii-rosetta extract" + }, + "cdk-build": { + "cloudformation": "AWS::LookoutVision", + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": "true" + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::LookoutVision", + "aws-lookoutvision" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "cdk-build-tools": "0.0.0", + "cfn2ts": "0.0.0", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/core": "0.0.0" + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "cfn-only", + "awscdkio": { + "announce": false + } +} diff --git a/packages/@aws-cdk/aws-lookoutvision/test/lookoutvision.test.ts b/packages/@aws-cdk/aws-lookoutvision/test/lookoutvision.test.ts new file mode 100644 index 0000000000000..e394ef336bfb4 --- /dev/null +++ b/packages/@aws-cdk/aws-lookoutvision/test/lookoutvision.test.ts @@ -0,0 +1,6 @@ +import '@aws-cdk/assert/jest'; +import {} from '../lib'; + +test('No tests are specified for this package', () => { + expect(true).toBe(true); +}); diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index bf8b43b6865e2..2d117eebb4978 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,87 @@ +# CloudFormation Resource Specification v26.0.0 + +## New Resource Types + +* AWS::LookoutVision::Project +* AWS::SageMaker::FeatureGroup + +## Attribute Changes + +* AWS::MediaConnect::FlowVpcInterface FlowArn (__deleted__) +* AWS::MediaConnect::FlowVpcInterface Name (__deleted__) +* AWS::S3::AccessPoint NetworkOrigin (__added__) +* AWS::S3::AccessPoint PolicyStatus (__added__) + +## Property Changes + +* AWS::ACMPCA::Certificate ApiPassthrough (__added__) +* AWS::ACMPCA::Certificate ValidityNotBefore (__added__) +* AWS::AmazonMQ::Configuration AuthenticationStrategy (__added__) +* AWS::ApiGatewayV2::Stage AccessPolicyId (__added__) +* AWS::ECS::Cluster Configuration (__added__) +* AWS::Kinesis::Stream Tags.DuplicatesAllowed (__changed__) + * Old: false + * New: true +* AWS::MediaConnect::FlowVpcInterface FlowArn (__added__) +* AWS::MediaConnect::FlowVpcInterface Name (__added__) +* AWS::S3::AccessPoint NetworkOrigin (__deleted__) +* AWS::S3::AccessPoint PolicyStatus (__deleted__) +* AWS::SSM::MaintenanceWindowTask MaxConcurrency.Required (__changed__) + * Old: true + * New: false +* AWS::SSM::MaintenanceWindowTask MaxErrors.Required (__changed__) + * Old: true + * New: false +* AWS::SSM::MaintenanceWindowTask Targets.Required (__changed__) + * Old: true + * New: false +* AWS::SSO::InstanceAccessControlAttributeConfiguration InstanceAccessControlAttributeConfiguration (__deleted__) +* AWS::SageMaker::Device Tags.ItemType (__changed__) + * Old: Json + * New: Tag +* AWS::SageMaker::Device Tags.Type (__changed__) + * Old: Tag + * New: List +* AWS::SageMaker::DeviceFleet Tags.ItemType (__changed__) + * Old: Json + * New: Tag +* AWS::SageMaker::DeviceFleet Tags.Type (__changed__) + * Old: Tag + * New: List +* AWS::SageMaker::Model InferenceExecutionConfig (__added__) + +## Property Type Changes + +* AWS::ACMPCA::Certificate.ApiPassthrough (__added__) +* AWS::ACMPCA::Certificate.CertificatePolicyList (__added__) +* AWS::ACMPCA::Certificate.EdiPartyName (__added__) +* AWS::ACMPCA::Certificate.ExtendedKeyUsage (__added__) +* AWS::ACMPCA::Certificate.ExtendedKeyUsageList (__added__) +* AWS::ACMPCA::Certificate.Extensions (__added__) +* AWS::ACMPCA::Certificate.GeneralName (__added__) +* AWS::ACMPCA::Certificate.GeneralNameList (__added__) +* AWS::ACMPCA::Certificate.KeyUsage (__added__) +* AWS::ACMPCA::Certificate.OtherName (__added__) +* AWS::ACMPCA::Certificate.PolicyInformation (__added__) +* AWS::ACMPCA::Certificate.PolicyQualifierInfo (__added__) +* AWS::ACMPCA::Certificate.PolicyQualifierInfoList (__added__) +* AWS::ACMPCA::Certificate.Qualifier (__added__) +* AWS::ACMPCA::Certificate.Subject (__added__) +* AWS::AppFlow::Flow.IdFieldNamesList (__added__) +* AWS::ECS::Cluster.ClusterConfiguration (__added__) +* AWS::ECS::Cluster.ExecuteCommandConfiguration (__added__) +* AWS::ECS::Cluster.ExecuteCommandLogConfiguration (__added__) +* AWS::SageMaker::Model.InferenceExecutionConfig (__added__) +* AWS::AppFlow::Flow.SalesforceDestinationProperties IdFieldNames (__added__) +* AWS::AppFlow::Flow.SalesforceDestinationProperties WriteOperationType (__added__) +* AWS::DLM::LifecyclePolicy.CreateRule Location (__added__) +* AWS::DLM::LifecyclePolicy.CrossRegionCopyRule Target (__added__) +* AWS::DLM::LifecyclePolicy.CrossRegionCopyRule TargetRegion.Required (__changed__) + * Old: true + * New: false +* AWS::DLM::LifecyclePolicy.PolicyDetails ResourceLocations (__added__) + + # CloudFormation Resource Specification v24.0.0 ## New Resource Types diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index 51105aade994f..1e212a919f492 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -24.0.0 +26.0.0 diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index 909704a8ddc33..9391cf7efc80d 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -1,5 +1,396 @@ { "PropertyTypes": { + "AWS::ACMPCA::Certificate.ApiPassthrough": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-apipassthrough.html", + "Properties": { + "Extensions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-apipassthrough.html#cfn-acmpca-certificate-apipassthrough-extensions", + "Required": false, + "Type": "Extensions", + "UpdateType": "Immutable" + }, + "Subject": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-apipassthrough.html#cfn-acmpca-certificate-apipassthrough-subject", + "Required": false, + "Type": "Subject", + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.CertificatePolicyList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-certificatepolicylist.html", + "Properties": { + "CertificatePolicyList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-certificatepolicylist.html#cfn-acmpca-certificate-certificatepolicylist-certificatepolicylist", + "ItemType": "PolicyInformation", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.EdiPartyName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-edipartyname.html", + "Properties": { + "NameAssigner": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-edipartyname.html#cfn-acmpca-certificate-edipartyname-nameassigner", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PartyName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-edipartyname.html#cfn-acmpca-certificate-edipartyname-partyname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.ExtendedKeyUsage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-extendedkeyusage.html", + "Properties": { + "ExtendedKeyUsageObjectIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-extendedkeyusage.html#cfn-acmpca-certificate-extendedkeyusage-extendedkeyusageobjectidentifier", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "ExtendedKeyUsageType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-extendedkeyusage.html#cfn-acmpca-certificate-extendedkeyusage-extendedkeyusagetype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.ExtendedKeyUsageList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-extendedkeyusagelist.html", + "Properties": { + "ExtendedKeyUsageList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-extendedkeyusagelist.html#cfn-acmpca-certificate-extendedkeyusagelist-extendedkeyusagelist", + "ItemType": "ExtendedKeyUsage", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.Extensions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-extensions.html", + "Properties": { + "CertificatePolicies": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-extensions.html#cfn-acmpca-certificate-extensions-certificatepolicies", + "Required": false, + "Type": "CertificatePolicyList", + "UpdateType": "Immutable" + }, + "ExtendedKeyUsage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-extensions.html#cfn-acmpca-certificate-extensions-extendedkeyusage", + "Required": false, + "Type": "ExtendedKeyUsageList", + "UpdateType": "Immutable" + }, + "KeyUsage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-extensions.html#cfn-acmpca-certificate-extensions-keyusage", + "Required": false, + "Type": "KeyUsage", + "UpdateType": "Immutable" + }, + "SubjectAlternativeNames": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-extensions.html#cfn-acmpca-certificate-extensions-subjectalternativenames", + "Required": false, + "Type": "GeneralNameList", + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.GeneralName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-generalname.html", + "Properties": { + "DirectoryName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-generalname.html#cfn-acmpca-certificate-generalname-directoryname", + "Required": false, + "Type": "Subject", + "UpdateType": "Immutable" + }, + "DnsName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-generalname.html#cfn-acmpca-certificate-generalname-dnsname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "EdiPartyName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-generalname.html#cfn-acmpca-certificate-generalname-edipartyname", + "Required": false, + "Type": "EdiPartyName", + "UpdateType": "Immutable" + }, + "IpAddress": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-generalname.html#cfn-acmpca-certificate-generalname-ipaddress", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "OtherName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-generalname.html#cfn-acmpca-certificate-generalname-othername", + "Required": false, + "Type": "OtherName", + "UpdateType": "Immutable" + }, + "RegisteredId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-generalname.html#cfn-acmpca-certificate-generalname-registeredid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Rfc822Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-generalname.html#cfn-acmpca-certificate-generalname-rfc822name", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "UniformResourceIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-generalname.html#cfn-acmpca-certificate-generalname-uniformresourceidentifier", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.GeneralNameList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-generalnamelist.html", + "Properties": { + "GeneralNameList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-generalnamelist.html#cfn-acmpca-certificate-generalnamelist-generalnamelist", + "ItemType": "GeneralName", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.KeyUsage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-keyusage.html", + "Properties": { + "CRLSign": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-keyusage.html#cfn-acmpca-certificate-keyusage-crlsign", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "DataEncipherment": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-keyusage.html#cfn-acmpca-certificate-keyusage-dataencipherment", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "DecipherOnly": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-keyusage.html#cfn-acmpca-certificate-keyusage-decipheronly", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "DigitalSignature": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-keyusage.html#cfn-acmpca-certificate-keyusage-digitalsignature", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "EncipherOnly": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-keyusage.html#cfn-acmpca-certificate-keyusage-encipheronly", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "KeyAgreement": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-keyusage.html#cfn-acmpca-certificate-keyusage-keyagreement", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "KeyCertSign": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-keyusage.html#cfn-acmpca-certificate-keyusage-keycertsign", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "KeyEncipherment": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-keyusage.html#cfn-acmpca-certificate-keyusage-keyencipherment", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "NonRepudiation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-keyusage.html#cfn-acmpca-certificate-keyusage-nonrepudiation", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.OtherName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-othername.html", + "Properties": { + "TypeId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-othername.html#cfn-acmpca-certificate-othername-typeid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-othername.html#cfn-acmpca-certificate-othername-value", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.PolicyInformation": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-policyinformation.html", + "Properties": { + "CertPolicyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-policyinformation.html#cfn-acmpca-certificate-policyinformation-certpolicyid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "PolicyQualifiers": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-policyinformation.html#cfn-acmpca-certificate-policyinformation-policyqualifiers", + "Required": false, + "Type": "PolicyQualifierInfoList", + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.PolicyQualifierInfo": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-policyqualifierinfo.html", + "Properties": { + "PolicyQualifierId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-policyqualifierinfo.html#cfn-acmpca-certificate-policyqualifierinfo-policyqualifierid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Qualifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-policyqualifierinfo.html#cfn-acmpca-certificate-policyqualifierinfo-qualifier", + "Required": true, + "Type": "Qualifier", + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.PolicyQualifierInfoList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-policyqualifierinfolist.html", + "Properties": { + "PolicyQualifierInfoList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-policyqualifierinfolist.html#cfn-acmpca-certificate-policyqualifierinfolist-policyqualifierinfolist", + "ItemType": "PolicyQualifierInfo", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.Qualifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-qualifier.html", + "Properties": { + "CpsUri": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-qualifier.html#cfn-acmpca-certificate-qualifier-cpsuri", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::ACMPCA::Certificate.Subject": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html", + "Properties": { + "CommonName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-commonname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Country": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-country", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "DistinguishedNameQualifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-distinguishednamequalifier", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "GenerationQualifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-generationqualifier", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "GivenName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-givenname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Initials": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-initials", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Locality": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-locality", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Organization": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-organization", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "OrganizationalUnit": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-organizationalunit", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Pseudonym": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-pseudonym", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "SerialNumber": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-serialnumber", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "State": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-state", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Surname": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-surname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Title": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-subject.html#cfn-acmpca-certificate-subject-title", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::ACMPCA::Certificate.Validity": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-acmpca-certificate-validity.html", "Properties": { @@ -3033,6 +3424,18 @@ } } }, + "AWS::AppFlow::Flow.IdFieldNamesList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-idfieldnameslist.html", + "Properties": { + "IdFieldNamesList": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-idfieldnameslist.html#cfn-appflow-flow-idfieldnameslist-idfieldnameslist", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, "AWS::AppFlow::Flow.IncrementalPullConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-incrementalpullconfig.html", "Properties": { @@ -3184,11 +3587,23 @@ "Type": "ErrorHandlingConfig", "UpdateType": "Mutable" }, + "IdFieldNames": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-salesforcedestinationproperties.html#cfn-appflow-flow-salesforcedestinationproperties-idfieldnames", + "Required": false, + "Type": "IdFieldNamesList", + "UpdateType": "Mutable" + }, "Object": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-salesforcedestinationproperties.html#cfn-appflow-flow-salesforcedestinationproperties-object", "PrimitiveType": "String", "Required": true, "UpdateType": "Mutable" + }, + "WriteOperationType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appflow-flow-salesforcedestinationproperties.html#cfn-appflow-flow-salesforcedestinationproperties-writeoperationtype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" } } }, @@ -12981,6 +13396,12 @@ "Required": false, "UpdateType": "Mutable" }, + "Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-createrule.html#cfn-dlm-lifecyclepolicy-createrule-location", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "Times": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-createrule.html#cfn-dlm-lifecyclepolicy-createrule-times", "PrimitiveItemType": "String", @@ -13057,10 +13478,16 @@ "Type": "CrossRegionCopyRetainRule", "UpdateType": "Mutable" }, + "Target": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-crossregioncopyrule.html#cfn-dlm-lifecyclepolicy-crossregioncopyrule-target", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "TargetRegion": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-crossregioncopyrule.html#cfn-dlm-lifecyclepolicy-crossregioncopyrule-targetregion", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -13198,6 +13625,13 @@ "Required": false, "UpdateType": "Mutable" }, + "ResourceLocations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-policydetails.html#cfn-dlm-lifecyclepolicy-policydetails-resourcelocations", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "ResourceTypes": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-policydetails.html#cfn-dlm-lifecyclepolicy-policydetails-resourcetypes", "PrimitiveItemType": "String", @@ -18020,6 +18454,17 @@ } } }, + "AWS::ECS::Cluster.ClusterConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-clusterconfiguration.html", + "Properties": { + "ExecuteCommandConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-clusterconfiguration.html#cfn-ecs-cluster-clusterconfiguration-executecommandconfiguration", + "Required": false, + "Type": "ExecuteCommandConfiguration", + "UpdateType": "Mutable" + } + } + }, "AWS::ECS::Cluster.ClusterSettings": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-clustersettings.html", "Properties": { @@ -18037,6 +18482,64 @@ } } }, + "AWS::ECS::Cluster.ExecuteCommandConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandconfiguration.html", + "Properties": { + "KmsKeyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandconfiguration.html#cfn-ecs-cluster-executecommandconfiguration-kmskeyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "LogConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandconfiguration.html#cfn-ecs-cluster-executecommandconfiguration-logconfiguration", + "Required": false, + "Type": "ExecuteCommandLogConfiguration", + "UpdateType": "Mutable" + }, + "Logging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandconfiguration.html#cfn-ecs-cluster-executecommandconfiguration-logging", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::ECS::Cluster.ExecuteCommandLogConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandlogconfiguration.html", + "Properties": { + "CloudWatchEncryptionEnabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandlogconfiguration.html#cfn-ecs-cluster-executecommandlogconfiguration-cloudwatchencryptionenabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "CloudWatchLogGroupName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandlogconfiguration.html#cfn-ecs-cluster-executecommandlogconfiguration-cloudwatchloggroupname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "S3BucketName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandlogconfiguration.html#cfn-ecs-cluster-executecommandlogconfiguration-s3bucketname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "S3EncryptionEnabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandlogconfiguration.html#cfn-ecs-cluster-executecommandlogconfiguration-s3encryptionenabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "S3KeyPrefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandlogconfiguration.html#cfn-ecs-cluster-executecommandlogconfiguration-s3keyprefix", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::ECS::Service.AwsVpcConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-awsvpcconfiguration.html", "Properties": { @@ -47726,6 +48229,23 @@ } } }, + "AWS::SageMaker::FeatureGroup.FeatureDefinition": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sagemaker-featuregroup-featuredefinition.html", + "Properties": { + "FeatureName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sagemaker-featuregroup-featuredefinition.html#cfn-sagemaker-featuregroup-featuredefinition-featurename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "FeatureType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sagemaker-featuregroup-featuredefinition.html#cfn-sagemaker-featuregroup-featuredefinition-featuretype", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::SageMaker::Model.ContainerDefinition": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sagemaker-model-containerdefinition.html", "Properties": { @@ -47790,6 +48310,17 @@ } } }, + "AWS::SageMaker::Model.InferenceExecutionConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sagemaker-model-inferenceexecutionconfig.html", + "Properties": { + "Mode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sagemaker-model-inferenceexecutionconfig.html#cfn-sagemaker-model-inferenceexecutionconfig-mode", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::SageMaker::Model.MultiModelConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sagemaker-model-containerdefinition-multimodelconfig.html", "Properties": { @@ -51929,7 +52460,7 @@ } } }, - "ResourceSpecificationVersion": "24.0.0", + "ResourceSpecificationVersion": "26.0.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -51942,6 +52473,12 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-acmpca-certificate.html", "Properties": { + "ApiPassthrough": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-acmpca-certificate.html#cfn-acmpca-certificate-apipassthrough", + "Required": false, + "Type": "ApiPassthrough", + "UpdateType": "Immutable" + }, "CertificateAuthorityArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-acmpca-certificate.html#cfn-acmpca-certificate-certificateauthorityarn", "PrimitiveType": "String", @@ -51971,6 +52508,12 @@ "Required": true, "Type": "Validity", "UpdateType": "Immutable" + }, + "ValidityNotBefore": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-acmpca-certificate.html#cfn-acmpca-certificate-validitynotbefore", + "Required": false, + "Type": "Validity", + "UpdateType": "Immutable" } } }, @@ -52267,6 +52810,12 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amazonmq-configuration.html", "Properties": { + "AuthenticationStrategy": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amazonmq-configuration.html#cfn-amazonmq-configuration-authenticationstrategy", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "Data": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amazonmq-configuration.html#cfn-amazonmq-configuration-data", "PrimitiveType": "String", @@ -54041,6 +54590,12 @@ "Type": "AccessLogSettings", "UpdateType": "Mutable" }, + "AccessPolicyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-stage.html#cfn-apigatewayv2-stage-accesspolicyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "ApiId": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-stage.html#cfn-apigatewayv2-stage-apiid", "PrimitiveType": "String", @@ -64723,6 +65278,12 @@ "Type": "List", "UpdateType": "Mutable" }, + "Configuration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html#cfn-ecs-cluster-configuration", + "Required": false, + "Type": "ClusterConfiguration", + "UpdateType": "Mutable" + }, "DefaultCapacityProviderStrategy": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html#cfn-ecs-cluster-defaultcapacityproviderstrategy", "ItemType": "CapacityProviderStrategyItem", @@ -72208,7 +72769,7 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesis-stream.html#cfn-kinesis-stream-tags", - "DuplicatesAllowed": false, + "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", @@ -73334,6 +73895,22 @@ } } }, + "AWS::LookoutVision::Project": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lookoutvision-project.html", + "Properties": { + "ProjectName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lookoutvision-project.html#cfn-lookoutvision-project-projectname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::MSK::Cluster": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-cluster.html", "Properties": { @@ -74013,12 +74590,6 @@ }, "AWS::MediaConnect::FlowVpcInterface": { "Attributes": { - "FlowArn": { - "PrimitiveType": "String" - }, - "Name": { - "PrimitiveType": "String" - }, "NetworkInterfaceIds": { "PrimitiveItemType": "String", "Type": "List" @@ -74026,6 +74597,18 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediaconnect-flowvpcinterface.html", "Properties": { + "FlowArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediaconnect-flowvpcinterface.html#cfn-mediaconnect-flowvpcinterface-flowarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediaconnect-flowvpcinterface.html#cfn-mediaconnect-flowvpcinterface-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, "RoleArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediaconnect-flowvpcinterface.html#cfn-mediaconnect-flowvpcinterface-rolearn", "PrimitiveType": "String", @@ -79615,6 +80198,14 @@ } }, "AWS::S3::AccessPoint": { + "Attributes": { + "NetworkOrigin": { + "PrimitiveType": "String" + }, + "PolicyStatus": { + "PrimitiveType": "Json" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-accesspoint.html", "Properties": { "Bucket": { @@ -79629,24 +80220,12 @@ "Required": false, "UpdateType": "Mutable" }, - "NetworkOrigin": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-accesspoint.html#cfn-s3-accesspoint-networkorigin", - "PrimitiveType": "String", - "Required": false, - "UpdateType": "Mutable" - }, "Policy": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-accesspoint.html#cfn-s3-accesspoint-policy", "PrimitiveType": "Json", "Required": false, "UpdateType": "Mutable" }, - "PolicyStatus": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-accesspoint.html#cfn-s3-accesspoint-policystatus", - "PrimitiveType": "Json", - "Required": false, - "UpdateType": "Mutable" - }, "PublicAccessBlockConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-accesspoint.html#cfn-s3-accesspoint-publicaccessblockconfiguration", "Required": false, @@ -80454,13 +81033,13 @@ "MaxConcurrency": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-maintenancewindowtask.html#cfn-ssm-maintenancewindowtask-maxconcurrency", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "MaxErrors": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-maintenancewindowtask.html#cfn-ssm-maintenancewindowtask-maxerrors", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Name": { @@ -80484,7 +81063,7 @@ "Targets": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-maintenancewindowtask.html#cfn-ssm-maintenancewindowtask-targets", "ItemType": "Target", - "Required": true, + "Required": false, "Type": "List", "UpdateType": "Mutable" }, @@ -80785,12 +81364,6 @@ "Type": "List", "UpdateType": "Mutable" }, - "InstanceAccessControlAttributeConfiguration": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sso-instanceaccesscontrolattributeconfiguration.html#cfn-sso-instanceaccesscontrolattributeconfiguration-instanceaccesscontrolattributeconfiguration", - "PrimitiveType": "Json", - "Required": false, - "UpdateType": "Mutable" - }, "InstanceArn": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sso-instanceaccesscontrolattributeconfiguration.html#cfn-sso-instanceaccesscontrolattributeconfiguration-instancearn", "PrimitiveType": "String", @@ -80973,9 +81546,9 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-device.html#cfn-sagemaker-device-tags", - "ItemType": "Json", + "ItemType": "Tag", "Required": false, - "Type": "Tag", + "Type": "List", "UpdateType": "Mutable" } } @@ -81009,9 +81582,9 @@ }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-devicefleet.html#cfn-sagemaker-devicefleet-tags", - "ItemType": "Json", + "ItemType": "Tag", "Required": false, - "Type": "Tag", + "Type": "List", "UpdateType": "Mutable" } } @@ -81106,6 +81679,69 @@ } } }, + "AWS::SageMaker::FeatureGroup": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-featuregroup.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-featuregroup.html#cfn-sagemaker-featuregroup-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "EventTimeFeatureName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-featuregroup.html#cfn-sagemaker-featuregroup-eventtimefeaturename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "FeatureDefinitions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-featuregroup.html#cfn-sagemaker-featuregroup-featuredefinitions", + "DuplicatesAllowed": true, + "ItemType": "FeatureDefinition", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + }, + "FeatureGroupName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-featuregroup.html#cfn-sagemaker-featuregroup-featuregroupname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "OfflineStoreConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-featuregroup.html#cfn-sagemaker-featuregroup-offlinestoreconfig", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Immutable" + }, + "OnlineStoreConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-featuregroup.html#cfn-sagemaker-featuregroup-onlinestoreconfig", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Immutable" + }, + "RecordIdentifierFeatureName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-featuregroup.html#cfn-sagemaker-featuregroup-recordidentifierfeaturename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "RoleArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-featuregroup.html#cfn-sagemaker-featuregroup-rolearn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-featuregroup.html#cfn-sagemaker-featuregroup-tags", + "DuplicatesAllowed": true, + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + } + } + }, "AWS::SageMaker::Model": { "Attributes": { "ModelName": { @@ -81133,6 +81769,12 @@ "Required": true, "UpdateType": "Immutable" }, + "InferenceExecutionConfig": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-model.html#cfn-sagemaker-model-inferenceexecutionconfig", + "Required": false, + "Type": "InferenceExecutionConfig", + "UpdateType": "Immutable" + }, "ModelName": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-model.html#cfn-sagemaker-model-modelname", "PrimitiveType": "String", diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index 6a3dd4c17346d..1732065bd3a96 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -159,6 +159,7 @@ "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-lookoutvision": "0.0.0", "@aws-cdk/aws-macie": "0.0.0", "@aws-cdk/aws-managedblockchain": "0.0.0", "@aws-cdk/aws-mediaconnect": "0.0.0", @@ -306,6 +307,7 @@ "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-licensemanager": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-lookoutvision": "0.0.0", "@aws-cdk/aws-macie": "0.0.0", "@aws-cdk/aws-managedblockchain": "0.0.0", "@aws-cdk/aws-mediaconnect": "0.0.0", diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 7c938b86bd7e0..10849cc784a4a 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -213,6 +213,7 @@ "@aws-cdk/aws-licensemanager": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-logs-destinations": "0.0.0", + "@aws-cdk/aws-lookoutvision": "0.0.0", "@aws-cdk/aws-macie": "0.0.0", "@aws-cdk/aws-managedblockchain": "0.0.0", "@aws-cdk/aws-mediaconnect": "0.0.0", diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 7f8ace298e383..a5c184e65f1d5 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -139,6 +139,7 @@ "@aws-cdk/aws-licensemanager": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-logs-destinations": "0.0.0", + "@aws-cdk/aws-lookoutvision": "0.0.0", "@aws-cdk/aws-macie": "0.0.0", "@aws-cdk/aws-managedblockchain": "0.0.0", "@aws-cdk/aws-mediaconnect": "0.0.0", diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index dddf5eb0b77fd..a02fee1b78939 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -218,6 +218,7 @@ "@aws-cdk/aws-licensemanager": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-logs-destinations": "0.0.0", + "@aws-cdk/aws-lookoutvision": "0.0.0", "@aws-cdk/aws-macie": "0.0.0", "@aws-cdk/aws-managedblockchain": "0.0.0", "@aws-cdk/aws-mediaconnect": "0.0.0", From 77b9d3930ec722be3a40e4013cd9335f90b0d945 Mon Sep 17 00:00:00 2001 From: Alex Kokachev Date: Fri, 5 Feb 2021 04:31:38 +1100 Subject: [PATCH 59/70] fix(core): append file extension to s3 asset key in new style synthesizer (#12765) - This change fixes the issue with new style synthesizer, when s3 files assets are uploaded without file extension. This was breaking integration with some services, in particular Glue, which requires jar files to have "jar" extension in order to compile. - Legacy synthesizer was creating s3 key with extension, so this change effectively aligns old and new synthesizers. fixes #12740 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../core/lib/stack-synthesizers/default-synthesizer.ts | 3 ++- .../core/test/stack-synthesis/new-style-synthesis.test.ts | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts index b4f9f29cf732a..a92d3b6fe04d0 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts @@ -291,7 +291,8 @@ export class DefaultStackSynthesizer extends StackSynthesizer { assertBound(this.bucketName); validateFileAssetSource(asset); - const objectKey = this.bucketPrefix + asset.sourceHash + (asset.packaging === FileAssetPackaging.ZIP_DIRECTORY ? '.zip' : ''); + const extension = asset.fileName != undefined ? path.extname(asset.fileName) : ''; + const objectKey = this.bucketPrefix + asset.sourceHash + (asset.packaging === FileAssetPackaging.ZIP_DIRECTORY ? '.zip' : extension); // Add to manifest this.files[asset.sourceHash] = { diff --git a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts index 0131b7a4acc63..6780f07fbd879 100644 --- a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts +++ b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts @@ -52,7 +52,7 @@ nodeunitShim({ destinations: { 'current_account-current_region': { bucketName: 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}', - objectKey: templateHash, + objectKey: templateHash + '.json', assumeRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}', }, }, @@ -111,7 +111,7 @@ nodeunitShim({ // THEN - we have a fixed asset location with region placeholders test.equals(evalCFN(location.bucketName), 'cdk-hnb659fds-assets-the_account-the_region'); - test.equals(evalCFN(location.s3Url), 'https://s3.the_region.domain.aws/cdk-hnb659fds-assets-the_account-the_region/abcdef'); + test.equals(evalCFN(location.s3Url), 'https://s3.the_region.domain.aws/cdk-hnb659fds-assets-the_account-the_region/abcdef.js'); // THEN - object key contains source hash somewhere test.ok(location.objectKey.indexOf('abcdef') > -1); @@ -208,7 +208,7 @@ nodeunitShim({ test.deepEqual(manifest.files?.['file-asset-hash']?.destinations?.['current_account-current_region'], { bucketName: 'file-asset-bucket', - objectKey: 'file-asset-hash', + objectKey: 'file-asset-hash.js', assumeRoleArn: 'file:role:arn', assumeRoleExternalId: 'file-external-id', }); @@ -256,7 +256,7 @@ nodeunitShim({ // THEN test.deepEqual(manifest.files?.['file-asset-hash-with-prefix']?.destinations?.['current_account-current_region'], { bucketName: 'file-asset-bucket', - objectKey: '000000000000/file-asset-hash-with-prefix', + objectKey: '000000000000/file-asset-hash-with-prefix.js', assumeRoleArn: 'file:role:arn', assumeRoleExternalId: 'file-external-id', }); From 12c224a0f54230b6226de8defa527f7b53f9bc65 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 4 Feb 2021 10:49:22 -0800 Subject: [PATCH 60/70] feat(lambda): nodejs14.x runtime (#12861) Blog post: [Node.js 14.x runtime now available in AWS Lambda](https://aws.amazon.com/blogs/compute/node-js-14-x-runtime-now-available-in-aws-lambda/) Developer Guide: [Node.js lambda runtimes](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-lambda/lib/runtime.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/@aws-cdk/aws-lambda/lib/runtime.ts b/packages/@aws-cdk/aws-lambda/lib/runtime.ts index 05d4996ff5e4f..3bbd8bcc43f6e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/runtime.ts +++ b/packages/@aws-cdk/aws-lambda/lib/runtime.ts @@ -78,6 +78,11 @@ export class Runtime { */ public static readonly NODEJS_12_X = new Runtime('nodejs12.x', RuntimeFamily.NODEJS, { supportsInlineCode: true }); + /** + * The NodeJS 14.x runtime (nodejs14.x) + */ + public static readonly NODEJS_14_X = new Runtime('nodejs14.x', RuntimeFamily.NODEJS, { supportsInlineCode: false }); + /** * The Python 2.7 runtime (python2.7) */ From 349a6e2bfaa72440deb3767fb1e28e38cc4d73ef Mon Sep 17 00:00:00 2001 From: Robert Djurasaj Date: Thu, 4 Feb 2021 12:26:43 -0700 Subject: [PATCH 61/70] feat(cloudfront): add support for TrustedKeyGroups in Distribution and CloudFrontWebDistribution (#12847) @njlynch Closes #11791 https://media3.giphy.com/media/3o7aCWJavAgtBzLWrS/giphy.gif ---- *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-cloudfront/README.md | 76 +++++++++++++++++-- .../aws-cloudfront/lib/distribution.ts | 9 +++ .../lib/private/cache-behavior.ts | 13 ++-- .../aws-cloudfront/lib/web-distribution.ts | 11 +++ ...integ.distribution-key-group.expected.json | 57 ++++++++++++++ .../test/integ.distribution-key-group.ts | 32 ++++++++ .../test/private/cache-behavior.test.ts | 24 +++++- .../test/web-distribution.test.ts | 53 ++++++++++++- 8 files changed, 259 insertions(+), 16 deletions(-) create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.distribution-key-group.expected.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.distribution-key-group.ts diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index 1355d7a64d31d..3a5834d98d51f 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -239,6 +239,34 @@ new cloudfront.Distribution(this, 'myDistCustomPolicy', { }); ``` +### Validating signed URLs or signed cookies with Trusted Key Groups + +CloudFront Distribution now supports validating signed URLs or signed cookies using key groups. When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed cookies for all requests that match the cache behavior. + +Example: + +```ts +// public key in PEM format +const pubKey = new PublicKey(stack, 'MyPubKey', { + encodedKey: publicKey, +}); + +const keyGroup = new KeyGroup(stack, 'MyKeyGroup', { + items: [ + pubKey, + ], +}); + +new cloudfront.Distribution(stack, 'Dist', { + defaultBehavior: { + origin: new origins.HttpOrigin('www.example.com'), + trustedKeyGroups: [ + keyGroup, + ], + }, +}); +``` + ### Lambda@Edge Lambda@Edge is an extension of AWS Lambda, a compute service that lets you execute functions that customize the content that CloudFront delivers. @@ -274,7 +302,7 @@ new cloudfront.Distribution(this, 'myDist', { > **Note:** Lambda@Edge functions must be created in the `us-east-1` region, regardless of the region of the CloudFront distribution and stack. > To make it easier to request functions for Lambda@Edge, the `EdgeFunction` construct can be used. > The `EdgeFunction` construct will automatically request a function in `us-east-1`, regardless of the region of the current stack. -> `EdgeFunction` has the same interface as `Function` and can be created and used interchangably. +> `EdgeFunction` has the same interface as `Function` and can be created and used interchangeably. > Please note that using `EdgeFunction` requires that the `us-east-1` region has been bootstrapped. > See https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html for more about bootstrapping regions. @@ -289,7 +317,7 @@ const myFunc = new lambda.Function(this, 'MyFunction', { ``` If the stack is not in `us-east-1`, and you need references from different applications on the same account, -you can also set a specific stack ID for each Lamba@Edge. +you can also set a specific stack ID for each Lambda@Edge. ```ts const myFunc1 = new cloudfront.experimental.EdgeFunction(this, 'MyFunction1', { @@ -427,7 +455,7 @@ You can customize the default certificate aliases. This is intended to be used i Example: -[create a distrubution with an default certificiate example](test/example.default-cert-alias.lit.ts) +[create a distribution with an default certificate example](test/example.default-cert-alias.lit.ts) #### ACM certificate @@ -438,7 +466,7 @@ For more information, see [the aws-certificatemanager module documentation](http Example: -[create a distrubution with an acm certificate example](test/example.acm-cert-alias.lit.ts) +[create a distribution with an acm certificate example](test/example.acm-cert-alias.lit.ts) #### IAM certificate @@ -448,7 +476,43 @@ See [Importing an SSL/TLS Certificate](https://docs.aws.amazon.com/AmazonCloudFr Example: -[create a distrubution with an iam certificate example](test/example.iam-cert-alias.lit.ts) +[create a distribution with an iam certificate example](test/example.iam-cert-alias.lit.ts) + +### Trusted Key Groups + +CloudFront Web Distributions supports validating signed URLs or signed cookies using key groups. When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed cookies for all requests that match the cache behavior. + +Example: + +```ts +const pubKey = new PublicKey(stack, 'MyPubKey', { + encodedKey: publicKey, +}); + +const keyGroup = new KeyGroup(stack, 'MyKeyGroup', { + items: [ + pubKey, + ], +}); + +new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: sourceBucket, + }, + behaviors: [ + { + isDefaultBehavior: true, + trustedKeyGroups: [ + keyGroup, + ], + }, + ], + }, + ], +}); +``` ### Restrictions @@ -505,7 +569,7 @@ new CloudFrontWebDistribution(stack, 'ADistribution', { }, failoverS3OriginSource: { s3BucketSource: s3.Bucket.fromBucketName(stack, 'aBucketFallback', 'myoriginbucketfallback'), - originPath: '/somwhere', + originPath: '/somewhere', originHeaders: { 'myHeader2': '21', }, diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 05534862026ae..02e3d092295f3 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -6,6 +6,7 @@ import { Construct } from 'constructs'; import { ICachePolicy } from './cache-policy'; import { CfnDistribution } from './cloudfront.generated'; import { GeoRestriction } from './geo-restriction'; +import { IKeyGroup } from './key-group'; import { IOrigin, OriginBindConfig, OriginBindOptions } from './origin'; import { IOriginRequestPolicy } from './origin-request-policy'; import { CacheBehavior } from './private/cache-behavior'; @@ -706,6 +707,14 @@ export interface AddBehaviorOptions { * @see https://aws.amazon.com/lambda/edge */ readonly edgeLambdas?: EdgeLambda[]; + + /** + * A list of Key Groups that CloudFront can use to validate signed URLs or signed cookies. + * + * @default - no KeyGroups are associated with cache behavior + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html + */ + readonly trustedKeyGroups?: IKeyGroup[]; } /** diff --git a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts index 4b700e166cecc..d804dd8465750 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts @@ -50,13 +50,12 @@ export class CacheBehavior { originRequestPolicyId: this.props.originRequestPolicy?.originRequestPolicyId, smoothStreaming: this.props.smoothStreaming, viewerProtocolPolicy: this.props.viewerProtocolPolicy ?? ViewerProtocolPolicy.ALLOW_ALL, - lambdaFunctionAssociations: this.props.edgeLambdas - ? this.props.edgeLambdas.map(edgeLambda => ({ - lambdaFunctionArn: edgeLambda.functionVersion.edgeArn, - eventType: edgeLambda.eventType.toString(), - includeBody: edgeLambda.includeBody, - })) - : undefined, + lambdaFunctionAssociations: this.props.edgeLambdas?.map(edgeLambda => ({ + lambdaFunctionArn: edgeLambda.functionVersion.edgeArn, + eventType: edgeLambda.eventType.toString(), + includeBody: edgeLambda.includeBody, + })), + trustedKeyGroups: this.props.trustedKeyGroups?.map(keyGroup => keyGroup.keyGroupId), }; } diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts index c62e1e09ed3f4..ae3cbae6051d3 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts @@ -7,6 +7,7 @@ import { Construct } from 'constructs'; import { CfnDistribution } from './cloudfront.generated'; import { HttpVersion, IDistribution, LambdaEdgeEventType, OriginProtocolPolicy, PriceClass, ViewerProtocolPolicy, SSLMethod, SecurityPolicyProtocol } from './distribution'; import { GeoRestriction } from './geo-restriction'; +import { IKeyGroup } from './key-group'; import { IOriginAccessIdentity } from './origin-access-identity'; /** @@ -347,9 +348,18 @@ export interface Behavior { * The signers are the account IDs that are allowed to sign cookies/presigned URLs for this distribution. * * If you pass a non empty value, all requests for this behavior must be signed (no public access will be allowed) + * @deprecated - We recommend using trustedKeyGroups instead of trustedSigners. */ readonly trustedSigners?: string[]; + /** + * A list of Key Groups that CloudFront can use to validate signed URLs or signed cookies. + * + * @default - no KeyGroups are associated with cache behavior + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html + */ + readonly trustedKeyGroups?: IKeyGroup[]; + /** * * The default amount of time CloudFront will cache an object. @@ -932,6 +942,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu forwardedValues: input.forwardedValues || { queryString: false, cookies: { forward: 'none' } }, maxTtl: input.maxTtl && input.maxTtl.toSeconds(), minTtl: input.minTtl && input.minTtl.toSeconds(), + trustedKeyGroups: input.trustedKeyGroups?.map(key => key.keyGroupId), trustedSigners: input.trustedSigners, targetOriginId: input.targetOriginId, viewerProtocolPolicy: protoPolicy || ViewerProtocolPolicy.REDIRECT_TO_HTTPS, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-key-group.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-key-group.expected.json new file mode 100644 index 0000000000000..afae558dee709 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-key-group.expected.json @@ -0,0 +1,57 @@ +{ + "Resources": { + "MyPublicKey78071F3D": { + "Type": "AWS::CloudFront::PublicKey", + "Properties": { + "PublicKeyConfig": { + "CallerReference": "c8752fac3fe06fc93f3fbd12d7e0282d8967409e4d", + "EncodedKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS\nJAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa\ndlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj\n6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e\n0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD\n/3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx\nNQIDAQAB\n-----END PUBLIC KEY-----", + "Name": "integdistributionkeygroupMyPublicKeyC0F3B115" + } + } + }, + "MyKeyGroupAF22FD35": { + "Type": "AWS::CloudFront::KeyGroup", + "Properties": { + "KeyGroupConfig": { + "Items": [ + { + "Ref": "MyPublicKey78071F3D" + } + ], + "Name": "integdistributionkeygroupMyKeyGroupF179E01A" + } + } + }, + "DistB3B78991": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, + "TargetOriginId": "integdistributionkeygroupDistOrigin1B9677703", + "TrustedKeyGroups": [ + { + "Ref": "MyKeyGroupAF22FD35" + } + ], + "ViewerProtocolPolicy": "allow-all" + }, + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only" + }, + "DomainName": "www.example.com", + "Id": "integdistributionkeygroupDistOrigin1B9677703" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-key-group.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-key-group.ts new file mode 100644 index 0000000000000..e0a124b26f904 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-key-group.ts @@ -0,0 +1,32 @@ +import * as cdk from '@aws-cdk/core'; +import * as cloudfront from '../lib'; +import { TestOrigin } from './test-origin'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integ-distribution-key-group'); +const publicKey = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS +JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa +dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj +6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e +0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD +/3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx +NQIDAQAB +-----END PUBLIC KEY-----`; + +new cloudfront.Distribution(stack, 'Dist', { + defaultBehavior: { + origin: new TestOrigin('www.example.com'), + trustedKeyGroups: [ + new cloudfront.KeyGroup(stack, 'MyKeyGroup', { + items: [ + new cloudfront.PublicKey(stack, 'MyPublicKey', { + encodedKey: publicKey, + }), + ], + }), + ], + }, +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts b/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts index a1e315fdcdd96..c9500b23eaddf 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts @@ -1,11 +1,20 @@ import '@aws-cdk/assert/jest'; import * as lambda from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; -import { AllowedMethods, CachedMethods, CachePolicy, LambdaEdgeEventType, OriginRequestPolicy, ViewerProtocolPolicy } from '../../lib'; +import { AllowedMethods, CachedMethods, CachePolicy, KeyGroup, LambdaEdgeEventType, OriginRequestPolicy, PublicKey, ViewerProtocolPolicy } from '../../lib'; import { CacheBehavior } from '../../lib/private/cache-behavior'; let app: App; let stack: Stack; +const publicKey = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS +JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa +dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj +6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e +0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD +/3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx +NQIDAQAB +-----END PUBLIC KEY-----`; beforeEach(() => { app = new App(); @@ -30,6 +39,15 @@ test('renders the minimum template with an origin and path specified', () => { test('renders with all properties specified', () => { const fnVersion = lambda.Version.fromVersionArn(stack, 'Version', 'arn:aws:lambda:testregion:111111111111:function:myTestFun:v1'); + const pubKey = new PublicKey(stack, 'MyPublicKey', { + encodedKey: publicKey, + }); + const keyGroup = new KeyGroup(stack, 'MyKeyGroup', { + items: [ + pubKey, + ], + }); + const behavior = new CacheBehavior('origin_id', { pathPattern: '*', allowedMethods: AllowedMethods.ALLOW_ALL, @@ -44,6 +62,7 @@ test('renders with all properties specified', () => { includeBody: true, functionVersion: fnVersion, }], + trustedKeyGroups: [keyGroup], }); expect(behavior._renderBehavior()).toEqual({ @@ -61,6 +80,9 @@ test('renders with all properties specified', () => { eventType: 'origin-request', includeBody: true, }], + trustedKeyGroups: [ + keyGroup.keyGroupId, + ], }); }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts index ea2fbf3f22295..8eea2cad5bc92 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts @@ -8,7 +8,9 @@ import { CfnDistribution, CloudFrontWebDistribution, GeoRestriction, + KeyGroup, LambdaEdgeEventType, + PublicKey, SecurityPolicyProtocol, SSLMethod, ViewerCertificate, @@ -17,6 +19,16 @@ import { /* eslint-disable quote-props */ +const publicKey = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS +JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa +dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj +6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e +0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD +/3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx +NQIDAQAB +-----END PUBLIC KEY-----`; + nodeunitShim({ 'distribution with custom origin adds custom origin'(test: Test) { @@ -188,6 +200,14 @@ nodeunitShim({ 'distribution with trusted signers on default distribution'(test: Test) { const stack = new cdk.Stack(); const sourceBucket = new s3.Bucket(stack, 'Bucket'); + const pubKey = new PublicKey(stack, 'MyPubKey', { + encodedKey: publicKey, + }); + const keyGroup = new KeyGroup(stack, 'MyKeyGroup', { + items: [ + pubKey, + ], + }); new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { originConfigs: [ @@ -199,6 +219,9 @@ nodeunitShim({ { isDefaultBehavior: true, trustedSigners: ['1234'], + trustedKeyGroups: [ + keyGroup, + ], }, ], }, @@ -212,6 +235,29 @@ nodeunitShim({ 'DeletionPolicy': 'Retain', 'UpdateReplacePolicy': 'Retain', }, + 'MyPubKey6ADA4CF5': { + 'Type': 'AWS::CloudFront::PublicKey', + 'Properties': { + 'PublicKeyConfig': { + 'CallerReference': 'c8141e732ea37b19375d4cbef2b2d2c6f613f0649a', + 'EncodedKey': publicKey, + 'Name': 'MyPubKey', + }, + }, + }, + 'MyKeyGroupAF22FD35': { + 'Type': 'AWS::CloudFront::KeyGroup', + 'Properties': { + 'KeyGroupConfig': { + 'Items': [ + { + 'Ref': 'MyPubKey6ADA4CF5', + }, + ], + 'Name': 'MyKeyGroup', + }, + }, + }, 'AnAmazingWebsiteProbablyCFDistribution47E3983B': { 'Type': 'AWS::CloudFront::Distribution', 'Properties': { @@ -250,9 +296,12 @@ nodeunitShim({ 'QueryString': false, 'Cookies': { 'Forward': 'none' }, }, - 'TrustedSigners': [ - '1234', + 'TrustedKeyGroups': [ + { + 'Ref': 'MyKeyGroupAF22FD35', + }, ], + 'TrustedSigners': ['1234'], 'Compress': true, }, 'Enabled': true, From 22eb7e1816fa457cbbba5474aff08974f2793e2d Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 4 Feb 2021 21:21:55 +0000 Subject: [PATCH 62/70] chore(eslint-plugin-cdk): clean up around tests (#12867) Couple of minor changes: - remove the exclusion for running the 'construct-import-order' rule for the 'core' module. This isn't required, and should be fixed if the rule is violated in any module. Currently, it makes no difference. - update the test runner so that it also handles the case that where no eslint fixes are required. Prior to this fix, no 'actual' file will be produced leading to weird error messaging. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/rules/construct-import-order.ts | 5 ----- .../eslint-plugin-cdk/test/rules/fixtures.test.ts | 14 ++++++++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tools/eslint-plugin-cdk/lib/rules/construct-import-order.ts b/tools/eslint-plugin-cdk/lib/rules/construct-import-order.ts index bc6d5a0e160bf..fc6066e486747 100644 --- a/tools/eslint-plugin-cdk/lib/rules/construct-import-order.ts +++ b/tools/eslint-plugin-cdk/lib/rules/construct-import-order.ts @@ -20,11 +20,6 @@ let coreConstructImportLine: ImportDeclaration | undefined; let lastImport: Rule.Node | undefined; export function create(context: Rule.RuleContext): Rule.NodeListener { - // skip core - if (context.getFilename().includes('@aws-cdk/core')) { - return {}; - } - return { Program: _ => { // reset for every file diff --git a/tools/eslint-plugin-cdk/test/rules/fixtures.test.ts b/tools/eslint-plugin-cdk/test/rules/fixtures.test.ts index b65670f43b429..bf1c03f359047 100644 --- a/tools/eslint-plugin-cdk/test/rules/fixtures.test.ts +++ b/tools/eslint-plugin-cdk/test/rules/fixtures.test.ts @@ -55,9 +55,15 @@ fs.readdirSync(fixturesRoot).filter(f => fs.lstatSync(path.join(fixturesRoot, f) async function lintAndFix(file: string, outputDir: string) { const newPath = path.join(outputDir, path.basename(file)) let result = await linter.lintFiles(file); - await ESLint.outputFixes(result.map(r => { - r.filePath = newPath; - return r; - })); + const hasFixes = result.find(r => typeof(r.output) === 'string') !== undefined; + if (hasFixes) { + await ESLint.outputFixes(result.map(r => { + r.filePath = newPath; + return r; + })); + } else { + // If there are no fixes, copy the input file as output + await fs.copyFile(file, newPath); + } return newPath; } From b4e1b682604946c30ab63164017c73bd8d828728 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 5 Feb 2021 11:29:03 +0100 Subject: [PATCH 63/70] chore(core): fix bug introduced in recent PR (#12880) In #12765, a bug was introduced where there was a mismatch between the file entry written to the asset manifest for the stack template, and the template location written to the cloud assembly, thereby causing all deployments to fail. The error was "Access Denied" (because that's what S3 says when it doesn't want to tell you "File not found"). Fix the inconsistency. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../core/lib/stack-synthesizers/default-synthesizer.ts | 7 ++++++- .../core/test/stack-synthesis/new-style-synthesis.test.ts | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts index a92d3b6fe04d0..2527bca98c9d6 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts @@ -449,7 +449,12 @@ export class DefaultStackSynthesizer extends StackSynthesizer { // // Instead, we'll have a protocol with the CLI that we put an 's3://.../...' URL here, and the CLI // is going to resolve it to the correct 'https://.../' URL before it gives it to CloudFormation. - return `s3://${this.bucketName}/${this.bucketPrefix}${sourceHash}`; + // + // ALSO: it would be great to reuse the return value of `addFileAsset()` here, except those contain + // CloudFormation REFERENCES to locations, not actual locations (can contain `{ Ref: AWS::Region }` and + // `{ Ref: SomeParameter }` etc). We therefore have to duplicate some logic here :(. + const extension = path.extname(this.stack.templateFile); + return `s3://${this.bucketName}/${this.bucketPrefix}${sourceHash}${extension}`; } /** diff --git a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts index 6780f07fbd879..73f8f185f06ba 100644 --- a/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts +++ b/packages/@aws-cdk/core/test/stack-synthesis/new-style-synthesis.test.ts @@ -36,9 +36,9 @@ nodeunitShim({ // THEN -- the S3 url is advertised on the stack artifact const stackArtifact = asm.getStackArtifact('Stack'); - const templateHash = last(stackArtifact.stackTemplateAssetObjectUrl?.split('/')); + const templateObjectKey = last(stackArtifact.stackTemplateAssetObjectUrl?.split('/')); - test.equals(stackArtifact.stackTemplateAssetObjectUrl, `s3://cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}/${templateHash}`); + test.equals(stackArtifact.stackTemplateAssetObjectUrl, `s3://cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}/${templateObjectKey}`); // THEN - the template is in the asset manifest const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0]; @@ -52,7 +52,7 @@ nodeunitShim({ destinations: { 'current_account-current_region': { bucketName: 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}', - objectKey: templateHash + '.json', + objectKey: templateObjectKey, assumeRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}', }, }, From 32c0de74cf40f08a291c8589fd85f3dd636749ea Mon Sep 17 00:00:00 2001 From: Daniel Neilson <53624638+ddneilson@users.noreply.github.com> Date: Fri, 5 Feb 2021 11:19:45 -0600 Subject: [PATCH 64/70] feat(ec2): can define Launch Templates (not use them yet) (#12385) This provides an initial implementation of a level 2 construct for EC2 Launch Templates. It is a start that is intended to help get the ball rolling on implementation of Launch Template support within the CDK. It is a step towards resolving https://github.com/aws/aws-cdk/issues/6734 Launch Templates have value even without the integrations into Instance and AutoScalingGroup being implemented yet. Thus, the intention with this PR is to solely implement the L2 for LaunchTemplate. Future work in a separate PR would be required to implement its integration into Instance, AutoScalingGroup, and others. ---- *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-ec2/README.md | 21 + packages/@aws-cdk/aws-ec2/lib/index.ts | 1 + packages/@aws-cdk/aws-ec2/lib/instance.ts | 5 +- .../@aws-cdk/aws-ec2/lib/launch-template.ts | 665 +++++++++++++++++ .../@aws-cdk/aws-ec2/lib/private/ebs-util.ts | 42 ++ packages/@aws-cdk/aws-ec2/lib/volume.ts | 35 +- .../aws-ec2/test/launch-template.test.ts | 685 ++++++++++++++++++ 7 files changed, 1419 insertions(+), 35 deletions(-) create mode 100644 packages/@aws-cdk/aws-ec2/lib/launch-template.ts create mode 100644 packages/@aws-cdk/aws-ec2/lib/private/ebs-util.ts create mode 100644 packages/@aws-cdk/aws-ec2/test/launch-template.test.ts diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 6ab25bfb78b3f..1a442e104e4b0 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -999,3 +999,24 @@ const subnet = Subnet.fromSubnetAttributes(this, 'SubnetFromAttributes', { // Supply only subnet id const subnet = Subnet.fromSubnetId(this, 'SubnetFromId', 's-1234'); ``` + +## Launch Templates + +A Launch Template is a standardized template that contains the configuration information to launch an instance. +They can be used when launching instances on their own, through Amazon EC2 Auto Scaling, EC2 Fleet, and Spot Fleet. +Launch templates enable you to store launch parameters so that you do not have to specify them every time you launch +an instance. For information on Launch Templates please see the +[official documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-launch-templates.html). + +The following demonstrates how to create a launch template with an Amazon Machine Image, and security group. + +```ts +const vpc = new ec2.Vpc(...); +// ... +const template = new ec2.LaunchTemplate(this, 'LaunchTemplate', { + machineImage: new ec2.AmazonMachineImage(), + securityGroup: new ec2.SecurityGroup(this, 'LaunchTemplateSG', { + vpc: vpc, + }), +}); +``` diff --git a/packages/@aws-cdk/aws-ec2/lib/index.ts b/packages/@aws-cdk/aws-ec2/lib/index.ts index ca25a02f3f8d1..9f70a4320060d 100644 --- a/packages/@aws-cdk/aws-ec2/lib/index.ts +++ b/packages/@aws-cdk/aws-ec2/lib/index.ts @@ -4,6 +4,7 @@ export * from './cfn-init'; export * from './cfn-init-elements'; export * from './instance-types'; export * from './instance'; +export * from './launch-template'; export * from './machine-image'; export * from './nat'; export * from './network-acl'; diff --git a/packages/@aws-cdk/aws-ec2/lib/instance.ts b/packages/@aws-cdk/aws-ec2/lib/instance.ts index 22c4fa7cf880f..82fbb22bea4e3 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance.ts @@ -8,9 +8,10 @@ import { Connections, IConnectable } from './connections'; import { CfnInstance } from './ec2.generated'; import { InstanceType } from './instance-types'; import { IMachineImage, OperatingSystemType } from './machine-image'; +import { instanceBlockDeviceMappings } from './private/ebs-util'; import { ISecurityGroup, SecurityGroup } from './security-group'; import { UserData } from './user-data'; -import { BlockDevice, synthesizeBlockDeviceMappings } from './volume'; +import { BlockDevice } from './volume'; import { IVpc, Subnet, SubnetSelection } from './vpc'; /** @@ -362,7 +363,7 @@ export class Instance extends Resource implements IInstance { subnetId: subnet.subnetId, availabilityZone: subnet.availabilityZone, sourceDestCheck: props.sourceDestCheck, - blockDeviceMappings: props.blockDevices !== undefined ? synthesizeBlockDeviceMappings(this, props.blockDevices) : undefined, + blockDeviceMappings: props.blockDevices !== undefined ? instanceBlockDeviceMappings(this, props.blockDevices) : undefined, privateIpAddress: props.privateIpAddress, }); this.instance.node.addDependency(this.role); diff --git a/packages/@aws-cdk/aws-ec2/lib/launch-template.ts b/packages/@aws-cdk/aws-ec2/lib/launch-template.ts new file mode 100644 index 0000000000000..3b5b39f9b6370 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/launch-template.ts @@ -0,0 +1,665 @@ +import * as iam from '@aws-cdk/aws-iam'; + +import { + Annotations, + Duration, + Expiration, + Fn, + IResource, + Lazy, + Resource, + TagManager, + TagType, + Tags, + Token, +} from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { Connections, IConnectable } from './connections'; +import { CfnLaunchTemplate } from './ec2.generated'; +import { InstanceType } from './instance-types'; +import { IMachineImage, MachineImageConfig, OperatingSystemType } from './machine-image'; +import { launchTemplateBlockDeviceMappings } from './private/ebs-util'; +import { ISecurityGroup } from './security-group'; +import { UserData } from './user-data'; +import { BlockDevice } from './volume'; + +/** + * Name tag constant + */ +const NAME_TAG: string = 'Name'; + +/** + * Provides the options for specifying the CPU credit type for burstable EC2 instance types (T2, T3, T3a, etc). + * + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-how-to.html + */ +// dev-note: This could be used in the Instance L2 +export enum CpuCredits { + /** + * Standard bursting mode. + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-standard-mode.html + */ + STANDARD = 'standard', + + /** + * Unlimited bursting mode. + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-unlimited-mode.html + */ + UNLIMITED = 'unlimited', +}; + +/** + * Provides the options for specifying the instance initiated shutdown behavior. + * + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior + */ +// dev-note: This could be used in the Instance L2 +export enum InstanceInitiatedShutdownBehavior { + /** + * The instance will stop when it initiates a shutdown. + */ + STOP = 'stop', + + /** + * The instance will be terminated when it initiates a shutdown. + */ + TERMINATE = 'terminate', +}; + +/** + * Interface for LaunchTemplate-like objects. + */ +export interface ILaunchTemplate extends IResource { + /** + * The version number of this launch template to use + * + * @attribute + */ + readonly versionNumber: string; + + /** + * The identifier of the Launch Template + * + * Exactly one of `launchTemplateId` and `launchTemplateName` will be set. + * + * @attribute + */ + readonly launchTemplateId?: string; + + /** + * The name of the Launch Template + * + * Exactly one of `launchTemplateId` and `launchTemplateName` will be set. + * + * @attribute + */ + readonly launchTemplateName?: string; +} + +/** + * Provides the options for the types of interruption for spot instances. + */ +// dev-note: This could be used in a SpotFleet L2 if one gets developed. +export enum SpotInstanceInterruption { + /** + * The instance will stop when interrupted. + */ + STOP = 'stop', + + /** + * The instance will be terminated when interrupted. + */ + TERMINATE = 'terminate', + + /** + * The instance will hibernate when interrupted. + */ + HIBERNATE = 'hibernate', +} + +/** + * The Spot Instance request type. + * + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-requests.html + */ +export enum SpotRequestType { + /** + * A one-time Spot Instance request remains active until Amazon EC2 launches the Spot Instance, + * the request expires, or you cancel the request. If the Spot price exceeds your maximum price + * or capacity is not available, your Spot Instance is terminated and the Spot Instance request + * is closed. + */ + ONE_TIME = 'one-time', + + /** + * A persistent Spot Instance request remains active until it expires or you cancel it, even if + * the request is fulfilled. If the Spot price exceeds your maximum price or capacity is not available, + * your Spot Instance is interrupted. After your instance is interrupted, when your maximum price exceeds + * the Spot price or capacity becomes available again, the Spot Instance is started if stopped or resumed + * if hibernated. + */ + PERSISTENT = 'persistent', +} + +/** + * Interface for the Spot market instance options provided in a LaunchTemplate. + */ +export interface LaunchTemplateSpotOptions { + /** + * Spot Instances with a defined duration (also known as Spot blocks) are designed not to be interrupted and will run continuously for the duration you select. + * You can use a duration of 1, 2, 3, 4, 5, or 6 hours. + * + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-requests.html#fixed-duration-spot-instances + * + * @default Requested spot instances do not have a pre-defined duration. + */ + readonly blockDuration?: Duration; + + /** + * The behavior when a Spot Instance is interrupted. + * + * @default Spot instances will terminate when interrupted. + */ + readonly interruptionBehavior?: SpotInstanceInterruption; + + /** + * Maximum hourly price you're willing to pay for each Spot instance. The value is given + * in dollars. ex: 0.01 for 1 cent per hour, or 0.001 for one-tenth of a cent per hour. + * + * @default Maximum hourly price will default to the on-demand price for the instance type. + */ + readonly maxPrice?: number; + + /** + * The Spot Instance request type. + * + * If you are using Spot Instances with an Auto Scaling group, use one-time requests, as the + * Amazon EC2 Auto Scaling service handles requesting new Spot Instances whenever the group is + * below its desired capacity. + * + * @default One-time spot request. + */ + readonly requestType?: SpotRequestType; + + /** + * The end date of the request. For a one-time request, the request remains active until all instances + * launch, the request is canceled, or this date is reached. If the request is persistent, it remains + * active until it is canceled or this date and time is reached. + * + * @default The default end date is 7 days from the current date. + */ + readonly validUntil?: Expiration; +}; + +/** + * Properties of a LaunchTemplate. + */ +export interface LaunchTemplateProps { + /** + * Name for this launch template. + * + * @default Automatically generated name + */ + readonly launchTemplateName?: string; + + /** + * Type of instance to launch. + * + * @default - This Launch Template does not specify a default Instance Type. + */ + readonly instanceType?: InstanceType; + + /** + * The AMI that will be used by instances. + * + * @default - This Launch Template does not specify a default AMI. + */ + readonly machineImage?: IMachineImage; + + /** + * The AMI that will be used by instances. + * + * @default - This Launch Template creates a UserData based on the type of provided + * machineImage; no UserData is created if a machineImage is not provided + */ + readonly userData?: UserData; + + /** + * An IAM role to associate with the instance profile that is used by instances. + * + * The role must be assumable by the service principal `ec2.amazonaws.com`: + * + * @example + * const role = new iam.Role(this, 'MyRole', { + * assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com') + * }); + * + * @default - No new role is created. + */ + readonly role?: iam.IRole; + + /** + * Specifies how block devices are exposed to the instance. You can specify virtual devices and EBS volumes. + * + * Each instance that is launched has an associated root device volume, + * either an Amazon EBS volume or an instance store volume. + * You can use block device mappings to specify additional EBS volumes or + * instance store volumes to attach to an instance when it is launched. + * + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html + * + * @default - Uses the block device mapping of the AMI + */ + readonly blockDevices?: BlockDevice[]; + + /** + * CPU credit type for burstable EC2 instance types. + * + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances.html + * + * @default - No credit type is specified in the Launch Template. + */ + readonly cpuCredits?: CpuCredits; + + /** + * If you set this parameter to true, you cannot terminate the instances launched with this launch template + * using the Amazon EC2 console, CLI, or API; otherwise, you can. + * + * @default - The API termination setting is not specified in the Launch Template. + */ + readonly disableApiTermination?: boolean; + + /** + * Indicates whether the instances are optimized for Amazon EBS I/O. This optimization provides dedicated throughput + * to Amazon EBS and an optimized configuration stack to provide optimal Amazon EBS I/O performance. This optimization + * isn't available with all instance types. Additional usage charges apply when using an EBS-optimized instance. + * + * @default - EBS optimization is not specified in the launch template. + */ + readonly ebsOptimized?: boolean; + + /** + * If this parameter is set to true, the instance is enabled for AWS Nitro Enclaves; otherwise, it is not enabled for AWS Nitro Enclaves. + * + * @default - Enablement of Nitro enclaves is not specified in the launch template; defaulting to false. + */ + readonly nitroEnclaveEnabled?: boolean; + + /** + * If you set this parameter to true, the instance is enabled for hibernation. + * + * @default - Hibernation configuration is not specified in the launch template; defaulting to false. + */ + readonly hibernationConfigured?: boolean; + + /** + * Indicates whether an instance stops or terminates when you initiate shutdown from the instance (using the operating system command for system shutdown). + * + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior + * + * @default - Shutdown behavior is not specified in the launch template; defaults to STOP. + */ + readonly instanceInitiatedShutdownBehavior?: InstanceInitiatedShutdownBehavior; + + /** + * If this property is defined, then the Launch Template's InstanceMarketOptions will be + * set to use Spot instances, and the options for the Spot instances will be as defined. + * + * @default - Instance launched with this template will not be spot instances. + */ + readonly spotOptions?: LaunchTemplateSpotOptions; + + /** + * Name of SSH keypair to grant access to instance + * + * @default - No SSH access will be possible. + */ + readonly keyName?: string; + + /** + * If set to true, then detailed monitoring will be enabled on instances created with this + * launch template. + * + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-cloudwatch-new.html + * + * @default False - Detailed monitoring is disabled. + */ + readonly detailedMonitoring?: boolean; + + /** + * Security group to assign to instances created with the launch template. + * + * @default No security group is assigned. + */ + readonly securityGroup?: ISecurityGroup; +} + +/** + * A class that provides convenient access to special version tokens for LaunchTemplate + * versions. + */ +export class LaunchTemplateSpecialVersions { + /** + * The special value that denotes that users of a Launch Template should + * reference the LATEST version of the template. + */ + public static readonly LATEST_VERSION: string = '$Latest'; + + /** + * The special value that denotes that users of a Launch Template should + * reference the DEFAULT version of the template. + */ + public static readonly DEFAULT_VERSION: string = '$Default'; +} + +/** + * Attributes for an imported LaunchTemplate. + */ +export interface LaunchTemplateAttributes { + /** + * The version number of this launch template to use + * + * @default Version: "$Default" + */ + readonly versionNumber?: string; + + /** + * The identifier of the Launch Template + * + * Exactly one of `launchTemplateId` and `launchTemplateName` may be set. + * + * @default None + */ + readonly launchTemplateId?: string; + + /** + * The name of the Launch Template + * + * Exactly one of `launchTemplateId` and `launchTemplateName` may be set. + * + * @default None + */ + readonly launchTemplateName?: string; +} + +/** + * This represents an EC2 LaunchTemplate. + * + * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-launch-templates.html + */ +export class LaunchTemplate extends Resource implements ILaunchTemplate, iam.IGrantable, IConnectable { + /** + * Import an existing LaunchTemplate. + */ + public static fromLaunchTemplateAttributes(scope: Construct, id: string, attrs: LaunchTemplateAttributes): ILaunchTemplate { + const haveId = Boolean(attrs.launchTemplateId); + const haveName = Boolean(attrs.launchTemplateName); + if (haveId == haveName) { + throw new Error('LaunchTemplate.fromLaunchTemplateAttributes() requires exactly one of launchTemplateId or launchTemplateName be provided.'); + } + + class Import extends Resource implements ILaunchTemplate { + public readonly versionNumber = attrs.versionNumber ?? LaunchTemplateSpecialVersions.DEFAULT_VERSION; + public readonly launchTemplateId? = attrs.launchTemplateId; + public readonly launchTemplateName? = attrs.launchTemplateName; + } + return new Import(scope, id); + } + + // ============================================ + // Members for ILaunchTemplate interface + + public readonly versionNumber: string; + public readonly launchTemplateId?: string; + public readonly launchTemplateName?: string; + + // ============================================= + // Data members + + /** + * The default version for the launch template. + * + * @attribute + */ + public readonly defaultVersionNumber: string; + + /** + * The latest version of the launch template. + * + * @attribute + */ + public readonly latestVersionNumber: string; + + /** + * The type of OS the instance is running. + * + * @attribute + */ + public readonly osType?: OperatingSystemType; + + /** + * IAM Role assumed by instances that are launched from this template. + * + * @attribute + */ + public readonly role?: iam.IRole; + + /** + * UserData executed by instances that are launched from this template. + * + * @attribute + */ + public readonly userData?: UserData; + + // ============================================= + // Private/protected data members + + /** + * Principal to grant permissions to. + * @internal + */ + protected readonly _grantPrincipal?: iam.IPrincipal; + + /** + * Allows specifying security group connections for the instance. + * @internal + */ + protected readonly _connections?: Connections; + + /** + * TagManager for tagging support. + */ + protected readonly tags: TagManager; + + // ============================================= + + constructor(scope: Construct, id: string, props: LaunchTemplateProps = {}) { + super(scope, id); + + // Basic validation of the provided spot block duration + const spotDuration = props?.spotOptions?.blockDuration?.toHours({ integral: true }); + if (spotDuration !== undefined && (spotDuration < 1 || spotDuration > 6)) { + // See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-requests.html#fixed-duration-spot-instances + Annotations.of(this).addError('Spot block duration must be exactly 1, 2, 3, 4, 5, or 6 hours.'); + } + + this.role = props.role; + this._grantPrincipal = this.role; + const iamProfile: iam.CfnInstanceProfile | undefined = this.role ? new iam.CfnInstanceProfile(this, 'Profile', { + roles: [this.role!.roleName], + }) : undefined; + + if (props.securityGroup) { + this._connections = new Connections({ securityGroups: [props.securityGroup] }); + } + const securityGroupsToken = Lazy.list({ + produce: () => { + if (this._connections && this._connections.securityGroups.length > 0) { + return this._connections.securityGroups.map(sg => sg.securityGroupId); + } + return undefined; + }, + }); + + if (props.userData) { + this.userData = props.userData; + } + const userDataToken = Lazy.string({ + produce: () => { + if (this.userData) { + return Fn.base64(this.userData.render()); + } + return undefined; + }, + }); + + const imageConfig: MachineImageConfig | undefined = props.machineImage?.getImage(this); + if (imageConfig) { + this.osType = imageConfig.osType; + } + + let marketOptions: any = undefined; + if (props?.spotOptions) { + marketOptions = { + marketType: 'spot', + spotOptions: { + blockDurationMinutes: spotDuration !== undefined ? spotDuration * 60 : undefined, + instanceInterruptionBehavior: props.spotOptions.interruptionBehavior, + maxPrice: props.spotOptions.maxPrice?.toString(), + spotInstanceType: props.spotOptions.requestType, + validUntil: props.spotOptions.validUntil?.date.toUTCString(), + }, + }; + // Remove SpotOptions if there are none. + if (Object.keys(marketOptions.spotOptions).filter(k => marketOptions.spotOptions[k]).length == 0) { + marketOptions.spotOptions = undefined; + } + } + + this.tags = new TagManager(TagType.KEY_VALUE, 'AWS::EC2::LaunchTemplate'); + const tagsToken = Lazy.any({ + produce: () => { + if (this.tags.hasTags()) { + const renderedTags = this.tags.renderTags(); + const lowerCaseRenderedTags = renderedTags.map( (tag: { [key: string]: string}) => { + return { + key: tag.Key, + value: tag.Value, + }; + }); + return [ + { + resourceType: 'instance', + tags: lowerCaseRenderedTags, + }, + { + resourceType: 'volume', + tags: lowerCaseRenderedTags, + }, + ]; + } + return undefined; + }, + }); + + const resource = new CfnLaunchTemplate(this, 'Resource', { + launchTemplateName: props?.launchTemplateName, + launchTemplateData: { + blockDeviceMappings: props?.blockDevices !== undefined ? launchTemplateBlockDeviceMappings(this, props.blockDevices) : undefined, + creditSpecification: props?.cpuCredits !== undefined ? { + cpuCredits: props.cpuCredits, + } : undefined, + disableApiTermination: props?.disableApiTermination, + ebsOptimized: props?.ebsOptimized, + enclaveOptions: props?.nitroEnclaveEnabled !== undefined ? { + enabled: props.nitroEnclaveEnabled, + } : undefined, + hibernationOptions: props?.hibernationConfigured !== undefined ? { + configured: props.hibernationConfigured, + } : undefined, + iamInstanceProfile: iamProfile !== undefined ? { + arn: iamProfile.getAtt('Arn').toString(), + } : undefined, + imageId: imageConfig?.imageId, + instanceType: props?.instanceType?.toString(), + instanceInitiatedShutdownBehavior: props?.instanceInitiatedShutdownBehavior, + instanceMarketOptions: marketOptions, + keyName: props?.keyName, + monitoring: props?.detailedMonitoring !== undefined ? { + enabled: props.detailedMonitoring, + } : undefined, + securityGroupIds: securityGroupsToken, + tagSpecifications: tagsToken, + userData: userDataToken, + + // Fields not yet implemented: + // ========================== + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata-capacityreservationspecification.html + // Will require creating an L2 for AWS::EC2::CapacityReservation + // capacityReservationSpecification: undefined, + + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata-cpuoptions.html + // cpuOptions: undefined, + + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-elasticgpuspecification.html + // elasticGpuSpecifications: undefined, + + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata.html#cfn-ec2-launchtemplate-launchtemplatedata-elasticinferenceaccelerators + // elasticInferenceAccelerators: undefined, + + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata.html#cfn-ec2-launchtemplate-launchtemplatedata-kernelid + // kernelId: undefined, + // ramDiskId: undefined, + + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata.html#cfn-ec2-launchtemplate-launchtemplatedata-licensespecifications + // Also not implemented in Instance L2 + // licenseSpecifications: undefined, + + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata.html#cfn-ec2-launchtemplate-launchtemplatedata-metadataoptions + // metadataOptions: undefined, + + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata.html#cfn-ec2-launchtemplate-launchtemplatedata-tagspecifications + // Should be implemented via the Tagging aspect in CDK core. Complication will be that this tagging interface is very unique to LaunchTemplates. + // tagSpecification: undefined + + // CDK has no abstraction for Network Interfaces yet. + // networkInterfaces: undefined, + + // CDK has no abstraction for Placement yet. + // placement: undefined, + + }, + }); + + Tags.of(this).add(NAME_TAG, this.node.path); + + this.defaultVersionNumber = resource.attrDefaultVersionNumber; + this.latestVersionNumber = resource.attrLatestVersionNumber; + this.launchTemplateId = resource.ref; + this.versionNumber = Token.asString(resource.getAtt('LatestVersionNumber')); + } + + /** + * Allows specifying security group connections for the instance. + * + * @note Only available if you provide a securityGroup when constructing the LaunchTemplate. + */ + public get connections(): Connections { + if (!this._connections) { + throw new Error('LaunchTemplate can only be used as IConnectable if a securityGroup is provided when contructing it.'); + } + return this._connections; + } + + /** + * Principal to grant permissions to. + * + * @note Only available if you provide a role when constructing the LaunchTemplate. + */ + public get grantPrincipal(): iam.IPrincipal { + if (!this._grantPrincipal) { + throw new Error('LaunchTemplate can only be used as IGrantable if a role is provided when constructing it.'); + } + return this._grantPrincipal; + } +} diff --git a/packages/@aws-cdk/aws-ec2/lib/private/ebs-util.ts b/packages/@aws-cdk/aws-ec2/lib/private/ebs-util.ts new file mode 100644 index 0000000000000..dc91f6d795011 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/private/ebs-util.ts @@ -0,0 +1,42 @@ +import { Annotations } from '@aws-cdk/core'; +import { CfnInstance, CfnLaunchTemplate } from '../ec2.generated'; +import { BlockDevice, EbsDeviceVolumeType } from '../volume'; + +// keep this import separate from other imports to reduce chance for merge conflicts with v2-main +// eslint-disable-next-line no-duplicate-imports, import/order +import { Construct } from '@aws-cdk/core'; + +export function instanceBlockDeviceMappings(construct: Construct, blockDevices: BlockDevice[]): CfnInstance.BlockDeviceMappingProperty[] { + return synthesizeBlockDeviceMappings(construct, blockDevices, {}); +} + +export function launchTemplateBlockDeviceMappings(construct: Construct, blockDevices: BlockDevice[]): CfnLaunchTemplate.BlockDeviceMappingProperty[] { + return synthesizeBlockDeviceMappings(construct, blockDevices, ''); +} + +/** + * Synthesize an array of block device mappings from a list of block device + * + * @param construct the instance/asg construct, used to host any warning + * @param blockDevices list of block devices + */ +function synthesizeBlockDeviceMappings(construct: Construct, blockDevices: BlockDevice[], noDeviceValue: NDT): RT[] { + return blockDevices.map(({ deviceName, volume, mappingEnabled }): RT => { + const { virtualName, ebsDevice: ebs } = volume; + + if (ebs) { + const { iops, volumeType } = ebs; + + if (!iops) { + if (volumeType === EbsDeviceVolumeType.IO1) { + throw new Error('iops property is required with volumeType: EbsDeviceVolumeType.IO1'); + } + } else if (volumeType !== EbsDeviceVolumeType.IO1) { + Annotations.of(construct).addWarning('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); + } + } + + const noDevice = mappingEnabled === false ? noDeviceValue : undefined; + return { deviceName, ebs, virtualName, noDevice } as any; + }); +} diff --git a/packages/@aws-cdk/aws-ec2/lib/volume.ts b/packages/@aws-cdk/aws-ec2/lib/volume.ts index 65d84b30eed55..36fb86faa2bac 100644 --- a/packages/@aws-cdk/aws-ec2/lib/volume.ts +++ b/packages/@aws-cdk/aws-ec2/lib/volume.ts @@ -2,9 +2,9 @@ import * as crypto from 'crypto'; import { AccountRootPrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; import { IKey, ViaServicePrincipal } from '@aws-cdk/aws-kms'; -import { Annotations, IResource, Resource, Size, SizeRoundingBehavior, Stack, Token, Tags, Names } from '@aws-cdk/core'; +import { IResource, Resource, Size, SizeRoundingBehavior, Stack, Token, Tags, Names } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { CfnInstance, CfnVolume } from './ec2.generated'; +import { CfnVolume } from './ec2.generated'; import { IInstance } from './instance'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. @@ -164,37 +164,6 @@ export class BlockDeviceVolume { } } -/** - * Synthesize an array of block device mappings from a list of block device - * - * @param construct the instance/asg construct, used to host any warning - * @param blockDevices list of block devices - */ -export function synthesizeBlockDeviceMappings(construct: Construct, blockDevices: BlockDevice[]): CfnInstance.BlockDeviceMappingProperty[] { - return blockDevices.map(({ deviceName, volume, mappingEnabled }) => { - const { virtualName, ebsDevice: ebs } = volume; - - if (ebs) { - const { iops, volumeType } = ebs; - - if (!iops) { - if (volumeType === EbsDeviceVolumeType.IO1) { - throw new Error('iops property is required with volumeType: EbsDeviceVolumeType.IO1'); - } - } else if (volumeType !== EbsDeviceVolumeType.IO1) { - Annotations.of(construct).addWarning('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); - } - } - - return { - deviceName, - ebs, - virtualName, - noDevice: mappingEnabled === false ? {} : undefined, - }; - }); -} - /** * Supported EBS volume types for blockDevices */ diff --git a/packages/@aws-cdk/aws-ec2/test/launch-template.test.ts b/packages/@aws-cdk/aws-ec2/test/launch-template.test.ts new file mode 100644 index 0000000000000..882cd69ed282f --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/launch-template.test.ts @@ -0,0 +1,685 @@ +import { + countResources, + expect as expectCDK, + haveResource, + haveResourceLike, + stringLike, +} from '@aws-cdk/assert'; +import { + CfnInstanceProfile, + Role, + ServicePrincipal, +} from '@aws-cdk/aws-iam'; +import { + App, + Duration, + Expiration, + Stack, + Tags, +} from '@aws-cdk/core'; +import { + AmazonLinuxImage, + BlockDevice, + BlockDeviceVolume, + CpuCredits, + EbsDeviceVolumeType, + InstanceInitiatedShutdownBehavior, + InstanceType, + LaunchTemplate, + OperatingSystemType, + SecurityGroup, + SpotInstanceInterruption, + SpotRequestType, + UserData, + Vpc, + WindowsImage, + WindowsVersion, +} from '../lib'; + +/* eslint-disable jest/expect-expect */ + +describe('LaunchTemplate', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + }); + + test('Empty props', () => { + // WHEN + const template = new LaunchTemplate(stack, 'Template'); + + // THEN + // Note: The following is intentionally a haveResource instead of haveResourceLike + // to ensure that only the bare minimum of properties have values when no properties + // are given to a LaunchTemplate. + expectCDK(stack).to(haveResource('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + TagSpecifications: [ + { + ResourceType: 'instance', + Tags: [ + { + Key: 'Name', + Value: 'Default/Template', + }, + ], + }, + { + ResourceType: 'volume', + Tags: [ + { + Key: 'Name', + Value: 'Default/Template', + }, + ], + }, + ], + }, + })); + expectCDK(stack).notTo(haveResource('AWS::IAM::InstanceProfile')); + expect(() => { template.grantPrincipal; }).toThrow(); + expect(() => { template.connections; }).toThrow(); + expect(template.osType).toBeUndefined(); + expect(template.role).toBeUndefined(); + expect(template.userData).toBeUndefined(); + }); + + test('Import from attributes with name', () => { + // WHEN + const template = LaunchTemplate.fromLaunchTemplateAttributes(stack, 'Template', { + launchTemplateName: 'TestName', + versionNumber: 'TestVersion', + }); + + // THEN + expect(template.launchTemplateId).toBeUndefined(); + expect(template.launchTemplateName).toBe('TestName'); + expect(template.versionNumber).toBe('TestVersion'); + }); + + test('Import from attributes with id', () => { + // WHEN + const template = LaunchTemplate.fromLaunchTemplateAttributes(stack, 'Template', { + launchTemplateId: 'TestId', + versionNumber: 'TestVersion', + }); + + // THEN + expect(template.launchTemplateId).toBe('TestId'); + expect(template.launchTemplateName).toBeUndefined(); + expect(template.versionNumber).toBe('TestVersion'); + }); + + test('Import from attributes fails with name and id', () => { + expect(() => { + LaunchTemplate.fromLaunchTemplateAttributes(stack, 'Template', { + launchTemplateName: 'TestName', + launchTemplateId: 'TestId', + versionNumber: 'TestVersion', + }); + }).toThrow(); + }); + + test('Given name', () => { + // WHEN + new LaunchTemplate(stack, 'Template', { + launchTemplateName: 'LTName', + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateName: 'LTName', + })); + }); + + test('Given instanceType', () => { + // WHEN + new LaunchTemplate(stack, 'Template', { + instanceType: new InstanceType('tt.test'), + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + InstanceType: 'tt.test', + }, + })); + }); + + test('Given machineImage (Linux)', () => { + // WHEN + const template = new LaunchTemplate(stack, 'Template', { + machineImage: new AmazonLinuxImage(), + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + ImageId: { + Ref: stringLike('SsmParameterValueawsserviceamiamazonlinuxlatestamznami*Parameter'), + }, + }, + })); + expect(template.osType).toBe(OperatingSystemType.LINUX); + expect(template.userData).toBeUndefined(); + }); + + test('Given machineImage (Windows)', () => { + // WHEN + const template = new LaunchTemplate(stack, 'Template', { + machineImage: new WindowsImage(WindowsVersion.WINDOWS_SERVER_2019_ENGLISH_FULL_BASE), + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + ImageId: { + Ref: stringLike('SsmParameterValueawsserviceamiwindowslatestWindowsServer2019EnglishFullBase*Parameter'), + }, + }, + })); + expect(template.osType).toBe(OperatingSystemType.WINDOWS); + expect(template.userData).toBeUndefined(); + }); + + test('Given userData', () => { + // GIVEN + const userData = UserData.forLinux(); + userData.addCommands('echo Test'); + + // WHEN + const template = new LaunchTemplate(stack, 'Template', { + userData, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + UserData: { + 'Fn::Base64': '#!/bin/bash\necho Test', + }, + }, + })); + expect(template.userData).toBeDefined(); + }); + + test('Given role', () => { + // GIVEN + const role = new Role(stack, 'TestRole', { + assumedBy: new ServicePrincipal('ec2.amazonaws.com'), + }); + + // WHEN + const template = new LaunchTemplate(stack, 'Template', { + role, + }); + + // THEN + expectCDK(stack).to(countResources('AWS::IAM::Role', 1)); + expectCDK(stack).to(haveResourceLike('AWS::IAM::InstanceProfile', { + Roles: [ + { + Ref: 'TestRole6C9272DF', + }, + ], + })); + expectCDK(stack).to(haveResource('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + IamInstanceProfile: { + Arn: stack.resolve((template.node.findChild('Profile') as CfnInstanceProfile).getAtt('Arn')), + }, + TagSpecifications: [ + { + ResourceType: 'instance', + Tags: [ + { + Key: 'Name', + Value: 'Default/Template', + }, + ], + }, + { + ResourceType: 'volume', + Tags: [ + { + Key: 'Name', + Value: 'Default/Template', + }, + ], + }, + ], + }, + })); + expect(template.role).toBeDefined(); + expect(template.grantPrincipal).toBeDefined(); + }); + + test('Given blockDeviceMapping', () => { + // GIVEN + const blockDevices: BlockDevice[] = [ + { + deviceName: 'ebs', + mappingEnabled: true, + volume: BlockDeviceVolume.ebs(15, { + deleteOnTermination: true, + encrypted: true, + volumeType: EbsDeviceVolumeType.IO1, + iops: 5000, + }), + }, { + deviceName: 'ebs-snapshot', + mappingEnabled: false, + volume: BlockDeviceVolume.ebsFromSnapshot('snapshot-id', { + volumeSize: 500, + deleteOnTermination: false, + volumeType: EbsDeviceVolumeType.SC1, + }), + }, { + deviceName: 'ephemeral', + volume: BlockDeviceVolume.ephemeral(0), + }, + ]; + + // WHEN + new LaunchTemplate(stack, 'Template', { + blockDevices, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + BlockDeviceMappings: [ + { + DeviceName: 'ebs', + Ebs: { + DeleteOnTermination: true, + Encrypted: true, + Iops: 5000, + VolumeSize: 15, + VolumeType: 'io1', + }, + }, + { + DeviceName: 'ebs-snapshot', + Ebs: { + DeleteOnTermination: false, + SnapshotId: 'snapshot-id', + VolumeSize: 500, + VolumeType: 'sc1', + }, + NoDevice: '', + }, + { + DeviceName: 'ephemeral', + VirtualName: 'ephemeral0', + }, + ], + }, + })); + }); + + test.each([ + [CpuCredits.STANDARD, 'standard'], + [CpuCredits.UNLIMITED, 'unlimited'], + ])('Given cpuCredits %p', (given: CpuCredits, expected: string) => { + // WHEN + new LaunchTemplate(stack, 'Template', { + cpuCredits: given, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + CreditSpecification: { + CpuCredits: expected, + }, + }, + })); + }); + + test.each([ + [true, true], + [false, false], + ])('Given disableApiTermination %p', (given: boolean, expected: boolean) => { + // WHEN + new LaunchTemplate(stack, 'Template', { + disableApiTermination: given, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + DisableApiTermination: expected, + }, + })); + }); + + test.each([ + [true, true], + [false, false], + ])('Given ebsOptimized %p', (given: boolean, expected: boolean) => { + // WHEN + new LaunchTemplate(stack, 'Template', { + ebsOptimized: given, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + EbsOptimized: expected, + }, + })); + }); + + test.each([ + [true, true], + [false, false], + ])('Given nitroEnclaveEnabled %p', (given: boolean, expected: boolean) => { + // WHEN + new LaunchTemplate(stack, 'Template', { + nitroEnclaveEnabled: given, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + EnclaveOptions: { + Enabled: expected, + }, + }, + })); + }); + + test.each([ + [InstanceInitiatedShutdownBehavior.STOP, 'stop'], + [InstanceInitiatedShutdownBehavior.TERMINATE, 'terminate'], + ])('Given instanceInitiatedShutdownBehavior %p', (given: InstanceInitiatedShutdownBehavior, expected: string) => { + // WHEN + new LaunchTemplate(stack, 'Template', { + instanceInitiatedShutdownBehavior: given, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + InstanceInitiatedShutdownBehavior: expected, + }, + })); + }); + + test('Given keyName', () => { + // WHEN + new LaunchTemplate(stack, 'Template', { + keyName: 'TestKeyname', + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + KeyName: 'TestKeyname', + }, + })); + }); + + test.each([ + [true, true], + [false, false], + ])('Given detailedMonitoring %p', (given: boolean, expected: boolean) => { + // WHEN + new LaunchTemplate(stack, 'Template', { + detailedMonitoring: given, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + Monitoring: { + Enabled: expected, + }, + }, + })); + }); + + test('Given securityGroup', () => { + // GIVEN + const vpc = new Vpc(stack, 'VPC'); + const sg = new SecurityGroup(stack, 'SG', { vpc }); + + // WHEN + const template = new LaunchTemplate(stack, 'Template', { + securityGroup: sg, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + SecurityGroupIds: [ + { + 'Fn::GetAtt': [ + 'SGADB53937', + 'GroupId', + ], + }, + ], + }, + })); + expect(template.connections).toBeDefined(); + expect(template.connections.securityGroups).toHaveLength(1); + expect(template.connections.securityGroups[0]).toBe(sg); + }); + + test('Adding tags', () => { + // GIVEN + const template = new LaunchTemplate(stack, 'Template'); + + // WHEN + Tags.of(template).add('TestKey', 'TestValue'); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + TagSpecifications: [ + { + ResourceType: 'instance', + Tags: [ + { + Key: 'Name', + Value: 'Default/Template', + }, + { + Key: 'TestKey', + Value: 'TestValue', + }, + ], + }, + { + ResourceType: 'volume', + Tags: [ + { + Key: 'Name', + Value: 'Default/Template', + }, + { + Key: 'TestKey', + Value: 'TestValue', + }, + ], + }, + ], + }, + })); + }); +}); + +describe('LaunchTemplate marketOptions', () => { + let app: App; + let stack: Stack; + + beforeEach(() => { + app = new App(); + stack = new Stack(app); + }); + + test('given spotOptions', () => { + // WHEN + new LaunchTemplate(stack, 'Template', { + spotOptions: {}, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + InstanceMarketOptions: { + MarketType: 'spot', + }, + }, + })); + }); + + test.each([ + [0, 1], + [1, 0], + [6, 0], + [7, 1], + ])('for range duration errors: %p', (duration: number, expectedErrors: number) => { + // WHEN + const template = new LaunchTemplate(stack, 'Template', { + spotOptions: { + blockDuration: Duration.hours(duration), + }, + }); + + // THEN + expect(template.node.metadata).toHaveLength(expectedErrors); + }); + + test('for bad duration', () => { + expect(() => { + new LaunchTemplate(stack, 'Template', { + spotOptions: { + // Duration must be an integral number of hours. + blockDuration: Duration.minutes(61), + }, + }); + }).toThrow(); + }); + + test('given blockDuration', () => { + // WHEN + new LaunchTemplate(stack, 'Template', { + spotOptions: { + blockDuration: Duration.hours(1), + }, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + InstanceMarketOptions: { + MarketType: 'spot', + SpotOptions: { + BlockDurationMinutes: 60, + }, + }, + }, + })); + }); + + test.each([ + [SpotInstanceInterruption.STOP, 'stop'], + [SpotInstanceInterruption.TERMINATE, 'terminate'], + [SpotInstanceInterruption.HIBERNATE, 'hibernate'], + ])('given interruptionBehavior %p', (given: SpotInstanceInterruption, expected: string) => { + // WHEN + new LaunchTemplate(stack, 'Template', { + spotOptions: { + interruptionBehavior: given, + }, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + InstanceMarketOptions: { + MarketType: 'spot', + SpotOptions: { + InstanceInterruptionBehavior: expected, + }, + }, + }, + })); + }); + + test.each([ + [0.001, '0.001'], + [1, '1'], + [2.5, '2.5'], + ])('given maxPrice %p', (given: number, expected: string) => { + // WHEN + new LaunchTemplate(stack, 'Template', { + spotOptions: { + maxPrice: given, + }, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + InstanceMarketOptions: { + MarketType: 'spot', + SpotOptions: { + MaxPrice: expected, + }, + }, + }, + })); + }); + + test.each([ + [SpotRequestType.ONE_TIME, 'one-time'], + [SpotRequestType.PERSISTENT, 'persistent'], + ])('given requestType %p', (given: SpotRequestType, expected: string) => { + // WHEN + new LaunchTemplate(stack, 'Template', { + spotOptions: { + requestType: given, + }, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + InstanceMarketOptions: { + MarketType: 'spot', + SpotOptions: { + SpotInstanceType: expected, + }, + }, + }, + })); + }); + + test('given validUntil', () => { + // WHEN + new LaunchTemplate(stack, 'Template', { + spotOptions: { + validUntil: Expiration.atTimestamp(0), + }, + }); + + // THEN + expectCDK(stack).to(haveResourceLike('AWS::EC2::LaunchTemplate', { + LaunchTemplateData: { + InstanceMarketOptions: { + MarketType: 'spot', + SpotOptions: { + ValidUntil: 'Thu, 01 Jan 1970 00:00:00 GMT', + }, + }, + }, + })); + }); +}); From 98ab29206903a235331a9a0b4b38a5e6ad6d89dc Mon Sep 17 00:00:00 2001 From: flemjame-at-amazon <57235867+flemjame-at-amazon@users.noreply.github.com> Date: Fri, 5 Feb 2021 18:46:28 -0500 Subject: [PATCH 65/70] refactor: make logic more concise with optional chaining and nullish coalescing operators (#12277) This is an unnecessary refactor that shrinks logic across CDK, mostly by replacing phrases like: ```ts const var = props.property !== undefined ? props.property : 'defaultValue'; const var2 = props.property === undefined ? 'defaultValue' : props.property; const var3 = props.property ? props.property.subproperty : 'defaultValue'; ``` with: ```ts const var = props.property ?? 'defaultValue'; const var2 = props.property ?? 'defaultValue'; const var3 = props.property?.subproperty ?? 'defaultValue'; ``` It's a slightly more concise way of writing the logic which takes advantage of TS native operators. I basically did a regex search and replaced obvious logic. There are a couple modules where the logic itself was unnecessarily redundant, which I replaced. For example: https://github.com/aws/aws-cdk/blob/31132caec4565735d2ccc32b43f078d634a5ca43/packages/%40aws-cdk/aws-ecs/lib/base/base-service.ts#L217-L218 Can be replaced by: ```ts const port = props.port ?? (protocol === elbv2.ApplicationProtocol.HTTPS ? 443 : 80); ``` Because the check for ```protocol === undefined``` setting the default of port 80 can be rolled into the check for ```protocol === elbv2.ApplicationProtocol.HTTPS``` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../assert/lib/assertions/have-resource.ts | 2 +- packages/@aws-cdk/aws-amplify/lib/app.ts | 4 +-- packages/@aws-cdk/aws-amplify/lib/branch.ts | 4 +-- packages/@aws-cdk/aws-amplify/lib/domain.ts | 2 +- .../aws-apigateway/lib/integrations/http.ts | 2 +- .../aws-apigateway/lib/integrations/lambda.ts | 4 +-- .../@aws-cdk/aws-apigateway/lib/resource.ts | 4 +-- .../@aws-cdk/aws-apigateway/lib/restapi.ts | 10 +++---- .../lib/base-scalable-attribute.ts | 2 +- .../lib/interval-utils.ts | 2 +- .../aws-autoscaling/lib/auto-scaling-group.ts | 9 +++---- .../aws-batch/lib/compute-environment.ts | 9 +++---- .../aws-cloudformation/lib/nested-stack.ts | 2 +- .../test/integ.nested-stack.ts | 2 +- .../aws-cloudfront/lib/web-distribution.ts | 4 +-- packages/@aws-cdk/aws-cloudwatch/lib/graph.ts | 6 ++--- packages/@aws-cdk/aws-codebuild/lib/source.ts | 4 +-- .../lib/lambda/custom-deployment-config.ts | 5 ++-- .../lib/server/deployment-group.ts | 2 +- .../lib/custom-action-registration.ts | 2 +- .../lib/private/full-action-descriptor.ts | 4 +-- .../@aws-cdk/aws-cognito/lib/user-pool.ts | 2 +- packages/@aws-cdk/aws-dynamodb/lib/table.ts | 4 +-- packages/@aws-cdk/aws-ec2/lib/network-acl.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/user-data.ts | 2 +- .../aws-ec2/lib/vpc-endpoint-service.ts | 7 +++-- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 4 +-- packages/@aws-cdk/aws-ecr/lib/repository.ts | 6 ++--- .../application-load-balanced-service-base.ts | 8 +++--- ...ion-multiple-target-groups-service-base.ts | 10 +++---- .../network-load-balanced-service-base.ts | 9 +++---- ...ork-multiple-target-groups-service-base.ts | 8 +++--- .../lib/base/queue-processing-service-base.ts | 12 +++------ .../application-load-balanced-ecs-service.ts | 8 +++--- ...tion-multiple-target-groups-ecs-service.ts | 2 +- .../ecs/network-load-balanced-ecs-service.ts | 8 +++--- ...work-multiple-target-groups-ecs-service.ts | 2 +- .../lib/ecs/scheduled-ecs-task.ts | 2 +- ...plication-load-balanced-fargate-service.ts | 6 ++--- ...-multiple-target-groups-fargate-service.ts | 4 +-- .../network-load-balanced-fargate-service.ts | 10 +++---- ...-multiple-target-groups-fargate-service.ts | 4 +-- .../lib/fargate/scheduled-fargate-task.ts | 2 +- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 13 ++++----- .../aws-ecs/lib/base/task-definition.ts | 3 +-- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 2 +- .../aws-ecs/lib/container-definition.ts | 10 +++---- .../@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 3 +-- .../aws-ecs/lib/fargate/fargate-service.ts | 3 +-- .../@aws-cdk/aws-eks-legacy/lib/cluster.ts | 10 +++---- .../@aws-cdk/aws-eks-legacy/lib/user-data.ts | 2 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 8 +++--- packages/@aws-cdk/aws-eks/lib/user-data.ts | 2 +- .../lib/load-balancer.ts | 2 +- .../lib/shared/base-listener.ts | 2 +- .../lib/shared/base-target-group.ts | 18 ++++++------- .../lib/shared/util.ts | 2 +- .../test/alb/security-group.test.ts | 2 +- .../aws-events-targets/lib/ecs-task.ts | 2 +- packages/@aws-cdk/aws-events/lib/rule.ts | 4 +-- packages/@aws-cdk/aws-events/lib/schedule.ts | 2 +- packages/@aws-cdk/aws-glue/lib/table.ts | 4 +-- packages/@aws-cdk/aws-iam/lib/policy.ts | 2 +- .../aws-lambda/lib/event-invoke-config.ts | 2 +- .../@aws-cdk/aws-logs/lib/metric-filter.ts | 2 +- packages/@aws-cdk/aws-rds/lib/instance.ts | 18 ++++++------- packages/@aws-cdk/aws-rds/lib/private/util.ts | 4 +-- packages/@aws-cdk/aws-redshift/lib/cluster.ts | 27 +++++++++---------- .../lib/bucket-deployment.ts | 2 +- packages/@aws-cdk/aws-s3/lib/bucket.ts | 6 ++--- .../@aws-cdk/aws-ses-actions/lib/bounce.ts | 2 +- .../@aws-cdk/aws-ses-actions/lib/lambda.ts | 2 +- packages/@aws-cdk/aws-ses-actions/lib/s3.ts | 4 +-- packages/@aws-cdk/aws-ses-actions/lib/stop.ts | 2 +- .../@aws-cdk/aws-ses/lib/receipt-rule-set.ts | 2 +- packages/@aws-cdk/aws-ses/lib/receipt-rule.ts | 4 +-- .../lib/emr/emr-create-cluster.ts | 2 +- .../aws-stepfunctions/lib/state-machine.ts | 4 +-- .../aws-stepfunctions/lib/states/pass.ts | 2 +- .../aws-stepfunctions/lib/states/state.ts | 6 ++--- packages/@aws-cdk/core/lib/app.ts | 2 +- packages/@aws-cdk/core/lib/arn.ts | 8 +++--- packages/@aws-cdk/core/lib/fs/copy.ts | 2 +- packages/@aws-cdk/core/lib/stack.ts | 2 +- packages/@aws-cdk/core/lib/tag-aspect.ts | 4 +-- packages/@aws-cdk/core/lib/token.ts | 2 +- .../@aws-cdk/core/test/tag-aspect.test.ts | 6 ++--- packages/aws-cdk/lib/cdk-toolkit.ts | 2 +- 88 files changed, 193 insertions(+), 229 deletions(-) diff --git a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts index 3e44013188b2c..5a977d8252fd4 100644 --- a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts +++ b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts @@ -59,7 +59,7 @@ export class HaveResourceAssertion extends JestFriendlyAssertion properties === undefined ? anything() : allowValueExtension ? deepObjectLike(properties) : objectLike(properties); - this.part = part !== undefined ? part : ResourcePart.Properties; + this.part = part ?? ResourcePart.Properties; } public assertUsing(inspector: StackInspector): boolean { diff --git a/packages/@aws-cdk/aws-amplify/lib/app.ts b/packages/@aws-cdk/aws-amplify/lib/app.ts index 8e7217a59cc58..bf1d5bc3d5e89 100644 --- a/packages/@aws-cdk/aws-amplify/lib/app.ts +++ b/packages/@aws-cdk/aws-amplify/lib/app.ts @@ -218,9 +218,9 @@ export class App extends Resource implements IApp, iam.IGrantable { basicAuthConfig: props.autoBranchCreation.basicAuth && props.autoBranchCreation.basicAuth.bind(this, 'BranchBasicAuth'), buildSpec: props.autoBranchCreation.buildSpec && props.autoBranchCreation.buildSpec.toBuildSpec(), enableAutoBranchCreation: true, - enableAutoBuild: props.autoBranchCreation.autoBuild === undefined ? true : props.autoBranchCreation.autoBuild, + enableAutoBuild: props.autoBranchCreation.autoBuild ?? true, environmentVariables: Lazy.any({ produce: () => renderEnvironmentVariables(this.autoBranchEnvironmentVariables ) }, { omitEmptyArray: true }), // eslint-disable-line max-len - enablePullRequestPreview: props.autoBranchCreation.pullRequestPreview === undefined ? true : props.autoBranchCreation.pullRequestPreview, + enablePullRequestPreview: props.autoBranchCreation.pullRequestPreview ?? true, pullRequestEnvironmentName: props.autoBranchCreation.pullRequestEnvironmentName, stage: props.autoBranchCreation.stage, }, diff --git a/packages/@aws-cdk/aws-amplify/lib/branch.ts b/packages/@aws-cdk/aws-amplify/lib/branch.ts index e26fe6c4d46a2..210f27d4831e2 100644 --- a/packages/@aws-cdk/aws-amplify/lib/branch.ts +++ b/packages/@aws-cdk/aws-amplify/lib/branch.ts @@ -139,8 +139,8 @@ export class Branch extends Resource implements IBranch { branchName, buildSpec: props.buildSpec && props.buildSpec.toBuildSpec(), description: props.description, - enableAutoBuild: props.autoBuild === undefined ? true : props.autoBuild, - enablePullRequestPreview: props.pullRequestPreview === undefined ? true : props.pullRequestPreview, + enableAutoBuild: props.autoBuild ?? true, + enablePullRequestPreview: props.pullRequestPreview ?? true, environmentVariables: Lazy.any({ produce: () => renderEnvironmentVariables(this.environmentVariables) }, { omitEmptyArray: true }), pullRequestEnvironmentName: props.pullRequestEnvironmentName, stage: props.stage, diff --git a/packages/@aws-cdk/aws-amplify/lib/domain.ts b/packages/@aws-cdk/aws-amplify/lib/domain.ts index b657ebdefb247..bf6ba7afe8017 100644 --- a/packages/@aws-cdk/aws-amplify/lib/domain.ts +++ b/packages/@aws-cdk/aws-amplify/lib/domain.ts @@ -147,7 +147,7 @@ export class Domain extends Resource { private renderSubDomainSettings() { return this.subDomains.map(s => ({ branchName: s.branch.branchName, - prefix: s.prefix === undefined ? s.branch.branchName : s.prefix, + prefix: s.prefix ?? s.branch.branchName, })); } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts index fac5bb7cc6800..65f0657e19817 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts @@ -38,7 +38,7 @@ export interface HttpIntegrationProps { */ export class HttpIntegration extends Integration { constructor(url: string, props: HttpIntegrationProps = { }) { - const proxy = props.proxy !== undefined ? props.proxy : true; + const proxy = props.proxy ?? true; const method = props.httpMethod || 'GET'; super({ type: proxy ? IntegrationType.HTTP_PROXY : IntegrationType.HTTP, diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts index 399484510f343..97772fe7eddb5 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts @@ -41,7 +41,7 @@ export class LambdaIntegration extends AwsIntegration { private readonly enableTest: boolean; constructor(handler: lambda.IFunction, options: LambdaIntegrationOptions = { }) { - const proxy = options.proxy === undefined ? true : options.proxy; + const proxy = options.proxy ?? true; super({ proxy, @@ -51,7 +51,7 @@ export class LambdaIntegration extends AwsIntegration { }); this.handler = handler; - this.enableTest = options.allowTestInvoke === undefined ? true : options.allowTestInvoke; + this.enableTest = options.allowTestInvoke ?? true; } public bind(method: Method): IntegrationConfig { diff --git a/packages/@aws-cdk/aws-apigateway/lib/resource.ts b/packages/@aws-cdk/aws-apigateway/lib/resource.ts index 33ec6f1d5f7fd..49d0bf0356d80 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/resource.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/resource.ts @@ -276,7 +276,7 @@ export abstract class ResourceBase extends ResourceConstruct implements IResourc // // statusCode - const statusCode = options.statusCode !== undefined ? options.statusCode : 204; + const statusCode = options.statusCode ?? 204; // // prepare responseParams @@ -522,7 +522,7 @@ export class ProxyResource extends Resource { defaultMethodOptions: props.defaultMethodOptions, }); - const anyMethod = props.anyMethod !== undefined ? props.anyMethod : true; + const anyMethod = props.anyMethod ?? true; if (anyMethod) { this.anyMethod = this.addMethod('ANY'); } diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 0911c92dda94b..32c400c52d23e 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -504,7 +504,7 @@ export abstract class RestApiBase extends Resource implements IRestApi { } protected configureDeployment(props: RestApiOptions) { - const deploy = props.deploy === undefined ? true : props.deploy; + const deploy = props.deploy ?? true; if (deploy) { this._latestDeployment = new Deployment(this, 'Deployment', { @@ -593,7 +593,7 @@ export class SpecRestApi extends RestApiBase { name: this.restApiName, policy: props.policy, failOnWarnings: props.failOnWarnings, - body: apiDefConfig.inlineDefinition ? apiDefConfig.inlineDefinition : undefined, + body: apiDefConfig.inlineDefinition ?? undefined, bodyS3Location: apiDefConfig.inlineDefinition ? undefined : apiDefConfig.s3Location, endpointConfiguration: this._configureEndpoints(props), parameters: props.parameters, @@ -608,7 +608,7 @@ export class SpecRestApi extends RestApiBase { this.addDomainName('CustomDomain', props.domainName); } - const cloudWatchRole = props.cloudWatchRole !== undefined ? props.cloudWatchRole : true; + const cloudWatchRole = props.cloudWatchRole ?? true; if (cloudWatchRole) { this.configureCloudWatchRole(resource); } @@ -700,13 +700,13 @@ export class RestApi extends RestApiBase { binaryMediaTypes: props.binaryMediaTypes, endpointConfiguration: this._configureEndpoints(props), apiKeySourceType: props.apiKeySourceType, - cloneFrom: props.cloneFrom ? props.cloneFrom.restApiId : undefined, + cloneFrom: props.cloneFrom?.restApiId, parameters: props.parameters, }); this.node.defaultChild = resource; this.restApiId = resource.ref; - const cloudWatchRole = props.cloudWatchRole !== undefined ? props.cloudWatchRole : true; + const cloudWatchRole = props.cloudWatchRole ?? true; if (cloudWatchRole) { this.configureCloudWatchRole(resource); } diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts index 762da275c33a5..95d2a19c8a17d 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts @@ -58,7 +58,7 @@ export abstract class BaseScalableAttribute extends CoreConstruct { scalableDimension: this.props.dimension, resourceId: this.props.resourceId, role: this.props.role, - minCapacity: props.minCapacity !== undefined ? props.minCapacity : 1, + minCapacity: props.minCapacity ?? 1, maxCapacity: props.maxCapacity, }); } diff --git a/packages/@aws-cdk/aws-autoscaling-common/lib/interval-utils.ts b/packages/@aws-cdk/aws-autoscaling-common/lib/interval-utils.ts index 39a05beb1e70e..246bfc018507b 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/lib/interval-utils.ts +++ b/packages/@aws-cdk/aws-autoscaling-common/lib/interval-utils.ts @@ -43,7 +43,7 @@ function orderAndCompleteIntervals(intervals: ScalingInterval[]): CompleteScalin intervals = intervals.map(x => ({ ...x })); // Sort by whatever number we have for each interval - intervals.sort(comparatorFromKey((x: ScalingInterval) => x.lower !== undefined ? x.lower : x.upper)); + intervals.sort(comparatorFromKey((x: ScalingInterval) => x.lower ?? x.upper)); // Propagate boundaries until no more change while (propagateBounds(intervals)) { /* Repeat */ } diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index 0d5dd3f147235..4b14a4ea710cb 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -957,9 +957,8 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements // desiredCapacity just reflects what the user has supplied. const desiredCapacity = props.desiredCapacity; - const minCapacity = props.minCapacity !== undefined ? props.minCapacity : 1; - const maxCapacity = props.maxCapacity !== undefined ? props.maxCapacity : - desiredCapacity !== undefined ? desiredCapacity : Math.max(minCapacity, 1); + const minCapacity = props.minCapacity ?? 1; + const maxCapacity = props.maxCapacity ?? desiredCapacity ?? Math.max(minCapacity, 1); withResolved(minCapacity, maxCapacity, (min, max) => { if (min > max) { @@ -1009,7 +1008,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements const { subnetIds, hasPublic } = props.vpc.selectSubnets(props.vpcSubnets); const asgProps: CfnAutoScalingGroupProps = { autoScalingGroupName: this.physicalName, - cooldown: props.cooldown !== undefined ? props.cooldown.toSeconds().toString() : undefined, + cooldown: props.cooldown?.toSeconds().toString(), minSize: Tokenization.stringifyNumber(minCapacity), maxSize: Tokenization.stringifyNumber(maxCapacity), desiredCapacity: desiredCapacity !== undefined ? Tokenization.stringifyNumber(desiredCapacity) : undefined, @@ -1509,7 +1508,7 @@ enum HealthCheckType { * Render the rolling update configuration into the appropriate object */ function renderRollingUpdateConfig(config: RollingUpdateConfiguration = {}): CfnAutoScalingRollingUpdate { - const waitOnResourceSignals = config.minSuccessfulInstancesPercent !== undefined ? true : false; + const waitOnResourceSignals = config.minSuccessfulInstancesPercent !== undefined; const pauseTime = config.pauseTime || (waitOnResourceSignals ? Duration.minutes(5) : Duration.seconds(0)); return { diff --git a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts index 96c356ac13d50..18a2d1a446325 100644 --- a/packages/@aws-cdk/aws-batch/lib/compute-environment.ts +++ b/packages/@aws-cdk/aws-batch/lib/compute-environment.ts @@ -374,7 +374,7 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment minvCpus: props.computeResources.minvCpus || 0, placementGroup: props.computeResources.placementGroup, securityGroupIds: this.buildSecurityGroupIds(props.computeResources.vpc, props.computeResources.securityGroups), - spotIamFleetRole: spotFleetRole ? spotFleetRole.roleArn : undefined, + spotIamFleetRole: spotFleetRole?.roleArn, subnets: props.computeResources.vpc.selectSubnets(props.computeResources.vpcSubnets).subnetIds, tags: props.computeResources.computeResourcesTags, type: props.computeResources.type || ComputeResourceType.ON_DEMAND, @@ -384,9 +384,8 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment const computeEnvironment = new CfnComputeEnvironment(this, 'Resource', { computeEnvironmentName: this.physicalName, computeResources, - serviceRole: props.serviceRole - ? props.serviceRole.roleArn - : new iam.Role(this, 'Resource-Service-Instance-Role', { + serviceRole: props.serviceRole?.roleArn + ?? new iam.Role(this, 'Resource-Service-Instance-Role', { managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSBatchServiceRole'), ], @@ -409,7 +408,7 @@ export class ComputeEnvironment extends Resource implements IComputeEnvironment } private isManaged(props: ComputeEnvironmentProps): boolean { - return props.managed === undefined ? true : props.managed; + return props.managed ?? true; } /** diff --git a/packages/@aws-cdk/aws-cloudformation/lib/nested-stack.ts b/packages/@aws-cdk/aws-cloudformation/lib/nested-stack.ts index 57268c5291bb9..c72d94598ecfc 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/nested-stack.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/nested-stack.ts @@ -71,7 +71,7 @@ export class NestedStack extends core.NestedStack { super(scope, id, { parameters: props.parameters, timeout: props.timeout, - notificationArns: props.notifications ? props.notifications.map(n => n.topicArn) : undefined, + notificationArns: props.notifications?.map(n => n.topicArn), }); } } diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts index 9bb2ba7e5f852..b6357115f7667 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stack.ts @@ -46,7 +46,7 @@ class MyNestedStack extends cfn.NestedStack { code: lambda.Code.inline('console.error("hi")'), handler: 'index.handler', environment: { - TOPIC_ARN: props.siblingTopic ? props.siblingTopic.topicArn : '', + TOPIC_ARN: props.siblingTopic?.topicArn ?? '', QUEUE_URL: props.subscriber.queueUrl, // nested stack references a resource in the parent }, }); diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts index ae3cbae6051d3..f191813dada2b 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts @@ -771,10 +771,10 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu let distributionConfig: CfnDistribution.DistributionConfigProperty = { comment: props.comment, enabled: true, - defaultRootObject: props.defaultRootObject !== undefined ? props.defaultRootObject : 'index.html', + defaultRootObject: props.defaultRootObject ?? 'index.html', httpVersion: props.httpVersion || HttpVersion.HTTP2, priceClass: props.priceClass || PriceClass.PRICE_CLASS_100, - ipv6Enabled: (props.enableIpV6 !== undefined) ? props.enableIpV6 : true, + ipv6Enabled: props.enableIpV6 ?? true, // eslint-disable-next-line max-len customErrorResponses: props.errorConfigurations, // TODO: validation : https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-customerrorresponse.html#cfn-cloudfront-distribution-customerrorresponse-errorcachingminttl webAclId: props.webACLId, diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts index f232b5d27edf5..98f8980db4f4b 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts @@ -114,7 +114,7 @@ export class AlarmWidget extends ConcreteWidget { alarms: [this.props.alarm.alarmArn], }, yAxis: { - left: this.props.leftYAxis !== undefined ? this.props.leftYAxis : undefined, + left: this.props.leftYAxis ?? undefined, }, }, }]; @@ -271,8 +271,8 @@ export class GraphWidget extends ConcreteWidget { metrics: metrics.length > 0 ? metrics : undefined, annotations: horizontalAnnotations.length > 0 ? { horizontal: horizontalAnnotations } : undefined, yAxis: { - left: this.props.leftYAxis !== undefined ? this.props.leftYAxis : undefined, - right: this.props.rightYAxis !== undefined ? this.props.rightYAxis : undefined, + left: this.props.leftYAxis ?? undefined, + right: this.props.rightYAxis ?? undefined, }, legend: this.props.legendPosition !== undefined ? { position: this.props.legendPosition } : undefined, liveData: this.props.liveData, diff --git a/packages/@aws-cdk/aws-codebuild/lib/source.ts b/packages/@aws-cdk/aws-codebuild/lib/source.ts index 7706328a92bc7..19161ef9b6172 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/source.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/source.ts @@ -515,14 +515,14 @@ abstract class ThirdPartyGitSource extends GitSource { super(props); this.webhook = props.webhook; - this.reportBuildStatus = props.reportBuildStatus === undefined ? true : props.reportBuildStatus; + this.reportBuildStatus = props.reportBuildStatus ?? true; this.webhookFilters = props.webhookFilters || []; this.webhookTriggersBatchBuild = props.webhookTriggersBatchBuild; } public bind(_scope: CoreConstruct, project: IProject): SourceConfig { const anyFilterGroupsProvided = this.webhookFilters.length > 0; - const webhook = this.webhook === undefined ? (anyFilterGroupsProvided ? true : undefined) : this.webhook; + const webhook = this.webhook ?? (anyFilterGroupsProvided ? true : undefined); if (!webhook && anyFilterGroupsProvided) { throw new Error('`webhookFilters` cannot be used when `webhook` is `false`'); diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts index 20822a0d2356b..55077fe93f273 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts @@ -99,9 +99,8 @@ export class CustomLambdaDeploymentConfig extends Resource implements ILambdaDep // Generates the name of the deployment config. It's also what you'll see in the AWS console // The name of the config is .LambdaPercentMinutes // Unless the user provides an explicit name - this.deploymentConfigName = props.deploymentConfigName !== undefined - ? props.deploymentConfigName - : `${Names.uniqueId(this)}.Lambda${props.type}${props.percentage}Percent${props.type === CustomLambdaDeploymentConfigType.LINEAR + this.deploymentConfigName = props.deploymentConfigName + ?? `${Names.uniqueId(this)}.Lambda${props.type}${props.percentage}Percent${props.type === CustomLambdaDeploymentConfigType.LINEAR ? 'Every' : ''}${props.interval.toMinutes()}Minutes`; this.deploymentConfigArn = arnForDeploymentConfig(this.deploymentConfigName); diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts index 0864d603d2e28..e1108acb5205d 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts @@ -277,7 +277,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { }); this._autoScalingGroups = props.autoScalingGroups || []; - this.installAgent = props.installAgent === undefined ? true : props.installAgent; + this.installAgent = props.installAgent ?? true; this.codeDeployBucket = s3.Bucket.fromBucketName(this, 'Bucket', `aws-codedeploy-${cdk.Stack.of(this).region}`); for (const asg of this._autoScalingGroups) { this.addCodeDeployAgentInstallUserData(asg); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts index 41e34ac8a0b5d..dad9e84a47094 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts @@ -132,7 +132,7 @@ export class CustomActionRegistration extends Construct { entityUrlTemplate: props.entityUrl, executionUrlTemplate: props.executionUrl, }, - configurationProperties: props.actionProperties === undefined ? undefined : props.actionProperties.map((ap) => { + configurationProperties: props.actionProperties?.map((ap) => { return { key: ap.key || false, secret: ap.secret || false, diff --git a/packages/@aws-cdk/aws-codepipeline/lib/private/full-action-descriptor.ts b/packages/@aws-cdk/aws-codepipeline/lib/private/full-action-descriptor.ts index 6d58bf815d3db..895d3cf15430c 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/private/full-action-descriptor.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/private/full-action-descriptor.ts @@ -36,13 +36,13 @@ export class FullActionDescriptor { this.owner = actionProperties.owner || 'AWS'; this.provider = actionProperties.provider; this.version = actionProperties.version || '1'; - this.runOrder = actionProperties.runOrder === undefined ? 1 : actionProperties.runOrder; + this.runOrder = actionProperties.runOrder ?? 1; this.artifactBounds = actionProperties.artifactBounds; this.namespace = actionProperties.variablesNamespace; this.inputs = deduplicateArtifacts(actionProperties.inputs); this.outputs = deduplicateArtifacts(actionProperties.outputs); this.region = props.actionRegion || actionProperties.region; - this.role = actionProperties.role !== undefined ? actionProperties.role : props.actionRole; + this.role = actionProperties.role ?? props.actionRole; this.configuration = props.actionConfig.configuration; } diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index 960d14bb8b426..d85199ee6507f 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -723,7 +723,7 @@ export class UserPool extends UserPoolBase { emailSubject: props.userInvitation?.emailSubject, smsMessage: props.userInvitation?.smsMessage, }; - const selfSignUpEnabled = props.selfSignUpEnabled !== undefined ? props.selfSignUpEnabled : false; + const selfSignUpEnabled = props.selfSignUpEnabled ?? false; const adminCreateUserConfig: CfnUserPool.AdminCreateUserConfigProperty = { allowAdminCreateUserOnly: !selfSignUpEnabled, inviteMessageTemplate: props.userInvitation !== undefined ? inviteMessageTemplate : undefined, diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index b1c7c4f339ccd..1b12eef42de1f 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -1391,8 +1391,8 @@ export class Table extends TableBase { } return { - projectionType: props.projectionType ? props.projectionType : ProjectionType.ALL, - nonKeyAttributes: props.nonKeyAttributes ? props.nonKeyAttributes : undefined, + projectionType: props.projectionType ?? ProjectionType.ALL, + nonKeyAttributes: props.nonKeyAttributes ?? undefined, }; } diff --git a/packages/@aws-cdk/aws-ec2/lib/network-acl.ts b/packages/@aws-cdk/aws-ec2/lib/network-acl.ts index bea1b030d2c9a..911948e8df39c 100644 --- a/packages/@aws-cdk/aws-ec2/lib/network-acl.ts +++ b/packages/@aws-cdk/aws-ec2/lib/network-acl.ts @@ -277,7 +277,7 @@ export class NetworkAclEntry extends NetworkAclEntryBase { new CfnNetworkAclEntry(this, 'Resource', { networkAclId: this.networkAcl.networkAclId, ruleNumber: props.ruleNumber, - ruleAction: props.ruleAction !== undefined ? props.ruleAction : Action.ALLOW, + ruleAction: props.ruleAction ?? Action.ALLOW, egress: props.direction !== undefined ? props.direction === TrafficDirection.EGRESS : undefined, ...props.traffic.toTrafficConfig(), ...props.cidr.toCidrConfig(), diff --git a/packages/@aws-cdk/aws-ec2/lib/user-data.ts b/packages/@aws-cdk/aws-ec2/lib/user-data.ts index 21727212948c0..20061bd609636 100644 --- a/packages/@aws-cdk/aws-ec2/lib/user-data.ts +++ b/packages/@aws-cdk/aws-ec2/lib/user-data.ts @@ -147,7 +147,7 @@ class LinuxUserData extends UserData { } public render(): string { - const shebang = this.props.shebang !== undefined ? this.props.shebang : '#!/bin/bash'; + const shebang = this.props.shebang ?? '#!/bin/bash'; return [shebang, ...(this.renderOnExitLines()), ...this.lines].join('\n'); } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts index d17e56aab202c..87f67391f8c9b 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint-service.ts @@ -86,8 +86,8 @@ export class VpcEndpointService extends Resource implements IVpcEndpointService } this.vpcEndpointServiceLoadBalancers = props.vpcEndpointServiceLoadBalancers; - this.acceptanceRequired = props.acceptanceRequired !== undefined ? props.acceptanceRequired : true; - this.whitelistedPrincipals = props.whitelistedPrincipals !== undefined ? props.whitelistedPrincipals : []; + this.acceptanceRequired = props.acceptanceRequired ?? true; + this.whitelistedPrincipals = props.whitelistedPrincipals ?? []; this.endpointService = new CfnVPCEndpointService(this, id, { networkLoadBalancerArns: this.vpcEndpointServiceLoadBalancers.map(lb => lb.loadBalancerArn), @@ -98,8 +98,7 @@ export class VpcEndpointService extends Resource implements IVpcEndpointService const { region } = Stack.of(this); const serviceNamePrefix = !Token.isUnresolved(region) ? - RegionInfo.get(region).vpcEndpointServiceNamePrefix ?? - Default.VPC_ENDPOINT_SERVICE_NAME_PREFIX : + (RegionInfo.get(region).vpcEndpointServiceNamePrefix ?? Default.VPC_ENDPOINT_SERVICE_NAME_PREFIX) : Default.VPC_ENDPOINT_SERVICE_NAME_PREFIX; this.vpcEndpointServiceName = Fn.join('.', [serviceNamePrefix, Aws.REGION, this.vpcEndpointServiceId]); diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index d23433b2637d8..4e3f90780db4c 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1213,7 +1213,7 @@ export class Vpc extends VpcBase { this.availabilityZones = stack.availabilityZones; - const maxAZs = props.maxAzs !== undefined ? props.maxAzs : 3; + const maxAZs = props.maxAzs ?? 3; this.availabilityZones = this.availabilityZones.slice(0, maxAZs); this.vpcId = this.resource.ref; @@ -1788,7 +1788,7 @@ export class PrivateSubnet extends Subnet implements IPrivateSubnet { } function ifUndefined(value: T | undefined, defaultValue: T): T { - return value !== undefined ? value : defaultValue; + return value ?? defaultValue; } class ImportedVpc extends VpcBase { diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 36e14cf861adc..83c05ba1ed308 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -202,7 +202,7 @@ export abstract class RepositoryBase extends Resource implements IRepository { detail: { 'repository-name': [this.repositoryName], 'scan-status': ['COMPLETE'], - 'image-tags': options.imageTags ? options.imageTags : undefined, + 'image-tags': options.imageTags ?? undefined, }, }); return rule; @@ -526,7 +526,7 @@ export class Repository extends RepositoryBase { for (const rule of prioritizedRules.concat(autoPrioritizedRules).concat(anyRules)) { ret.push({ ...rule, - rulePriority: rule.rulePriority !== undefined ? rule.rulePriority : autoPrio++, + rulePriority: rule.rulePriority ?? autoPrio++, }); } @@ -557,7 +557,7 @@ function renderLifecycleRule(rule: LifecycleRule) { tagStatus: rule.tagStatus || TagStatus.ANY, tagPrefixList: rule.tagPrefixList, countType: rule.maxImageAge !== undefined ? CountType.SINCE_IMAGE_PUSHED : CountType.IMAGE_COUNT_MORE_THAN, - countNumber: rule.maxImageAge !== undefined ? rule.maxImageAge.toDays() : rule.maxImageCount, + countNumber: rule.maxImageAge?.toDays() ?? rule.maxImageCount, countUnit: rule.maxImageAge !== undefined ? 'days' : undefined, }, action: { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index 769756999ad0c..74411bb217558 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -370,21 +370,19 @@ export abstract class ApplicationLoadBalancedServiceBase extends CoreConstruct { } this.desiredCount = props.desiredCount || 1; - const internetFacing = props.publicLoadBalancer !== undefined ? props.publicLoadBalancer : true; + const internetFacing = props.publicLoadBalancer ?? true; const lbProps = { vpc: this.cluster.vpc, internetFacing, }; - const loadBalancer = props.loadBalancer !== undefined ? props.loadBalancer - : new ApplicationLoadBalancer(this, 'LB', lbProps); + const loadBalancer = props.loadBalancer ?? new ApplicationLoadBalancer(this, 'LB', lbProps); if (props.certificate !== undefined && props.protocol !== undefined && props.protocol !== ApplicationProtocol.HTTPS) { throw new Error('The HTTPS protocol must be used when a certificate is given'); } - const protocol = props.protocol !== undefined ? props.protocol : - (props.certificate ? ApplicationProtocol.HTTPS : ApplicationProtocol.HTTP); + const protocol = props.protocol ?? (props.certificate ? ApplicationProtocol.HTTPS : ApplicationProtocol.HTTP); if (protocol !== ApplicationProtocol.HTTPS && props.redirectHTTP === true) { throw new Error('The HTTPS protocol must be used when redirecting HTTP traffic'); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts index 67ce9a02e77b6..f3eb132e934ae 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts @@ -478,10 +478,8 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends CoreCon * Create log driver if logging is enabled. */ private createLogDriver(enableLoggingProp?: boolean, logDriverProp?: LogDriver): LogDriver | undefined { - const enableLogging = enableLoggingProp !== undefined ? enableLoggingProp : true; - const logDriver = logDriverProp !== undefined - ? logDriverProp : enableLogging - ? this.createAWSLogDriver(this.node.id) : undefined; + const enableLogging = enableLoggingProp ?? true; + const logDriver = logDriverProp ?? (enableLogging ? this.createAWSLogDriver(this.node.id) : undefined); return logDriver; } @@ -522,7 +520,7 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends CoreCon } private createLoadBalancer(name: string, publicLoadBalancer?: boolean): ApplicationLoadBalancer { - const internetFacing = publicLoadBalancer !== undefined ? publicLoadBalancer : true; + const internetFacing = publicLoadBalancer ?? true; const lbProps = { vpc: this.cluster.vpc, internetFacing, @@ -532,7 +530,7 @@ export abstract class ApplicationMultipleTargetGroupsServiceBase extends CoreCon } private createListenerProtocol(listenerProtocol?: ApplicationProtocol, certificate?: ICertificate): ApplicationProtocol { - return listenerProtocol !== undefined ? listenerProtocol : (certificate ? ApplicationProtocol.HTTPS : ApplicationProtocol.HTTP); + return listenerProtocol ?? (certificate ? ApplicationProtocol.HTTPS : ApplicationProtocol.HTTP); } private createListenerCertificate(listenerName: string, certificate?: ICertificate, domainName?: string, domainZone?: IHostedZone): ICertificate { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts index ffdcb3b75912a..656cc19d19d43 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts @@ -308,18 +308,15 @@ export abstract class NetworkLoadBalancedServiceBase extends CoreConstruct { } this.desiredCount = props.desiredCount || 1; - const internetFacing = props.publicLoadBalancer !== undefined ? props.publicLoadBalancer : true; + const internetFacing = props.publicLoadBalancer ?? true; const lbProps = { vpc: this.cluster.vpc, internetFacing, }; - const loadBalancer = props.loadBalancer !== undefined ? props.loadBalancer : - new NetworkLoadBalancer(this, 'LB', lbProps); - - const listenerPort = props.listenerPort !== undefined ? props.listenerPort : 80; - + const loadBalancer = props.loadBalancer ?? new NetworkLoadBalancer(this, 'LB', lbProps); + const listenerPort = props.listenerPort ?? 80; const targetProps = { port: 80, }; diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts index d3f74588fd341..d34a6b548076d 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts @@ -384,10 +384,8 @@ export abstract class NetworkMultipleTargetGroupsServiceBase extends CoreConstru * Create log driver if logging is enabled. */ private createLogDriver(enableLoggingProp?: boolean, logDriverProp?: LogDriver): LogDriver | undefined { - const enableLogging = enableLoggingProp !== undefined ? enableLoggingProp : true; - const logDriver = logDriverProp !== undefined - ? logDriverProp : enableLogging - ? this.createAWSLogDriver(this.node.id) : undefined; + const enableLogging = enableLoggingProp ?? true; + const logDriver = logDriverProp ?? (enableLogging ? this.createAWSLogDriver(this.node.id) : undefined); return logDriver; } @@ -413,7 +411,7 @@ export abstract class NetworkMultipleTargetGroupsServiceBase extends CoreConstru } private createLoadBalancer(name: string, publicLoadBalancer?: boolean): NetworkLoadBalancer { - const internetFacing = publicLoadBalancer !== undefined ? publicLoadBalancer : true; + const internetFacing = publicLoadBalancer ?? true; const lbProps = { vpc: this.cluster.vpc, internetFacing, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts index 66bdea9619629..3248514931f4d 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts @@ -262,22 +262,18 @@ export abstract class QueueProcessingServiceBase extends CoreConstruct { // Setup autoscaling scaling intervals const defaultScalingSteps = [{ upper: 0, change: -1 }, { lower: 100, change: +1 }, { lower: 500, change: +5 }]; - this.scalingSteps = props.scalingSteps !== undefined ? props.scalingSteps : defaultScalingSteps; + this.scalingSteps = props.scalingSteps ?? defaultScalingSteps; // Create log driver if logging is enabled - const enableLogging = props.enableLogging !== undefined ? props.enableLogging : true; - this.logDriver = props.logDriver !== undefined - ? props.logDriver - : enableLogging - ? this.createAWSLogDriver(this.node.id) - : undefined; + const enableLogging = props.enableLogging ?? true; + this.logDriver = props.logDriver ?? (enableLogging ? this.createAWSLogDriver(this.node.id) : undefined); // Add the queue name to environment variables this.environment = { ...(props.environment || {}), QUEUE_NAME: this.sqsQueue.queueName }; this.secrets = props.secrets; // Determine the desired task count (minimum) and maximum scaling capacity - this.desiredCount = props.desiredTaskCount !== undefined ? props.desiredTaskCount : 1; + this.desiredCount = props.desiredTaskCount ?? 1; this.maxCapacity = props.maxScalingCapacity || (2 * this.desiredCount); if (!this.desiredCount && !this.maxCapacity) { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts index 9cec9e1d9e083..b9bdcf2d100ad 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts @@ -97,12 +97,10 @@ export class ApplicationLoadBalancedEc2Service extends ApplicationLoadBalancedSe }); // Create log driver if logging is enabled - const enableLogging = taskImageOptions.enableLogging !== undefined ? taskImageOptions.enableLogging : true; - const logDriver = taskImageOptions.logDriver !== undefined - ? taskImageOptions.logDriver : enableLogging - ? this.createAWSLogDriver(this.node.id) : undefined; + const enableLogging = taskImageOptions.enableLogging ?? true; + const logDriver = taskImageOptions.logDriver ?? (enableLogging ? this.createAWSLogDriver(this.node.id) : undefined); - const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; + const containerName = taskImageOptions.containerName ?? 'web'; const container = this.taskDefinition.addContainer(containerName, { image: taskImageOptions.image, cpu: props.cpu, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts index 4e6fa5c04b6f3..6ed6b6b71802f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts @@ -94,7 +94,7 @@ export class ApplicationMultipleTargetGroupsEc2Service extends ApplicationMultip taskRole: taskImageOptions.taskRole, }); - const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; + const containerName = taskImageOptions.containerName ?? 'web'; const container = this.taskDefinition.addContainer(containerName, { image: taskImageOptions.image, cpu: props.cpu, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts index 2d2c00fcf345e..fae46b68e7380 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts @@ -95,12 +95,10 @@ export class NetworkLoadBalancedEc2Service extends NetworkLoadBalancedServiceBas }); // Create log driver if logging is enabled - const enableLogging = taskImageOptions.enableLogging !== undefined ? taskImageOptions.enableLogging : true; - const logDriver = taskImageOptions.logDriver !== undefined - ? taskImageOptions.logDriver : enableLogging - ? this.createAWSLogDriver(this.node.id) : undefined; + const enableLogging = taskImageOptions.enableLogging ?? true; + const logDriver = taskImageOptions.logDriver ?? (enableLogging ? this.createAWSLogDriver(this.node.id) : undefined); - const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; + const containerName = taskImageOptions.containerName ?? 'web'; const container = this.taskDefinition.addContainer(containerName, { image: taskImageOptions.image, cpu: props.cpu, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts index a283c9ab55dec..eb8392d3b2148 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts @@ -93,7 +93,7 @@ export class NetworkMultipleTargetGroupsEc2Service extends NetworkMultipleTarget taskRole: taskImageOptions.taskRole, }); - const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; + const containerName = taskImageOptions.containerName ?? 'web'; const container = this.taskDefinition.addContainer(containerName, { image: taskImageOptions.image, cpu: props.cpu, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts index 7350ae82bb2cc..7f64a18df9ff8 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/scheduled-ecs-task.ts @@ -107,7 +107,7 @@ export class ScheduledEc2Task extends ScheduledTaskBase { command: taskImageOptions.command, environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, - logging: taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : this.createAWSLogDriver(this.node.id), + logging: taskImageOptions.logDriver ?? this.createAWSLogDriver(this.node.id), }); } else { throw new Error('You must specify a taskDefinition or image'); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts index 00049e662ee0f..2ae468bcae558 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts @@ -117,7 +117,7 @@ export class ApplicationLoadBalancedFargateService extends ApplicationLoadBalanc constructor(scope: Construct, id: string, props: ApplicationLoadBalancedFargateServiceProps = {}) { super(scope, id, props); - this.assignPublicIp = props.assignPublicIp !== undefined ? props.assignPublicIp : false; + this.assignPublicIp = props.assignPublicIp ?? false; if (props.taskDefinition && props.taskImageOptions) { throw new Error('You must specify either a taskDefinition or an image, not both.'); @@ -134,12 +134,12 @@ export class ApplicationLoadBalancedFargateService extends ApplicationLoadBalanc }); // Create log driver if logging is enabled - const enableLogging = taskImageOptions.enableLogging !== undefined ? taskImageOptions.enableLogging : true; + const enableLogging = taskImageOptions.enableLogging ?? true; const logDriver = taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : enableLogging ? this.createAWSLogDriver(this.node.id) : undefined; - const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; + const containerName = taskImageOptions.containerName ?? 'web'; const container = this.taskDefinition.addContainer(containerName, { image: taskImageOptions.image, logging: logDriver, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts index a5840bc2f2e76..495049dfccfa8 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts @@ -113,7 +113,7 @@ export class ApplicationMultipleTargetGroupsFargateService extends ApplicationMu constructor(scope: Construct, id: string, props: ApplicationMultipleTargetGroupsFargateServiceProps = {}) { super(scope, id, props); - this.assignPublicIp = props.assignPublicIp !== undefined ? props.assignPublicIp : false; + this.assignPublicIp = props.assignPublicIp ?? false; if (props.taskDefinition && props.taskImageOptions) { throw new Error('You must specify only one of TaskDefinition or TaskImageOptions.'); @@ -129,7 +129,7 @@ export class ApplicationMultipleTargetGroupsFargateService extends ApplicationMu family: taskImageOptions.family, }); - const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; + const containerName = taskImageOptions.containerName ?? 'web'; const container = this.taskDefinition.addContainer(containerName, { image: taskImageOptions.image, logging: this.logDriver, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts index 10dcfebdc2f80..4aad4b31e7efe 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts @@ -106,7 +106,7 @@ export class NetworkLoadBalancedFargateService extends NetworkLoadBalancedServic constructor(scope: Construct, id: string, props: NetworkLoadBalancedFargateServiceProps = {}) { super(scope, id, props); - this.assignPublicIp = props.assignPublicIp !== undefined ? props.assignPublicIp : false; + this.assignPublicIp = props.assignPublicIp ?? false; if (props.taskDefinition && props.taskImageOptions) { throw new Error('You must specify either a taskDefinition or an image, not both.'); @@ -123,12 +123,10 @@ export class NetworkLoadBalancedFargateService extends NetworkLoadBalancedServic }); // Create log driver if logging is enabled - const enableLogging = taskImageOptions.enableLogging !== undefined ? taskImageOptions.enableLogging : true; - const logDriver = taskImageOptions.logDriver !== undefined - ? taskImageOptions.logDriver : enableLogging - ? this.createAWSLogDriver(this.node.id) : undefined; + const enableLogging = taskImageOptions.enableLogging ?? true; + const logDriver = taskImageOptions.logDriver ?? (enableLogging ? this.createAWSLogDriver(this.node.id) : undefined); - const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; + const containerName = taskImageOptions.containerName ?? 'web'; const container = this.taskDefinition.addContainer(containerName, { image: taskImageOptions.image, logging: logDriver, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts index 0d97d730daeab..dab033b1938ce 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts @@ -113,7 +113,7 @@ export class NetworkMultipleTargetGroupsFargateService extends NetworkMultipleTa constructor(scope: Construct, id: string, props: NetworkMultipleTargetGroupsFargateServiceProps = {}) { super(scope, id, props); - this.assignPublicIp = props.assignPublicIp !== undefined ? props.assignPublicIp : false; + this.assignPublicIp = props.assignPublicIp ?? false; if (props.taskDefinition && props.taskImageOptions) { throw new Error('You must specify only one of TaskDefinition or TaskImageOptions.'); @@ -129,7 +129,7 @@ export class NetworkMultipleTargetGroupsFargateService extends NetworkMultipleTa family: taskImageOptions.family, }); - const containerName = taskImageOptions.containerName !== undefined ? taskImageOptions.containerName : 'web'; + const containerName = taskImageOptions.containerName ?? 'web'; const container = this.taskDefinition.addContainer(containerName, { image: taskImageOptions.image, logging: this.logDriver, diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts index 27b9d8b6ad224..ab46883fa9c90 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/scheduled-fargate-task.ts @@ -115,7 +115,7 @@ export class ScheduledFargateTask extends ScheduledTaskBase { command: taskImageOptions.command, environment: taskImageOptions.environment, secrets: taskImageOptions.secrets, - logging: taskImageOptions.logDriver !== undefined ? taskImageOptions.logDriver : this.createAWSLogDriver(this.node.id), + logging: taskImageOptions.logDriver ?? this.createAWSLogDriver(this.node.id), }); } else { throw new Error('You must specify one of: taskDefinition or image'); diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 6704ab8d79ca9..5c5160a849835 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -234,8 +234,7 @@ class ApplicationListenerConfig extends ListenerConfig { public addTargets(id: string, target: LoadBalancerTargetOptions, service: BaseService) { const props = this.props || {}; const protocol = props.protocol; - const port = props.port !== undefined ? props.port : (protocol === undefined ? 80 : - (protocol === elbv2.ApplicationProtocol.HTTPS ? 443 : 80)); + const port = props.port ?? (protocol === elbv2.ApplicationProtocol.HTTPS ? 443 : 80); this.listener.addTargets(id, { ... props, targets: [ @@ -260,7 +259,7 @@ class NetworkListenerConfig extends ListenerConfig { * Create and attach a target group to listener. */ public addTargets(id: string, target: LoadBalancerTargetOptions, service: BaseService) { - const port = this.props !== undefined ? this.props.port : 80; + const port = this.props?.port ?? 80; this.listener.addTargets(id, { ... this.props, targets: [ @@ -370,7 +369,7 @@ export abstract class BaseService extends Resource } : undefined, }, propagateTags: props.propagateTags === PropagatedTagSource.NONE ? undefined : props.propagateTags, - enableEcsManagedTags: props.enableECSManagedTags === undefined ? false : props.enableECSManagedTags, + enableEcsManagedTags: props.enableECSManagedTags ?? false, deploymentController: props.deploymentController, launchType: props.deploymentController?.type === DeploymentControllerType.EXTERNAL ? undefined : props.launchType, healthCheckGracePeriodSeconds: this.evaluateHealthGracePeriod(props.healthCheckGracePeriod), @@ -525,7 +524,7 @@ export abstract class BaseService extends Resource * @returns The created CloudMap service */ public enableCloudMap(options: CloudMapOptions): cloudmap.Service { - const sdNamespace = options.cloudMapNamespace !== undefined ? options.cloudMapNamespace : this.cluster.defaultCloudMapNamespace; + const sdNamespace = options.cloudMapNamespace ?? this.cluster.defaultCloudMapNamespace; if (sdNamespace === undefined) { throw new Error('Cannot enable service discovery if a Cloudmap Namespace has not been created in the cluster.'); } @@ -739,9 +738,7 @@ export abstract class BaseService extends Resource */ private evaluateHealthGracePeriod(providedHealthCheckGracePeriod?: Duration): IResolvable { return Lazy.any({ - produce: () => providedHealthCheckGracePeriod !== undefined ? providedHealthCheckGracePeriod.toSeconds() : - this.loadBalancers.length > 0 ? 60 : - undefined, + produce: () => providedHealthCheckGracePeriod?.toSeconds() ?? (this.loadBalancers.length > 0 ? 60 : undefined), }); } } 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 ff27f00cb79a8..f7b0f05c92800 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -284,8 +284,7 @@ export class TaskDefinition extends TaskDefinitionBase { props.volumes.forEach(v => this.addVolume(v)); } - this.networkMode = props.networkMode !== undefined ? props.networkMode : - this.isFargateCompatible ? NetworkMode.AWS_VPC : NetworkMode.BRIDGE; + this.networkMode = props.networkMode ?? (this.isFargateCompatible ? NetworkMode.AWS_VPC : NetworkMode.BRIDGE); if (this.isFargateCompatible && this.networkMode !== NetworkMode.AWS_VPC) { throw new Error(`Fargate tasks can only have AwsVpc network mode, got: ${this.networkMode}`); } diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index fcc28784229e5..ca84679e7b970 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -790,7 +790,7 @@ class ImportedCluster extends Resource implements ICluster { this.hasEc2Capacity = props.hasEc2Capacity !== false; this._defaultCloudMapNamespace = props.defaultCloudMapNamespace; - this.clusterArn = props.clusterArn !== undefined ? props.clusterArn : Stack.of(this).formatArn({ + this.clusterArn = props.clusterArn ?? Stack.of(this).formatArn({ service: 'ecs', resource: 'cluster', resourceName: props.clusterName, diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index b4d31c56133fc..983489743482f 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -399,7 +399,7 @@ export class ContainerDefinition extends CoreConstruct { throw new Error('MemoryLimitMiB should not be less than MemoryReservationMiB.'); } } - this.essential = props.essential !== undefined ? props.essential : true; + this.essential = props.essential ?? true; this.taskDefinition = props.taskDefinition; this.memoryLimitSpecified = props.memoryLimitMiB !== undefined || props.memoryReservationMiB !== undefined; this.linuxParameters = props.linuxParameters; @@ -705,10 +705,10 @@ function renderEnvironmentFiles(environmentFiles: EnvironmentFileConfig[]): any[ function renderHealthCheck(hc: HealthCheck): CfnTaskDefinition.HealthCheckProperty { return { command: getHealthCheckCommand(hc), - interval: hc.interval != null ? hc.interval.toSeconds() : 30, - retries: hc.retries !== undefined ? hc.retries : 3, - startPeriod: hc.startPeriod && hc.startPeriod.toSeconds(), - timeout: hc.timeout !== undefined ? hc.timeout.toSeconds() : 5, + interval: hc.interval?.toSeconds() ?? 30, + retries: hc.retries ?? 3, + startPeriod: hc.startPeriod?.toSeconds(), + timeout: hc.timeout?.toSeconds() ?? 5, }; } diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index a119e445e6571..18c6df350fb4e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -181,8 +181,7 @@ export class Ec2Service extends BaseService implements IEc2Service { throw new Error('Only one of SecurityGroup or SecurityGroups can be populated.'); } - const propagateTagsFromSource = props.propagateTaskTagsFrom !== undefined ? props.propagateTaskTagsFrom - : (props.propagateTags !== undefined ? props.propagateTags : PropagatedTagSource.NONE); + const propagateTagsFromSource = props.propagateTaskTagsFrom ?? props.propagateTags ?? PropagatedTagSource.NONE; super(scope, id, { ...props, 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 f70ca796ed3f6..01a9f75665c57 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -147,8 +147,7 @@ export class FargateService extends BaseService implements IFargateService { 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.`); } - const propagateTagsFromSource = props.propagateTaskTagsFrom !== undefined ? props.propagateTaskTagsFrom - : (props.propagateTags !== undefined ? props.propagateTags : PropagatedTagSource.NONE); + const propagateTagsFromSource = props.propagateTaskTagsFrom ?? props.propagateTags ?? PropagatedTagSource.NONE; super(scope, id, { ...props, diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts index a00358d7395ce..1f8c699180aad 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts @@ -382,7 +382,7 @@ export class Cluster extends Resource implements ICluster { }; let resource; - this.kubectlEnabled = props.kubectlEnabled === undefined ? true : props.kubectlEnabled; + this.kubectlEnabled = props.kubectlEnabled ?? true; if (this.kubectlEnabled) { resource = new ClusterResource(this, 'Resource', clusterProps); this._defaultMastersRole = resource.creationRole; @@ -429,13 +429,13 @@ export class Cluster extends Resource implements ICluster { } // allocate default capacity if non-zero (or default). - const desiredCapacity = props.defaultCapacity === undefined ? DEFAULT_CAPACITY_COUNT : props.defaultCapacity; + const desiredCapacity = props.defaultCapacity ?? DEFAULT_CAPACITY_COUNT; if (desiredCapacity > 0) { const instanceType = props.defaultCapacityInstance || DEFAULT_CAPACITY_TYPE; this.defaultCapacity = this.addCapacity('DefaultCapacity', { instanceType, desiredCapacity }); } - const outputConfigCommand = props.outputConfigCommand === undefined ? true : props.outputConfigCommand; + const outputConfigCommand = props.outputConfigCommand ?? true; if (outputConfigCommand) { const postfix = commonCommandOptions.join(' '); new CfnOutput(this, 'ConfigCommand', { value: `${updateConfigCommandPrefix} ${postfix}` }); @@ -512,7 +512,7 @@ export class Cluster extends Resource implements ICluster { autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allUdp()); autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allIcmp()); - const bootstrapEnabled = options.bootstrapEnabled !== undefined ? options.bootstrapEnabled : true; + const bootstrapEnabled = options.bootstrapEnabled ?? true; if (options.bootstrapOptions && !bootstrapEnabled) { throw new Error('Cannot specify "bootstrapOptions" if "bootstrapEnabled" is false'); } @@ -537,7 +537,7 @@ export class Cluster extends Resource implements ICluster { // do not attempt to map the role if `kubectl` is not enabled for this // cluster or if `mapRole` is set to false. By default this should happen. - const mapRole = options.mapRole === undefined ? true : options.mapRole; + const mapRole = options.mapRole ?? true; if (mapRole && this.kubectlEnabled) { // see https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html this.awsAuth.addRoleMapping(autoScalingGroup.role, { diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts b/packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts index e889663520f32..75d6e009d657a 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/user-data.ts @@ -12,7 +12,7 @@ export function renderUserData(clusterName: string, autoScalingGroup: autoscalin const extraArgs = new Array(); - extraArgs.push(`--use-max-pods ${options.useMaxPods === undefined ? true : options.useMaxPods}`); + extraArgs.push(`--use-max-pods ${options.useMaxPods ?? true}`); if (options.awsApiRetryAttempts) { extraArgs.push(`--aws-api-retry-attempts ${options.awsApiRetryAttempts}`); diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 6737ac1c7f604..706a23720c157 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -1105,7 +1105,7 @@ export class Cluster extends ClusterBase { commonCommandOptions.push(`--role-arn ${mastersRole.roleArn}`); // allocate default capacity if non-zero (or default). - const minCapacity = props.defaultCapacity === undefined ? DEFAULT_CAPACITY_COUNT : props.defaultCapacity; + const minCapacity = props.defaultCapacity ?? DEFAULT_CAPACITY_COUNT; if (minCapacity > 0) { const instanceType = props.defaultCapacityInstance || DEFAULT_CAPACITY_TYPE; this.defaultCapacity = props.defaultCapacityType === DefaultCapacityType.EC2 ? @@ -1115,7 +1115,7 @@ export class Cluster extends ClusterBase { this.addNodegroupCapacity('DefaultCapacity', { instanceTypes: [instanceType], minSize: minCapacity }) : undefined; } - const outputConfigCommand = props.outputConfigCommand === undefined ? true : props.outputConfigCommand; + const outputConfigCommand = props.outputConfigCommand ?? true; if (outputConfigCommand) { const postfix = commonCommandOptions.join(' '); new CfnOutput(this, 'ConfigCommand', { value: `${updateConfigCommandPrefix} ${postfix}` }); @@ -1251,7 +1251,7 @@ export class Cluster extends ClusterBase { // allow traffic to/from managed node groups (eks attaches this security group to the managed nodes) autoScalingGroup.addSecurityGroup(this.clusterSecurityGroup); - const bootstrapEnabled = options.bootstrapEnabled !== undefined ? options.bootstrapEnabled : true; + const bootstrapEnabled = options.bootstrapEnabled ?? true; if (options.bootstrapOptions && !bootstrapEnabled) { throw new Error('Cannot specify "bootstrapOptions" if "bootstrapEnabled" is false'); } @@ -1278,7 +1278,7 @@ export class Cluster extends ClusterBase { // do not attempt to map the role if `kubectl` is not enabled for this // cluster or if `mapRole` is set to false. By default this should happen. - const mapRole = options.mapRole === undefined ? true : options.mapRole; + const mapRole = options.mapRole ?? true; if (mapRole) { // see https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html this.awsAuth.addRoleMapping(autoScalingGroup.role, { diff --git a/packages/@aws-cdk/aws-eks/lib/user-data.ts b/packages/@aws-cdk/aws-eks/lib/user-data.ts index cf38cf7ee9761..3b8d997535771 100644 --- a/packages/@aws-cdk/aws-eks/lib/user-data.ts +++ b/packages/@aws-cdk/aws-eks/lib/user-data.ts @@ -13,7 +13,7 @@ export function renderAmazonLinuxUserData(clusterName: string, autoScalingGroup: const extraArgs = new Array(); - extraArgs.push(`--use-max-pods ${options.useMaxPods === undefined ? true : options.useMaxPods}`); + extraArgs.push(`--use-max-pods ${options.useMaxPods ?? true}`); if (options.awsApiRetryAttempts) { extraArgs.push(`--aws-api-retry-attempts ${options.awsApiRetryAttempts}`); diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts index 55c423f157a55..eaed2daee58d2 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts @@ -248,7 +248,7 @@ export class LoadBalancer extends Resource implements IConnectable { listeners: Lazy.any({ produce: () => this.listeners }), scheme: props.internetFacing ? 'internet-facing' : 'internal', healthCheck: props.healthCheck && healthCheckToJSON(props.healthCheck), - crossZone: (props.crossZone === undefined || props.crossZone) ? true : false, + crossZone: props.crossZone ?? true, }); if (props.internetFacing) { this.elb.node.addDependency(selectedSubnets.internetConnectivityEstablished); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts index 17170f4402b1a..db52671e5a368 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -108,7 +108,7 @@ export abstract class BaseListener extends Resource { const resource = new CfnListener(this, 'Resource', { ...additionalProps, - defaultActions: Lazy.any({ produce: () => this.defaultAction ? this.defaultAction.renderActions() : [] }), + defaultActions: Lazy.any({ produce: () => this.defaultAction?.renderActions() ?? [] }), }); this.listenerArn = resource.ref; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index 0ff63805aab90..5e569fd8213ca 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -246,20 +246,20 @@ export abstract class TargetGroupBase extends CoreConstruct implements ITargetGr vpcId: cdk.Lazy.string({ produce: () => this.vpc && this.targetType !== TargetType.LAMBDA ? this.vpc.vpcId : undefined }), // HEALTH CHECK - healthCheckEnabled: cdk.Lazy.any({ produce: () => this.healthCheck && this.healthCheck.enabled }), + healthCheckEnabled: cdk.Lazy.any({ produce: () => this.healthCheck?.enabled }), healthCheckIntervalSeconds: cdk.Lazy.number({ - produce: () => this.healthCheck && this.healthCheck.interval && this.healthCheck.interval.toSeconds(), + produce: () => this.healthCheck?.interval?.toSeconds(), }), - healthCheckPath: cdk.Lazy.string({ produce: () => this.healthCheck && this.healthCheck.path }), - healthCheckPort: cdk.Lazy.string({ produce: () => this.healthCheck && this.healthCheck.port }), - healthCheckProtocol: cdk.Lazy.string({ produce: () => this.healthCheck && this.healthCheck.protocol }), + healthCheckPath: cdk.Lazy.string({ produce: () => this.healthCheck?.path }), + healthCheckPort: cdk.Lazy.string({ produce: () => this.healthCheck?.port }), + healthCheckProtocol: cdk.Lazy.string({ produce: () => this.healthCheck?.protocol }), healthCheckTimeoutSeconds: cdk.Lazy.number({ - produce: () => this.healthCheck && this.healthCheck.timeout && this.healthCheck.timeout.toSeconds(), + produce: () => this.healthCheck?.timeout?.toSeconds(), }), - healthyThresholdCount: cdk.Lazy.number({ produce: () => this.healthCheck && this.healthCheck.healthyThresholdCount }), - unhealthyThresholdCount: cdk.Lazy.number({ produce: () => this.healthCheck && this.healthCheck.unhealthyThresholdCount }), + healthyThresholdCount: cdk.Lazy.number({ produce: () => this.healthCheck?.healthyThresholdCount }), + unhealthyThresholdCount: cdk.Lazy.number({ produce: () => this.healthCheck?.unhealthyThresholdCount }), matcher: cdk.Lazy.any({ - produce: () => this.healthCheck && this.healthCheck.healthyHttpCodes !== undefined ? { + produce: () => this.healthCheck?.healthyHttpCodes !== undefined ? { httpCode: this.healthCheck.healthyHttpCodes, } : undefined, }), diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts index 2ecc35c365694..a6d2692a59e83 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts @@ -67,7 +67,7 @@ export function determineProtocolAndPort(protocol: ApplicationProtocol | undefin * Helper function to default undefined input props */ export function ifUndefined(x: T | undefined, def: T) { - return x !== undefined ? x : def; + return x ?? def; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts index e4d45eb609335..67d14cd4f721e 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/security-group.test.ts @@ -272,7 +272,7 @@ class TestFixture { }); this.lb = new elbv2.ApplicationLoadBalancer(this.stack, 'LB', { vpc: this.vpc }); - createListener = createListener === undefined ? true : createListener; + createListener = createListener ?? true; if (createListener) { this._listener = this.lb.addListener('Listener', { port: 80, open: false }); } 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 a4b19981dd0b1..baff4c76cde66 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts @@ -115,7 +115,7 @@ export class EcsTask implements events.IRuleTarget { this.cluster = props.cluster; this.taskDefinition = props.taskDefinition; - this.taskCount = props.taskCount !== undefined ? props.taskCount : 1; + this.taskCount = props.taskCount ?? 1; this.platformVersion = props.platformVersion; if (props.role) { diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index 0079954e73558..969965021f75d 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -166,7 +166,7 @@ export class Rule extends Resource implements IRule { const targetProps = target.bind(this, autoGeneratedId); const inputProps = targetProps.input && targetProps.input.bind(this); - const roleArn = targetProps.role ? targetProps.role.roleArn : undefined; + const roleArn = targetProps.role?.roleArn; const id = targetProps.id || autoGeneratedId; if (targetProps.targetResource) { @@ -298,7 +298,7 @@ export class Rule extends Resource implements IRule { sqsParameters: targetProps.sqsParameters, input: inputProps && inputProps.input, inputPath: inputProps && inputProps.inputPath, - inputTransformer: inputProps && inputProps.inputTemplate !== undefined ? { + inputTransformer: inputProps?.inputTemplate !== undefined ? { inputTemplate: inputProps.inputTemplate, inputPathsMap: inputProps.inputPathsMap, } : undefined, diff --git a/packages/@aws-cdk/aws-events/lib/schedule.ts b/packages/@aws-cdk/aws-events/lib/schedule.ts index 1e0a391e2b911..8c2c04481c0d2 100644 --- a/packages/@aws-cdk/aws-events/lib/schedule.ts +++ b/packages/@aws-cdk/aws-events/lib/schedule.ts @@ -115,7 +115,7 @@ class LiteralSchedule extends Schedule { } function fallback(x: T | undefined, def: T): T { - return x === undefined ? def : x; + return x ?? def; } /** diff --git a/packages/@aws-cdk/aws-glue/lib/table.ts b/packages/@aws-cdk/aws-glue/lib/table.ts index 859bb0b2ee325..635fe67a68d35 100644 --- a/packages/@aws-cdk/aws-glue/lib/table.ts +++ b/packages/@aws-cdk/aws-glue/lib/table.ts @@ -243,7 +243,7 @@ export class Table extends Resource implements ITable { this.columns = props.columns; this.partitionKeys = props.partitionKeys; - this.compressed = props.compressed === undefined ? false : props.compressed; + this.compressed = props.compressed ?? false; const { bucket, encryption, encryptionKey } = createBucket(this, props); this.bucket = bucket; this.encryption = encryption; @@ -267,7 +267,7 @@ export class Table extends Resource implements ITable { storageDescriptor: { location: `s3://${this.bucket.bucketName}/${this.s3Prefix}`, compressed: this.compressed, - storedAsSubDirectories: props.storedAsSubDirectories === undefined ? false : props.storedAsSubDirectories, + storedAsSubDirectories: props.storedAsSubDirectories ?? false, columns: renderColumns(props.columns), inputFormat: props.dataFormat.inputFormat.className, outputFormat: props.dataFormat.outputFormat.className, diff --git a/packages/@aws-cdk/aws-iam/lib/policy.ts b/packages/@aws-cdk/aws-iam/lib/policy.ts index 795049a1cc163..60862dca07c56 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy.ts @@ -160,7 +160,7 @@ export class Policy extends Resource implements IPolicy { }); this._policyName = this.physicalName!; - this.force = props.force !== undefined ? props.force : false; + this.force = props.force ?? false; if (props.users) { props.users.forEach(u => this.attachToUser(u)); diff --git a/packages/@aws-cdk/aws-lambda/lib/event-invoke-config.ts b/packages/@aws-cdk/aws-lambda/lib/event-invoke-config.ts index 6c2419c6f8fd5..52e1021cd6878 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-invoke-config.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-invoke-config.ts @@ -90,7 +90,7 @@ export class EventInvokeConfig extends Resource { : undefined, functionName: props.function.functionName, maximumEventAgeInSeconds: props.maxEventAge && props.maxEventAge.toSeconds(), - maximumRetryAttempts: props.retryAttempts !== undefined ? props.retryAttempts : undefined, + maximumRetryAttempts: props.retryAttempts ?? undefined, qualifier: props.qualifier || '$LATEST', }); } diff --git a/packages/@aws-cdk/aws-logs/lib/metric-filter.ts b/packages/@aws-cdk/aws-logs/lib/metric-filter.ts index 8dda5f1728fd3..e9b076b55c8a3 100644 --- a/packages/@aws-cdk/aws-logs/lib/metric-filter.ts +++ b/packages/@aws-cdk/aws-logs/lib/metric-filter.ts @@ -42,7 +42,7 @@ export class MetricFilter extends Resource { metricTransformations: [{ metricNamespace: props.metricNamespace, metricName: props.metricName, - metricValue: props.metricValue !== undefined ? props.metricValue : '1', + metricValue: props.metricValue ?? '1', defaultValue: props.defaultValue, }], }); diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index 74a28666496d2..cb4c3d3487a8c 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -690,8 +690,8 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData this.newCfnProps = { autoMinorVersionUpgrade: props.autoMinorVersionUpgrade, availabilityZone: props.multiAz ? undefined : props.availabilityZone, - backupRetentionPeriod: props.backupRetention ? props.backupRetention.toDays() : undefined, - copyTagsToSnapshot: props.copyTagsToSnapshot !== undefined ? props.copyTagsToSnapshot : true, + backupRetentionPeriod: props.backupRetention?.toDays(), + copyTagsToSnapshot: props.copyTagsToSnapshot ?? true, dbInstanceClass: Lazy.string({ produce: () => `db.${this.instanceType}` }), dbInstanceIdentifier: props.instanceIdentifier, dbSubnetGroupName: subnetGroup.subnetGroupName, @@ -701,15 +701,15 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData enableIamDatabaseAuthentication: Lazy.any({ produce: () => this.enableIamAuthentication }), enablePerformanceInsights: enablePerformanceInsights || props.enablePerformanceInsights, // fall back to undefined if not set, iops, - monitoringInterval: props.monitoringInterval && props.monitoringInterval.toSeconds(), - monitoringRoleArn: monitoringRole && monitoringRole.roleArn, + monitoringInterval: props.monitoringInterval?.toSeconds(), + monitoringRoleArn: monitoringRole?.roleArn, multiAz: props.multiAz, optionGroupName: props.optionGroup?.optionGroupName, performanceInsightsKmsKeyId: props.performanceInsightEncryptionKey?.keyArn, performanceInsightsRetentionPeriod: enablePerformanceInsights ? (props.performanceInsightRetention || PerformanceInsightRetention.DEFAULT) : undefined, - port: props.port ? props.port.toString() : undefined, + port: props.port?.toString(), preferredBackupWindow: props.preferredBackupWindow, preferredMaintenanceWindow: props.preferredMaintenanceWindow, processorFeatures: props.processorFeatures && renderProcessorFeatures(props.processorFeatures), @@ -849,7 +849,7 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa ...this.newCfnProps, associatedRoles: instanceAssociatedRoles.length > 0 ? instanceAssociatedRoles : undefined, optionGroupName: engineConfig.optionGroup?.optionGroupName, - allocatedStorage: props.allocatedStorage ? props.allocatedStorage.toString() : '100', + allocatedStorage: props.allocatedStorage?.toString() ?? '100', allowMajorVersionUpgrade: props.allowMajorVersionUpgrade, dbName: props.databaseName, dbParameterGroupName: instanceParameterGroupConfig?.parameterGroupName, @@ -1041,9 +1041,7 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme const instance = new CfnDBInstance(this, 'Resource', { ...this.sourceCfnProps, dbSnapshotIdentifier: props.snapshotIdentifier, - masterUserPassword: secret - ? secret.secretValueFromJson('password').toString() - : credentials?.password?.toString(), + masterUserPassword: secret?.secretValueFromJson('password')?.toString() ?? credentials?.password?.toString(), }); this.instanceIdentifier = instance.ref; @@ -1117,7 +1115,7 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements ...this.newCfnProps, // this must be ARN, not ID, because of https://github.com/terraform-providers/terraform-provider-aws/issues/528#issuecomment-391169012 sourceDbInstanceIdentifier: props.sourceDatabaseInstance.instanceArn, - kmsKeyId: props.storageEncryptionKey && props.storageEncryptionKey.keyArn, + kmsKeyId: props.storageEncryptionKey?.keyArn, storageEncrypted: props.storageEncryptionKey ? true : props.storageEncrypted, }); diff --git a/packages/@aws-cdk/aws-rds/lib/private/util.ts b/packages/@aws-cdk/aws-rds/lib/private/util.ts index ca5f16d21e038..f97486b5daddf 100644 --- a/packages/@aws-cdk/aws-rds/lib/private/util.ts +++ b/packages/@aws-cdk/aws-rds/lib/private/util.ts @@ -92,9 +92,7 @@ export function applyRemovalPolicy(cfnDatabase: CfnResource, removalPolicy?: Rem * Enable if explicitly provided or if the RemovalPolicy has been set to RETAIN */ export function defaultDeletionProtection(deletionProtection?: boolean, removalPolicy?: RemovalPolicy): boolean | undefined { - return deletionProtection !== undefined - ? deletionProtection - : (removalPolicy === RemovalPolicy.RETAIN ? true : undefined); + return deletionProtection ?? (removalPolicy === RemovalPolicy.RETAIN ? true : undefined); } /** diff --git a/packages/@aws-cdk/aws-redshift/lib/cluster.ts b/packages/@aws-cdk/aws-redshift/lib/cluster.ts index 9126ef4fda737..0add37fbbdff3 100644 --- a/packages/@aws-cdk/aws-redshift/lib/cluster.ts +++ b/packages/@aws-cdk/aws-redshift/lib/cluster.ts @@ -406,11 +406,11 @@ export class Cluster extends ClusterBase { super(scope, id); this.vpc = props.vpc; - this.vpcSubnets = props.vpcSubnets ? props.vpcSubnets : { + this.vpcSubnets = props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE, }; - const removalPolicy = props.removalPolicy ? props.removalPolicy : RemovalPolicy.RETAIN; + const removalPolicy = props.removalPolicy ?? RemovalPolicy.RETAIN; const subnetGroup = props.subnetGroup ?? new ClusterSubnetGroup(this, 'Subnets', { description: `Subnets for ${id} Redshift cluster`, @@ -419,11 +419,10 @@ export class Cluster extends ClusterBase { removalPolicy: removalPolicy, }); - const securityGroups = props.securityGroups !== undefined ? - props.securityGroups : [new ec2.SecurityGroup(this, 'SecurityGroup', { - description: 'Redshift security group', - vpc: this.vpc, - })]; + const securityGroups = props.securityGroups ?? [new ec2.SecurityGroup(this, 'SecurityGroup', { + description: 'Redshift security group', + vpc: this.vpc, + })]; const securityGroupIds = securityGroups.map(sg => sg.securityGroupId); @@ -464,22 +463,20 @@ export class Cluster extends ClusterBase { port: props.port, clusterParameterGroupName: props.parameterGroup && props.parameterGroup.clusterParameterGroupName, // Admin - masterUsername: secret ? secret.secretValueFromJson('username').toString() : props.masterUser.masterUsername, - masterUserPassword: secret - ? secret.secretValueFromJson('password').toString() - : (props.masterUser.masterPassword - ? props.masterUser.masterPassword.toString() - : 'default'), + masterUsername: secret?.secretValueFromJson('username').toString() ?? props.masterUser.masterUsername, + masterUserPassword: secret?.secretValueFromJson('password').toString() + ?? props.masterUser.masterPassword?.toString() + ?? 'default', preferredMaintenanceWindow: props.preferredMaintenanceWindow, nodeType: props.nodeType || NodeType.DC2_LARGE, numberOfNodes: nodeCount, loggingProperties, - iamRoles: props.roles ? props.roles.map(role => role.roleArn) : undefined, + iamRoles: props?.roles?.map(role => role.roleArn), dbName: props.defaultDatabaseName || 'default_db', publiclyAccessible: props.publiclyAccessible || false, // Encryption kmsKeyId: props.encryptionKey && props.encryptionKey.keyArn, - encrypted: props.encrypted !== undefined ? props.encrypted : true, + encrypted: props.encrypted ?? true, }); cluster.applyRemovalPolicy(removalPolicy, { diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts index bbf526b79df86..4ca33864952f3 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts @@ -227,7 +227,7 @@ export class BucketDeployment extends CoreConstruct { Prune: props.prune ?? true, UserMetadata: props.metadata ? mapUserMetadata(props.metadata) : undefined, SystemMetadata: mapSystemMetadata(props), - DistributionId: props.distribution ? props.distribution.distributionId : undefined, + DistributionId: props.distribution?.distributionId, DistributionPaths: props.distributionPaths, }, }); diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 5c5abfbfec559..525b22f6afd1d 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -413,7 +413,7 @@ abstract class BucketBase extends Resource implements IBucket { detailType: ['AWS API Call via CloudTrail'], detail: { resources: { - ARN: options.paths ? options.paths.map(p => this.arnForObjects(p)) : [this.bucketArn], + ARN: options.paths?.map(p => this.arnForObjects(p)) ?? [this.bucketArn], }, }, }); @@ -1607,13 +1607,13 @@ export class Bucket extends BucketBase { return { rules: this.lifecycleRules.map(parseLifecycleRule) }; function parseLifecycleRule(rule: LifecycleRule): CfnBucket.RuleProperty { - const enabled = rule.enabled !== undefined ? rule.enabled : true; + const enabled = rule.enabled ?? true; const x: CfnBucket.RuleProperty = { // eslint-disable-next-line max-len abortIncompleteMultipartUpload: rule.abortIncompleteMultipartUploadAfter !== undefined ? { daysAfterInitiation: rule.abortIncompleteMultipartUploadAfter.toDays() } : undefined, expirationDate: rule.expirationDate, - expirationInDays: rule.expiration && rule.expiration.toDays(), + expirationInDays: rule.expiration?.toDays(), id: rule.id, noncurrentVersionExpirationInDays: rule.noncurrentVersionExpiration && rule.noncurrentVersionExpiration.toDays(), noncurrentVersionTransitions: mapOrUndefined(rule.noncurrentVersionTransitions, t => ({ diff --git a/packages/@aws-cdk/aws-ses-actions/lib/bounce.ts b/packages/@aws-cdk/aws-ses-actions/lib/bounce.ts index b84c414c5c43e..a1a385651614a 100644 --- a/packages/@aws-cdk/aws-ses-actions/lib/bounce.ts +++ b/packages/@aws-cdk/aws-ses-actions/lib/bounce.ts @@ -100,7 +100,7 @@ export class Bounce implements ses.IReceiptRuleAction { sender: this.props.sender, smtpReplyCode: this.props.template.props.smtpReplyCode, message: this.props.template.props.message, - topicArn: this.props.topic ? this.props.topic.topicArn : undefined, + topicArn: this.props.topic?.topicArn, statusCode: this.props.template.props.statusCode, }, }; diff --git a/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts b/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts index 290211bf1806f..d6be68f92ce24 100644 --- a/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-ses-actions/lib/lambda.ts @@ -78,7 +78,7 @@ export class Lambda implements ses.IReceiptRuleAction { lambdaAction: { functionArn: this.props.function.functionArn, invocationType: this.props.invocationType, - topicArn: this.props.topic ? this.props.topic.topicArn : undefined, + topicArn: this.props.topic?.topicArn, }, }; } diff --git a/packages/@aws-cdk/aws-ses-actions/lib/s3.ts b/packages/@aws-cdk/aws-ses-actions/lib/s3.ts index 9be2fd8750378..1f288afe2a887 100644 --- a/packages/@aws-cdk/aws-ses-actions/lib/s3.ts +++ b/packages/@aws-cdk/aws-ses-actions/lib/s3.ts @@ -92,9 +92,9 @@ export class S3 implements ses.IReceiptRuleAction { return { s3Action: { bucketName: this.props.bucket.bucketName, - kmsKeyArn: this.props.kmsKey ? this.props.kmsKey.keyArn : undefined, + kmsKeyArn: this.props.kmsKey?.keyArn, objectKeyPrefix: this.props.objectKeyPrefix, - topicArn: this.props.topic ? this.props.topic.topicArn : undefined, + topicArn: this.props.topic?.topicArn, }, }; } diff --git a/packages/@aws-cdk/aws-ses-actions/lib/stop.ts b/packages/@aws-cdk/aws-ses-actions/lib/stop.ts index ab2e9cbb91d1e..f42d206d3d350 100644 --- a/packages/@aws-cdk/aws-ses-actions/lib/stop.ts +++ b/packages/@aws-cdk/aws-ses-actions/lib/stop.ts @@ -23,7 +23,7 @@ export class Stop implements ses.IReceiptRuleAction { return { stopAction: { scope: 'RuleSet', - topicArn: this.props.topic ? this.props.topic.topicArn : undefined, + topicArn: this.props.topic?.topicArn, }, }; } diff --git a/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts b/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts index c94d4d4edd138..dcb3d011d4251 100644 --- a/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts +++ b/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts @@ -62,7 +62,7 @@ abstract class ReceiptRuleSetBase extends Resource implements IReceiptRuleSet { */ public addRule(id: string, options?: ReceiptRuleOptions): ReceiptRule { this.lastAddedRule = new ReceiptRule(this, id, { - after: this.lastAddedRule ? this.lastAddedRule : undefined, + after: this.lastAddedRule ?? undefined, ruleSet: this, ...options, }); diff --git a/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts b/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts index 72c056d913693..5b6a276929c8e 100644 --- a/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts +++ b/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts @@ -124,10 +124,10 @@ export class ReceiptRule extends Resource implements IReceiptRule { }); const resource = new CfnReceiptRule(this, 'Resource', { - after: props.after ? props.after.receiptRuleName : undefined, + after: props.after?.receiptRuleName, rule: { actions: Lazy.any({ produce: () => this.renderActions() }), - enabled: props.enabled === undefined ? true : props.enabled, + enabled: props.enabled ?? true, name: this.physicalName, recipients: props.recipients, scanEnabled: props.scanEnabled, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts index 4b3ecd5ca0013..2e38d60aac150 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/emr/emr-create-cluster.ts @@ -173,7 +173,7 @@ export class EmrCreateCluster extends sfn.TaskStateBase { constructor(scope: Construct, id: string, private readonly props: EmrCreateClusterProps) { super(scope, id, props); - this.visibleToAllUsers = this.props.visibleToAllUsers !== undefined ? this.props.visibleToAllUsers : true; + this.visibleToAllUsers = this.props.visibleToAllUsers ?? true; this.integrationPattern = props.integrationPattern || sfn.IntegrationPattern.RUN_JOB; validatePatternSupported(this.integrationPattern, EmrCreateCluster.SUPPORTED_INTEGRATION_PATTERNS); diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index 56b14e69fa5d8..8c7ee585e4b09 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -380,11 +380,11 @@ export class StateMachine extends StateMachineBase { const graph = new StateGraph(props.definition.startState, `State Machine ${id} definition`); graph.timeout = props.timeout; - this.stateMachineType = props.stateMachineType ? props.stateMachineType : StateMachineType.STANDARD; + this.stateMachineType = props.stateMachineType ?? StateMachineType.STANDARD; const resource = new CfnStateMachine(this, 'Resource', { stateMachineName: this.physicalName, - stateMachineType: props.stateMachineType ? props.stateMachineType : undefined, + stateMachineType: props.stateMachineType ?? undefined, roleArn: this.role.roleArn, definitionString: Stack.of(this).toJsonString(graph.toGraphJson()), loggingConfiguration: props.logs ? this.buildLoggingConfiguration(props.logs) : undefined, diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts index de9b4d16aab01..fb5e52d2831b2 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts @@ -145,7 +145,7 @@ export class Pass extends State implements INextable { return { Type: StateType.PASS, Comment: this.comment, - Result: this.result ? this.result.value : undefined, + Result: this.result?.value, ResultPath: renderJsonPath(this.resultPath), ...this.renderInputOutput(), ...this.renderParameters(), diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts index 02cceb6e64ed2..42a4c8e9bf1b5 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts @@ -254,7 +254,7 @@ export abstract class State extends CoreConstruct implements IChainable { this.retries.push({ ...props, - errors: props.errors ? props.errors : [Errors.ALL], + errors: props.errors ?? [Errors.ALL], }); } @@ -268,7 +268,7 @@ export abstract class State extends CoreConstruct implements IChainable { this.catches.push({ next: handler, props: { - errors: props.errors ? props.errors : [Errors.ALL], + errors: props.errors ?? [Errors.ALL], resultPath: props.resultPath, }, }); @@ -352,7 +352,7 @@ export abstract class State extends CoreConstruct implements IChainable { protected renderChoices(): any { return { Choices: renderList(this.choices, renderChoice), - Default: this.defaultChoice ? this.defaultChoice.stateId : undefined, + Default: this.defaultChoice?.stateId, }; } diff --git a/packages/@aws-cdk/core/lib/app.ts b/packages/@aws-cdk/core/lib/app.ts index eb095f801ee4f..28e04607a0228 100644 --- a/packages/@aws-cdk/core/lib/app.ts +++ b/packages/@aws-cdk/core/lib/app.ts @@ -116,7 +116,7 @@ export class App extends Stage { this.node.setContext(cxapi.ANALYTICS_REPORTING_ENABLED_CONTEXT, analyticsReporting); } - const autoSynth = props.autoSynth !== undefined ? props.autoSynth : cxapi.OUTDIR_ENV in process.env; + const autoSynth = props.autoSynth ?? cxapi.OUTDIR_ENV in process.env; if (autoSynth) { // synth() guarantuees it will only execute once, so a default of 'true' // doesn't bite manual calling of the function. diff --git a/packages/@aws-cdk/core/lib/arn.ts b/packages/@aws-cdk/core/lib/arn.ts index 2ed3485a6c9b6..1ffcc2da2b33c 100644 --- a/packages/@aws-cdk/core/lib/arn.ts +++ b/packages/@aws-cdk/core/lib/arn.ts @@ -77,10 +77,10 @@ export class Arn { * can be 'undefined'. */ public static format(components: ArnComponents, stack: Stack): string { - const partition = components.partition !== undefined ? components.partition : stack.partition; - const region = components.region !== undefined ? components.region : stack.region; - const account = components.account !== undefined ? components.account : stack.account; - const sep = components.sep !== undefined ? components.sep : '/'; + const partition = components.partition ?? stack.partition; + const region = components.region ?? stack.region; + const account = components.account ?? stack.account; + const sep = components.sep ?? '/'; const values = ['arn', ':', partition, ':', components.service, ':', region, ':', account, ':', components.resource]; diff --git a/packages/@aws-cdk/core/lib/fs/copy.ts b/packages/@aws-cdk/core/lib/fs/copy.ts index b9feb555c8f65..627dc2aa988dc 100644 --- a/packages/@aws-cdk/core/lib/fs/copy.ts +++ b/packages/@aws-cdk/core/lib/fs/copy.ts @@ -5,7 +5,7 @@ import { CopyOptions, SymlinkFollowMode } from './options'; import { shouldFollow } from './utils'; export function copyDirectory(srcDir: string, destDir: string, options: CopyOptions = { }, rootDir?: string) { - const follow = options.follow !== undefined ? options.follow : SymlinkFollowMode.EXTERNAL; + const follow = options.follow ?? SymlinkFollowMode.EXTERNAL; rootDir = rootDir || srcDir; diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index 53e7adfdc206e..2284ddedc203f 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -371,7 +371,7 @@ export class Stack extends CoreConstruct implements ITaggable { this.templateOptions.description = props.description; } - this._stackName = props.stackName !== undefined ? props.stackName : this.generateStackName(); + this._stackName = props.stackName ?? this.generateStackName(); this.tags = new TagManager(TagType.KEY_VALUE, 'aws:cdk:stack', props.tags); if (!VALID_STACK_NAME_REGEX.test(this.stackName)) { diff --git a/packages/@aws-cdk/core/lib/tag-aspect.ts b/packages/@aws-cdk/core/lib/tag-aspect.ts index d56c28b551d87..09d79c7ea9799 100644 --- a/packages/@aws-cdk/core/lib/tag-aspect.ts +++ b/packages/@aws-cdk/core/lib/tag-aspect.ts @@ -126,7 +126,7 @@ export class Tag extends TagBase { resource.tags.setTag( this.key, this.value, - this.props.priority !== undefined ? this.props.priority : this.defaultPriority, + this.props.priority ?? this.defaultPriority, this.props.applyToLaunchedInstances !== false, ); } @@ -175,7 +175,7 @@ export class RemoveTag extends TagBase { protected applyTag(resource: ITaggable): void { if (resource.tags.applyTagAspectHere(this.props.includeResourceTypes, this.props.excludeResourceTypes)) { - resource.tags.removeTag(this.key, this.props.priority !== undefined ? this.props.priority : this.defaultPriority); + resource.tags.removeTag(this.key, this.props.priority ?? this.defaultPriority); } } } diff --git a/packages/@aws-cdk/core/lib/token.ts b/packages/@aws-cdk/core/lib/token.ts index 4bbcdb454f9bd..f92a2560cac7c 100644 --- a/packages/@aws-cdk/core/lib/token.ts +++ b/packages/@aws-cdk/core/lib/token.ts @@ -183,7 +183,7 @@ export class Tokenization { return resolve(obj, { scope: options.scope, resolver: options.resolver, - preparing: (options.preparing !== undefined ? options.preparing : false), + preparing: (options.preparing ?? false), }); } diff --git a/packages/@aws-cdk/core/test/tag-aspect.test.ts b/packages/@aws-cdk/core/test/tag-aspect.test.ts index cb2c5363e2153..b0871e2d13c02 100644 --- a/packages/@aws-cdk/core/test/tag-aspect.test.ts +++ b/packages/@aws-cdk/core/test/tag-aspect.test.ts @@ -6,7 +6,7 @@ class TaggableResource extends CfnResource { public readonly tags: TagManager; constructor(scope: Construct, id: string, props: CfnResourceProps) { super(scope, id, props); - const tags = props.properties === undefined ? undefined : props.properties.tags; + const tags = props.properties?.tags; this.tags = new TagManager(TagType.STANDARD, 'AWS::Fake::Resource', tags); } public testProperties() { @@ -18,7 +18,7 @@ class AsgTaggableResource extends CfnResource { public readonly tags: TagManager; constructor(scope: Construct, id: string, props: CfnResourceProps) { super(scope, id, props); - const tags = props.properties === undefined ? undefined : props.properties.tags; + const tags = props.properties?.tags; this.tags = new TagManager(TagType.AUTOSCALING_GROUP, 'AWS::Fake::Resource', tags); } public testProperties() { @@ -30,7 +30,7 @@ class MapTaggableResource extends CfnResource { public readonly tags: TagManager; constructor(scope: Construct, id: string, props: CfnResourceProps) { super(scope, id, props); - const tags = props.properties === undefined ? undefined : props.properties.tags; + const tags = props.properties?.tags; this.tags = new TagManager(TagType.MAP, 'AWS::Fake::Resource', tags); } public testProperties() { diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index c3e8c649eaa7b..84542eb8f4c7f 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -110,7 +110,7 @@ export class CdkToolkit { public async deploy(options: DeployOptions) { const stacks = await this.selectStacksForDeploy(options.stackNames, options.exclusively); - const requireApproval = options.requireApproval !== undefined ? options.requireApproval : RequireApproval.Broadening; + const requireApproval = options.requireApproval ?? RequireApproval.Broadening; const parameterMap: { [name: string]: { [name: string]: string | undefined } } = { '*': {} }; for (const key in options.parameters) { From c08c814b3072c65cfc1fb12a910b70ac5aae7b5f Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Sun, 7 Feb 2021 17:02:01 +0200 Subject: [PATCH 66/70] chore(deps): Yarn upgrade workflow is failing (#12910) --- .github/workflows/yarn-upgrade.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/yarn-upgrade.yml b/.github/workflows/yarn-upgrade.yml index 586e7f2c9357a..6734719a67184 100644 --- a/.github/workflows/yarn-upgrade.yml +++ b/.github/workflows/yarn-upgrade.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v2 - name: Set up Node - uses: actions/setup-node@v2.1.0 + uses: actions/setup-node@v2.1.4 with: node-version: 10 @@ -55,12 +55,15 @@ jobs: lerna exec --parallel ncu -- --upgrade --filter=@types/node,@types/fs-extra --target=minor lerna exec --parallel ncu -- --upgrade --filter=typescript --target=patch lerna exec --parallel ncu -- --upgrade --reject='@types/node,@types/fs-extra,constructs,typescript,aws-sdk,${{ steps.list-packages.outputs.list }}' --target=minor - # This will create a brand new `yarn.lock` file (this is more efficient than `yarn install && yarn upgrade`) - - name: Run "yarn install --force" - run: yarn install --force + # This will ensure the current lockfile is up-to-date with the dependency specifications (necessary for "yarn update" to run) + - name: Run "yarn install" + run: yarn install + + - name: Run "yarn upgrade" + run: yarn upgrade - name: Make Pull Request - uses: peter-evans/create-pull-request@v2 + uses: peter-evans/create-pull-request@v3 with: # Git commit details branch: automation/yarn-upgrade From 0f718b291758e64f3add55eff102c808ed5c44d0 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Mon, 8 Feb 2021 09:56:10 +0000 Subject: [PATCH 67/70] chore: run pkglint via Github actions for v2 pull requests (#12889) The [merge-forward.sh] script runs a 'git merge' followed by pkglint in automated scheduled job, which finally posts a pull request with the changes. If any merge conflicts arise during the run which need manual intervention, the job simply fails and does not post the pull request. This is sub-optimal. It would be a much nicer experience if the pull request was posted along with the merge conflicts, allowing users to resolve them on the pull request using the Github web experience. This commit changes the flow so that pkglint is run as a Github action after the pull request is posted, thereby enabling this experience. [merge-forward.sh]: https://github.com/aws/aws-cdk/blob/b4e1b682604946c30ab63164017c73bd8d828728/scripts/merge-forward.sh --- .../auto-approve-v2-merge-forward.yml | 26 ---------- .github/workflows/v2-pull-request.yml | 49 +++++++++++++++++++ 2 files changed, 49 insertions(+), 26 deletions(-) delete mode 100644 .github/workflows/auto-approve-v2-merge-forward.yml create mode 100644 .github/workflows/v2-pull-request.yml diff --git a/.github/workflows/auto-approve-v2-merge-forward.yml b/.github/workflows/auto-approve-v2-merge-forward.yml deleted file mode 100644 index f05cd6753316c..0000000000000 --- a/.github/workflows/auto-approve-v2-merge-forward.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Automatically approve PRs that merge master forward to v2-main -# -# Only does approvals! mergify takes care of the actual merge. -name: Auto-approve automated PRs around CDK v2 -on: - pull_request: - types: - - labeled - - opened - - ready_for_review - - reopened - - synchronize - - unlabeled - - unlocked - -jobs: - approve: - runs-on: ubuntu-latest - steps: - - uses: hmarr/auto-approve-action@v2.0.0 - if: > - github.event.pull_request.user.login == 'aws-cdk-automation' - && github.event.pull_request.base.ref == 'v2-main' - && contains(github.event.pull_request.labels.*.name, 'pr/auto-approve') - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/v2-pull-request.yml b/.github/workflows/v2-pull-request.yml new file mode 100644 index 0000000000000..d82955767b01a --- /dev/null +++ b/.github/workflows/v2-pull-request.yml @@ -0,0 +1,49 @@ +# Automated actions for PRs against the v2-main branch +name: v2 +on: + pull_request: + branches: + - v2-main + types: + - labeled + - opened + - ready_for_review + - reopened + - synchronize + - unlabeled + - unlocked + +jobs: + # Run yarn pkglint on merge forward PRs and commit the results + checkout: + name: pkglint + if: contains(github.event.pull_request.labels.*.name, 'pr/forward-merge') + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v2 + with: + branch: ${{ github.event.pull_request.head.ref }} + token: ${{ secrets.GITHUB_TOKEN }} + - name: lint + run: |- + yarn install --frozen-lockfile + yarn pkglint + - name: push + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: 'automatic pkglint fixes' + + # Approve automated PRs + # Only approve! mergify takes care of the actual merge. + approve: + name: auto-approve + needs: pkglint + if: > + github.event.pull_request.user.login == 'aws-cdk-automation' + && contains(github.event.pull_request.labels.*.name, 'pr/auto-approve') + runs-on: ubuntu-latest + steps: + - uses: hmarr/auto-approve-action@v2.0.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" From 6de792c3453cec17a9c4b0fca47477d69e5ed36e Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Mon, 8 Feb 2021 12:08:06 +0000 Subject: [PATCH 68/70] chore: fix github actions name and dependencies (#12919) Github actions for v2 are failing because of a dependency definition on an incorrect name. See [failures]. Further, the dependency is incorrect. The 'auto-approve' job needs to run on PRs besides forward merges. Fix Github action names and remove the incorrect dependency. [failures]: https://github.com/aws/aws-cdk/actions/runs/547681911 --- .github/workflows/v2-pull-request.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/v2-pull-request.yml b/.github/workflows/v2-pull-request.yml index d82955767b01a..c4118d3298a00 100644 --- a/.github/workflows/v2-pull-request.yml +++ b/.github/workflows/v2-pull-request.yml @@ -15,8 +15,7 @@ on: jobs: # Run yarn pkglint on merge forward PRs and commit the results - checkout: - name: pkglint + pkglint: if: contains(github.event.pull_request.labels.*.name, 'pr/forward-merge') runs-on: ubuntu-latest steps: @@ -36,9 +35,7 @@ jobs: # Approve automated PRs # Only approve! mergify takes care of the actual merge. - approve: - name: auto-approve - needs: pkglint + auto-approve: if: > github.event.pull_request.user.login == 'aws-cdk-automation' && contains(github.event.pull_request.labels.*.name, 'pr/auto-approve') From b68acf828e04841dd7e62b30fe80db8c25e5d96e Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 9 Feb 2021 11:28:50 +0200 Subject: [PATCH 69/70] fix(lambda-python): cryptography >= 3.4 is not supported by older pip version (#12934) `poetry`, which is needed when creating python bundles, has a dependency on the `cryptography` module. As described in their [changelog], starting version 3.4, they require the latest version of `pip`. Otherwise, the installer will attempt to compile the module, and the Rust compiler will be required. This fails in the amazon/aws-sam-cli-build-image-python3.6 image since it has an older version of pip installed. To fix, simply add an instruction to the dockerfile to upgrade to the latest pip version before installing. [changelog]: https://cryptography.io/en/3.4/changelog.html#v3-4 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/Dockerfile.dependencies | 3 + .../test/integ.function.expected.json | 20 +++---- .../test/integ.function.pipenv.expected.json | 60 +++++++++---------- .../test/integ.function.poetry.expected.json | 60 +++++++++---------- .../test/integ.function.project.expected.json | 24 ++++---- .../test/integ.function.py38.expected.json | 20 +++---- ...unction.requirements.removed.expected.json | 2 +- .../test/integ.function.vpc.expected.json | 20 +++---- 8 files changed, 106 insertions(+), 103 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies b/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies index bfdb9e093ed4a..536887fd69a9a 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies +++ b/packages/@aws-cdk/aws-lambda-python/lib/Dockerfile.dependencies @@ -6,6 +6,9 @@ FROM $IMAGE # Ensure rsync is installed RUN yum -q list installed rsync &>/dev/null || yum install -y rsync +# Upgrade pip (required by cryptography v3.4 and above, which is a dependency of poetry) +RUN pip install --upgrade pip + # Install pipenv and poetry so we can create a requirements.txt if we detect pipfile or poetry.lock respectively RUN pip install pipenv poetry diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json index c3adcd34e3e95..3690005685439 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3Bucket035B0B74" + "Ref": "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353S3Bucket414E0E30" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F" + "Ref": "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353S3VersionKey5ABC9098" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F" + "Ref": "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353S3VersionKey5ABC9098" } ] } @@ -72,13 +72,13 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myhandlerServiceRole77891068", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python3.6" }, "DependsOn": [ @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3Bucket035B0B74": { + "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353S3Bucket414E0E30": { "Type": "String", - "Description": "S3 bucket for asset \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" + "Description": "S3 bucket for asset \"4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353\"" }, - "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F": { + "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353S3VersionKey5ABC9098": { "Type": "String", - "Description": "S3 key for asset version \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" + "Description": "S3 key for asset version \"4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353\"" }, - "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57ArtifactHash70AD5A1E": { + "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353ArtifactHashECA6C88C": { "Type": "String", - "Description": "Artifact hash for asset \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" + "Description": "Artifact hash for asset \"4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json index 0c310b5f52e7e..ef1f355e528c3 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3BucketDF70124D" + "Ref": "AssetParameters94972df8a01484c56b50bec3793ac6c4302bc044db29d3502007bdc0f83db45cS3BucketA501FC08" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3VersionKey530C68B0" + "Ref": "AssetParameters94972df8a01484c56b50bec3793ac6c4302bc044db29d3502007bdc0f83db45cS3VersionKey1C3AFB39" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3VersionKey530C68B0" + "Ref": "AssetParameters94972df8a01484c56b50bec3793ac6c4302bc044db29d3502007bdc0f83db45cS3VersionKey1C3AFB39" } ] } @@ -72,13 +72,13 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myhandlerinlineServiceRole10C681F6", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python3.6" }, "DependsOn": [ @@ -121,7 +121,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3BucketB5A59BD8" + "Ref": "AssetParameters3b0b0f3cd46ea1490006d6cefca359385ec059bb00a0fbee4de2eecf48038e28S3Bucket7DE4D4D5" }, "S3Key": { "Fn::Join": [ @@ -134,7 +134,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3VersionKey7657015C" + "Ref": "AssetParameters3b0b0f3cd46ea1490006d6cefca359385ec059bb00a0fbee4de2eecf48038e28S3VersionKeyAEB67E87" } ] } @@ -147,7 +147,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3VersionKey7657015C" + "Ref": "AssetParameters3b0b0f3cd46ea1490006d6cefca359385ec059bb00a0fbee4de2eecf48038e28S3VersionKeyAEB67E87" } ] } @@ -157,13 +157,13 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myhandlerpython27ServiceRole2ED49C06", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python2.7" }, "DependsOn": [ @@ -206,7 +206,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3Bucket31144813" + "Ref": "AssetParameters876959f777c5a23bf4408991959c55c91810329d159608feb7ede69418b35009S3BucketA66E9035" }, "S3Key": { "Fn::Join": [ @@ -219,7 +219,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3VersionKeyB48E8383" + "Ref": "AssetParameters876959f777c5a23bf4408991959c55c91810329d159608feb7ede69418b35009S3VersionKeyAFEB5FDA" } ] } @@ -232,7 +232,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3VersionKeyB48E8383" + "Ref": "AssetParameters876959f777c5a23bf4408991959c55c91810329d159608feb7ede69418b35009S3VersionKeyAFEB5FDA" } ] } @@ -242,13 +242,13 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myhandlerpython38ServiceRole2049AFF7", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python3.8" }, "DependsOn": [ @@ -257,41 +257,41 @@ } }, "Parameters": { - "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3BucketDF70124D": { + "AssetParameters94972df8a01484c56b50bec3793ac6c4302bc044db29d3502007bdc0f83db45cS3BucketA501FC08": { "Type": "String", - "Description": "S3 bucket for asset \"eef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255b\"" + "Description": "S3 bucket for asset \"94972df8a01484c56b50bec3793ac6c4302bc044db29d3502007bdc0f83db45c\"" }, - "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3VersionKey530C68B0": { + "AssetParameters94972df8a01484c56b50bec3793ac6c4302bc044db29d3502007bdc0f83db45cS3VersionKey1C3AFB39": { "Type": "String", - "Description": "S3 key for asset version \"eef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255b\"" + "Description": "S3 key for asset version \"94972df8a01484c56b50bec3793ac6c4302bc044db29d3502007bdc0f83db45c\"" }, - "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bArtifactHashEE8E0CE9": { + "AssetParameters94972df8a01484c56b50bec3793ac6c4302bc044db29d3502007bdc0f83db45cArtifactHash99DC751A": { "Type": "String", - "Description": "Artifact hash for asset \"eef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255b\"" + "Description": "Artifact hash for asset \"94972df8a01484c56b50bec3793ac6c4302bc044db29d3502007bdc0f83db45c\"" }, - "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3BucketB5A59BD8": { + "AssetParameters3b0b0f3cd46ea1490006d6cefca359385ec059bb00a0fbee4de2eecf48038e28S3Bucket7DE4D4D5": { "Type": "String", - "Description": "S3 bucket for asset \"f37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014\"" + "Description": "S3 bucket for asset \"3b0b0f3cd46ea1490006d6cefca359385ec059bb00a0fbee4de2eecf48038e28\"" }, - "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3VersionKey7657015C": { + "AssetParameters3b0b0f3cd46ea1490006d6cefca359385ec059bb00a0fbee4de2eecf48038e28S3VersionKeyAEB67E87": { "Type": "String", - "Description": "S3 key for asset version \"f37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014\"" + "Description": "S3 key for asset version \"3b0b0f3cd46ea1490006d6cefca359385ec059bb00a0fbee4de2eecf48038e28\"" }, - "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014ArtifactHash7768674B": { + "AssetParameters3b0b0f3cd46ea1490006d6cefca359385ec059bb00a0fbee4de2eecf48038e28ArtifactHashE51CE860": { "Type": "String", - "Description": "Artifact hash for asset \"f37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014\"" + "Description": "Artifact hash for asset \"3b0b0f3cd46ea1490006d6cefca359385ec059bb00a0fbee4de2eecf48038e28\"" }, - "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3Bucket31144813": { + "AssetParameters876959f777c5a23bf4408991959c55c91810329d159608feb7ede69418b35009S3BucketA66E9035": { "Type": "String", - "Description": "S3 bucket for asset \"3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846a\"" + "Description": "S3 bucket for asset \"876959f777c5a23bf4408991959c55c91810329d159608feb7ede69418b35009\"" }, - "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3VersionKeyB48E8383": { + "AssetParameters876959f777c5a23bf4408991959c55c91810329d159608feb7ede69418b35009S3VersionKeyAFEB5FDA": { "Type": "String", - "Description": "S3 key for asset version \"3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846a\"" + "Description": "S3 key for asset version \"876959f777c5a23bf4408991959c55c91810329d159608feb7ede69418b35009\"" }, - "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aArtifactHash652F614E": { + "AssetParameters876959f777c5a23bf4408991959c55c91810329d159608feb7ede69418b35009ArtifactHashB9A1080D": { "Type": "String", - "Description": "Artifact hash for asset \"3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846a\"" + "Description": "Artifact hash for asset \"876959f777c5a23bf4408991959c55c91810329d159608feb7ede69418b35009\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json index 0c310b5f52e7e..5ea17bca31920 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.poetry.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3BucketDF70124D" + "Ref": "AssetParameters61d8d26f10d1d73dee2732bec7ed381d2c987fc2912a339f2f119f3b0ea175efS3BucketD53ED9C5" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3VersionKey530C68B0" + "Ref": "AssetParameters61d8d26f10d1d73dee2732bec7ed381d2c987fc2912a339f2f119f3b0ea175efS3VersionKey3C218A3E" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3VersionKey530C68B0" + "Ref": "AssetParameters61d8d26f10d1d73dee2732bec7ed381d2c987fc2912a339f2f119f3b0ea175efS3VersionKey3C218A3E" } ] } @@ -72,13 +72,13 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myhandlerinlineServiceRole10C681F6", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python3.6" }, "DependsOn": [ @@ -121,7 +121,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3BucketB5A59BD8" + "Ref": "AssetParameters1d66b06c3b3ee86b3126fb58d7a06ff055d366d8aeeb4dfbaf28d40f0930956dS3BucketFDE171D0" }, "S3Key": { "Fn::Join": [ @@ -134,7 +134,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3VersionKey7657015C" + "Ref": "AssetParameters1d66b06c3b3ee86b3126fb58d7a06ff055d366d8aeeb4dfbaf28d40f0930956dS3VersionKey6209E240" } ] } @@ -147,7 +147,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3VersionKey7657015C" + "Ref": "AssetParameters1d66b06c3b3ee86b3126fb58d7a06ff055d366d8aeeb4dfbaf28d40f0930956dS3VersionKey6209E240" } ] } @@ -157,13 +157,13 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myhandlerpython27ServiceRole2ED49C06", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python2.7" }, "DependsOn": [ @@ -206,7 +206,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3Bucket31144813" + "Ref": "AssetParameters96a447e468bf9d3b52d13213757160cd43f28737a29b8682c281fde388762e68S3BucketA23E6312" }, "S3Key": { "Fn::Join": [ @@ -219,7 +219,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3VersionKeyB48E8383" + "Ref": "AssetParameters96a447e468bf9d3b52d13213757160cd43f28737a29b8682c281fde388762e68S3VersionKey1E21AF83" } ] } @@ -232,7 +232,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3VersionKeyB48E8383" + "Ref": "AssetParameters96a447e468bf9d3b52d13213757160cd43f28737a29b8682c281fde388762e68S3VersionKey1E21AF83" } ] } @@ -242,13 +242,13 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myhandlerpython38ServiceRole2049AFF7", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python3.8" }, "DependsOn": [ @@ -257,41 +257,41 @@ } }, "Parameters": { - "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3BucketDF70124D": { + "AssetParameters61d8d26f10d1d73dee2732bec7ed381d2c987fc2912a339f2f119f3b0ea175efS3BucketD53ED9C5": { "Type": "String", - "Description": "S3 bucket for asset \"eef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255b\"" + "Description": "S3 bucket for asset \"61d8d26f10d1d73dee2732bec7ed381d2c987fc2912a339f2f119f3b0ea175ef\"" }, - "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bS3VersionKey530C68B0": { + "AssetParameters61d8d26f10d1d73dee2732bec7ed381d2c987fc2912a339f2f119f3b0ea175efS3VersionKey3C218A3E": { "Type": "String", - "Description": "S3 key for asset version \"eef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255b\"" + "Description": "S3 key for asset version \"61d8d26f10d1d73dee2732bec7ed381d2c987fc2912a339f2f119f3b0ea175ef\"" }, - "AssetParameterseef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255bArtifactHashEE8E0CE9": { + "AssetParameters61d8d26f10d1d73dee2732bec7ed381d2c987fc2912a339f2f119f3b0ea175efArtifactHash6A1881A8": { "Type": "String", - "Description": "Artifact hash for asset \"eef17c074659b655f9b413019323db3976d06067e78d53c4e609ebe177ce255b\"" + "Description": "Artifact hash for asset \"61d8d26f10d1d73dee2732bec7ed381d2c987fc2912a339f2f119f3b0ea175ef\"" }, - "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3BucketB5A59BD8": { + "AssetParameters1d66b06c3b3ee86b3126fb58d7a06ff055d366d8aeeb4dfbaf28d40f0930956dS3BucketFDE171D0": { "Type": "String", - "Description": "S3 bucket for asset \"f37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014\"" + "Description": "S3 bucket for asset \"1d66b06c3b3ee86b3126fb58d7a06ff055d366d8aeeb4dfbaf28d40f0930956d\"" }, - "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014S3VersionKey7657015C": { + "AssetParameters1d66b06c3b3ee86b3126fb58d7a06ff055d366d8aeeb4dfbaf28d40f0930956dS3VersionKey6209E240": { "Type": "String", - "Description": "S3 key for asset version \"f37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014\"" + "Description": "S3 key for asset version \"1d66b06c3b3ee86b3126fb58d7a06ff055d366d8aeeb4dfbaf28d40f0930956d\"" }, - "AssetParametersf37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014ArtifactHash7768674B": { + "AssetParameters1d66b06c3b3ee86b3126fb58d7a06ff055d366d8aeeb4dfbaf28d40f0930956dArtifactHash02B929EC": { "Type": "String", - "Description": "Artifact hash for asset \"f37a4de97ca8831930cd2d0dc3f0962e653d756a118ce33271752a745489c014\"" + "Description": "Artifact hash for asset \"1d66b06c3b3ee86b3126fb58d7a06ff055d366d8aeeb4dfbaf28d40f0930956d\"" }, - "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3Bucket31144813": { + "AssetParameters96a447e468bf9d3b52d13213757160cd43f28737a29b8682c281fde388762e68S3BucketA23E6312": { "Type": "String", - "Description": "S3 bucket for asset \"3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846a\"" + "Description": "S3 bucket for asset \"96a447e468bf9d3b52d13213757160cd43f28737a29b8682c281fde388762e68\"" }, - "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aS3VersionKeyB48E8383": { + "AssetParameters96a447e468bf9d3b52d13213757160cd43f28737a29b8682c281fde388762e68S3VersionKey1E21AF83": { "Type": "String", - "Description": "S3 key for asset version \"3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846a\"" + "Description": "S3 key for asset version \"96a447e468bf9d3b52d13213757160cd43f28737a29b8682c281fde388762e68\"" }, - "AssetParameters3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846aArtifactHash652F614E": { + "AssetParameters96a447e468bf9d3b52d13213757160cd43f28737a29b8682c281fde388762e68ArtifactHash0043D2A0": { "Type": "String", - "Description": "Artifact hash for asset \"3eb927f8df31281e22c710f842018fa10b0dde86f74f89313c9a27db6e75846a\"" + "Description": "Artifact hash for asset \"96a447e468bf9d3b52d13213757160cd43f28737a29b8682c281fde388762e68\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json index 5bc285e5c5769..9a81c901d7451 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.expected.json @@ -5,7 +5,7 @@ "Properties": { "Content": { "S3Bucket": { - "Ref": "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cS3Bucket6D2DF2A1" + "Ref": "AssetParameters6a4b9ce26d3228c4effd7b46ed51ab439e79a530934ad9bde7d77d7f6b6ebd4aS3BucketCCD07444" }, "S3Key": { "Fn::Join": [ @@ -18,7 +18,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cS3VersionKey897AD818" + "Ref": "AssetParameters6a4b9ce26d3228c4effd7b46ed51ab439e79a530934ad9bde7d77d7f6b6ebd4aS3VersionKeyA8B74284" } ] } @@ -31,7 +31,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cS3VersionKey897AD818" + "Ref": "AssetParameters6a4b9ce26d3228c4effd7b46ed51ab439e79a530934ad9bde7d77d7f6b6ebd4aS3VersionKeyA8B74284" } ] } @@ -118,19 +118,19 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myhandlerServiceRole77891068", "Arn" ] }, - "Runtime": "python3.6", + "Handler": "index.handler", "Layers": [ { "Ref": "SharedDACC02AA" } - ] + ], + "Runtime": "python3.6" }, "DependsOn": [ "myhandlerServiceRole77891068" @@ -138,17 +138,17 @@ } }, "Parameters": { - "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cS3Bucket6D2DF2A1": { + "AssetParameters6a4b9ce26d3228c4effd7b46ed51ab439e79a530934ad9bde7d77d7f6b6ebd4aS3BucketCCD07444": { "Type": "String", - "Description": "S3 bucket for asset \"314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4c\"" + "Description": "S3 bucket for asset \"6a4b9ce26d3228c4effd7b46ed51ab439e79a530934ad9bde7d77d7f6b6ebd4a\"" }, - "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cS3VersionKey897AD818": { + "AssetParameters6a4b9ce26d3228c4effd7b46ed51ab439e79a530934ad9bde7d77d7f6b6ebd4aS3VersionKeyA8B74284": { "Type": "String", - "Description": "S3 key for asset version \"314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4c\"" + "Description": "S3 key for asset version \"6a4b9ce26d3228c4effd7b46ed51ab439e79a530934ad9bde7d77d7f6b6ebd4a\"" }, - "AssetParameters314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4cArtifactHashF8341E5E": { + "AssetParameters6a4b9ce26d3228c4effd7b46ed51ab439e79a530934ad9bde7d77d7f6b6ebd4aArtifactHashB3093591": { "Type": "String", - "Description": "Artifact hash for asset \"314dd9f824ae895011cd7bb81d52a0ba316c902995491d7f4072c5aefccb6e4c\"" + "Description": "Artifact hash for asset \"6a4b9ce26d3228c4effd7b46ed51ab439e79a530934ad9bde7d77d7f6b6ebd4a\"" }, "AssetParameters71de8786d26e9f9205375b6cea9342e92d8a622a97d01d7e7d2f7661f056f218S3Bucket89C9DB12": { "Type": "String", diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json index 2f0a607ecaecb..b5b137205752f 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.expected.json @@ -36,7 +36,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cS3BucketEEA58FD6" + "Ref": "AssetParameters1482f01217b8bed41000ca172724dc762f68208d3faa315bd6e8e07bbea148adS3BucketA9379638" }, "S3Key": { "Fn::Join": [ @@ -49,7 +49,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cS3VersionKey5B5DB95F" + "Ref": "AssetParameters1482f01217b8bed41000ca172724dc762f68208d3faa315bd6e8e07bbea148adS3VersionKey4376B462" } ] } @@ -62,7 +62,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cS3VersionKey5B5DB95F" + "Ref": "AssetParameters1482f01217b8bed41000ca172724dc762f68208d3faa315bd6e8e07bbea148adS3VersionKey4376B462" } ] } @@ -72,13 +72,13 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myhandlerServiceRole77891068", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python3.8" }, "DependsOn": [ @@ -87,17 +87,17 @@ } }, "Parameters": { - "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cS3BucketEEA58FD6": { + "AssetParameters1482f01217b8bed41000ca172724dc762f68208d3faa315bd6e8e07bbea148adS3BucketA9379638": { "Type": "String", - "Description": "S3 bucket for asset \"428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381c\"" + "Description": "S3 bucket for asset \"1482f01217b8bed41000ca172724dc762f68208d3faa315bd6e8e07bbea148ad\"" }, - "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cS3VersionKey5B5DB95F": { + "AssetParameters1482f01217b8bed41000ca172724dc762f68208d3faa315bd6e8e07bbea148adS3VersionKey4376B462": { "Type": "String", - "Description": "S3 key for asset version \"428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381c\"" + "Description": "S3 key for asset version \"1482f01217b8bed41000ca172724dc762f68208d3faa315bd6e8e07bbea148ad\"" }, - "AssetParameters428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381cArtifactHash239A9708": { + "AssetParameters1482f01217b8bed41000ca172724dc762f68208d3faa315bd6e8e07bbea148adArtifactHashB9B928DC": { "Type": "String", - "Description": "Artifact hash for asset \"428642c68731bb90502dd14a13320f1f31db649ea6f770f56acdc6f4bb1b381c\"" + "Description": "Artifact hash for asset \"1482f01217b8bed41000ca172724dc762f68208d3faa315bd6e8e07bbea148ad\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json index ba8fee87727b4..6b3b8230c2874 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.expected.json @@ -72,13 +72,13 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "functionServiceRoleEF216095", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python2.7" }, "DependsOn": [ diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json b/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json index 63e6ba74ccfe9..63fad4c61de14 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.expected.json @@ -296,7 +296,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3Bucket035B0B74" + "Ref": "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353S3Bucket414E0E30" }, "S3Key": { "Fn::Join": [ @@ -309,7 +309,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F" + "Ref": "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353S3VersionKey5ABC9098" } ] } @@ -322,7 +322,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F" + "Ref": "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353S3VersionKey5ABC9098" } ] } @@ -332,13 +332,13 @@ ] } }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "myhandlerServiceRole77891068", "Arn" ] }, + "Handler": "index.handler", "Runtime": "python3.6", "VpcConfig": { "SecurityGroupIds": [ @@ -368,17 +368,17 @@ } }, "Parameters": { - "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3Bucket035B0B74": { + "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353S3Bucket414E0E30": { "Type": "String", - "Description": "S3 bucket for asset \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" + "Description": "S3 bucket for asset \"4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353\"" }, - "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57S3VersionKey781CC06F": { + "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353S3VersionKey5ABC9098": { "Type": "String", - "Description": "S3 key for asset version \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" + "Description": "S3 key for asset version \"4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353\"" }, - "AssetParametersc677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57ArtifactHash70AD5A1E": { + "AssetParameters4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353ArtifactHashECA6C88C": { "Type": "String", - "Description": "Artifact hash for asset \"c677eb7e524b9819a25fefd7267be6618341cd2b2d81f4b4aaa40911d698db57\"" + "Description": "Artifact hash for asset \"4ee6ce8b6ee4bd51743dc0c39d6e52baebaeafff9c9dfea0ff84de98d1dbf353\"" } }, "Outputs": { From 953957a2c3e630b5ad2196e113f943e27ee21067 Mon Sep 17 00:00:00 2001 From: jwoehrle Date: Tue, 9 Feb 2021 11:24:39 +0100 Subject: [PATCH 70/70] fix(ec2): VpnConnection fails if `ip` is a Token (#12923) Add support to use Token for `VpnConnectionProps.ip` and skip `net.isIPv4(...)` validation in that case. Fixes issue #11633 ---- *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-ec2/lib/vpn.ts | 2 +- packages/@aws-cdk/aws-ec2/test/vpn.test.ts | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpn.ts b/packages/@aws-cdk/aws-ec2/lib/vpn.ts index 4683180d2af84..19fb4f963acd3 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpn.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpn.ts @@ -224,7 +224,7 @@ export class VpnConnection extends Resource implements IVpnConnection { }); } - if (!net.isIPv4(props.ip)) { + if (!Token.isUnresolved(props.ip) && !net.isIPv4(props.ip)) { throw new Error(`The \`ip\` ${props.ip} is not a valid IPv4 address.`); } diff --git a/packages/@aws-cdk/aws-ec2/test/vpn.test.ts b/packages/@aws-cdk/aws-ec2/test/vpn.test.ts index 72be541bf45e2..4cf8880c3b0ab 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpn.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpn.test.ts @@ -1,5 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; -import { Duration, Stack } from '@aws-cdk/core'; +import { Duration, Stack, Token } from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; import { PublicSubnet, Vpc, VpnConnection } from '../lib'; @@ -322,4 +322,24 @@ nodeunitShim({ })); test.done(); }, + 'can add a vpn connection with a Token as customer gateway ip'(test:Test) { + // GIVEN + const stack = new Stack(); + const token = Token.asAny('192.0.2.1'); + + // WHEN + new Vpc(stack, 'VpcNetwork', { + vpnConnections: { + VpnConnection: { + ip: token as any, + }, + }, + }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::CustomerGateway', { + IpAddress: '192.0.2.1', + })); + test.done(); + }, });