From f7732a1b06927d84e79ea1c9fb671ad184a9efea Mon Sep 17 00:00:00 2001 From: Kaizen Conroy <36202692+kaizencc@users.noreply.github.com> Date: Wed, 18 May 2022 09:59:55 -0400 Subject: [PATCH 01/29] fix(apigateway): arnForExecuteApi fails on tokenized path (#20323) Fixes #20252. ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-apigateway/lib/restapi.ts | 4 ++-- .../@aws-cdk/aws-apigateway/test/restapi.test.ts | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 7300f1da4b9b3..ae628f6c52d3b 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,7 +1,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import { IVpcEndpoint } from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { ArnFormat, CfnOutput, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; +import { ArnFormat, CfnOutput, IResource as IResourceBase, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ApiDefinition } from './api-definition'; import { ApiKey, ApiKeyOptions, IApiKey } from './api-key'; @@ -368,7 +368,7 @@ export abstract class RestApiBase extends Resource implements IRestApi { } public arnForExecuteApi(method: string = '*', path: string = '/*', stage: string = '*') { - if (!path.startsWith('/')) { + if (!Token.isUnresolved(path) && !path.startsWith('/')) { throw new Error(`"path" must begin with a "/": '${path}'`); } diff --git a/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts b/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts index f873c6c643f5d..0b019719a4873 100644 --- a/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/restapi.test.ts @@ -1,7 +1,7 @@ import { Template } from '@aws-cdk/assertions'; import { GatewayVpcEndpoint } from '@aws-cdk/aws-ec2'; import { testDeprecated } from '@aws-cdk/cdk-build-tools'; -import { App, CfnElement, CfnResource, Stack } from '@aws-cdk/core'; +import { App, CfnElement, CfnResource, Lazy, Stack } from '@aws-cdk/core'; import * as apigw from '../lib'; describe('restapi', () => { @@ -424,6 +424,16 @@ describe('restapi', () => { expect(() => api.arnForExecuteApi('method', 'hey-path', 'stage')).toThrow(/"path" must begin with a "\/": 'hey-path'/); }); + test('"executeApiArn" path can be a token', () => { + // GIVEN + const stack = new Stack(); + const api = new apigw.RestApi(stack, 'api'); + api.root.addMethod('GET'); + + // THEN + expect(() => api.arnForExecuteApi('method', Lazy.string(({ produce: () => 'path' })), 'stage')).not.toThrow(); + }); + test('"executeApiArn" will convert ANY to "*"', () => { // GIVEN const stack = new Stack(); From a58a8037b79636e9f973beff2483baecad73f15d Mon Sep 17 00:00:00 2001 From: Josh Kellendonk <misterjoshua@users.noreply.github.com> Date: Wed, 18 May 2022 08:44:13 -0600 Subject: [PATCH 02/29] fix(assets): parallel docker image publishing fails on macOS (#20117) Changes container image publishing so that publishing doesn't re-run docker logins for the same repository and so logins are run one at a time. Fixes #20116 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../cdk-assets/lib/private/asset-handler.ts | 2 + packages/cdk-assets/lib/private/docker.ts | 60 +++++++++++ .../lib/private/handlers/container-images.ts | 78 ++++++++------ packages/cdk-assets/lib/private/util.ts | 12 +++ packages/cdk-assets/lib/publishing.ts | 2 + .../cdk-assets/test/docker-images.test.ts | 100 ++++++++++++++++++ packages/cdk-assets/test/util.test.ts | 32 ++++++ 7 files changed, 251 insertions(+), 35 deletions(-) create mode 100644 packages/cdk-assets/lib/private/util.ts create mode 100644 packages/cdk-assets/test/util.test.ts diff --git a/packages/cdk-assets/lib/private/asset-handler.ts b/packages/cdk-assets/lib/private/asset-handler.ts index af57422f95915..9b4eb4fa305c7 100644 --- a/packages/cdk-assets/lib/private/asset-handler.ts +++ b/packages/cdk-assets/lib/private/asset-handler.ts @@ -1,5 +1,6 @@ import { IAws } from '../aws'; import { EventType } from '../progress'; +import { DockerFactory } from './docker'; export interface IAssetHandler { publish(): Promise<void>; @@ -8,6 +9,7 @@ export interface IAssetHandler { export interface IHandlerHost { readonly aws: IAws; readonly aborted: boolean; + readonly dockerFactory: DockerFactory; emitMessage(type: EventType, m: string): void; } \ No newline at end of file diff --git a/packages/cdk-assets/lib/private/docker.ts b/packages/cdk-assets/lib/private/docker.ts index dac365b176dcc..1a9b2293230f6 100644 --- a/packages/cdk-assets/lib/private/docker.ts +++ b/packages/cdk-assets/lib/private/docker.ts @@ -3,6 +3,7 @@ import * as os from 'os'; import * as path from 'path'; import { cdkCredentialsConfig, obtainEcrCredentials } from './docker-credentials'; import { Logger, shell, ShellOptions } from './shell'; +import { createCriticalSection } from './util'; interface BuildOptions { readonly directory: string; @@ -146,6 +147,65 @@ export class Docker { } } +export interface DockerFactoryOptions { + readonly repoUri: string; + readonly ecr: AWS.ECR; + readonly logger: (m: string) => void; +} + +/** + * Helps get appropriately configured Docker instances during the container + * image publishing process. + */ +export class DockerFactory { + private enterLoggedInDestinationsCriticalSection = createCriticalSection(); + private loggedInDestinations = new Set<string>(); + + /** + * Gets a Docker instance for building images. + */ + public async forBuild(options: DockerFactoryOptions): Promise<Docker> { + const docker = new Docker(options.logger); + + // Default behavior is to login before build so that the Dockerfile can reference images in the ECR repo + // However, if we're in a pipelines environment (for example), + // we may have alternative credentials to the default ones to use for the build itself. + // If the special config file is present, delay the login to the default credentials until the push. + // If the config file is present, we will configure and use those credentials for the build. + let cdkDockerCredentialsConfigured = docker.configureCdkCredentials(); + if (!cdkDockerCredentialsConfigured) { + await this.loginOncePerDestination(docker, options); + } + + return docker; + } + + /** + * Gets a Docker instance for pushing images to ECR. + */ + public async forEcrPush(options: DockerFactoryOptions) { + const docker = new Docker(options.logger); + await this.loginOncePerDestination(docker, options); + return docker; + } + + private async loginOncePerDestination(docker: Docker, options: DockerFactoryOptions) { + // Changes: 012345678910.dkr.ecr.us-west-2.amazonaws.com/tagging-test + // To this: 012345678910.dkr.ecr.us-west-2.amazonaws.com + const repositoryDomain = options.repoUri.split('/')[0]; + + // Ensure one-at-a-time access to loggedInDestinations. + await this.enterLoggedInDestinationsCriticalSection(async () => { + if (this.loggedInDestinations.has(repositoryDomain)) { + return; + } + + await docker.login(options.ecr); + this.loggedInDestinations.add(repositoryDomain); + }); + } +} + function flatten(x: string[][]) { return Array.prototype.concat([], ...x); } diff --git a/packages/cdk-assets/lib/private/handlers/container-images.ts b/packages/cdk-assets/lib/private/handlers/container-images.ts index 6e8af3e787289..61ac1004cc714 100644 --- a/packages/cdk-assets/lib/private/handlers/container-images.ts +++ b/packages/cdk-assets/lib/private/handlers/container-images.ts @@ -8,8 +8,6 @@ import { replaceAwsPlaceholders } from '../placeholders'; import { shell } from '../shell'; export class ContainerImageAssetHandler implements IAssetHandler { - private readonly docker = new Docker(m => this.host.emitMessage(EventType.DEBUG, m)); - constructor( private readonly workDir: string, private readonly asset: DockerImageManifestEntry, @@ -31,17 +29,16 @@ export class ContainerImageAssetHandler implements IAssetHandler { if (await this.destinationAlreadyExists(ecr, destination, imageUri)) { return; } if (this.host.aborted) { return; } - // Default behavior is to login before build so that the Dockerfile can reference images in the ECR repo - // However, if we're in a pipelines environment (for example), - // we may have alternative credentials to the default ones to use for the build itself. - // If the special config file is present, delay the login to the default credentials until the push. - // If the config file is present, we will configure and use those credentials for the build. - let cdkDockerCredentialsConfigured = this.docker.configureCdkCredentials(); - if (!cdkDockerCredentialsConfigured) { await this.docker.login(ecr); } + const containerImageDockerOptions = { + repoUri, + logger: (m: string) => this.host.emitMessage(EventType.DEBUG, m), + ecr, + }; + + const dockerForBuilding = await this.host.dockerFactory.forBuild(containerImageDockerOptions); - const localTagName = this.asset.source.executable - ? await this.buildExternalAsset(this.asset.source.executable) - : await this.buildDirectoryAsset(); + const builder = new ContainerImageBuilder(dockerForBuilding, this.workDir, this.asset, this.host); + const localTagName = await builder.build(); if (localTagName === undefined || this.host.aborted) { return; @@ -49,14 +46,43 @@ export class ContainerImageAssetHandler implements IAssetHandler { this.host.emitMessage(EventType.UPLOAD, `Push ${imageUri}`); if (this.host.aborted) { return; } - await this.docker.tag(localTagName, imageUri); - if (cdkDockerCredentialsConfigured) { - this.docker.resetAuthPlugins(); - await this.docker.login(ecr); + await dockerForBuilding.tag(localTagName, imageUri); + + const dockerForPushing = await this.host.dockerFactory.forEcrPush(containerImageDockerOptions); + await dockerForPushing.push(imageUri); + } + + /** + * Check whether the image already exists in the ECR repo + * + * Use the fields from the destination to do the actual check. The imageUri + * should correspond to that, but is only used to print Docker image location + * for user benefit (the format is slightly different). + */ + private async destinationAlreadyExists(ecr: AWS.ECR, destination: DockerImageDestination, imageUri: string): Promise<boolean> { + this.host.emitMessage(EventType.CHECK, `Check ${imageUri}`); + if (await imageExists(ecr, destination.repositoryName, destination.imageTag)) { + this.host.emitMessage(EventType.FOUND, `Found ${imageUri}`); + return true; } - await this.docker.push(imageUri); + return false; + } +} + +class ContainerImageBuilder { + constructor( + private readonly docker: Docker, + private readonly workDir: string, + private readonly asset: DockerImageManifestEntry, + private readonly host: IHandlerHost) { + } + + async build(): Promise<string | undefined> { + return this.asset.source.executable + ? this.buildExternalAsset(this.asset.source.executable) + : this.buildDirectoryAsset(); } /** @@ -84,7 +110,6 @@ export class ContainerImageAssetHandler implements IAssetHandler { * and is expected to return the generated image identifier on stdout. */ private async buildExternalAsset(executable: string[], cwd?: string): Promise<string | undefined> { - const assetPath = cwd ?? this.workDir; this.host.emitMessage(EventType.BUILD, `Building Docker image using command '${executable}'`); @@ -93,23 +118,6 @@ export class ContainerImageAssetHandler implements IAssetHandler { return (await shell(executable, { cwd: assetPath, quiet: true })).trim(); } - /** - * Check whether the image already exists in the ECR repo - * - * Use the fields from the destination to do the actual check. The imageUri - * should correspond to that, but is only used to print Docker image location - * for user benefit (the format is slightly different). - */ - private async destinationAlreadyExists(ecr: AWS.ECR, destination: DockerImageDestination, imageUri: string): Promise<boolean> { - this.host.emitMessage(EventType.CHECK, `Check ${imageUri}`); - if (await imageExists(ecr, destination.repositoryName, destination.imageTag)) { - this.host.emitMessage(EventType.FOUND, `Found ${imageUri}`); - return true; - } - - return false; - } - private async buildImage(localTagName: string): Promise<void> { const source = this.asset.source; if (!source.directory) { diff --git a/packages/cdk-assets/lib/private/util.ts b/packages/cdk-assets/lib/private/util.ts new file mode 100644 index 0000000000000..88a87a18e6ba9 --- /dev/null +++ b/packages/cdk-assets/lib/private/util.ts @@ -0,0 +1,12 @@ +/** + * Creates a critical section, ensuring that at most one function can + * enter the critical section at a time. + */ +export function createCriticalSection() { + let lock = Promise.resolve(); + return async (criticalFunction: () => Promise<void>) => { + const res = lock.then(() => criticalFunction()); + lock = res.catch(e => e); + return res; + }; +}; \ No newline at end of file diff --git a/packages/cdk-assets/lib/publishing.ts b/packages/cdk-assets/lib/publishing.ts index 804265a56acc8..a4eb709df0efd 100644 --- a/packages/cdk-assets/lib/publishing.ts +++ b/packages/cdk-assets/lib/publishing.ts @@ -1,6 +1,7 @@ import { AssetManifest, IManifestEntry } from './asset-manifest'; import { IAws } from './aws'; import { IHandlerHost } from './private/asset-handler'; +import { DockerFactory } from './private/docker'; import { makeAssetHandler } from './private/handlers'; import { EventType, IPublishProgress, IPublishProgressListener } from './progress'; @@ -76,6 +77,7 @@ export class AssetPublishing implements IPublishProgress { aws: this.options.aws, get aborted() { return self.aborted; }, emitMessage(t, m) { self.progressEvent(t, m); }, + dockerFactory: new DockerFactory(), }; } diff --git a/packages/cdk-assets/test/docker-images.test.ts b/packages/cdk-assets/test/docker-images.test.ts index a8d2561197f06..62af7cf399abb 100644 --- a/packages/cdk-assets/test/docker-images.test.ts +++ b/packages/cdk-assets/test/docker-images.test.ts @@ -36,6 +36,37 @@ beforeEach(() => { }, }, }), + '/multi/cdk.out/assets.json': JSON.stringify({ + version: Manifest.version(), + dockerImages: { + theAsset1: { + source: { + directory: 'dockerdir', + }, + destinations: { + theDestination: { + region: 'us-north-50', + assumeRoleArn: 'arn:aws:role', + repositoryName: 'repo', + imageTag: 'theAsset1', + }, + }, + }, + theAsset2: { + source: { + directory: 'dockerdir', + }, + destinations: { + theDestination: { + region: 'us-north-50', + assumeRoleArn: 'arn:aws:role', + repositoryName: 'repo', + imageTag: 'theAsset2', + }, + }, + }, + }, + }), '/external/cdk.out/assets.json': JSON.stringify({ version: Manifest.version(), dockerImages: { @@ -295,3 +326,72 @@ test('when external credentials are present, explicit Docker config directories expectAllSpawns(); }); + +test('logging in only once for two assets', async () => { + const pub = new AssetPublishing(AssetManifest.fromPath('/multi/cdk.out'), { aws, throwOnError: false }); + aws.mockEcr.describeImages = mockedApiFailure('ImageNotFoundException', 'File does not exist'); + aws.mockEcr.getAuthorizationToken = mockedApiResult({ + authorizationData: [ + { authorizationToken: 'dXNlcjpwYXNz', proxyEndpoint: 'https://proxy.com/' }, + ], + }); + + const expectAllSpawns = mockSpawn( + { commandLine: ['docker', 'login', '--username', 'user', '--password-stdin', 'https://proxy.com/'] }, + { commandLine: ['docker', 'inspect', 'cdkasset-theasset1'], exitCode: 1 }, + { commandLine: ['docker', 'build', '--tag', 'cdkasset-theasset1', '.'], cwd: '/multi/cdk.out/dockerdir' }, + { commandLine: ['docker', 'tag', 'cdkasset-theasset1', '12345.amazonaws.com/repo:theAsset1'] }, + { commandLine: ['docker', 'push', '12345.amazonaws.com/repo:theAsset1'] }, + { commandLine: ['docker', 'inspect', 'cdkasset-theasset2'], exitCode: 1 }, + { commandLine: ['docker', 'build', '--tag', 'cdkasset-theasset2', '.'], cwd: '/multi/cdk.out/dockerdir' }, + { commandLine: ['docker', 'tag', 'cdkasset-theasset2', '12345.amazonaws.com/repo:theAsset2'] }, + { commandLine: ['docker', 'push', '12345.amazonaws.com/repo:theAsset2'] }, + ); + + await pub.publish(); + + expectAllSpawns(); + expect(true).toBeTruthy(); // Expect no exception, satisfy linter +}); + +test('logging in twice for two repository domains (containing account id & region)', async () => { + const pub = new AssetPublishing(AssetManifest.fromPath('/multi/cdk.out'), { aws, throwOnError: false }); + aws.mockEcr.describeImages = mockedApiFailure('ImageNotFoundException', 'File does not exist'); + + let repoIdx = 12345; + aws.mockEcr.describeRepositories = jest.fn().mockReturnValue({ + promise: jest.fn().mockImplementation(() => Promise.resolve({ + repositories: [ + // Usually looks like: 012345678910.dkr.ecr.us-west-2.amazonaws.com/aws-cdk/assets + { repositoryUri: `${repoIdx++}.amazonaws.com/aws-cdk/assets` }, + ], + })), + }); + + let proxyIdx = 12345; + aws.mockEcr.getAuthorizationToken = jest.fn().mockReturnValue({ + promise: jest.fn().mockImplementation(() => Promise.resolve({ + authorizationData: [ + { authorizationToken: 'dXNlcjpwYXNz', proxyEndpoint: `https://${proxyIdx++}.proxy.com/` }, + ], + })), + }); + + const expectAllSpawns = mockSpawn( + { commandLine: ['docker', 'login', '--username', 'user', '--password-stdin', 'https://12345.proxy.com/'] }, + { commandLine: ['docker', 'inspect', 'cdkasset-theasset1'], exitCode: 1 }, + { commandLine: ['docker', 'build', '--tag', 'cdkasset-theasset1', '.'], cwd: '/multi/cdk.out/dockerdir' }, + { commandLine: ['docker', 'tag', 'cdkasset-theasset1', '12345.amazonaws.com/aws-cdk/assets:theAsset1'] }, + { commandLine: ['docker', 'push', '12345.amazonaws.com/aws-cdk/assets:theAsset1'] }, + { commandLine: ['docker', 'login', '--username', 'user', '--password-stdin', 'https://12346.proxy.com/'] }, + { commandLine: ['docker', 'inspect', 'cdkasset-theasset2'], exitCode: 1 }, + { commandLine: ['docker', 'build', '--tag', 'cdkasset-theasset2', '.'], cwd: '/multi/cdk.out/dockerdir' }, + { commandLine: ['docker', 'tag', 'cdkasset-theasset2', '12346.amazonaws.com/aws-cdk/assets:theAsset2'] }, + { commandLine: ['docker', 'push', '12346.amazonaws.com/aws-cdk/assets:theAsset2'] }, + ); + + await pub.publish(); + + expectAllSpawns(); + expect(true).toBeTruthy(); // Expect no exception, satisfy linter +}); diff --git a/packages/cdk-assets/test/util.test.ts b/packages/cdk-assets/test/util.test.ts new file mode 100644 index 0000000000000..8e498076913f2 --- /dev/null +++ b/packages/cdk-assets/test/util.test.ts @@ -0,0 +1,32 @@ +import { createCriticalSection } from '../lib/private/util'; + +test('critical section', async () => { + // GIVEN + const criticalSection = createCriticalSection(); + + // WHEN + const arr = new Array<string>(); + void criticalSection(async () => { + await new Promise(res => setTimeout(res, 500)); + arr.push('first'); + }); + await criticalSection(async () => { + arr.push('second'); + }); + + // THEN + expect(arr).toEqual([ + 'first', + 'second', + ]); +}); + +test('exceptions in critical sections', async () => { + // GIVEN + const criticalSection = createCriticalSection(); + + // WHEN/THEN + await expect(() => criticalSection(async () => { + throw new Error('Thrown'); + })).rejects.toThrow('Thrown'); +}); \ No newline at end of file From 4df9a4fa9ef24266b2bcde378ecc112c7dcaf8aa Mon Sep 17 00:00:00 2001 From: Adam Ruka <adamruka@amazon.com> Date: Wed, 18 May 2022 08:29:04 -0700 Subject: [PATCH 03/29] fix(cfn-include): allow CFN Functions in Tags (#19923) Our `TagManger` does not allow expressing Tags that included top-level CloudFormation functions, like `Fn::If`. While that's not a big issue when writing CDK code directly, it makes it impossible to include some CloudFormation templates that use that pattern. Introduce the concept of "dynamic tags" to the `TagManager` that allows preserving these, and return them alongside the "regular" Tags when rendering. Fixes #16889 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../test/test-templates/if-in-tags.json | 29 +++++ .../test/valid-templates.test.ts | 8 ++ packages/@aws-cdk/core/lib/tag-manager.ts | 117 +++++++++++------- 3 files changed, 112 insertions(+), 42 deletions(-) create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/if-in-tags.json diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/if-in-tags.json b/packages/@aws-cdk/cloudformation-include/test/test-templates/if-in-tags.json new file mode 100644 index 0000000000000..8b7a21358d10a --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/if-in-tags.json @@ -0,0 +1,29 @@ +{ + "Conditions": { + "ValcacheServerEnabled": true + }, + "Resources": { + "TxAutoScalingGroup": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MinSize": "1", + "MaxSize": "3", + "Tags": [ + { + "Fn::If": [ + "ValcacheServerEnabled", + { + "Key": "datomic:cache-group", + "Value": "SystemName", + "PropagateAtLaunch": true + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ] + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts index eec714ac5d7d6..f5d4504cfc00e 100644 --- a/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/valid-templates.test.ts @@ -228,6 +228,14 @@ describe('CDK Include', () => { ); }); + test('can ingest a template using Fn::If in Tags, and output it unchanged', () => { + includeTestTemplate(stack, 'if-in-tags.json'); + + Template.fromStack(stack).templateMatches( + loadTestFileToJsObject('if-in-tags.json'), + ); + }); + test('can ingest a UserData script, and output it unchanged', () => { includeTestTemplate(stack, 'user-data.json'); diff --git a/packages/@aws-cdk/core/lib/tag-manager.ts b/packages/@aws-cdk/core/lib/tag-manager.ts index 8f4893d5036f0..851c4406e4b24 100644 --- a/packages/@aws-cdk/core/lib/tag-manager.ts +++ b/packages/@aws-cdk/core/lib/tag-manager.ts @@ -24,6 +24,24 @@ interface StackTag { Key: string; Value: string; } + +/** + * The results of parsing Tags. + */ +interface ParseTagsResult { + /** + * The "simple" (meaning, not including complex CloudFormation functions) + * tags that were found. + */ + readonly tags: Tag[]; + + /** + * The collection of "dynamic" (meaning, including complex CloudFormation functions) + * tags that were found. + */ + readonly dynamicTags: any; +} + /** * Interface for converter between CloudFormation and internal tag representations */ @@ -38,31 +56,33 @@ interface ITagFormatter { * * Use the given priority. */ - parseTags(cfnPropertyTags: any, priority: number): Tag[]; + parseTags(cfnPropertyTags: any, priority: number): ParseTagsResult; } /** * Standard tags are a list of { key, value } objects */ class StandardFormatter implements ITagFormatter { - public parseTags(cfnPropertyTags: any, priority: number): Tag[] { + public parseTags(cfnPropertyTags: any, priority: number): ParseTagsResult { if (!Array.isArray(cfnPropertyTags)) { throw new Error(`Invalid tag input expected array of {key, value} have ${JSON.stringify(cfnPropertyTags)}`); } const tags: Tag[] = []; + const dynamicTags: any = []; for (const tag of cfnPropertyTags) { if (tag.key === undefined || tag.value === undefined) { - throw new Error(`Invalid tag input expected {key, value} have ${JSON.stringify(tag)}`); + dynamicTags.push(tag); + } else { + // using interp to ensure Token is now string + tags.push({ + key: `${tag.key}`, + value: `${tag.value}`, + priority, + }); } - // using interp to ensure Token is now string - tags.push({ - key: `${tag.key}`, - value: `${tag.value}`, - priority, - }); } - return tags; + return { tags, dynamicTags }; } public formatTags(tags: Tag[]): any { @@ -73,7 +93,7 @@ class StandardFormatter implements ITagFormatter { value: tag.value, }); } - return cfnTags.length === 0 ? undefined : cfnTags; + return cfnTags; } } @@ -81,28 +101,30 @@ class StandardFormatter implements ITagFormatter { * ASG tags are a list of { key, value, propagateAtLaunch } objects */ class AsgFormatter implements ITagFormatter { - public parseTags(cfnPropertyTags: any, priority: number): Tag[] { - const tags: Tag[] = []; + public parseTags(cfnPropertyTags: any, priority: number): ParseTagsResult { if (!Array.isArray(cfnPropertyTags)) { throw new Error(`Invalid tag input expected array of {key, value, propagateAtLaunch} have ${JSON.stringify(cfnPropertyTags)}`); } + const tags: Tag[] = []; + const dynamicTags: any = []; for (const tag of cfnPropertyTags) { if (tag.key === undefined || - tag.value === undefined || - tag.propagateAtLaunch === undefined) { - throw new Error(`Invalid tag input expected {key, value, propagateAtLaunch} have ${JSON.stringify(tag)}`); + tag.value === undefined || + tag.propagateAtLaunch === undefined) { + dynamicTags.push(tag); + } else { + // using interp to ensure Token is now string + tags.push({ + key: `${tag.key}`, + value: `${tag.value}`, + priority, + applyToLaunchedInstances: !!tag.propagateAtLaunch, + }); } - // using interp to ensure Token is now string - tags.push({ - key: `${tag.key}`, - value: `${tag.value}`, - priority, - applyToLaunchedInstances: !!tag.propagateAtLaunch, - }); } - return tags; + return { tags, dynamicTags }; } public formatTags(tags: Tag[]): any { @@ -114,7 +136,7 @@ class AsgFormatter implements ITagFormatter { propagateAtLaunch: tag.applyToLaunchedInstances !== false, }); } - return cfnTags.length === 0 ? undefined : cfnTags; + return cfnTags; } } @@ -122,12 +144,12 @@ class AsgFormatter implements ITagFormatter { * Some CloudFormation constructs use a { key: value } map for tags */ class MapFormatter implements ITagFormatter { - public parseTags(cfnPropertyTags: any, priority: number): Tag[] { - const tags: Tag[] = []; + public parseTags(cfnPropertyTags: any, priority: number): ParseTagsResult { if (Array.isArray(cfnPropertyTags) || typeof(cfnPropertyTags) !== 'object') { throw new Error(`Invalid tag input expected map of {key: value} have ${JSON.stringify(cfnPropertyTags)}`); } + const tags: Tag[] = []; for (const [key, value] of Object.entries(cfnPropertyTags)) { tags.push({ key, @@ -136,15 +158,15 @@ class MapFormatter implements ITagFormatter { }); } - return tags; + return { tags, dynamicTags: undefined }; } public formatTags(tags: Tag[]): any { - const cfnTags: {[key: string]: string} = {}; + const cfnTags: { [key: string]: string } = {}; for (const tag of tags) { cfnTags[`${tag.key}`] = `${tag.value}`; } - return Object.keys(cfnTags).length === 0 ? undefined : cfnTags; + return cfnTags; } } @@ -152,7 +174,7 @@ class MapFormatter implements ITagFormatter { * StackTags are of the format { Key: key, Value: value } */ class KeyValueFormatter implements ITagFormatter { - public parseTags(keyValueTags: any, priority: number): Tag[] { + public parseTags(keyValueTags: any, priority: number): ParseTagsResult { const tags: Tag[] = []; for (const key in keyValueTags) { if (keyValueTags.hasOwnProperty(key)) { @@ -164,8 +186,9 @@ class KeyValueFormatter implements ITagFormatter { }); } } - return tags; + return { tags, dynamicTags: undefined }; } + public formatTags(unformattedTags: Tag[]): any { const tags: StackTag[] = []; unformattedTags.forEach(tag => { @@ -174,20 +197,20 @@ class KeyValueFormatter implements ITagFormatter { Value: tag.value, }); }); - return tags.length > 0 ? tags : undefined; + return tags; } } class NoFormat implements ITagFormatter { - public parseTags(_cfnPropertyTags: any): Tag[] { - return []; + public parseTags(_cfnPropertyTags: any): ParseTagsResult { + return { tags: [], dynamicTags: undefined }; } + public formatTags(_tags: Tag[]): any { return undefined; } } - let _tagFormattersCache: {[key: string]: ITagFormatter} | undefined; /** @@ -203,7 +226,7 @@ function TAG_FORMATTERS(): {[key: string]: ITagFormatter} { [TagType.KEY_VALUE]: new KeyValueFormatter(), [TagType.NOT_TAGGABLE]: new NoFormat(), }); -}; +} /** * Interface to implement tags. @@ -258,7 +281,6 @@ export interface TagManagerOptions { * */ export class TagManager { - /** * Check whether the given construct is Taggable */ @@ -283,6 +305,7 @@ export class TagManager { public readonly renderedTags: IResolvable; private readonly tags = new Map<string, Tag>(); + private readonly dynamicTags: any; private readonly priorities = new Map<string, number>(); private readonly tagFormatter: ITagFormatter; private readonly resourceTypeName: string; @@ -292,7 +315,9 @@ export class TagManager { this.resourceTypeName = resourceTypeName; this.tagFormatter = TAG_FORMATTERS()[tagType]; if (tagStructure !== undefined) { - this._setTag(...this.tagFormatter.parseTags(tagStructure, this.initialTagPriority)); + const parseTagsResult = this.tagFormatter.parseTags(tagStructure, this.initialTagPriority); + this.dynamicTags = parseTagsResult.dynamicTags; + this._setTag(...parseTagsResult.tags); } this.tagPropertyName = options.tagPropertyName || 'tags'; @@ -331,7 +356,14 @@ export class TagManager { * tags at synthesis time. */ public renderTags(): any { - return this.tagFormatter.formatTags(this.sortedTags); + const formattedTags = this.tagFormatter.formatTags(this.sortedTags); + if (Array.isArray(formattedTags) || Array.isArray(this.dynamicTags)) { + const ret = [...formattedTags ?? [], ...this.dynamicTags ?? []]; + return ret.length > 0 ? ret : undefined; + } else { + const ret = { ...formattedTags ?? {}, ...this.dynamicTags ?? {} }; + return Object.keys(ret).length > 0 ? ret : undefined; + } } /** @@ -378,7 +410,8 @@ export class TagManager { } } - private get sortedTags() { - return Array.from(this.tags.values()).sort((a, b) => a.key.localeCompare(b.key)); + private get sortedTags(): Tag[] { + return Array.from(this.tags.values()) + .sort((a, b) => a.key.localeCompare(b.key)); } } From a14d9145e7ad4d2d730e91e6c2fdff4793c7d755 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy <36202692+kaizencc@users.noreply.github.com> Date: Wed, 18 May 2022 12:14:23 -0400 Subject: [PATCH 04/29] chore: add reprioritized issues to ownership project board (#20386) The current workflow for re-prioritization is to automatically comment on the issue that a CDK maintainer will provide an update. But we do not track that anywhere. I added a feature to the github action that will add re-prioritized issues to a repository project. This update aligns the workflow with the new feature. See the relevant [readme](https://github.com/kaizencc/issue-reprioritization-manager#add-reprioritized-issues-to-a-github-project) for the `project-column-url` property for more. `repository-projects: write` allows github actions permission to add new cards to the project. ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .github/workflows/issue-reprioritization.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/issue-reprioritization.yml b/.github/workflows/issue-reprioritization.yml index 9a6ade90de43a..8c1495e3397ca 100644 --- a/.github/workflows/issue-reprioritization.yml +++ b/.github/workflows/issue-reprioritization.yml @@ -7,6 +7,7 @@ jobs: issue-reprioritization: permissions: issues: write + repository-projects: write runs-on: ubuntu-latest steps: - uses: kaizencc/issue-reprioritization-manager@main @@ -16,6 +17,7 @@ jobs: original-label: p2 new-label: p1 reprioritization-threshold: 20 + project-column-url: https://github.com/aws/aws-cdk/projects/13#column-18002436 - uses: kaizencc/pr-triage-manager@main with: github-token: ${{ secrets.GITHUB_TOKEN }} From 33b983ca76c91f182e60dcab8c6ead6be4d4712d Mon Sep 17 00:00:00 2001 From: Peter Woodworth <44349620+peterwoodworth@users.noreply.github.com> Date: Wed, 18 May 2022 09:58:49 -0700 Subject: [PATCH 05/29] feat(ec2): more router types (#20151) Fixes #19057 https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html#aws-resource-ec2-route-properties ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 24 ++++++++++++++ packages/@aws-cdk/aws-ec2/test/vpc.test.ts | 38 ++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 741a6ceba4f07..05cc0de040404 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1811,6 +1811,11 @@ export interface AddRouteOptions { * Type of router used in route */ export enum RouterType { + /** + * Carrier gateway + */ + CARRIER_GATEWAY = 'CarrierGateway', + /** * Egress-only Internet Gateway */ @@ -1826,6 +1831,11 @@ export enum RouterType { */ INSTANCE = 'Instance', + /** + * Local Gateway + */ + LOCAL_GATEWAY = 'LocalGateway', + /** * NAT Gateway */ @@ -1836,20 +1846,34 @@ export enum RouterType { */ NETWORK_INTERFACE = 'NetworkInterface', + /** + * Transit Gateway + */ + TRANSIT_GATEWAY = 'TransitGateway', + /** * VPC peering connection */ VPC_PEERING_CONNECTION = 'VpcPeeringConnection', + + /** + * VPC Endpoint for gateway load balancers + */ + VPC_ENDPOINT = 'VpcEndpoint', } function routerTypeToPropName(routerType: RouterType) { return ({ + [RouterType.CARRIER_GATEWAY]: 'carrierGatewayId', [RouterType.EGRESS_ONLY_INTERNET_GATEWAY]: 'egressOnlyInternetGatewayId', [RouterType.GATEWAY]: 'gatewayId', [RouterType.INSTANCE]: 'instanceId', + [RouterType.LOCAL_GATEWAY]: 'localGatewayId', [RouterType.NAT_GATEWAY]: 'natGatewayId', [RouterType.NETWORK_INTERFACE]: 'networkInterfaceId', + [RouterType.TRANSIT_GATEWAY]: 'transitGatewayId', [RouterType.VPC_PEERING_CONNECTION]: 'vpcPeeringConnectionId', + [RouterType.VPC_ENDPOINT]: 'vpcEndpointId', })[routerType]; } diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts index 8e383e1e630fd..1bc1379ebfdfe 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts @@ -1869,6 +1869,44 @@ describe('vpc', () => { expect(subnetIds).toEqual(expected.map(s => s.subnetId)); }); + + test('tests router types', () => { + // GIVEN + const stack = getTestStack(); + const vpc = new Vpc(stack, 'Vpc'); + + // WHEN + (vpc.publicSubnets[0] as Subnet).addRoute('TransitRoute', { + routerType: RouterType.TRANSIT_GATEWAY, + routerId: 'transit-id', + }); + (vpc.publicSubnets[0] as Subnet).addRoute('CarrierRoute', { + routerType: RouterType.CARRIER_GATEWAY, + routerId: 'carrier-gateway-id', + }); + (vpc.publicSubnets[0] as Subnet).addRoute('LocalGatewayRoute', { + routerType: RouterType.LOCAL_GATEWAY, + routerId: 'local-gateway-id', + }); + (vpc.publicSubnets[0] as Subnet).addRoute('VpcEndpointRoute', { + routerType: RouterType.VPC_ENDPOINT, + routerId: 'vpc-endpoint-id', + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::EC2::Route', { + TransitGatewayId: 'transit-id', + }); + Template.fromStack(stack).hasResourceProperties('AWS::EC2::Route', { + LocalGatewayId: 'local-gateway-id', + }); + Template.fromStack(stack).hasResourceProperties('AWS::EC2::Route', { + CarrierGatewayId: 'carrier-gateway-id', + }); + Template.fromStack(stack).hasResourceProperties('AWS::EC2::Route', { + VpcEndpointId: 'vpc-endpoint-id', + }); + }); }); }); From 765f44177298b645c88a29587b52619e91a8757c Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser <jogold@users.noreply.github.com> Date: Wed, 18 May 2022 19:42:08 +0200 Subject: [PATCH 06/29] fix(amplify): custom headers break with tokens (#20395) `YAML.stringify` generates YAML with a fixed line length, splitting long strings. This can split the token string value on multiple lines making it unresolvable: `${Token[AWS.URLSuf\\\n fix.2]}` This can be the case with `Content-Security-Policy` headers with lots of directives and referencing API endpoints in the `connect-src` for example. Get rid of `YAML.stringify` and generate this "simple" YAML string "manually". ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- package.json | 4 ---- packages/@aws-cdk/aws-amplify/NOTICE | 21 ------------------- packages/@aws-cdk/aws-amplify/lib/app.ts | 20 +++++++++++------- packages/@aws-cdk/aws-amplify/package.json | 7 +------ .../cdk-amplify-app.template.json | 13 +++++++++++- .../test/app.integ.snapshot/cdk.out | 2 +- .../test/app.integ.snapshot/integ.json | 4 ++-- .../test/app.integ.snapshot/manifest.json | 2 +- .../test/app.integ.snapshot/tree.json | 13 +++++++++++- .../@aws-cdk/aws-amplify/test/app.test.ts | 19 ++++++++++++++++- .../@aws-cdk/aws-amplify/test/integ.app.ts | 1 + 11 files changed, 61 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index c6e7cb3182aff..b100e63437b16 100644 --- a/package.json +++ b/package.json @@ -80,12 +80,8 @@ "@aws-cdk/assertions-alpha/fs-extra/**", "@aws-cdk/assertions/fs-extra", "@aws-cdk/assertions/fs-extra/**", - "@aws-cdk/aws-amplify-alpha/yaml", - "@aws-cdk/aws-amplify-alpha/yaml/**", "@aws-cdk/aws-iot-actions-alpha/case", "@aws-cdk/aws-iot-actions-alpha/case/**", - "@aws-cdk/aws-amplify/yaml", - "@aws-cdk/aws-amplify/yaml/**", "@aws-cdk/aws-codebuild/yaml", "@aws-cdk/aws-codebuild/yaml/**", "@aws-cdk/aws-codepipeline-actions/case", diff --git a/packages/@aws-cdk/aws-amplify/NOTICE b/packages/@aws-cdk/aws-amplify/NOTICE index ee9b8119d893f..1b7adbb891265 100644 --- a/packages/@aws-cdk/aws-amplify/NOTICE +++ b/packages/@aws-cdk/aws-amplify/NOTICE @@ -1,23 +1,2 @@ AWS Cloud Development Kit (AWS CDK) Copyright 2018-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -------------------------------------------------------------------------------- - -The AWS CDK includes the following third-party software/licensing: - -** yaml - https://www.npmjs.com/package/yaml -Copyright 2018 Eemeli Aro <eemeli@gmail.com> - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - ----------------- diff --git a/packages/@aws-cdk/aws-amplify/lib/app.ts b/packages/@aws-cdk/aws-amplify/lib/app.ts index 9006d75ec5563..387e89110568b 100644 --- a/packages/@aws-cdk/aws-amplify/lib/app.ts +++ b/packages/@aws-cdk/aws-amplify/lib/app.ts @@ -2,7 +2,6 @@ import * as codebuild from '@aws-cdk/aws-codebuild'; import * as iam from '@aws-cdk/aws-iam'; import { IResource, Lazy, Resource, SecretValue } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import * as YAML from 'yaml'; import { CfnApp } from './amplify.generated'; import { BasicAuth } from './basic-auth'; import { Branch, BranchOptions } from './branch'; @@ -515,11 +514,18 @@ export interface CustomResponseHeader { } function renderCustomResponseHeaders(customHeaders: CustomResponseHeader[]): string { - const modifiedHeaders = customHeaders.map(customHeader => ({ - ...customHeader, - headers: Object.entries(customHeader.headers).map(([key, value]) => ({ key, value })), - })); + const yaml = [ + 'customHeaders:', + ]; + + for (const customHeader of customHeaders) { + yaml.push(` - pattern: "${customHeader.pattern}"`); + yaml.push(' headers:'); + for (const [key, value] of Object.entries(customHeader.headers)) { + yaml.push(` - key: "${key}"`); + yaml.push(` value: "${value}"`); + } + } - const customHeadersObject = { customHeaders: modifiedHeaders }; - return YAML.stringify(customHeadersObject); + return `${yaml.join('\n')}\n`; } diff --git a/packages/@aws-cdk/aws-amplify/package.json b/packages/@aws-cdk/aws-amplify/package.json index eb02bfe5872b3..d94f5b09d8351 100644 --- a/packages/@aws-cdk/aws-amplify/package.json +++ b/packages/@aws-cdk/aws-amplify/package.json @@ -87,7 +87,6 @@ "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.0", - "@types/yaml": "1.9.6", "aws-sdk": "^2.848.0" }, "dependencies": { @@ -101,12 +100,8 @@ "@aws-cdk/aws-secretsmanager": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/custom-resources": "0.0.0", - "constructs": "^3.3.69", - "yaml": "1.10.2" + "constructs": "^3.3.69" }, - "bundledDependencies": [ - "yaml" - ], "peerDependencies": { "@aws-cdk/aws-codebuild": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", diff --git a/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/cdk-amplify-app.template.json b/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/cdk-amplify-app.template.json index e1cca2efc837c..de3117a0134f0 100644 --- a/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/cdk-amplify-app.template.json +++ b/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/cdk-amplify-app.template.json @@ -56,7 +56,18 @@ }, "Username": "aws" }, - "CustomHeaders": "customHeaders:\n - pattern: \"*.json\"\n headers:\n - key: custom-header-name-1\n value: custom-header-value-1\n - key: custom-header-name-2\n value: custom-header-value-2\n - pattern: /path/*\n headers:\n - key: custom-header-name-1\n value: custom-header-value-2\n", + "CustomHeaders": { + "Fn::Join": [ + "", + [ + "customHeaders:\n - pattern: \"*.json\"\n headers:\n - key: \"custom-header-name-1\"\n value: \"custom-header-value-1\"\n - key: \"custom-header-name-2\"\n value: \"custom-header-value-2\"\n - pattern: \"/path/*\"\n headers:\n - key: \"custom-header-name-1\"\n value: \"custom-header-value-2\"\n - key: \"x-aws-url-suffix\"\n value: \"this-is-the-suffix-", + { + "Ref": "AWS::URLSuffix" + }, + "\"\n" + ] + ] + }, "CustomRules": [ { "Source": "/source", diff --git a/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/cdk.out index 90bef2e09ad39..ccdfc1ff96a9d 100644 --- a/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"17.0.0"} \ No newline at end of file +{"version":"19.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/integ.json b/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/integ.json index cca9d18d99d00..93176aef2bf66 100644 --- a/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/integ.json +++ b/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/integ.json @@ -1,7 +1,7 @@ { - "version": "18.0.0", + "version": "19.0.0", "testCases": { - "aws-amplify/test/integ.app": { + "integ.app": { "stacks": [ "cdk-amplify-app" ], diff --git a/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/manifest.json index 65552a1fcf8ed..87a66ac86ab92 100644 --- a/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "17.0.0", + "version": "19.0.0", "artifacts": { "Tree": { "type": "cdk:tree", diff --git a/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/tree.json b/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/tree.json index 9512a839b4c35..1704713d2033f 100644 --- a/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-amplify/test/app.integ.snapshot/tree.json @@ -113,7 +113,18 @@ ] } }, - "customHeaders": "customHeaders:\n - pattern: \"*.json\"\n headers:\n - key: custom-header-name-1\n value: custom-header-value-1\n - key: custom-header-name-2\n value: custom-header-value-2\n - pattern: /path/*\n headers:\n - key: custom-header-name-1\n value: custom-header-value-2\n", + "customHeaders": { + "Fn::Join": [ + "", + [ + "customHeaders:\n - pattern: \"*.json\"\n headers:\n - key: \"custom-header-name-1\"\n value: \"custom-header-value-1\"\n - key: \"custom-header-name-2\"\n value: \"custom-header-value-2\"\n - pattern: \"/path/*\"\n headers:\n - key: \"custom-header-name-1\"\n value: \"custom-header-value-2\"\n - key: \"x-aws-url-suffix\"\n value: \"this-is-the-suffix-", + { + "Ref": "AWS::URLSuffix" + }, + "\"\n" + ] + ] + }, "customRules": [ { "source": "/source", diff --git a/packages/@aws-cdk/aws-amplify/test/app.test.ts b/packages/@aws-cdk/aws-amplify/test/app.test.ts index bdb59e43df1fa..d778ce133b337 100644 --- a/packages/@aws-cdk/aws-amplify/test/app.test.ts +++ b/packages/@aws-cdk/aws-amplify/test/app.test.ts @@ -417,11 +417,28 @@ test('with custom headers', () => { 'custom-header-name-1': 'custom-header-value-2', }, }, + { + pattern: '/with-tokens/*', + headers: { + 'x-custom': `${'hello'.repeat(10)}${Stack.of(stack).urlSuffix} `, + }, + }, ], }); // THEN Template.fromStack(stack).hasResourceProperties('AWS::Amplify::App', { - CustomHeaders: 'customHeaders:\n - pattern: "*.json"\n headers:\n - key: custom-header-name-1\n value: custom-header-value-1\n - key: custom-header-name-2\n value: custom-header-value-2\n - pattern: /path/*\n headers:\n - key: custom-header-name-1\n value: custom-header-value-2\n', + CustomHeaders: { + 'Fn::Join': [ + '', + [ + 'customHeaders:\n - pattern: "*.json"\n headers:\n - key: "custom-header-name-1"\n value: "custom-header-value-1"\n - key: "custom-header-name-2"\n value: "custom-header-value-2"\n - pattern: "/path/*"\n headers:\n - key: "custom-header-name-1"\n value: "custom-header-value-2"\n - pattern: "/with-tokens/*"\n headers:\n - key: "x-custom"\n value: "hellohellohellohellohellohellohellohellohellohello', + { + Ref: 'AWS::URLSuffix', + }, + ' "\n', + ], + ], + }, }); }); diff --git a/packages/@aws-cdk/aws-amplify/test/integ.app.ts b/packages/@aws-cdk/aws-amplify/test/integ.app.ts index accdaed6840bf..b9c6f0e0872f2 100644 --- a/packages/@aws-cdk/aws-amplify/test/integ.app.ts +++ b/packages/@aws-cdk/aws-amplify/test/integ.app.ts @@ -21,6 +21,7 @@ class TestStack extends Stack { pattern: '/path/*', headers: { 'custom-header-name-1': 'custom-header-value-2', + 'x-aws-url-suffix': `this-is-the-suffix-${Stack.of(this).urlSuffix}`, }, }, ], From bb625c8d7edd72db7b6d12e12fb032884e3e0741 Mon Sep 17 00:00:00 2001 From: Peter Woodworth <44349620+peterwoodworth@users.noreply.github.com> Date: Wed, 18 May 2022 11:26:44 -0700 Subject: [PATCH 07/29] chore: remove instances of deprecated subnetType enums (#20183) ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-apigatewayv2/lib/http/vpc-link.ts | 2 +- .../aws-appsync/test/appsync-rds.test.ts | 4 ++-- .../test/auto-scaling-group.test.ts | 2 +- .../aws-batch/test/compute-environment.test.ts | 2 +- packages/@aws-cdk/aws-cloud9/README.md | 2 +- .../aws-cloud9/test/cloud9.environment.test.ts | 4 ++-- packages/@aws-cdk/aws-docdb/test/cluster.test.ts | 2 +- packages/@aws-cdk/aws-ec2/README.md | 6 +++--- packages/@aws-cdk/aws-ecs-patterns/README.md | 2 +- .../lib/base/scheduled-task-base.ts | 2 +- ....queue-processing-fargate-service-isolated.ts | 6 +++--- .../load-balanced-fargate-service.test.ts | 4 ++-- .../queue-processing-fargate-service.test.ts | 4 ++-- .../aws-efs/test/efs-file-system.test.ts | 2 +- packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts | 4 ++-- packages/@aws-cdk/aws-eks/README.md | 2 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 4 ++-- packages/@aws-cdk/aws-eks/lib/fargate-profile.ts | 2 +- packages/@aws-cdk/aws-eks/test/cluster.test.ts | 8 ++++---- .../lib/load-balancer.ts | 2 +- .../test/loadbalancer.test.ts | 4 ++-- .../test/nlb/load-balancer.test.ts | 16 ++++++++-------- .../@aws-cdk/aws-elasticsearch/lib/domain.ts | 2 +- .../@aws-cdk/aws-events-targets/lib/ecs-task.ts | 2 +- .../test/ecs/event-rule-target.test.ts | 4 ++-- .../@aws-cdk/aws-lambda/test/vpc-lambda.test.ts | 10 +++++----- packages/@aws-cdk/aws-neptune/lib/cluster.ts | 2 +- .../@aws-cdk/aws-neptune/lib/subnet-group.ts | 2 +- .../@aws-cdk/aws-neptune/test/cluster.test.ts | 2 +- .../@aws-cdk/aws-neptune/test/integ.cluster.ts | 2 +- .../aws-neptune/test/subnet-group.test.ts | 2 +- .../@aws-cdk/aws-opensearchservice/lib/domain.ts | 2 +- packages/@aws-cdk/aws-rds/README.md | 8 ++++---- packages/@aws-cdk/aws-rds/lib/subnet-group.ts | 2 +- packages/@aws-cdk/aws-rds/test/cluster.test.ts | 2 +- packages/@aws-cdk/aws-rds/test/instance.test.ts | 2 +- .../@aws-cdk/aws-rds/test/subnet-group.test.ts | 4 ++-- packages/@aws-cdk/aws-redshift/lib/cluster.ts | 2 +- .../@aws-cdk/aws-redshift/lib/subnet-group.ts | 2 +- .../test/integ.interface-vpc-endpoint-target.ts | 2 +- .../lib/ecs/run-ecs-task-base.ts | 2 +- .../aws-stepfunctions-tasks/lib/ecs/run-task.ts | 2 +- .../test/provider-framework/provider.test.ts | 4 ++-- packages/@aws-cdk/pipelines/README.md | 4 ++-- 44 files changed, 76 insertions(+), 76 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts index 27a98085e334e..c9a13e08d31ea 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts @@ -99,7 +99,7 @@ export class VpcLink extends Resource implements IVpcLink { this.vpcLinkId = cfnResource.ref; - const { subnets } = props.vpc.selectSubnets(props.subnets ?? { subnetType: ec2.SubnetType.PRIVATE }); + const { subnets } = props.vpc.selectSubnets(props.subnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }); this.addSubnets(...subnets); if (props.securityGroups) { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts index cc7329344b84e..c98b1bdedabab 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts @@ -36,7 +36,7 @@ describe('Rds Data Source configuration', () => { credentials: { username: 'clusteradmin' }, clusterIdentifier: 'db-endpoint-test', vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, securityGroups: [securityGroup], defaultDatabaseName: 'Animals', }); @@ -235,7 +235,7 @@ describe('adding rds data source from imported api', () => { credentials: { username: 'clusteradmin' }, clusterIdentifier: 'db-endpoint-test', vpc, - vpcSubnets: { subnetType: SubnetType.PRIVATE }, + vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT }, securityGroups: [securityGroup], defaultDatabaseName: 'Animals', }); diff --git a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts index 5c9da4e53d458..dbdf5ed4415f2 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts @@ -1791,7 +1791,7 @@ test('can use Vpc imported from unparseable list tokens', () => { vpc, allowAllOutbound: false, associatePublicIpAddress: false, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, }); // THEN diff --git a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts index 4cd446eec3774..e25f61bd06ded 100644 --- a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts +++ b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts @@ -347,7 +347,7 @@ describe('Batch Compute Environment', () => { ], type: batch.ComputeResourceType.ON_DEMAND, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, } as batch.ComputeResources, enabled: false, diff --git a/packages/@aws-cdk/aws-cloud9/README.md b/packages/@aws-cdk/aws-cloud9/README.md index ba418cc56dd46..86b38e3944cd3 100644 --- a/packages/@aws-cdk/aws-cloud9/README.md +++ b/packages/@aws-cdk/aws-cloud9/README.md @@ -55,7 +55,7 @@ new cloud9.Ec2Environment(this, 'Cloud9Env2', { const c9env = new cloud9.Ec2Environment(this, 'Cloud9Env3', { vpc, subnetSelection: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, }); diff --git a/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts b/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts index 079fe546d0a4f..6b5f87ab6c7d8 100644 --- a/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts +++ b/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts @@ -24,7 +24,7 @@ test('create resource correctly with both vpc and subnetSelectio', () => { new cloud9.Ec2Environment(stack, 'C9Env', { vpc, subnetSelection: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, }); // THEN @@ -54,7 +54,7 @@ test('throw error when subnetSelection not specified and the provided VPC has no maxAzs: 2, subnetConfiguration: [ { - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, name: 'IsolatedSubnet', cidrMask: 24, }, diff --git a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts index e9632f7765fc5..0c553e51b04b9 100644 --- a/packages/@aws-cdk/aws-docdb/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-docdb/test/cluster.test.ts @@ -108,7 +108,7 @@ describe('DatabaseCluster', () => { vpc, instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE), vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, }); }).toThrowError('Cluster requires at least 2 subnets, got 1'); diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 1e0f2919573db..e393fe3c7aeb4 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -131,7 +131,7 @@ new ec2.InterfaceVpcEndpoint(this, 'VPC Endpoint', { vpc, service: new ec2.InterfaceVpcEndpointService('com.amazonaws.vpce.us-east-1.vpce-svc-uuddlrlrbastrtsvc', 443), subnets: { - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, availabilityZones: ['us-east-1a', 'us-east-1c'] } }); @@ -325,7 +325,7 @@ const vpc = new ec2.Vpc(this, "VPC", { subnetType: ec2.SubnetType.PUBLIC, name: 'Public', },{ - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, name: 'Isolated', }] }); @@ -372,7 +372,7 @@ const vpc = new ec2.Vpc(this, 'TheVPC', { { cidrMask: 27, name: 'Database', - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, } ], }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index 593421abeaa1a..d94f732282c40 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -491,7 +491,7 @@ const queueProcessingFargateService = new ecsPatterns.QueueProcessingFargateServ memoryLimitMiB: 512, image: ecs.ContainerImage.fromRegistry('test'), securityGroups: [securityGroup], - taskSubnets: { subnetType: ec2.SubnetType.ISOLATED }, + taskSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, }); ``` 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 13348e103adec..1afdd7e2dce54 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 @@ -161,7 +161,7 @@ export abstract class ScheduledTaskBase extends CoreConstruct { throw new Error('You must specify a desiredTaskCount greater than 0'); } this.desiredTaskCount = props.desiredTaskCount || 1; - this.subnetSelection = props.subnetSelection || { subnetType: SubnetType.PRIVATE }; + this.subnetSelection = props.subnetSelection || { subnetType: SubnetType.PRIVATE_WITH_NAT }; this._securityGroups = props.securityGroups; // An EventRule that describes the event trigger (in this case a scheduled run) diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service-isolated.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service-isolated.ts index f985ac8c9d564..dea2cff4cf47a 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service-isolated.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.queue-processing-fargate-service-isolated.ts @@ -18,13 +18,13 @@ const vpc = new ec2.Vpc(stack, 'VPC', { { cidrMask: 24, name: 'Isolated', - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, }, ], }); -vpc.addS3Endpoint('S3Endpoint', [{ subnetType: ec2.SubnetType.ISOLATED }]); +vpc.addS3Endpoint('S3Endpoint', [{ subnetType: ec2.SubnetType.PRIVATE_ISOLATED }]); const securityGroup = new ec2.SecurityGroup(stack, 'MyCustomSG', { vpc, @@ -35,7 +35,7 @@ const queueProcessing = new QueueProcessingFargateService(stack, 'IsolatedQueueS memoryLimitMiB: 512, image: new ecs.AssetImage(path.join(__dirname, '..', 'sqs-reader')), securityGroups: [securityGroup], - taskSubnets: { subnetType: ec2.SubnetType.ISOLATED }, + taskSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, }); queueProcessing.service.node.addDependency( diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts index a00b676894c83..4acb1cc30150b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts @@ -245,7 +245,7 @@ test('selecting correct vpcSubnets', () => { name: 'Public', }, { - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, cidrMask: 20, name: 'ISOLATED', }, @@ -258,7 +258,7 @@ test('selecting correct vpcSubnets', () => { image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), }, taskSubnets: { - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, }, }); // THEN diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts index b6ca462f52a2b..ba653fc429963 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts @@ -463,7 +463,7 @@ test('can set custom networking options', () => { { cidrMask: 24, name: 'Isolated', - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, }, ], }); @@ -477,7 +477,7 @@ test('can set custom networking options', () => { memoryLimitMiB: 512, image: ecs.ContainerImage.fromRegistry('test'), securityGroups: [securityGroup], - taskSubnets: { subnetType: ec2.SubnetType.ISOLATED }, + taskSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, }); // THEN - NetworkConfiguration is created with the specific security groups and selected subnets 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 f6eb888575078..d824089f2ee5a 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 @@ -403,7 +403,7 @@ 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 }], + subnetConfiguration: [{ name: 'One', subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, { name: 'Two', subnetType: ec2.SubnetType.PRIVATE_ISOLATED }], natGateways: 0, }); new FileSystem(stack, 'EfsFileSystem', { diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts index 8c364f7268b3a..4c92fed073c7c 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/cluster.ts @@ -110,7 +110,7 @@ export interface ClusterProps { * * ```ts * const vpcSubnets = [ - * { subnetType: ec2.SubnetType.PRIVATE } + * { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT } * ] * ``` * @@ -368,7 +368,7 @@ export class Cluster extends Resource implements ICluster { }); // Get subnetIds for all selected subnets - const placements = props.vpcSubnets || [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE }]; + const placements = props.vpcSubnets || [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }]; const subnetIds = [...new Set(Array().concat(...placements.map(s => this.vpc.selectSubnets(s).subnetIds)))]; const clusterProps: CfnClusterProps = { diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 62c9c0d379d88..79125916d508b 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -579,7 +579,7 @@ declare const vpc: ec2.Vpc; new eks.Cluster(this, 'HelloEKS', { version: eks.KubernetesVersion.V1_21, vpc, - vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE }], + vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }], }); ``` diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index b73616179b9bd..9d5de9f183e9b 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -405,7 +405,7 @@ export interface CommonClusterOptions { * * For example, to only select private subnets, supply the following: * - * `vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE }]` + * `vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }]` * * @default - All public and private subnets */ @@ -1342,7 +1342,7 @@ export class Cluster extends ClusterBase { description: 'EKS Control Plane Security Group', }); - this.vpcSubnets = props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE }]; + this.vpcSubnets = props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }]; const selectedSubnetIdsPerGroup = this.vpcSubnets.map(s => this.vpc.selectSubnets(s).subnetIds); if (selectedSubnetIdsPerGroup.some(Token.isUnresolved) && selectedSubnetIdsPerGroup.length > 1) { diff --git a/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts b/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts index 8d5b0301ff24b..7625a4cfafb5d 100644 --- a/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts +++ b/packages/@aws-cdk/aws-eks/lib/fargate-profile.ts @@ -165,7 +165,7 @@ export class FargateProfile extends CoreConstruct implements ITaggable { let subnets: string[] | undefined; if (props.vpc) { - const selection: ec2.SubnetSelection = props.subnetSelection ?? { subnetType: ec2.SubnetType.PRIVATE }; + const selection: ec2.SubnetSelection = props.subnetSelection ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }; subnets = props.vpc.selectSubnets(selection).subnetIds; } diff --git a/packages/@aws-cdk/aws-eks/test/cluster.test.ts b/packages/@aws-cdk/aws-eks/test/cluster.test.ts index c10d9b2e49d23..b0abd8ffc28ef 100644 --- a/packages/@aws-cdk/aws-eks/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks/test/cluster.test.ts @@ -135,7 +135,7 @@ describe('cluster', () => { test('throws if selecting more than one subnet group', () => { expect(() => new eks.Cluster(stack, 'Cluster', { vpc: vpc, - vpcSubnets: [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE }], + vpcSubnets: [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }], defaultCapacity: 0, version: eks.KubernetesVersion.V1_21, })).toThrow(/cannot select multiple subnet groups/); @@ -2807,7 +2807,7 @@ describe('cluster', () => { natGateways: 1, subnetConfiguration: [ { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, name: 'Private1', }, { @@ -2866,7 +2866,7 @@ describe('cluster', () => { for (let i = 0; i < 20; i++) { subnetConfiguration.push({ - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, name: `Private${i}`, }, ); @@ -2915,7 +2915,7 @@ describe('cluster', () => { for (let i = 0; i < 20; i++) { subnetConfiguration.push({ - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, name: `Private${i}`, }, ); diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts index e4029c9dc55c6..830373ec6cd95 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts @@ -477,7 +477,7 @@ function loadBalancerSubnets(props: LoadBalancerProps): SelectedSubnets { }); } else { return props.vpc.selectSubnets({ - subnetType: SubnetType.PRIVATE, + subnetType: SubnetType.PRIVATE_WITH_NAT, }); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts index 5497f380c5f76..cc6c50ab8df8f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/loadbalancer.test.ts @@ -151,12 +151,12 @@ describe('tests', () => { }, { name: 'private1', - subnetType: SubnetType.PRIVATE, + subnetType: SubnetType.PRIVATE_WITH_NAT, cidrMask: 21, }, { name: 'private2', - subnetType: SubnetType.PRIVATE, + subnetType: SubnetType.PRIVATE_WITH_NAT, cidrMask: 21, }, ], diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts index afc90779dfff4..caf0b6ed751c5 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/load-balancer.test.ts @@ -402,7 +402,7 @@ describe('tests', () => { subnetConfiguration: [{ cidrMask: 20, name: 'Isolated', - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, }], }); @@ -433,11 +433,11 @@ describe('tests', () => { }, { cidrMask: 24, name: 'Private', - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, { cidrMask: 28, name: 'Isolated', - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, }], }); @@ -468,11 +468,11 @@ describe('tests', () => { }, { cidrMask: 24, name: 'Private', - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, { cidrMask: 28, name: 'Isolated', - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, }], }); @@ -525,11 +525,11 @@ describe('tests', () => { }, { cidrMask: 24, name: 'Private', - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, { cidrMask: 28, name: 'Isolated', - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, }], }); @@ -537,7 +537,7 @@ describe('tests', () => { new elbv2.NetworkLoadBalancer(stack, 'LB', { vpc, internetFacing: false, - vpcSubnets: { subnetType: ec2.SubnetType.ISOLATED }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, }); // THEN diff --git a/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts b/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts index 61dcd87e862ef..e48d794670c0d 100644 --- a/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts +++ b/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts @@ -1502,7 +1502,7 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { let subnets: ec2.ISubnet[] | undefined; if (props.vpc) { - subnets = selectSubnets(props.vpc, props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PRIVATE }]); + subnets = selectSubnets(props.vpc, props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }]); securityGroups = props.securityGroups ?? [new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc, description: `Security group for domain ${this.node.id}`, diff --git a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts index c3bed0dc2c048..13a08dbd8d4eb 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts @@ -162,7 +162,7 @@ export class EcsTask implements events.IRuleTarget { const taskCount = this.taskCount; const taskDefinitionArn = this.taskDefinition.taskDefinitionArn; - const subnetSelection = this.props.subnetSelection || { subnetType: ec2.SubnetType.PRIVATE }; + const subnetSelection = this.props.subnetSelection || { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }; const assignPublicIp = subnetSelection.subnetType === ec2.SubnetType.PUBLIC ? 'ENABLED' : 'DISABLED'; const baseEcsParameters = { taskCount, taskDefinitionArn }; diff --git a/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts b/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts index 86045087ab756..25b04148016b4 100644 --- a/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/ecs/event-rule-target.test.ts @@ -410,7 +410,7 @@ test('Isolated subnet does not have AssignPublicIp=true', () => { vpc = new ec2.Vpc(stack, 'Vpc2', { maxAzs: 1, subnetConfiguration: [{ - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, name: 'Isolated', }], }); @@ -430,7 +430,7 @@ test('Isolated subnet does not have AssignPublicIp=true', () => { cluster, taskDefinition, taskCount: 1, - subnetSelection: { subnetType: ec2.SubnetType.ISOLATED }, + subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, containerOverrides: [{ containerName: 'TheContainer', command: ['echo', 'yay'], diff --git a/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts index aa7587411fa26..d8ad105edaa6d 100644 --- a/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/vpc-lambda.test.ts @@ -238,7 +238,7 @@ describe('lambda + vpc', () => { handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, }); // THEN @@ -263,7 +263,7 @@ describe('lambda + vpc', () => { subnetConfiguration: [ { name: 'Isolated', - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, }, ], }); @@ -274,7 +274,7 @@ describe('lambda + vpc', () => { handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, vpc, - vpcSubnets: { subnetType: ec2.SubnetType.ISOLATED }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, }); // THEN @@ -303,11 +303,11 @@ describe('lambda + vpc', () => { }, { name: 'Private', - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, { name: 'Isolated', - subnetType: ec2.SubnetType.ISOLATED, + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, }, ], }); diff --git a/packages/@aws-cdk/aws-neptune/lib/cluster.ts b/packages/@aws-cdk/aws-neptune/lib/cluster.ts index 2a33f4e4629fa..a9303286b3693 100644 --- a/packages/@aws-cdk/aws-neptune/lib/cluster.ts +++ b/packages/@aws-cdk/aws-neptune/lib/cluster.ts @@ -435,7 +435,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu super(scope, id); this.vpc = props.vpc; - this.vpcSubnets = props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE }; + this.vpcSubnets = props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }; // Determine the subnet(s) to deploy the Neptune cluster to const { subnetIds, internetConnectivityEstablished } = this.vpc.selectSubnets(this.vpcSubnets); diff --git a/packages/@aws-cdk/aws-neptune/lib/subnet-group.ts b/packages/@aws-cdk/aws-neptune/lib/subnet-group.ts index 383435b7a0b38..d46c0163cfa34 100644 --- a/packages/@aws-cdk/aws-neptune/lib/subnet-group.ts +++ b/packages/@aws-cdk/aws-neptune/lib/subnet-group.ts @@ -74,7 +74,7 @@ export class SubnetGroup extends Resource implements ISubnetGroup { constructor(scope: Construct, id: string, props: SubnetGroupProps) { super(scope, id); - const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE }); + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }); const subnetGroup = new CfnDBSubnetGroup(this, 'Resource', { dbSubnetGroupDescription: props.description || 'Subnet group for Neptune', diff --git a/packages/@aws-cdk/aws-neptune/test/cluster.test.ts b/packages/@aws-cdk/aws-neptune/test/cluster.test.ts index e4318d5521028..915fe9cf34d0a 100644 --- a/packages/@aws-cdk/aws-neptune/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-neptune/test/cluster.test.ts @@ -91,7 +91,7 @@ describe('DatabaseCluster', () => { instances: 1, vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, instanceType: InstanceType.R5_LARGE, }); diff --git a/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts b/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts index 2a29ebe2cc1c2..1df64b274c226 100644 --- a/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-neptune/test/integ.cluster.ts @@ -30,7 +30,7 @@ class TestStack extends cdk.Stack { const cluster = new DatabaseCluster(this, 'Database', { vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, instanceType: InstanceType.R5_LARGE, clusterParameterGroup: params, kmsKey, diff --git a/packages/@aws-cdk/aws-neptune/test/subnet-group.test.ts b/packages/@aws-cdk/aws-neptune/test/subnet-group.test.ts index 5e736d212a12e..b1b355b6369bd 100644 --- a/packages/@aws-cdk/aws-neptune/test/subnet-group.test.ts +++ b/packages/@aws-cdk/aws-neptune/test/subnet-group.test.ts @@ -31,7 +31,7 @@ test('creates a subnet group from all properties', () => { description: 'My Shared Group', subnetGroupName: 'SharedGroup', vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, }); Template.fromStack(stack).hasResourceProperties('AWS::Neptune::DBSubnetGroup', { diff --git a/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts b/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts index 24d5a91fea54d..807fefc0e40ec 100644 --- a/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts +++ b/packages/@aws-cdk/aws-opensearchservice/lib/domain.ts @@ -1236,7 +1236,7 @@ export class Domain extends DomainBase implements IDomain, ec2.IConnectable { let subnets: ec2.ISubnet[] | undefined; if (props.vpc) { - subnets = selectSubnets(props.vpc, props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PRIVATE }]); + subnets = selectSubnets(props.vpc, props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }]); securityGroups = props.securityGroups ?? [new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc, description: `Security group for domain ${this.node.id}`, diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 2cd840e202504..dcf365bb2eb8d 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -31,7 +31,7 @@ const cluster = new rds.DatabaseCluster(this, 'Database', { // optional , defaults to t3.medium instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, vpc, }, @@ -78,7 +78,7 @@ const instance = new rds.DatabaseInstance(this, 'Instance', { credentials: rds.Credentials.fromGeneratedSecret('syscdk'), // Optional - will default to 'admin' username and generated password vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, } }); ``` @@ -154,7 +154,7 @@ new rds.DatabaseInstance(this, 'Instance', { }), vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, publiclyAccessible: true, }); @@ -165,7 +165,7 @@ new rds.DatabaseCluster(this, 'DatabaseCluster', { instanceProps: { vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, publiclyAccessible: true, }, diff --git a/packages/@aws-cdk/aws-rds/lib/subnet-group.ts b/packages/@aws-cdk/aws-rds/lib/subnet-group.ts index 373129702b2cc..c1fcd071ff154 100644 --- a/packages/@aws-cdk/aws-rds/lib/subnet-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/subnet-group.ts @@ -72,7 +72,7 @@ export class SubnetGroup extends Resource implements ISubnetGroup { constructor(scope: Construct, id: string, props: SubnetGroupProps) { super(scope, id); - const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE }); + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }); // Using 'Default' as the resource id for historical reasons (usage from `Instance` and `Cluster`). const subnetGroup = new CfnDBSubnetGroup(this, 'Default', { diff --git a/packages/@aws-cdk/aws-rds/test/cluster.test.ts b/packages/@aws-cdk/aws-rds/test/cluster.test.ts index c89a31ba45b98..718951a81580d 100644 --- a/packages/@aws-cdk/aws-rds/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-rds/test/cluster.test.ts @@ -2294,7 +2294,7 @@ describe('cluster', () => { instanceProps: { vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, publiclyAccessible: true, }, diff --git a/packages/@aws-cdk/aws-rds/test/instance.test.ts b/packages/@aws-cdk/aws-rds/test/instance.test.ts index 3d100aa0ab5b2..695342f02f255 100644 --- a/packages/@aws-cdk/aws-rds/test/instance.test.ts +++ b/packages/@aws-cdk/aws-rds/test/instance.test.ts @@ -1519,7 +1519,7 @@ describe('instance', () => { }), vpc, vpcSubnets: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, publiclyAccessible: true, }); diff --git a/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts b/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts index 44fd4e24482a8..397a1e6b774e4 100644 --- a/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts +++ b/packages/@aws-cdk/aws-rds/test/subnet-group.test.ts @@ -32,7 +32,7 @@ describe('subnet group', () => { description: 'My Shared Group', subnetGroupName: 'SharedGroup', vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, }); Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBSubnetGroup', { @@ -51,7 +51,7 @@ describe('subnet group', () => { description: 'My Shared Group', subnetGroupName: parameter.valueAsString, vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, }); Template.fromStack(stack).hasResourceProperties('AWS::RDS::DBSubnetGroup', { diff --git a/packages/@aws-cdk/aws-redshift/lib/cluster.ts b/packages/@aws-cdk/aws-redshift/lib/cluster.ts index b3e72c361e5c0..ab86540d3d7b3 100644 --- a/packages/@aws-cdk/aws-redshift/lib/cluster.ts +++ b/packages/@aws-cdk/aws-redshift/lib/cluster.ts @@ -415,7 +415,7 @@ export class Cluster extends ClusterBase { this.vpc = props.vpc; this.vpcSubnets = props.vpcSubnets ?? { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }; const removalPolicy = props.removalPolicy ?? RemovalPolicy.RETAIN; diff --git a/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts b/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts index 1d4a6cbb25eca..35d8c53c8826f 100644 --- a/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts +++ b/packages/@aws-cdk/aws-redshift/lib/subnet-group.ts @@ -65,7 +65,7 @@ export class ClusterSubnetGroup extends Resource implements IClusterSubnetGroup constructor(scope: Construct, id: string, props: ClusterSubnetGroupProps) { super(scope, id); - const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE }); + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }); const subnetGroup = new CfnClusterSubnetGroup(this, 'Default', { description: props.description, diff --git a/packages/@aws-cdk/aws-route53-targets/test/integ.interface-vpc-endpoint-target.ts b/packages/@aws-cdk/aws-route53-targets/test/integ.interface-vpc-endpoint-target.ts index d8ce4f0cab4ab..922d554267577 100644 --- a/packages/@aws-cdk/aws-route53-targets/test/integ.interface-vpc-endpoint-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/test/integ.interface-vpc-endpoint-target.ts @@ -21,7 +21,7 @@ const interfaceVpcEndpoint = new ec2.InterfaceVpcEndpoint(stack, 'InterfaceEndpo }, privateDnsEnabled: false, subnets: { - subnetType: ec2.SubnetType.PRIVATE, + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, }); const zone = new route53.PrivateHostedZone(stack, 'PrivateZone', { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts index 381c6ab324c73..99dbed1849b7a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-ecs-task-base.ts @@ -128,7 +128,7 @@ export class EcsRunTaskBase implements ec2.IConnectable, sfn.IStepFunctionsTask securityGroup?: ec2.ISecurityGroup) { if (subnetSelection === undefined) { - subnetSelection = { subnetType: assignPublicIp ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE }; + subnetSelection = { subnetType: assignPublicIp ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE_WITH_NAT }; } // If none is given here, one will be created later on during bind() diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts index 26dbc84a00e56..934bcd763a776 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs/run-task.ts @@ -289,7 +289,7 @@ export class EcsRunTask extends sfn.TaskStateBase implements ec2.IConnectable { } private configureAwsVpcNetworking() { - const subnetSelection = this.props.subnets ?? { subnetType: this.props.assignPublicIp ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE }; + const subnetSelection = this.props.subnets ?? { subnetType: this.props.assignPublicIp ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE_WITH_NAT }; this.networkConfiguration = { AwsvpcConfiguration: { diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts index ac09be73e0f89..ecab3a86edc7a 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts @@ -29,7 +29,7 @@ test('security groups are applied to all framework functions', () => { runtime: lambda.Runtime.NODEJS_10_X, }), vpc: vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, securityGroups: [securityGroup], }); @@ -97,7 +97,7 @@ test('vpc is applied to all framework functions', () => { runtime: lambda.Runtime.NODEJS_10_X, }), vpc: vpc, - vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, }); Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md index 3810bce422463..36a0a52488d18 100644 --- a/packages/@aws-cdk/pipelines/README.md +++ b/packages/@aws-cdk/pipelines/README.md @@ -725,7 +725,7 @@ new pipelines.CodeBuildStep('Synth', { // Control Elastic Network Interface creation vpc: vpc, - subnetSelection: { subnetType: ec2.SubnetType.PRIVATE }, + subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, securityGroups: [mySecurityGroup], // Additional policy statements for the execution role @@ -770,7 +770,7 @@ new pipelines.CodePipeline(this, 'Pipeline', { // Control Elastic Network Interface creation vpc: vpc, - subnetSelection: { subnetType: ec2.SubnetType.PRIVATE }, + subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }, securityGroups: [mySecurityGroup], // Additional policy statements for the execution role From 51d89f8e5d3eda643ff65d2782b5c1525b9458d1 Mon Sep 17 00:00:00 2001 From: Rico Huijbers <rix0rrr@gmail.com> Date: Wed, 18 May 2022 21:11:49 +0200 Subject: [PATCH 08/29] chore(core): show counts of resources when stack is overflowing (#20398) This makes it easier to diagnose why you are overlowing the maximum of 500 resources. ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/core/lib/stack.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index 91410813fa176..4e6287f72fc2b 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -791,7 +791,8 @@ export class Stack extends CoreConstruct implements ITaggable { const numberOfResources = Object.keys(resources).length; if (numberOfResources > this.maxResources) { - throw new Error(`Number of resources in stack '${this.node.path}': ${numberOfResources} is greater than allowed maximum of ${this.maxResources}`); + const counts = Object.entries(count(Object.values(resources).map((r: any) => `${r?.Type}`))).map(([type, c]) => `${type} (${c})`).join(', '); + throw new Error(`Number of resources in stack '${this.node.path}': ${numberOfResources} is greater than allowed maximum of ${this.maxResources}: ${counts}`); } else if (numberOfResources >= (this.maxResources * 0.8)) { Annotations.of(this).addInfo(`Number of resources: ${numberOfResources} is approaching allowed maximum of ${this.maxResources}`); } @@ -1357,6 +1358,18 @@ export interface ExportValueOptions { readonly name?: string; } +function count(xs: string[]): Record<string, number> { + const ret: Record<string, number> = {}; + for (const x of xs) { + if (x in ret) { + ret[x] += 1; + } else { + ret[x] = 1; + } + } + return ret; +} + // These imports have to be at the end to prevent circular imports import { CfnOutput } from './cfn-output'; import { addDependency } from './deps'; From dc9536a1ab31d9660571c5a68ee7cc1092283a01 Mon Sep 17 00:00:00 2001 From: Calvin Combs <66279577+comcalvi@users.noreply.github.com> Date: Wed, 18 May 2022 16:22:47 -0700 Subject: [PATCH 09/29] feat(core): allow disabling of LogicalID Metadata in case of large manifest (#20387) Users have encountered an error resulting from the manifest being too large to stringify. This allows users to prevent this metadata from ever being added to the manifest. Fixes #20211. ---- *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/cfn-element.ts | 5 +- packages/@aws-cdk/core/test/synthesis.test.ts | 85 +++++++++++++++++-- packages/@aws-cdk/cx-api/lib/app.ts | 6 ++ 3 files changed, 90 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/core/lib/cfn-element.ts b/packages/@aws-cdk/core/lib/cfn-element.ts index 9bb08746c4a47..84126add4e13c 100644 --- a/packages/@aws-cdk/core/lib/cfn-element.ts +++ b/packages/@aws-cdk/core/lib/cfn-element.ts @@ -1,4 +1,5 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cxapi from '@aws-cdk/cx-api'; import { Construct, Node } from 'constructs'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. @@ -64,7 +65,9 @@ export abstract class CfnElement extends CoreConstruct { displayHint: `${notTooLong(Node.of(this).path)}.LogicalID`, }); - Node.of(this).addMetadata(cxschema.ArtifactMetadataEntryType.LOGICAL_ID, this.logicalId, this.constructor); + if (!this.node.tryGetContext(cxapi.DISABLE_LOGICAL_ID_METADATA)) { + Node.of(this).addMetadata(cxschema.ArtifactMetadataEntryType.LOGICAL_ID, this.logicalId, this.constructor); + } } /** diff --git a/packages/@aws-cdk/core/test/synthesis.test.ts b/packages/@aws-cdk/core/test/synthesis.test.ts index 496fd76fdbd91..a419a481da3f3 100644 --- a/packages/@aws-cdk/core/test/synthesis.test.ts +++ b/packages/@aws-cdk/core/test/synthesis.test.ts @@ -36,7 +36,6 @@ describe('synthesis', () => { }, }), }); - }); test('synthesis respects disabling tree metadata', () => { @@ -45,7 +44,87 @@ describe('synthesis', () => { }); const assembly = app.synth(); expect(list(assembly.directory)).toEqual(['cdk.out', 'manifest.json']); + }); + + test('synthesis respects disabling logicalId metadata', () => { + const app = new cdk.App({ + context: { 'aws:cdk:disable-logicalId-metadata': true }, + }); + const stack = new cdk.Stack(app, 'one-stack'); + new cdk.CfnResource(stack, 'MagicResource', { type: 'Resource::Type' }); + + // WHEN + const session = app.synth(); + + // THEN + expect(session.manifest).toEqual({ + version: cxschema.Manifest.version(), + artifacts: { + 'Tree': { + type: 'cdk:tree', + properties: { file: 'tree.json' }, + }, + 'one-stack': { + type: 'aws:cloudformation:stack', + environment: 'aws://unknown-account/unknown-region', + properties: { + templateFile: 'one-stack.template.json', + validateOnSynth: false, + }, + displayName: 'one-stack', + // no metadata, because the only entry was a logicalId + }, + }, + }); + }); + + test('synthesis respects disabling logicalId metadata, and does not disable other metadata', () => { + const app = new cdk.App({ + context: { 'aws:cdk:disable-logicalId-metadata': true }, + stackTraces: false, + }); + const stack = new cdk.Stack(app, 'one-stack', { tags: { boomTag: 'BOOM' } }); + new cdk.CfnResource(stack, 'MagicResource', { type: 'Resource::Type' }); + + // WHEN + const session = app.synth(); + // THEN + expect(session.manifest).toEqual({ + version: cxschema.Manifest.version(), + artifacts: { + 'Tree': { + type: 'cdk:tree', + properties: { file: 'tree.json' }, + }, + 'one-stack': { + type: 'aws:cloudformation:stack', + environment: 'aws://unknown-account/unknown-region', + properties: { + templateFile: 'one-stack.template.json', + validateOnSynth: false, + tags: { + boomTag: 'BOOM', + }, + }, + displayName: 'one-stack', + metadata: { + '/one-stack': [ + { + type: 'aws:cdk:stack-tags', + data: [ + { + key: 'boomTag', + value: 'BOOM', + }, + ], + }, + ], + }, + // no logicalId entry + }, + }, + }); }); test('single empty stack', () => { @@ -58,7 +137,6 @@ describe('synthesis', () => { // THEN expect(list(session.directory).includes('one-stack.template.json')).toEqual(true); - }); test('some random construct implements "synthesize"', () => { @@ -112,7 +190,6 @@ describe('synthesis', () => { }, }, }); - }); test('random construct uses addCustomSynthesis', () => { @@ -172,7 +249,6 @@ describe('synthesis', () => { }, }, }); - }); testDeprecated('it should be possible to synthesize without an app', () => { @@ -220,7 +296,6 @@ describe('synthesis', () => { expect(stack.templateFile).toEqual('hey.json'); expect(stack.parameters).toEqual({ paramId: 'paramValue', paramId2: 'paramValue2' }); expect(stack.environment).toEqual({ region: 'us-east-1', account: 'unknown-account', name: 'aws://unknown-account/us-east-1' }); - }); }); diff --git a/packages/@aws-cdk/cx-api/lib/app.ts b/packages/@aws-cdk/cx-api/lib/app.ts index 41c03f374b408..c08fd5868207b 100644 --- a/packages/@aws-cdk/cx-api/lib/app.ts +++ b/packages/@aws-cdk/cx-api/lib/app.ts @@ -39,6 +39,12 @@ export const DISABLE_ASSET_STAGING_CONTEXT = 'aws:cdk:disable-asset-staging'; */ export const DISABLE_METADATA_STACK_TRACE = 'aws:cdk:disable-stack-trace'; +/** + * If this context key is set, the CDK will not store logical ID + * metadata in the manifest. + */ +export const DISABLE_LOGICAL_ID_METADATA = 'aws:cdk:disable-logicalId-metadata'; + /** * Run bundling for stacks specified in this context key */ From 0367fe850af419d0d9e39d9b84e2b22abd4d1250 Mon Sep 17 00:00:00 2001 From: Christopher Rybicki <rybickic@amazon.com> Date: Wed, 18 May 2022 18:07:05 -0700 Subject: [PATCH 10/29] chore: revert "DeployAssert should be private" (#20405) Reverts aws/aws-cdk#20382 This PR causes CDK Java packaging to fail because 'assert' cannot be used as an identifier in Java interfaces: ``` [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project cdk-integ-tests: Compilation failure: [ERROR] /tmp/npm-packrjmLgY/_aws-cdk_integ-tests/src/main/java/software/amazon/awscdk/integtests/IAwsApiCall.java:[28,10] as of release 1.4, 'assert' is a keyword, and may not be used as an identifier [ERROR] /tmp/npm-packrjmLgY/_aws-cdk_integ-tests/src/main/java/software/amazon/awscdk/integtests/IDeployAssert.java:[31,10] as of release 1.4, 'assert' is a keyword, and may not be used as an identifier ``` --- packages/@aws-cdk/integ-tests/README.md | 76 +++++----- .../integ-tests/lib/assertions/common.ts | 5 +- .../lib/assertions/deploy-assert.ts | 128 +++++++++++++++++ .../integ-tests/lib/assertions/index.ts | 4 +- .../lib/assertions/private/deploy-assert.ts | 76 ---------- .../providers/lambda-handler/index.ts | 2 + .../providers/lambda-handler/results.ts | 12 ++ .../providers/lambda-handler/types.ts | 22 +++ .../integ-tests/lib/assertions/sdk.ts | 135 ++++++++---------- .../integ-tests/lib/assertions/types.ts | 60 -------- .../@aws-cdk/integ-tests/lib/test-case.ts | 16 +-- .../integ-tests/rosetta/default.ts-fixture | 1 + .../test/assertions/deploy-assert.test.ts | 17 ++- .../integ-tests/test/assertions/sdk.test.ts | 79 ++++++---- 14 files changed, 327 insertions(+), 306 deletions(-) create mode 100644 packages/@aws-cdk/integ-tests/lib/assertions/deploy-assert.ts delete mode 100644 packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts create mode 100644 packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/results.ts delete mode 100644 packages/@aws-cdk/integ-tests/lib/assertions/types.ts diff --git a/packages/@aws-cdk/integ-tests/README.md b/packages/@aws-cdk/integ-tests/README.md index f428f7d683a7d..0e8fc9b1ca501 100644 --- a/packages/@aws-cdk/integ-tests/README.md +++ b/packages/@aws-cdk/integ-tests/README.md @@ -177,12 +177,7 @@ new IntegTest(app, 'Integ', { testCases: [stackUnderTest, testCaseWithAssets] }) This library also provides a utility to make assertions against the infrastructure that the integration test deploys. -There are two main scenarios in which assertions are created. - -- Part of an integration test using `integ-runner` - -In this case you would create an integration test using the `IntegTest` construct and then make assertions using the `assert` property. -You should **not** utilize the assertion constructs directly, but should instead use the `methods` on `IntegTest.assert`. +The easiest way to do this is to create a `TestCase` and then access the `DeployAssert` that is automatically created. ```ts declare const app: App; @@ -192,35 +187,31 @@ const integ = new IntegTest(app, 'Integ', { testCases: [stack] }); integ.assert.awsApiCall('S3', 'getObject'); ``` -- Part of a normal CDK deployment +### DeployAssert + +Assertions are created by using the `DeployAssert` construct. This construct creates it's own `Stack` separate from +any stacks that you create as part of your integration tests. This `Stack` is treated differently from other stacks +by the `integ-runner` tool. For example, this stack will not be diffed by the `integ-runner`. -In this case you may be using assertions as part of a normal CDK deployment in order to make an assertion on the infrastructure -before the deployment is considered successful. In this case you can utilize the assertions constructs directly. +Any assertions that you create should be created in the scope of `DeployAssert`. For example, ```ts -declare const myAppStack: Stack; +declare const app: App; -new AwsApiCall(myAppStack, 'GetObject', { +const assert = new DeployAssert(app); +new AwsApiCall(assert, 'GetObject', { service: 'S3', api: 'getObject', }); ``` -### DeployAssert - -Assertions are created by using the `DeployAssert` construct. This construct creates it's own `Stack` separate from -any stacks that you create as part of your integration tests. This `Stack` is treated differently from other stacks -by the `integ-runner` tool. For example, this stack will not be diffed by the `integ-runner`. - `DeployAssert` also provides utilities to register your own assertions. ```ts declare const myCustomResource: CustomResource; -declare const stack: Stack; declare const app: App; - -const integ = new IntegTest(app, 'Integ', { testCases: [stack] }); -integ.assert.assert( +const assert = new DeployAssert(app); +assert.assert( 'CustomAssertion', ExpectedResult.objectLike({ foo: 'bar' }), ActualResult.fromCustomResource(myCustomResource, 'data'), @@ -237,12 +228,12 @@ AWS API call to receive some data. This library does this by utilizing CloudForm which means that CloudFormation will call out to a Lambda Function which will use the AWS JavaScript SDK to make the API call. -This can be done by using the class directory (in the case of a normal deployment): +This can be done by using the class directory: ```ts -declare const stack: Stack; +declare const assert: DeployAssert; -new AwsApiCall(stack, 'MyAssertion', { +new AwsApiCall(assert, 'MyAssertion', { service: 'SQS', api: 'receiveMessage', parameters: { @@ -251,15 +242,12 @@ new AwsApiCall(stack, 'MyAssertion', { }); ``` -Or by using the `awsApiCall` method on `DeployAssert` (when writing integration tests): +Or by using the `awsApiCall` method on `DeployAssert`: ```ts declare const app: App; -declare const stack: Stack; -const integ = new IntegTest(app, 'Integ', { - testCases: [stack], -}); -integ.assert.awsApiCall('SQS', 'receiveMessage', { +const assert = new DeployAssert(app); +assert.awsApiCall('SQS', 'receiveMessage', { QueueUrl: 'url', }); ``` @@ -293,18 +281,21 @@ const message = integ.assert.awsApiCall('SQS', 'receiveMessage', { WaitTimeSeconds: 20, }); -message.assertAtPath('Messages.0.Body', ExpectedResult.objectLike({ - requestContext: { - condition: 'Success', - }, - requestPayload: { - status: 'OK', - }, - responseContext: { - statusCode: 200, - }, - responsePayload: 'success', -})); +new EqualsAssertion(integ.assert, 'ReceiveMessage', { + actual: ActualResult.fromAwsApiCall(message, 'Messages.0.Body'), + expected: ExpectedResult.objectLike({ + requestContext: { + condition: 'Success', + }, + requestPayload: { + status: 'OK', + }, + responseContext: { + statusCode: 200, + }, + responsePayload: 'success', + }), +}); ``` #### Match @@ -314,6 +305,7 @@ can be used to construct the `ExpectedResult`. ```ts declare const message: AwsApiCall; +declare const assert: DeployAssert; message.assert(ExpectedResult.objectLike({ Messages: Match.arrayWith([ diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/common.ts b/packages/@aws-cdk/integ-tests/lib/assertions/common.ts index 6daa9e510133c..6e4fadf5a0388 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/common.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/common.ts @@ -1,6 +1,5 @@ import { CustomResource } from '@aws-cdk/core'; -import { IAwsApiCall } from './sdk'; - +import { AwsApiCall } from './sdk'; /** * Represents the "actual" results to compare */ @@ -17,7 +16,7 @@ export abstract class ActualResult { /** * Get the actual results from a AwsApiCall */ - public static fromAwsApiCall(query: IAwsApiCall, attribute: string): ActualResult { + public static fromAwsApiCall(query: AwsApiCall, attribute: string): ActualResult { return { result: query.getAttString(attribute), }; diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/deploy-assert.ts b/packages/@aws-cdk/integ-tests/lib/assertions/deploy-assert.ts new file mode 100644 index 0000000000000..24bbfd6789fbf --- /dev/null +++ b/packages/@aws-cdk/integ-tests/lib/assertions/deploy-assert.ts @@ -0,0 +1,128 @@ +import { Stack } from '@aws-cdk/core'; +import { Construct, IConstruct, Node } from 'constructs'; +import { EqualsAssertion } from './assertions'; +import { ExpectedResult, ActualResult } from './common'; +import { md5hash } from './private/hash'; +import { AwsApiCall, LambdaInvokeFunction, LambdaInvokeFunctionProps } from './sdk'; + +const DEPLOY_ASSERT_SYMBOL = Symbol.for('@aws-cdk/integ-tests.DeployAssert'); + + +// 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 DeployAssert + */ +export interface DeployAssertProps { } + +/** + * Construct that allows for registering a list of assertions + * that should be performed on a construct + */ +export class DeployAssert extends CoreConstruct { + + /** + * Returns whether the construct is a DeployAssert construct + */ + public static isDeployAssert(x: any): x is DeployAssert { + return x !== null && typeof(x) === 'object' && DEPLOY_ASSERT_SYMBOL in x; + } + + /** + * Finds a DeployAssert construct in the given scope + */ + public static of(construct: IConstruct): DeployAssert { + const scopes = Node.of(Node.of(construct).root).findAll(); + const deployAssert = scopes.find(s => DeployAssert.isDeployAssert(s)); + if (!deployAssert) { + throw new Error('No DeployAssert construct found in scopes'); + } + return deployAssert as DeployAssert; + } + + constructor(scope: Construct) { + /** + * Normally we would not want to do a scope swapparoo like this + * but in this case this it allows us to provide a better experience + * for the user. This allows DeployAssert to be created _not_ in the + * scope of a Stack. DeployAssert is treated like a Stack, but doesn't + * exose any of the stack functionality (the methods that the user sees + * are just DeployAssert methods and not any Stack methods). So you can do + * something like this, which you would not normally be allowed to do + * + * const deployAssert = new DeployAssert(app); + * new AwsApiCall(deployAssert, 'AwsApiCall', {...}); + */ + scope = new Stack(scope, 'DeployAssert'); + super(scope, 'Default'); + + Object.defineProperty(this, DEPLOY_ASSERT_SYMBOL, { value: true }); + } + + /** + * Query AWS using JavaScript SDK V2 API calls. This can be used to either + * trigger an action or to return a result that can then be asserted against + * an expected value + * + * @example + * declare const app: App; + * const assert = new DeployAssert(app); + * assert.awsApiCall('SQS', 'sendMessage', { + * QueueUrl: 'url', + * MessageBody: 'hello', + * }); + * const message = assert.awsApiCall('SQS', 'receiveMessage', { + * QueueUrl: 'url', + * }); + * message.assert(ExpectedResult.objectLike({ + * Messages: [{ Body: 'hello' }], + * })); + */ + public awsApiCall(service: string, api: string, parameters?: any): AwsApiCall { + return new AwsApiCall(this, `AwsApiCall${service}${api}`, { + api, + service, + parameters, + }); + } + + /** + * Invoke a lambda function and return the response which can be asserted + * + * @example + * declare const app: App; + * const assert = new DeployAssert(app); + * const invoke = assert.invokeFunction({ + * functionName: 'my-function', + * }); + * invoke.assert(ExpectedResult.objectLike({ + * Payload: '200', + * })); + */ + public invokeFunction(props: LambdaInvokeFunctionProps): LambdaInvokeFunction { + const hash = md5hash(Stack.of(this).resolve(props)); + return new LambdaInvokeFunction(this, `LambdaInvoke${hash}`, props); + } + + /** + * Assert that the ExpectedResult is equal + * to the ActualResult + * + * @example + * declare const deployAssert: DeployAssert; + * declare const apiCall: AwsApiCall; + * deployAssert.assert( + * 'invoke', + * ExpectedResult.objectLike({ Payload: 'OK' }), + * ActualResult.fromAwsApiCall(apiCall, 'Body'), + * ); + */ + public assert(id: string, expected: ExpectedResult, actual: ActualResult): void { + new EqualsAssertion(this, `EqualsAssertion${id}`, { + expected, + actual, + }); + } +} diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/index.ts b/packages/@aws-cdk/integ-tests/lib/assertions/index.ts index 6622ddabcb560..3a9defd954be9 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/index.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/index.ts @@ -1,6 +1,6 @@ -export * from './types'; -export * from './sdk'; export * from './assertions'; +export * from './sdk'; +export * from './deploy-assert'; export * from './providers'; export * from './common'; export * from './match'; diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts b/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts deleted file mode 100644 index 361e340bc4a9c..0000000000000 --- a/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Stack } from '@aws-cdk/core'; -import { Construct, IConstruct, Node } from 'constructs'; -import { EqualsAssertion } from '../assertions'; -import { ExpectedResult, ActualResult } from '../common'; -import { md5hash } from '../private/hash'; -import { AwsApiCall, LambdaInvokeFunction, IAwsApiCall, LambdaInvokeFunctionProps } from '../sdk'; -import { IDeployAssert } from '../types'; - - -const DEPLOY_ASSERT_SYMBOL = Symbol.for('@aws-cdk/integ-tests.DeployAssert'); - - -// 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 DeployAssert - */ -export interface DeployAssertProps { } - -/** - * Construct that allows for registering a list of assertions - * that should be performed on a construct - */ -export class DeployAssert extends CoreConstruct implements IDeployAssert { - - /** - * Returns whether the construct is a DeployAssert construct - */ - public static isDeployAssert(x: any): x is DeployAssert { - return x !== null && typeof(x) === 'object' && DEPLOY_ASSERT_SYMBOL in x; - } - - /** - * Finds a DeployAssert construct in the given scope - */ - public static of(construct: IConstruct): DeployAssert { - const scopes = Node.of(Node.of(construct).root).findAll(); - const deployAssert = scopes.find(s => DeployAssert.isDeployAssert(s)); - if (!deployAssert) { - throw new Error('No DeployAssert construct found in scopes'); - } - return deployAssert as DeployAssert; - } - - public scope: Stack; - - constructor(scope: Construct) { - super(scope, 'Default'); - - this.scope = new Stack(scope, 'DeployAssert'); - - Object.defineProperty(this, DEPLOY_ASSERT_SYMBOL, { value: true }); - } - - public awsApiCall(service: string, api: string, parameters?: any): IAwsApiCall { - return new AwsApiCall(this.scope, `AwsApiCall${service}${api}`, { - api, - service, - parameters, - }); - } - - public invokeFunction(props: LambdaInvokeFunctionProps): IAwsApiCall { - const hash = md5hash(this.scope.resolve(props)); - return new LambdaInvokeFunction(this.scope, `LambdaInvoke${hash}`, props); - } - - public assert(id: string, expected: ExpectedResult, actual: ActualResult): void { - new EqualsAssertion(this.scope, `EqualsAssertion${id}`, { - expected, - actual, - }); - } -} diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/index.ts b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/index.ts index 72ca3544cb66d..78a47c83be1ef 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/index.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/index.ts @@ -1,4 +1,5 @@ import { AssertionHandler } from './assertion'; +import { ResultsCollectionHandler } from './results'; import { AwsApiCallHandler } from './sdk'; import * as types from './types'; @@ -13,6 +14,7 @@ function createResourceHandler(event: AWSLambda.CloudFormationCustomResourceEven } switch (event.ResourceType) { case types.ASSERT_RESOURCE_TYPE: return new AssertionHandler(event, context); + case types.RESULTS_RESOURCE_TYPE: return new ResultsCollectionHandler(event, context); default: throw new Error(`Unsupported resource type "${event.ResourceType}`); } diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/results.ts b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/results.ts new file mode 100644 index 0000000000000..784ff68a05ab6 --- /dev/null +++ b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/results.ts @@ -0,0 +1,12 @@ +import { CustomResourceHandler } from './base'; +import { ResultsCollectionRequest, ResultsCollectionResult } from './types'; + +export class ResultsCollectionHandler extends CustomResourceHandler<ResultsCollectionRequest, ResultsCollectionResult> { + protected async processEvent(request: ResultsCollectionRequest): Promise<ResultsCollectionResult | undefined> { + const reduced: string = request.assertionResults.reduce((agg, result, idx) => { + const msg = result.status === 'pass' ? 'pass' : `fail - ${result.message}`; + return `${agg}\nTest${idx}: ${msg}`; + }, '').trim(); + return { message: reduced }; + } +} diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/types.ts b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/types.ts index 68bd63202afe8..ae9f545476dac 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/types.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/types.ts @@ -2,6 +2,7 @@ // Kept in a separate file for sharing between the handler and the provider constructs. export const ASSERT_RESOURCE_TYPE = 'Custom::DeployAssert@AssertEquals'; +export const RESULTS_RESOURCE_TYPE = 'Custom::DeployAssert@ResultsCollection'; export const SDK_RESOURCE_TYPE_PREFIX = 'Custom::DeployAssert@SdkCall'; /** @@ -154,3 +155,24 @@ export interface AssertionResultData { */ readonly message?: string; } + +/** + * Represents a collection of assertion request results + */ +export interface ResultsCollectionRequest { + /** + * The results of all the assertions that have been + * registered + */ + readonly assertionResults: AssertionResultData[]; +} + +/** + * The result of a results request + */ +export interface ResultsCollectionResult { + /** + * A message containing the results of the assertion + */ + readonly message: string; +} diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts b/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts index da54b1dbe24db..b176c13456f37 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts @@ -4,84 +4,10 @@ import { EqualsAssertion } from './assertions'; import { ExpectedResult, ActualResult } from './common'; import { AssertionsProvider, SDK_RESOURCE_TYPE_PREFIX } from './providers'; -// 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 { IConstruct } 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'; -/** - * Interface for creating a custom resource that will perform - * an API call using the AWS SDK - */ -export interface IAwsApiCall extends IConstruct { - /** - * Returns the value of an attribute of the custom resource of an arbitrary - * type. Attributes are returned from the custom resource provider through the - * `Data` map where the key is the attribute name. - * - * @param attributeName the name of the attribute - * @returns a token for `Fn::GetAtt`. Use `Token.asXxx` to encode the returned `Reference` as a specific type or - * use the convenience `getAttString` for string attributes. - */ - getAtt(attributeName: string): Reference; - - /** - * Returns the value of an attribute of the custom resource of type string. - * Attributes are returned from the custom resource provider through the - * `Data` map where the key is the attribute name. - * - * @param attributeName the name of the attribute - * @returns a token for `Fn::GetAtt` encoded as a string. - */ - getAttString(attributeName: string): string; - - /** - * Assert that the ExpectedResult is equal - * to the result of the AwsApiCall - * - * @example - * declare const integ: IntegTest; - * const invoke = integ.assert.invokeFunction({ - * functionName: 'my-func', - * }); - * invoke.assert(ExpectedResult.objectLike({ Payload: 'OK' })); - */ - assert(expected: ExpectedResult): void; - - /** - * Assert that the ExpectedResult is equal - * to the result of the AwsApiCall at the given path. - * - * For example the SQS.receiveMessage api response would look - * like: - * - * If you wanted to assert the value of `Body` you could do - * - * @example - * const actual = { - * Messages: [{ - * MessageId: '', - * ReceiptHandle: '', - * MD5OfBody: '', - * Body: 'hello', - * Attributes: {}, - * MD5OfMessageAttributes: {}, - * MessageAttributes: {} - * }] - * }; - * - * - * declare const integ: IntegTest; - * const message = integ.assert.awsApiCall('SQS', 'receiveMessage'); - * - * message.assertAtPath('Messages.0.Body', ExpectedResult.stringLikeRegexp('hello')); - */ - assertAtPath(path: string, expected: ExpectedResult): void; -} - /** * Options to perform an AWS JavaScript V2 API call */ @@ -113,7 +39,7 @@ export interface AwsApiCallProps extends AwsApiCallOptions {} * Construct that creates a custom resource that will perform * a query using the AWS SDK */ -export class AwsApiCall extends CoreConstruct implements IAwsApiCall { +export class AwsApiCall extends CoreConstruct { private readonly sdkCallResource: CustomResource; private flattenResponse: string = 'false'; private readonly name: string; @@ -143,16 +69,44 @@ export class AwsApiCall extends CoreConstruct implements IAwsApiCall { this.sdkCallResource.node.addDependency(this.provider); } + /** + * Returns the value of an attribute of the custom resource of an arbitrary + * type. Attributes are returned from the custom resource provider through the + * `Data` map where the key is the attribute name. + * + * @param attributeName the name of the attribute + * @returns a token for `Fn::GetAtt`. Use `Token.asXxx` to encode the returned `Reference` as a specific type or + * use the convenience `getAttString` for string attributes. + */ public getAtt(attributeName: string): Reference { this.flattenResponse = 'true'; return this.sdkCallResource.getAtt(`apiCallResponse.${attributeName}`); } + /** + * Returns the value of an attribute of the custom resource of type string. + * Attributes are returned from the custom resource provider through the + * `Data` map where the key is the attribute name. + * + * @param attributeName the name of the attribute + * @returns a token for `Fn::GetAtt` encoded as a string. + */ public getAttString(attributeName: string): string { this.flattenResponse = 'true'; return this.sdkCallResource.getAttString(`apiCallResponse.${attributeName}`); } + /** + * Assert that the ExpectedResult is equal + * to the result of the AwsApiCall + * + * @example + * declare const assert: DeployAssert; + * const invoke = new LambdaInvokeFunction(assert, 'Invoke', { + * functionName: 'my-func', + * }); + * invoke.assert(ExpectedResult.objectLike({ Payload: 'OK' })); + */ public assert(expected: ExpectedResult): void { new EqualsAssertion(this, `AssertEquals${this.name}`, { expected, @@ -160,6 +114,37 @@ export class AwsApiCall extends CoreConstruct implements IAwsApiCall { }); } + /** + * Assert that the ExpectedResult is equal + * to the result of the AwsApiCall at the given path. + * + * For example the SQS.receiveMessage api response would look + * like: + * + * If you wanted to assert the value of `Body` you could do + * + * @example + * const actual = { + * Messages: [{ + * MessageId: '', + * ReceiptHandle: '', + * MD5OfBody: '', + * Body: 'hello', + * Attributes: {}, + * MD5OfMessageAttributes: {}, + * MessageAttributes: {} + * }] + * }; + * + * + * declare const assert: DeployAssert; + * const message = new AwsApiCall(assert, 'ReceiveMessage', { + * service: 'SQS', + * api: 'receiveMessage' + * }); + * + * message.assertAtPath('Messages.0.Body', ExpectedResult.stringLikeRegexp('hello')); + */ public assertAtPath(path: string, expected: ExpectedResult): void { new EqualsAssertion(this, `AssertEquals${this.name}`, { expected, diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/types.ts b/packages/@aws-cdk/integ-tests/lib/assertions/types.ts deleted file mode 100644 index 65025fb21d2e4..0000000000000 --- a/packages/@aws-cdk/integ-tests/lib/assertions/types.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ExpectedResult, ActualResult } from './common'; -import { IAwsApiCall, LambdaInvokeFunctionProps } from './sdk'; - -/** - * Interface that allows for registering a list of assertions - * that should be performed on a construct. This is only necessary - * when writing integration tests. - */ -export interface IDeployAssert { - /** - * Query AWS using JavaScript SDK V2 API calls. This can be used to either - * trigger an action or to return a result that can then be asserted against - * an expected value - * - * @example - * declare const app: App; - * declare const integ: IntegTest; - * integ.assert.awsApiCall('SQS', 'sendMessage', { - * QueueUrl: 'url', - * MessageBody: 'hello', - * }); - * const message = integ.assert.awsApiCall('SQS', 'receiveMessage', { - * QueueUrl: 'url', - * }); - * message.assert(ExpectedResult.objectLike({ - * Messages: [{ Body: 'hello' }], - * })); - */ - awsApiCall(service: string, api: string, parameters?: any): IAwsApiCall; - - /** - * Invoke a lambda function and return the response which can be asserted - * - * @example - * declare const app: App; - * declare const integ: IntegTest; - * const invoke = integ.assert.invokeFunction({ - * functionName: 'my-function', - * }); - * invoke.assert(ExpectedResult.objectLike({ - * Payload: '200', - * })); - */ - invokeFunction(props: LambdaInvokeFunctionProps): IAwsApiCall; - - /** - * Assert that the ExpectedResult is equal - * to the ActualResult - * - * @example - * declare const integ: IntegTest; - * declare const apiCall: AwsApiCall; - * integ.assert.assert( - * 'invoke', - * ExpectedResult.objectLike({ Payload: 'OK' }), - * ActualResult.fromAwsApiCall(apiCall, 'Body'), - * ); - */ - assert(id: string, expected: ExpectedResult, actual: ActualResult): void; -} diff --git a/packages/@aws-cdk/integ-tests/lib/test-case.ts b/packages/@aws-cdk/integ-tests/lib/test-case.ts index 8c9d66118ac76..de701bb63d24a 100644 --- a/packages/@aws-cdk/integ-tests/lib/test-case.ts +++ b/packages/@aws-cdk/integ-tests/lib/test-case.ts @@ -1,8 +1,7 @@ import { IntegManifest, Manifest, TestCase, TestOptions } from '@aws-cdk/cloud-assembly-schema'; import { attachCustomSynthesis, Stack, ISynthesisSession, StackProps } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { IDeployAssert } from './assertions'; -import { DeployAssert } from './assertions/private/deploy-assert'; +import { DeployAssert } from './assertions'; import { IntegManifestSynthesizer } from './manifest-synthesizer'; const TEST_CASE_STACK_SYMBOL = Symbol.for('@aws-cdk/integ-tests.IntegTestCaseStack'); @@ -32,15 +31,12 @@ export class IntegTestCase extends CoreConstruct { /** * Make assertions on resources in this test case */ - public readonly assert: IDeployAssert; - - private readonly _assert: DeployAssert; + public readonly assert: DeployAssert; constructor(scope: Construct, id: string, private readonly props: IntegTestCaseProps) { super(scope, id); - this._assert = new DeployAssert(this); - this.assert = this._assert; + this.assert = new DeployAssert(this); } /** @@ -57,7 +53,7 @@ export class IntegTestCase extends CoreConstruct { private toTestCase(props: IntegTestCaseProps): TestCase { return { ...props, - assertionStack: this._assert.scope.artifactId, + assertionStack: Stack.of(this.assert).artifactId, stacks: props.stacks.map(s => s.artifactId), }; } @@ -87,7 +83,7 @@ export class IntegTestCaseStack extends Stack { /** * Make assertions on resources in this test case */ - public readonly assert: IDeployAssert; + public readonly assert: DeployAssert; /** * The underlying IntegTestCase that is created @@ -128,7 +124,7 @@ export class IntegTest extends CoreConstruct { /** * Make assertions on resources in this test case */ - public readonly assert: IDeployAssert; + public readonly assert: DeployAssert; private readonly testCases: IntegTestCase[]; constructor(scope: Construct, id: string, props: IntegTestProps) { super(scope, id); diff --git a/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture b/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture index e85bf5884afdc..b9b4f3740b427 100644 --- a/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture @@ -3,6 +3,7 @@ import { IntegTestCase, IntegTest, IntegTestCaseStack, + DeployAssert, AwsApiCall, EqualsAssertion, ActualResult, diff --git a/packages/@aws-cdk/integ-tests/test/assertions/deploy-assert.test.ts b/packages/@aws-cdk/integ-tests/test/assertions/deploy-assert.test.ts index c779618d2c1aa..847086ed66f7a 100644 --- a/packages/@aws-cdk/integ-tests/test/assertions/deploy-assert.test.ts +++ b/packages/@aws-cdk/integ-tests/test/assertions/deploy-assert.test.ts @@ -1,13 +1,12 @@ import { Template } from '@aws-cdk/assertions'; import { App, Stack } from '@aws-cdk/core'; -import { LogType, InvocationType, ExpectedResult, ActualResult } from '../../lib/assertions'; -import { DeployAssert } from '../../lib/assertions/private/deploy-assert'; +import { DeployAssert, LogType, InvocationType, ExpectedResult, ActualResult } from '../../lib/assertions'; describe('DeployAssert', () => { test('of', () => { const app = new App(); - const stack = new Stack(app, 'TestStack'); + const stack = new Stack(app); new DeployAssert(app); expect(() => { DeployAssert.of(stack); @@ -16,7 +15,7 @@ describe('DeployAssert', () => { test('throws if no DeployAssert', () => { const app = new App(); - const stack = new Stack(app, 'TestStack'); + const stack = new Stack(app); expect(() => { DeployAssert.of(stack); }).toThrow(/No DeployAssert construct found in scopes/); @@ -44,7 +43,7 @@ describe('DeployAssert', () => { }); // THEN - const template = Template.fromStack(deployAssert.scope); + const template = Template.fromStack(Stack.of(deployAssert)); template.hasResourceProperties('Custom::DeployAssert@SdkCallLambdainvoke', { service: 'Lambda', api: 'invoke', @@ -73,7 +72,7 @@ describe('DeployAssert', () => { ); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@AssertEquals', { expected: JSON.stringify({ $StringLike: 'foo' }), actual: { @@ -99,7 +98,7 @@ describe('DeployAssert', () => { ); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@AssertEquals', { expected: JSON.stringify({ $ObjectLike: { foo: 'bar' } }), actual: { @@ -123,7 +122,7 @@ describe('DeployAssert', () => { // THEN - Template.fromStack(deplossert.scope).hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', { + Template.fromStack(Stack.of(deplossert)).hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', { api: 'MyApi', service: 'MyService', }); @@ -140,7 +139,7 @@ describe('DeployAssert', () => { // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.resourceCountIs('AWS::Lambda::Function', 1); template.resourceCountIs('Custom::DeployAssert@SdkCallMyServiceMyApi1', 1); template.resourceCountIs('Custom::DeployAssert@SdkCallMyServiceMyApi2', 1); diff --git a/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts b/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts index d2f486a6687d9..31f1bd5068a4b 100644 --- a/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts +++ b/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts @@ -1,7 +1,6 @@ import { Template, Match } from '@aws-cdk/assertions'; -import { App, CfnOutput } from '@aws-cdk/core'; -import { LogType, InvocationType, ExpectedResult } from '../../lib/assertions'; -import { DeployAssert } from '../../lib/assertions/private/deploy-assert'; +import { App, Stack, CfnOutput } from '@aws-cdk/core'; +import { DeployAssert, AwsApiCall, LambdaInvokeFunction, LogType, InvocationType, ExpectedResult } from '../../lib/assertions'; describe('AwsApiCall', () => { test('default', () => { @@ -10,10 +9,13 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - deplossert.awsApiCall('MyService', 'MyApi'); + new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.resourceCountIs('AWS::Lambda::Function', 1); template.hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', { service: 'MyService', @@ -28,13 +30,17 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - deplossert.awsApiCall('MyService', 'MyApi', { - param1: 'val1', - param2: 2, + new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + parameters: { + param1: 'val1', + param2: 2, + }, }); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.resourceCountIs('AWS::Lambda::Function', 1); template.hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', { service: 'MyService', @@ -53,18 +59,21 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - const query = deplossert.awsApiCall('MyService', 'MyApi'); + const query = new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); - new CfnOutput(deplossert.scope, 'GetAttString', { + new CfnOutput(deplossert, 'GetAttString', { value: query.getAttString('att'), }).overrideLogicalId('GetAtt'); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasOutput('GetAtt', { Value: { 'Fn::GetAtt': [ - 'AwsApiCallMyServiceMyApi', + 'AwsApiCall', 'apiCallResponse.att', ], }, @@ -76,25 +85,27 @@ describe('AwsApiCall', () => { flattenResponse: 'true', }); }); - test('getAtt', () => { // GIVEN const app = new App(); const deplossert = new DeployAssert(app); // WHEN - const query = deplossert.awsApiCall('MyService', 'MyApi'); + const query = new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); - new CfnOutput(deplossert.scope, 'GetAttString', { + new CfnOutput(deplossert, 'GetAttString', { value: query.getAtt('att').toString(), }).overrideLogicalId('GetAtt'); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasOutput('GetAtt', { Value: { 'Fn::GetAtt': [ - 'AwsApiCallMyServiceMyApi', + 'AwsApiCall', 'apiCallResponse.att', ], }, @@ -106,6 +117,7 @@ describe('AwsApiCall', () => { flattenResponse: 'true', }); }); + }); describe('assertEqual', () => { @@ -115,16 +127,19 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - const query = deplossert.awsApiCall('MyService', 'MyApi'); + const query = new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); query.assert(ExpectedResult.exact({ foo: 'bar' })); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@AssertEquals', { expected: JSON.stringify({ $Exact: { foo: 'bar' } }), actual: { 'Fn::GetAtt': [ - 'AwsApiCallMyServiceMyApi', + 'AwsApiCall', 'apiCallResponse', ], }, @@ -137,16 +152,19 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - const query = deplossert.awsApiCall('MyService', 'MyApi'); + const query = new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); query.assert(ExpectedResult.objectLike({ foo: 'bar' })); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@AssertEquals', { expected: JSON.stringify({ $ObjectLike: { foo: 'bar' } }), actual: { 'Fn::GetAtt': [ - 'AwsApiCallMyServiceMyApi', + 'AwsApiCall', 'apiCallResponse', ], }, @@ -159,16 +177,19 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - const query = deplossert.awsApiCall('MyService', 'MyApi'); + const query = new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); query.assert(ExpectedResult.exact('bar')); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@AssertEquals', { expected: JSON.stringify({ $Exact: 'bar' }), actual: { 'Fn::GetAtt': [ - 'AwsApiCallMyServiceMyApi', + 'AwsApiCall', 'apiCallResponse', ], }, @@ -182,14 +203,14 @@ describe('AwsApiCall', () => { const app = new App(); const deplossert = new DeployAssert(app); - deplossert.invokeFunction({ + new LambdaInvokeFunction(deplossert, 'Invoke', { functionName: 'my-func', logType: LogType.TAIL, payload: JSON.stringify({ key: 'val' }), invocationType: InvocationType.EVENT, }); - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@SdkCallLambdainvoke', { service: 'Lambda', api: 'invoke', From 07cc5062eda4b37417a1b6f658252e4a500020fe Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Thu, 19 May 2022 02:46:41 -0700 Subject: [PATCH 11/29] docs(cfnspec): update CloudFormation documentation (#20412) --- .../spec-source/cfn-docs/cfn-docs.json | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json index fc0ea483029bc..a7b19f7813278 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json +++ b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json @@ -2927,7 +2927,7 @@ }, "AWS::AppMesh::Mesh.MeshServiceDiscovery": { "attributes": {}, - "description": "", + "description": "An object that represents the service discovery information for a service mesh.", "properties": {} }, "AWS::AppMesh::Mesh.MeshSpec": { @@ -12941,7 +12941,7 @@ "attributes": { "Ref": "`Ref` returns the resource name." }, - "description": "Associates an Elastic IP address with an instance or a network interface. Before you can use an Elastic IP address, you must allocate it to your account.\n\nAn Elastic IP address is for use in either the EC2-Classic platform or in a VPC. For more information, see [Elastic IP Addresses](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html) in the *Amazon EC2 User Guide* .\n\n[EC2-Classic, VPC in an EC2-VPC-only account] If the Elastic IP address is already associated with a different instance, it is disassociated from that instance and associated with the specified instance. If you associate an Elastic IP address with an instance that has an existing Elastic IP address, the existing address is disassociated from the instance, but remains allocated to your account.\n\n[VPC in an EC2-Classic account] If you don't specify a private IP address, the Elastic IP address is associated with the primary IP address. If the Elastic IP address is already associated with a different instance or a network interface, you get an error unless you allow reassociation. You cannot associate an Elastic IP address with an instance or network interface that has an existing Elastic IP address.", + "description": "Associates an Elastic IP address with an instance or a network interface. Before you can use an Elastic IP address, you must allocate it to your account. For more information about working with Elastic IP addresses, see [Elastic IP address concepts and rules](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-eips.html#vpc-eip-overview) .\n\nAn Elastic IP address can be used in EC2-Classic and EC2-VPC accounts. There are differences between an Elastic IP address that you use in a VPC and one that you use in EC2-Classic. For more information, see [Differences between instances in EC2-Classic and a VPC](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-classic-platform.html#differences-ec2-classic-vpc) .\n\n[EC2-VPC] You must specify `AllocationId` and either `InstanceId` , `NetworkInterfaceId` , or `PrivateIpAddress` .\n\n[EC2-Classic] You must specify `EIP` and `InstanceId` .", "properties": { "AllocationId": "[EC2-VPC] The allocation ID. This is required for EC2-VPC.", "EIP": "[EC2-Classic] The Elastic IP address to associate with the instance. This is required for EC2-Classic.", @@ -14745,6 +14745,7 @@ "description": "Specifies a target for your Traffic Mirror session.\n\nA Traffic Mirror target is the destination for mirrored traffic. The Traffic Mirror source and the Traffic Mirror target (monitoring appliances) can be in the same VPC, or in different VPCs connected via VPC peering or a transit gateway.\n\nA Traffic Mirror target can be a network interface, or a Network Load Balancer.\n\nTo use the target in a Traffic Mirror session, use [AWS::EC2::TrafficMirrorSession](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-trafficmirrorsession.html) .", "properties": { "Description": "The description of the Traffic Mirror target.", + "GatewayLoadBalancerEndpointId": "", "NetworkInterfaceId": "The network interface ID that is associated with the target.", "NetworkLoadBalancerArn": "The Amazon Resource Name (ARN) of the Network Load Balancer that is associated with the target.", "Tags": "The tags to assign to the Traffic Mirror target." @@ -29310,7 +29311,7 @@ "description": "The setup to be used for brokers in the cluster.", "properties": { "BrokerAZDistribution": "This parameter is currently not in use.", - "ClientSubnets": "The list of subnets to connect to in the client virtual private cloud (VPC). Amazon creates elastic network interfaces inside these subnets. Client applications use elastic network interfaces to produce and consume data.\n\nSpecify exactly two subnets if you are using the US West (N. California) Region. For other Regions where Amazon MSK is available, you can specify either two or three subnets. The subnets that you specify must be in distinct Availability Zones. When you create a cluster, Amazon MSK distributes the broker nodes evenly across the subnets that you specify.\n\nClient subnets can't be in Availability Zone us-east-1e.", + "ClientSubnets": "The list of subnets to connect to in the client virtual private cloud (VPC). Amazon creates elastic network interfaces inside these subnets. Client applications use elastic network interfaces to produce and consume data.\n\nSpecify exactly two subnets if you are using the US West (N. California) Region. For other Regions where Amazon MSK is available, you can specify either two or three subnets. The subnets that you specify must be in distinct Availability Zones. When you create a cluster, Amazon MSK distributes the broker nodes evenly across the subnets that you specify.\n\nClient subnets can't occupy the Availability Zone with ID `use1-az3` .", "ConnectivityInfo": "Information about the cluster's connectivity setting.", "InstanceType": "The type of Amazon EC2 instances to use for brokers. The following instance types are allowed: kafka.m5.large, kafka.m5.xlarge, kafka.m5.2xlarge, kafka.m5.4xlarge, kafka.m5.8xlarge, kafka.m5.12xlarge, kafka.m5.16xlarge, and kafka.m5.24xlarge.", "SecurityGroups": "The security groups to associate with the elastic network interfaces in order to specify who can connect to and communicate with the Amazon MSK cluster. If you don't specify a security group, Amazon MSK uses the default security group associated with the VPC. If you specify security groups that were shared with you, you must ensure that you have permissions to them. Specifically, you need the `ec2:DescribeSecurityGroups` permission.", @@ -32101,6 +32102,15 @@ "ContentSegmentUrlPrefix": "A content delivery network (CDN) to cache content segments, so that content requests don\u2019t always have to go to the origin server. First, create a rule in your CDN for the content segment origin server. Then specify the rule's name in this ContentSegmentUrlPrefix. When MediaTailor serves a manifest, it reports your CDN as the source for content segments." } }, + "AWS::MediaTailor::PlaybackConfiguration.DashConfiguration": { + "attributes": {}, + "description": "The configuration for DASH content.", + "properties": { + "ManifestEndpointPrefix": "The URL generated by MediaTailor to initiate a playback session. The session uses server-side reporting. This setting is ignored in PUT operations.", + "MpdLocation": "The setting that controls whether MediaTailor includes the Location tag in DASH manifests. MediaTailor populates the Location tag with the URL for manifest update requests, to be used by players that don't support sticky redirects. Disable this if you have CDN routing rules set up for accessing MediaTailor manifests, and you are either using client-side reporting or your players support sticky HTTP redirects. Valid values are DISABLED and EMT_DEFAULT. The EMT_DEFAULT setting enables the inclusion of the tag and is the default value.", + "OriginManifestType": "The setting that controls whether MediaTailor handles manifests from the origin server as multi-period manifests or single-period manifests. If your origin server produces single-period manifests, set this to SINGLE_PERIOD. The default setting is MULTI_PERIOD. For multi-period manifests, omit this setting or set it to MULTI_PERIOD." + } + }, "AWS::MediaTailor::PlaybackConfiguration.DashConfigurationForPut": { "attributes": {}, "description": "The configuration for DASH PUT operations.", @@ -32109,6 +32119,13 @@ "OriginManifestType": "The setting that controls whether MediaTailor handles manifests from the origin server as multi-period manifests or single-period manifests. If your origin server produces single-period manifests, set this to SINGLE_PERIOD. The default setting is MULTI_PERIOD. For multi-period manifests, omit this setting or set it to MULTI_PERIOD." } }, + "AWS::MediaTailor::PlaybackConfiguration.HlsConfiguration": { + "attributes": {}, + "description": "The configuration for HLS content.", + "properties": { + "ManifestEndpointPrefix": "The URL that is used to initiate a playback session for devices that support Apple HLS. The session uses server-side reporting." + } + }, "AWS::MediaTailor::PlaybackConfiguration.LivePreRollConfiguration": { "attributes": {}, "description": "The configuration for pre-roll ad insertion.", From a6d830e32bf055c626cb4dba47170bd4f1f7490b Mon Sep 17 00:00:00 2001 From: Christopher Rybicki <rybickic@amazon.com> Date: Thu, 19 May 2022 05:30:28 -0700 Subject: [PATCH 12/29] chore: timeouts on PR builds by querying against npm (#20409) As part of AWS CDK builds, we run a script that checks that the API does not introduce any breaking changes with respect to the latest published version of AWS CDK. To do so, this currently queries against the GitHub API to check what the latest releases were. However, this can fail because without using specific GitHub credentials, we are limited to 60 API calls per minute. Even [with retries](https://github.com/aws/aws-cdk/pull/19336), this can still timeout, in which case the build fails suddenly without an error message: ``` <thousands of lines of logs omitted> lerna success - @aws-cdk/prlint lerna success - @aws-cdk/ubergen lerna success - @aws-cdk/yarn-cling real 21m40.119s user 353m7.607s sys 27m49.086s Listing jsii packages... lerna notice cli v4.0.0 lerna info ci enabled lerna success found 254 packages Filtering on existing packages on NPM... Determining baseline version... Build version: 1.156.1. ``` To avoid this failure mode entire, this PR updates the script to check for the latest AWS CDK version by querying against npm. Both npm and GitHub should be accurate sources of truth AFAIK, and npm does not impose as stringent rate limits on its callers. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- scripts/find-latest-release.js | 34 +++------------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/scripts/find-latest-release.js b/scripts/find-latest-release.js index bb51ccc539734..85081d495e9a1 100644 --- a/scripts/find-latest-release.js +++ b/scripts/find-latest-release.js @@ -16,33 +16,9 @@ async function main(matchRange) { throw new Error(`Not a valid range: ${matchRange}`); } - let releases; - for (let attempt = 0; attempt < 10; attempt++) { - const { stdout, error } = cp.spawnSync('curl', ['https://api.github.com/repos/aws/aws-cdk/releases?per_page=100'], { maxBuffer: 10_000_000 }); - if (error) { throw error; } - - const response = JSON.parse(stdout); - if (response.message) { - // This is actually an error response. Only recover from throttling errors. - if (!response.message.includes('API rate limit')) { - throw new Error(response.message); - } - - // 60 requests/hour, so we need to sleep for a full minute to get any kind of response - const sleepTime = Math.floor(Math.random() * 60 * Math.pow(2, attempt)); - await sleep(sleepTime * 1000); - continue; - } - - releases = response; - break; - } - if (!releases) { - throw new Error('Retries exhaused'); - } - - - const versions = releases.map(r => r.name.replace(/^v/, '')); // v1.2.3 -> 1.2.3 + const { stdout, error } = cp.spawnSync('npm', ['view', 'aws-cdk', 'versions', '--json'], { maxBuffer: 10_000_000 }); + if (error) { throw error; } + const versions = JSON.parse(stdout.toString('utf-8')); const sat = semver.maxSatisfying(versions, range); if (!sat) { @@ -51,10 +27,6 @@ async function main(matchRange) { console.log(sat); } -function sleep(ms) { - return new Promise(ok => setTimeout(ok, ms)); -} - main(process.argv[2]).catch(e => { console.error(e); process.exitCode = 1; From 85604d929978aa1c645dba8959d682892278f862 Mon Sep 17 00:00:00 2001 From: MayForBlue <60128296+MayForBlue@users.noreply.github.com> Date: Thu, 19 May 2022 22:14:30 +0900 Subject: [PATCH 13/29] feat(s3): add `noncurrentVersionsToRetain` property to lifecycle rule (#20348) Fixes #19784 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-s3/README.md | 36 ++++++++ packages/@aws-cdk/aws-s3/lib/bucket.ts | 5 +- packages/@aws-cdk/aws-s3/lib/rule.ts | 10 +++ .../aws-s3/test/integ.lifecycle-expiration.ts | 16 ++++ .../aws-cdk-s3.template.json | 25 ++++++ .../cdk.out | 1 + .../integ.json | 14 ++++ .../manifest.json | 28 +++++++ .../tree.json | 68 +++++++++++++++ packages/@aws-cdk/aws-s3/test/rules.test.ts | 84 ++++++++++++++++++- 10 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 packages/@aws-cdk/aws-s3/test/integ.lifecycle-expiration.ts create mode 100644 packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/aws-cdk-s3.template.json create mode 100644 packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/tree.json diff --git a/packages/@aws-cdk/aws-s3/README.md b/packages/@aws-cdk/aws-s3/README.md index 26a62df2d9f41..ddf1f9a729d48 100644 --- a/packages/@aws-cdk/aws-s3/README.md +++ b/packages/@aws-cdk/aws-s3/README.md @@ -551,3 +551,39 @@ bucket.transferAccelerationUrlForObject('objectname'); }); ``` + +## Lifecycle Rule + +[Managing lifecycle](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html) can be configured transition or expiration actions. + +```ts +const bucket = new s3.Bucket(this, 'MyBucket', { + lifecycleRules: [{ + abortIncompleteMultipartUploadAfter: cdk.Duration.minutes(30), + enabled: false, + expiration: cdk.Duration.days(30), + expirationDate: new Date(), + expiredObjectDeleteMarker: false, + id: 'id', + noncurrentVersionExpiration: cdk.Duration.days(30), + + // the properties below are optional + noncurrentVersionsToRetain: 123, + noncurrentVersionTransitions: [{ + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(30), + + // the properties below are optional + noncurrentVersionsToRetain: 123, + }], + prefix: 'prefix', + transitions: [{ + storageClass: s3.StorageClass.GLACIER, + + // the properties below are optional + transitionAfter: cdk.Duration.days(30), + transitionDate: new Date(), + }], + }] +}); +``` diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 4b299a1749f70..c1d73f54a0313 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -1905,7 +1905,10 @@ export class Bucket extends BucketBase { expirationDate: rule.expirationDate, expirationInDays: rule.expiration?.toDays(), id: rule.id, - noncurrentVersionExpirationInDays: rule.noncurrentVersionExpiration && rule.noncurrentVersionExpiration.toDays(), + noncurrentVersionExpiration: rule.noncurrentVersionExpiration && { + noncurrentDays: rule.noncurrentVersionExpiration.toDays(), + newerNoncurrentVersions: rule.noncurrentVersionsToRetain, + }, noncurrentVersionTransitions: mapOrUndefined(rule.noncurrentVersionTransitions, t => ({ storageClass: t.storageClass.value, transitionInDays: t.transitionAfter.toDays(), diff --git a/packages/@aws-cdk/aws-s3/lib/rule.ts b/packages/@aws-cdk/aws-s3/lib/rule.ts index 5ce32ba7ed798..9baf32efb9779 100644 --- a/packages/@aws-cdk/aws-s3/lib/rule.ts +++ b/packages/@aws-cdk/aws-s3/lib/rule.ts @@ -66,6 +66,16 @@ export interface LifecycleRule { */ readonly noncurrentVersionExpiration?: Duration; + /** + * Indicates a maximum number of noncurrent versions to retain. + * + * If there are this many more noncurrent versions, + * Amazon S3 permanently deletes them. + * + * @default No noncurrent versions to retain + */ + readonly noncurrentVersionsToRetain?: number; + /** * One or more transition rules that specify when non-current objects transition to a specified storage class. * diff --git a/packages/@aws-cdk/aws-s3/test/integ.lifecycle-expiration.ts b/packages/@aws-cdk/aws-s3/test/integ.lifecycle-expiration.ts new file mode 100644 index 0000000000000..a0f60a2ecd3a2 --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/integ.lifecycle-expiration.ts @@ -0,0 +1,16 @@ +import { App, Duration, Stack } from '@aws-cdk/core'; +import { Bucket } from '../lib'; + +const app = new App(); + +const stack = new Stack(app, 'aws-cdk-s3'); + +new Bucket(stack, 'MyBucket', { + lifecycleRules: [{ + noncurrentVersionExpiration: Duration.days(30), + noncurrentVersionsToRetain: 123, + }], + versioned: true, +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/aws-cdk-s3.template.json b/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/aws-cdk-s3.template.json new file mode 100644 index 0000000000000..0cd853f1efb44 --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/aws-cdk-s3.template.json @@ -0,0 +1,25 @@ +{ + "Resources": { + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "Properties": { + "LifecycleConfiguration": { + "Rules": [ + { + "NoncurrentVersionExpiration": { + "NewerNoncurrentVersions": 123, + "NoncurrentDays": 30 + }, + "Status": "Enabled" + } + ] + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..ccdfc1ff96a9d --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"19.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/integ.json b/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/integ.json new file mode 100644 index 0000000000000..0b54a65ccf3cd --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/integ.json @@ -0,0 +1,14 @@ +{ + "version": "19.0.0", + "testCases": { + "aws-s3/test/integ.lifecycle-expiration": { + "stacks": [ + "aws-cdk-s3" + ], + "diffAssets": false, + "stackUpdateWorkflow": true + } + }, + "synthContext": {}, + "enableLookups": false +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..74c7d95a30241 --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/manifest.json @@ -0,0 +1,28 @@ +{ + "version": "19.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-s3": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-s3.template.json", + "validateOnSynth": false + }, + "metadata": { + "/aws-cdk-s3/MyBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyBucketF68F3FF0" + } + ] + }, + "displayName": "aws-cdk-s3" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/tree.json b/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/tree.json new file mode 100644 index 0000000000000..c64d886e40e20 --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/lifecycle-expiration.integ.snapshot/tree.json @@ -0,0 +1,68 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "@aws-cdk/core.Construct", + "version": "0.0.0" + } + }, + "aws-cdk-s3": { + "id": "aws-cdk-s3", + "path": "aws-cdk-s3", + "children": { + "MyBucket": { + "id": "MyBucket", + "path": "aws-cdk-s3/MyBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-s3/MyBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "lifecycleConfiguration": { + "rules": [ + { + "noncurrentVersionExpiration": { + "noncurrentDays": 30, + "newerNoncurrentVersions": 123 + }, + "status": "Enabled" + } + ] + }, + "versioningConfiguration": { + "status": "Enabled" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-s3/test/rules.test.ts b/packages/@aws-cdk/aws-s3/test/rules.test.ts index 4adf00e4b3441..8432e35e67bc5 100644 --- a/packages/@aws-cdk/aws-s3/test/rules.test.ts +++ b/packages/@aws-cdk/aws-s3/test/rules.test.ts @@ -163,7 +163,9 @@ describe('rules', () => { Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { LifecycleConfiguration: { Rules: [{ - NoncurrentVersionExpirationInDays: 10, + NoncurrentVersionExpiration: { + NoncurrentDays: 10, + }, NoncurrentVersionTransitions: [ { NewerNoncurrentVersions: 1, @@ -199,7 +201,85 @@ describe('rules', () => { Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { LifecycleConfiguration: { Rules: [{ - NoncurrentVersionExpirationInDays: 10, + NoncurrentVersionExpiration: { + NoncurrentDays: 10, + }, + NoncurrentVersionTransitions: [ + { + StorageClass: 'GLACIER_IR', + TransitionInDays: 10, + }, + ], + Status: 'Enabled', + }], + }, + }); + }); + + test('Noncurrent expiration rule with versions to retain', () => { + // GIVEN + const stack = new Stack(); + + // WHEN: Noncurrent version to retain available + new Bucket(stack, 'Bucket1', { + versioned: true, + lifecycleRules: [{ + noncurrentVersionExpiration: Duration.days(10), + noncurrentVersionsToRetain: 1, + noncurrentVersionTransitions: [ + { + storageClass: StorageClass.GLACIER_INSTANT_RETRIEVAL, + transitionAfter: Duration.days(10), + }, + ], + }], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + LifecycleConfiguration: { + Rules: [{ + NoncurrentVersionExpiration: { + NoncurrentDays: 10, + NewerNoncurrentVersions: 1, + }, + NoncurrentVersionTransitions: [ + { + StorageClass: 'GLACIER_IR', + TransitionInDays: 10, + }, + ], + Status: 'Enabled', + }], + }, + }); + }); + + test('Noncurrent expiration rule without versions to retain', () => { + // GIVEN + const stack = new Stack(); + + // WHEN: Noncurrent version to retain not set + new Bucket(stack, 'Bucket1', { + versioned: true, + lifecycleRules: [{ + noncurrentVersionExpiration: Duration.days(10), + noncurrentVersionTransitions: [ + { + storageClass: StorageClass.GLACIER_INSTANT_RETRIEVAL, + transitionAfter: Duration.days(10), + }, + ], + }], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + LifecycleConfiguration: { + Rules: [{ + NoncurrentVersionExpiration: { + NoncurrentDays: 10, + }, NoncurrentVersionTransitions: [ { StorageClass: 'GLACIER_IR', From 7e824ab40772dc888aec7986e343b12ec1032657 Mon Sep 17 00:00:00 2001 From: cm-iwata <38879253+cm-iwata@users.noreply.github.com> Date: Thu, 19 May 2022 22:59:07 +0900 Subject: [PATCH 14/29] fix(eks): Cluster.FromClusterAttributes ignores KubectlLambdaRole (#20373) This PR will fix #20008. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-eks/lib/cluster.ts | 1 + .../@aws-cdk/aws-eks/test/cluster.test.ts | 93 +++++++++++++------ 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 9d5de9f183e9b..ce295f719abe6 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -2054,6 +2054,7 @@ class ImportedCluster extends ClusterBase { this.clusterName = props.clusterName; this.clusterArn = this.stack.formatArn(clusterArnComponents(props.clusterName)); this.kubectlRole = props.kubectlRoleArn ? iam.Role.fromRoleArn(this, 'KubectlRole', props.kubectlRoleArn) : undefined; + this.kubectlLambdaRole = props.kubectlLambdaRole; this.kubectlSecurityGroup = props.kubectlSecurityGroupId ? ec2.SecurityGroup.fromSecurityGroupId(this, 'KubectlSecurityGroup', props.kubectlSecurityGroupId) : undefined; this.kubectlEnvironment = props.kubectlEnvironment; this.kubectlPrivateSubnets = props.kubectlPrivateSubnetIds ? props.kubectlPrivateSubnetIds.map((subnetid, index) => ec2.Subnet.fromSubnetId(this, `KubectlSubnet${index}`, subnetid)) : undefined; diff --git a/packages/@aws-cdk/aws-eks/test/cluster.test.ts b/packages/@aws-cdk/aws-eks/test/cluster.test.ts index b0abd8ffc28ef..9a4e59f27201b 100644 --- a/packages/@aws-cdk/aws-eks/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-eks/test/cluster.test.ts @@ -11,6 +11,7 @@ import * as cdk8s from 'cdk8s'; import * as constructs from 'constructs'; import * as YAML from 'yaml'; import * as eks from '../lib'; +import { HelmChart } from '../lib'; import { KubectlProvider } from '../lib/kubectl-provider'; import { BottleRocketImage } from '../lib/private/bottlerocket'; import { testFixture, testFixtureNoVpc } from './util'; @@ -2422,43 +2423,77 @@ describe('cluster', () => { }); - test('kubectl provider passes iam role environment to kube ctl lambda', () => { + describe('kubectl provider passes iam role environment to kube ctl lambda', ()=>{ + test('new cluster', () => { - const { stack } = testFixture(); + const { stack } = testFixture(); - const kubectlRole = new iam.Role(stack, 'KubectlIamRole', { - assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), - }); + const kubectlRole = new iam.Role(stack, 'KubectlIamRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); - // using _ syntax to silence warning about _cluster not being used, when it is - const cluster = new eks.Cluster(stack, 'Cluster1', { - version: CLUSTER_VERSION, - prune: false, - endpointAccess: eks.EndpointAccess.PRIVATE, - kubectlLambdaRole: kubectlRole, - }); + // using _ syntax to silence warning about _cluster not being used, when it is + const cluster = new eks.Cluster(stack, 'Cluster1', { + version: CLUSTER_VERSION, + prune: false, + endpointAccess: eks.EndpointAccess.PRIVATE, + kubectlLambdaRole: kubectlRole, + }); - cluster.addManifest('resource', { - kind: 'ConfigMap', - apiVersion: 'v1', - data: { - hello: 'world', - }, - metadata: { - name: 'config-map', - }, - }); + cluster.addManifest('resource', { + kind: 'ConfigMap', + apiVersion: 'v1', + data: { + hello: 'world', + }, + metadata: { + name: 'config-map', + }, + }); + + // the kubectl provider is inside a nested stack. + const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + Role: { + Ref: 'referencetoStackKubectlIamRole02F8947EArn', + }, + }); - // the kubectl provider is inside a nested stack. - const nested = stack.node.tryFindChild('@aws-cdk/aws-eks.KubectlProvider') as cdk.NestedStack; - Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { - Role: { - Ref: 'referencetoStackKubectlIamRole02F8947EArn', - }, }); + test('imported cluster', ()=> { - }); + const clusterName = 'my-cluster'; + const stack = new cdk.Stack(); + const kubectlLambdaRole = new iam.Role(stack, 'KubectlLambdaRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + const cluster = eks.Cluster.fromClusterAttributes(stack, 'Imported', { + clusterName, + kubectlRoleArn: 'arn:aws:iam::1111111:role/iam-role-that-has-masters-access', + kubectlLambdaRole: kubectlLambdaRole, + }); + + const chart = 'hello-world'; + cluster.addHelmChart('test-chart', { + chart, + }); + const nested = stack.node.tryFindChild('Imported-KubectlProvider') as cdk.NestedStack; + Template.fromStack(nested).hasResourceProperties('AWS::Lambda::Function', { + Role: { + Ref: 'referencetoKubectlLambdaRole7D084D94Arn', + }, + }); + Template.fromStack(stack).hasResourceProperties(HelmChart.RESOURCE_TYPE, { + ClusterName: clusterName, + RoleArn: 'arn:aws:iam::1111111:role/iam-role-that-has-masters-access', + Release: 'importedcharttestchartf3acd6e5', + Chart: chart, + Namespace: 'default', + CreateNamespace: true, + }); + }); + }); describe('endpoint access', () => { test('public restricted', () => { From 719edfcb949828a423be2367b5c85b0e9a9c1c12 Mon Sep 17 00:00:00 2001 From: tobytipton <89043612+tobytipton@users.noreply.github.com> Date: Thu, 19 May 2022 10:43:26 -0400 Subject: [PATCH 15/29] fix(pipelines): specifying the Action Role for CodeBuild steps (#18293) This fix should address the issue #18291 fixes #18291 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/codepipeline/codebuild-step.ts | 15 +++++ .../codepipeline/private/codebuild-factory.ts | 9 +++ .../test/codepipeline/codebuild-step.test.ts | 65 ++++++++++++++++++- 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/codebuild-step.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/codebuild-step.ts index 43519fdda97bf..12bb058fceddb 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/codebuild-step.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/codebuild-step.ts @@ -66,6 +66,13 @@ export interface CodeBuildStepProps extends ShellStepProps { */ readonly role?: iam.IRole; + /** + * Custom execution role to be used for the Code Build Action + * + * @default - A role is automatically created + */ + readonly actionRole?: iam.IRole; + /** * Changes to environment * @@ -146,6 +153,13 @@ export class CodeBuildStep extends ShellStep { */ public readonly role?: iam.IRole; + /** + * Custom execution role to be used for the Code Build Action + * + * @default - A role is automatically created + */ + readonly actionRole?: iam.IRole; + /** * Build environment * @@ -183,6 +197,7 @@ export class CodeBuildStep extends ShellStep { this.vpc = props.vpc; this.subnetSelection = props.subnetSelection; this.role = props.role; + this.actionRole = props.actionRole; this.rolePolicyStatements = props.rolePolicyStatements; this.securityGroups = props.securityGroups; this.timeout = props.timeout; diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/private/codebuild-factory.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/private/codebuild-factory.ts index 3103586f71546..85697104e2cc4 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/private/codebuild-factory.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/private/codebuild-factory.ts @@ -44,6 +44,13 @@ export interface CodeBuildFactoryProps { */ readonly role?: iam.IRole; + /** + * Custom execution role to be used for the Code Build Action + * + * @default - A role is automatically created + */ + readonly actionRole?: iam.IRole; + /** * If true, the build spec will be passed via the Cloud Assembly instead of rendered onto the Project * @@ -145,6 +152,7 @@ export class CodeBuildFactory implements ICodePipelineActionFactory { const factory = CodeBuildFactory.fromShellStep(constructId, step, { projectName: step.projectName, role: step.role, + actionRole: step.actionRole, ...additional, projectOptions: mergeCodeBuildOptions(additional?.projectOptions, { buildEnvironment: step.buildEnvironment, @@ -322,6 +330,7 @@ export class CodeBuildFactory implements ICodePipelineActionFactory { outputs: outputArtifacts, project, runOrder: options.runOrder, + role: this.props.actionRole, variablesNamespace: options.variablesNamespace, // Inclusion of the hash here will lead to the pipeline structure for any changes diff --git a/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts b/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts index a48b41ce1d6c6..ad3a5b3be9923 100644 --- a/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts +++ b/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts @@ -1,4 +1,5 @@ import { Template, Match } from '@aws-cdk/assertions'; +import * as iam from '@aws-cdk/aws-iam'; import { Duration, Stack } from '@aws-cdk/core'; import * as cdkp from '../../lib'; import { PIPELINE_ENV, TestApp, ModernTestGitHubNpmPipeline, AppWithOutput } from '../testhelpers'; @@ -143,6 +144,68 @@ test('envFromOutputs works even with very long stage and stack names', () => { // THEN - did not throw an error about identifier lengths }); +test('role passed it used for project and code build action', () => { + const projectRole = new iam.Role( + pipelineStack, + 'ProjectRole', + { + roleName: 'ProjectRole', + assumedBy: new iam.ServicePrincipal('codebuild.amazon.com'), + }, + ); + const buildRole = new iam.Role( + pipelineStack, + 'BuildRole', + { + roleName: 'BuildRole', + assumedBy: new iam.ServicePrincipal('codebuild.amazon.com'), + }, + ); + // WHEN + new cdkp.CodePipeline(pipelineStack, 'Pipeline', { + synth: new cdkp.CodeBuildStep('Synth', { + commands: ['/bin/true'], + input: cdkp.CodePipelineSource.gitHub('test/test', 'main'), + role: projectRole, + actionRole: buildRole, + }), + }); + + // THEN + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { + ServiceRole: { + 'Fn::GetAtt': [ + 'ProjectRole5B707505', + 'Arn', + ], + }, + }); + + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + // source stage + {}, + // build stage, + { + Actions: [ + { + ActionTypeId: { + Category: 'Build', + Owner: 'AWS', + Provider: 'CodeBuild', + }, + RoleArn: { + 'Fn::GetAtt': [ + 'BuildRole41B77417', + 'Arn', + ], + }, + }, + ], + }, + ], + }); +}); test('exportedVariables', () => { const pipeline = new ModernTestGitHubNpmPipeline(pipelineStack, 'Cdk'); @@ -207,4 +270,4 @@ test('exportedVariables', () => { })), }, }); -}); \ No newline at end of file +}); From 6fae606f0a36a3b103d528f01ab9a30f28bcfd9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Tr=C4=99bski?= <kornicameister@gmail.com> Date: Thu, 19 May 2022 17:28:44 +0200 Subject: [PATCH 16/29] chore: stop mypy from complaining about the `Metric` class (#20369) Fixes: #20219 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) *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-cloudwatch/lib/metric.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts index 8f959f37ea201..2e8a14e08cf08 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts @@ -279,6 +279,9 @@ export class Metric implements IMetric { /** Region which this metric comes from. */ public readonly region?: string; + /** Warnings attached to this metric. */ + public readonly warnings?: string[]; + constructor(props: MetricProps) { this.period = props.period || cdk.Duration.minutes(5); const periodSec = this.period.toSeconds(); @@ -295,6 +298,7 @@ export class Metric implements IMetric { this.unit = props.unit; this.account = props.account; this.region = props.region; + this.warnings = undefined; } /** @@ -889,4 +893,4 @@ function matchAll(x: string, re: RegExp): RegExpMatchArray[] { ret.push(m); } return ret; -} \ No newline at end of file +} From f7693e3f981f60886c94fb61876a1e5e0f2c1a02 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser <jogold@users.noreply.github.com> Date: Thu, 19 May 2022 18:11:14 +0200 Subject: [PATCH 17/29] feat(cloudfront): REST API origin (#20335) Add an origin to work with API Gateway REST APIs. Extracts the domain name and origin path from the API url. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-cloudfront-origins/README.md | 14 + .../aws-cloudfront-origins/lib/http-origin.ts | 12 +- .../aws-cloudfront-origins/lib/index.ts | 1 + .../lib/private/utils.ts | 12 + .../lib/rest-api-origin.ts | 59 +++ .../aws-cloudfront-origins/package.json | 2 + .../rosetta/default.ts-fixture | 1 + .../test/integ.rest-api-origin.ts | 17 + .../rest-api-origin.integ.snapshot/cdk.out | 1 + ...g-cloudfront-rest-api-origin.template.json | 237 +++++++++++ .../rest-api-origin.integ.snapshot/integ.json | 14 + .../manifest.json | 70 ++++ .../rest-api-origin.integ.snapshot/tree.json | 370 ++++++++++++++++++ .../test/rest-api-origin.test.ts | 62 +++ .../@aws-cdk/aws-cloudfront/lib/origin.ts | 27 +- 15 files changed, 877 insertions(+), 22 deletions(-) create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/lib/private/utils.ts create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/lib/rest-api-origin.ts create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/integ.rest-api-origin.ts create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ-cloudfront-rest-api-origin.template.json create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.test.ts diff --git a/packages/@aws-cdk/aws-cloudfront-origins/README.md b/packages/@aws-cdk/aws-cloudfront-origins/README.md index d4e948b1e887e..b15c7627c8dcd 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/README.md +++ b/packages/@aws-cdk/aws-cloudfront-origins/README.md @@ -118,3 +118,17 @@ new cloudfront.Distribution(this, 'myDist', { }, }); ``` + +## From an API Gateway REST API + +Origins can be created from an API Gateway REST API. It is recommended to use a +[regional API](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-endpoint-types.html) in this case. + +```ts +declare const api: apigateway.RestApi; +new cloudfront.Distribution(this, 'Distribution', { + defaultBehavior: { origin: new origins.RestApiOrigin(api) }, +}); +``` + +The origin path will automatically be set as the stage name. diff --git a/packages/@aws-cdk/aws-cloudfront-origins/lib/http-origin.ts b/packages/@aws-cdk/aws-cloudfront-origins/lib/http-origin.ts index e4d6ac190dcff..19a4631221573 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/lib/http-origin.ts +++ b/packages/@aws-cdk/aws-cloudfront-origins/lib/http-origin.ts @@ -1,5 +1,6 @@ import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as cdk from '@aws-cdk/core'; +import { validateSecondsInRangeOrUndefined } from './private/utils'; /** * Properties for an Origin backed by an S3 website-configured bucket, load balancer, or custom HTTP server. @@ -79,14 +80,3 @@ export class HttpOrigin extends cloudfront.OriginBase { }; } } - -/** - * Throws an error if a duration is defined and not an integer number of seconds within a range. - */ -function validateSecondsInRangeOrUndefined(name: string, min: number, max: number, duration?: cdk.Duration) { - if (duration === undefined) { return; } - const value = duration.toSeconds(); - if (!Number.isInteger(value) || value < min || value > max) { - throw new Error(`${name}: Must be an int between ${min} and ${max} seconds (inclusive); received ${value}.`); - } -} diff --git a/packages/@aws-cdk/aws-cloudfront-origins/lib/index.ts b/packages/@aws-cdk/aws-cloudfront-origins/lib/index.ts index 6fc3bf1750637..00ac116b126a3 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudfront-origins/lib/index.ts @@ -2,3 +2,4 @@ export * from './http-origin'; export * from './load-balancer-origin'; export * from './s3-origin'; export * from './origin-group'; +export * from './rest-api-origin'; diff --git a/packages/@aws-cdk/aws-cloudfront-origins/lib/private/utils.ts b/packages/@aws-cdk/aws-cloudfront-origins/lib/private/utils.ts new file mode 100644 index 0000000000000..ad6dfc2e69392 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/lib/private/utils.ts @@ -0,0 +1,12 @@ +import * as cdk from '@aws-cdk/core'; + +/** + * Throws an error if a duration is defined and not an integer number of seconds within a range. + */ +export function validateSecondsInRangeOrUndefined(name: string, min: number, max: number, duration?: cdk.Duration) { + if (duration === undefined) { return; } + const value = duration.toSeconds(); + if (!Number.isInteger(value) || value < min || value > max) { + throw new Error(`${name}: Must be an int between ${min} and ${max} seconds (inclusive); received ${value}.`); + } +} diff --git a/packages/@aws-cdk/aws-cloudfront-origins/lib/rest-api-origin.ts b/packages/@aws-cdk/aws-cloudfront-origins/lib/rest-api-origin.ts new file mode 100644 index 0000000000000..53401b575566f --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/lib/rest-api-origin.ts @@ -0,0 +1,59 @@ +import * as apigateway from '@aws-cdk/aws-apigateway'; +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +import * as cdk from '@aws-cdk/core'; +import { validateSecondsInRangeOrUndefined } from './private/utils'; + +/** + * Properties for an Origin for an API Gateway REST API. + */ +export interface RestApiOriginProps extends cloudfront.OriginOptions { + /** + * Specifies how long, in seconds, CloudFront waits for a response from the origin, also known as the origin response timeout. + * The valid range is from 1 to 180 seconds, inclusive. + * + * Note that values over 60 seconds are possible only after a limit increase request for the origin response timeout quota + * has been approved in the target account; otherwise, values over 60 seconds will produce an error at deploy time. + * + * @default Duration.seconds(30) + */ + readonly readTimeout?: cdk.Duration; + + /** + * Specifies how long, in seconds, CloudFront persists its connection to the origin. + * The valid range is from 1 to 180 seconds, inclusive. + * + * Note that values over 60 seconds are possible only after a limit increase request for the origin response timeout quota + * has been approved in the target account; otherwise, values over 60 seconds will produce an error at deploy time. + * + * @default Duration.seconds(5) + */ + readonly keepaliveTimeout?: cdk.Duration; +} + +/** + * An Origin for an API Gateway REST API. + */ +export class RestApiOrigin extends cloudfront.OriginBase { + + constructor(restApi: apigateway.RestApi, private readonly props: RestApiOriginProps = {}) { + // urlForPath() is of the form 'https://<rest-api-id>.execute-api.<region>.amazonaws.com/<stage>' + // Splitting on '/' gives: ['https', '', '<rest-api-id>.execute-api.<region>.amazonaws.com', '<stage>'] + // The element at index 2 is the domain name, the element at index 3 is the stage name + super(cdk.Fn.select(2, cdk.Fn.split('/', restApi.url)), { + originPath: `/${cdk.Fn.select(3, cdk.Fn.split('/', restApi.url))}`, + ...props, + }); + + validateSecondsInRangeOrUndefined('readTimeout', 1, 180, props.readTimeout); + validateSecondsInRangeOrUndefined('keepaliveTimeout', 1, 180, props.keepaliveTimeout); + } + + protected renderCustomOriginConfig(): cloudfront.CfnDistribution.CustomOriginConfigProperty | undefined { + return { + originSslProtocols: [cloudfront.OriginSslPolicy.TLS_V1_2], + originProtocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY, + originReadTimeout: this.props.readTimeout?.toSeconds(), + originKeepaliveTimeout: this.props.keepaliveTimeout?.toSeconds(), + }; + } +} diff --git a/packages/@aws-cdk/aws-cloudfront-origins/package.json b/packages/@aws-cdk/aws-cloudfront-origins/package.json index 0a6922fd79233..ebd025912e116 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/package.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/package.json @@ -86,6 +86,7 @@ "aws-sdk": "^2.848.0" }, "dependencies": { + "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-cloudfront": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", @@ -95,6 +96,7 @@ }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-cloudfront": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudfront-origins/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-cloudfront-origins/rosetta/default.ts-fixture index 73800aee2e589..3c7781d356955 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-cloudfront-origins/rosetta/default.ts-fixture @@ -1,6 +1,7 @@ // Fixture with packages imported, but nothing else import { Duration, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; +import * as apigateway from '@aws-cdk/aws-apigateway'; import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as origins from '@aws-cdk/aws-cloudfront-origins'; import * as s3 from '@aws-cdk/aws-s3'; diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/integ.rest-api-origin.ts b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.rest-api-origin.ts new file mode 100644 index 0000000000000..5c130da58aa00 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/integ.rest-api-origin.ts @@ -0,0 +1,17 @@ +import * as apigateway from '@aws-cdk/aws-apigateway'; +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +import * as cdk from '@aws-cdk/core'; +import * as origins from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'integ-cloudfront-rest-api-origin'); + +const api = new apigateway.RestApi(stack, 'RestApi', { endpointTypes: [apigateway.EndpointType.REGIONAL] }); +api.root.addMethod('GET'); + +new cloudfront.Distribution(stack, 'Distribution', { + defaultBehavior: { origin: new origins.RestApiOrigin(api) }, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..2efc89439fab8 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"18.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ-cloudfront-rest-api-origin.template.json b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ-cloudfront-rest-api-origin.template.json new file mode 100644 index 0000000000000..e2df5f2ac56bd --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ-cloudfront-rest-api-origin.template.json @@ -0,0 +1,237 @@ +{ + "Resources": { + "RestApi0C43BF4B": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Name": "RestApi" + } + }, + "RestApiCloudWatchRoleE3ED6605": { + "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" + ] + ] + } + ] + } + }, + "RestApiAccount7C83CF5A": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "RestApiCloudWatchRoleE3ED6605", + "Arn" + ] + } + }, + "DependsOn": [ + "RestApi0C43BF4B" + ] + }, + "RestApiDeployment180EC50368af6d4b358eff290c08cb2de07c4042": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "RestApiGET0F59260B" + ] + }, + "RestApiDeploymentStageprod3855DE66": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "DeploymentId": { + "Ref": "RestApiDeployment180EC50368af6d4b358eff290c08cb2de07c4042" + }, + "StageName": "prod" + }, + "DependsOn": [ + "RestApiAccount7C83CF5A" + ] + }, + "RestApiGET0F59260B": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "ResourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "NONE", + "Integration": { + "Type": "MOCK" + } + } + }, + "Distribution830FAC52": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "Compress": true, + "TargetOriginId": "integcloudfrontrestapioriginDistributionOrigin1B3EE0E72", + "ViewerProtocolPolicy": "allow-all" + }, + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only", + "OriginSSLProtocols": [ + "TLSv1.2" + ] + }, + "DomainName": { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + ] + } + ] + }, + "Id": "integcloudfrontrestapioriginDistributionOrigin1B3EE0E72", + "OriginPath": { + "Fn::Join": [ + "", + [ + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + ] + } + ] + } + ] + ] + } + } + ] + } + } + } + }, + "Outputs": { + "RestApiEndpoint0551178A": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ.json b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ.json new file mode 100644 index 0000000000000..a6290c917cec4 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/integ.json @@ -0,0 +1,14 @@ +{ + "version": "18.0.0", + "testCases": { + "integ.rest-api-origin": { + "stacks": [ + "integ-cloudfront-rest-api-origin" + ], + "diffAssets": false, + "stackUpdateWorkflow": true + } + }, + "synthContext": {}, + "enableLookups": false +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..6c1cd993b1fe4 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/manifest.json @@ -0,0 +1,70 @@ +{ + "version": "18.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "integ-cloudfront-rest-api-origin": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integ-cloudfront-rest-api-origin.template.json", + "validateOnSynth": false + }, + "metadata": { + "/integ-cloudfront-rest-api-origin/RestApi/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApi0C43BF4B" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/CloudWatchRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiCloudWatchRoleE3ED6605" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/Account": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiAccount7C83CF5A" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/Deployment/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiDeployment180EC50368af6d4b358eff290c08cb2de07c4042" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/DeploymentStage.prod/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiDeploymentStageprod3855DE66" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/Endpoint": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiEndpoint0551178A" + } + ], + "/integ-cloudfront-rest-api-origin/RestApi/Default/GET/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RestApiGET0F59260B" + } + ], + "/integ-cloudfront-rest-api-origin/Distribution/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Distribution830FAC52" + } + ] + }, + "displayName": "integ-cloudfront-rest-api-origin" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/tree.json b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/tree.json new file mode 100644 index 0000000000000..21004e47d97fb --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.integ.snapshot/tree.json @@ -0,0 +1,370 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "@aws-cdk/core.Construct", + "version": "0.0.0" + } + }, + "integ-cloudfront-rest-api-origin": { + "id": "integ-cloudfront-rest-api-origin", + "path": "integ-cloudfront-rest-api-origin", + "children": { + "RestApi": { + "id": "RestApi", + "path": "integ-cloudfront-rest-api-origin/RestApi", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/RestApi/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::RestApi", + "aws:cdk:cloudformation:props": { + "endpointConfiguration": { + "types": [ + "REGIONAL" + ] + }, + "name": "RestApi" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnRestApi", + "version": "0.0.0" + } + }, + "CloudWatchRole": { + "id": "CloudWatchRole", + "path": "integ-cloudfront-rest-api-origin/RestApi/CloudWatchRole", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/RestApi/CloudWatchRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "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" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Account": { + "id": "Account", + "path": "integ-cloudfront-rest-api-origin/RestApi/Account", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Account", + "aws:cdk:cloudformation:props": { + "cloudWatchRoleArn": { + "Fn::GetAtt": [ + "RestApiCloudWatchRoleE3ED6605", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnAccount", + "version": "0.0.0" + } + }, + "Deployment": { + "id": "Deployment", + "path": "integ-cloudfront-rest-api-origin/RestApi/Deployment", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/RestApi/Deployment/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Deployment", + "aws:cdk:cloudformation:props": { + "restApiId": { + "Ref": "RestApi0C43BF4B" + }, + "description": "Automatically created by the RestApi construct" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnDeployment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.Deployment", + "version": "0.0.0" + } + }, + "DeploymentStage.prod": { + "id": "DeploymentStage.prod", + "path": "integ-cloudfront-rest-api-origin/RestApi/DeploymentStage.prod", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/RestApi/DeploymentStage.prod/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Stage", + "aws:cdk:cloudformation:props": { + "restApiId": { + "Ref": "RestApi0C43BF4B" + }, + "deploymentId": { + "Ref": "RestApiDeployment180EC50368af6d4b358eff290c08cb2de07c4042" + }, + "stageName": "prod" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnStage", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Endpoint": { + "id": "Endpoint", + "path": "integ-cloudfront-rest-api-origin/RestApi/Endpoint", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "integ-cloudfront-rest-api-origin/RestApi/Default", + "children": { + "GET": { + "id": "GET", + "path": "integ-cloudfront-rest-api-origin/RestApi/Default/GET", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/RestApi/Default/GET/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::Method", + "aws:cdk:cloudformation:props": { + "httpMethod": "GET", + "resourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "restApiId": { + "Ref": "RestApi0C43BF4B" + }, + "authorizationType": "NONE", + "integration": { + "type": "MOCK" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnMethod", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.Method", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.ResourceBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Distribution": { + "id": "Distribution", + "path": "integ-cloudfront-rest-api-origin/Distribution", + "children": { + "Origin1": { + "id": "Origin1", + "path": "integ-cloudfront-rest-api-origin/Distribution/Origin1", + "constructInfo": { + "fqn": "@aws-cdk/core.Construct", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-rest-api-origin/Distribution/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudFront::Distribution", + "aws:cdk:cloudformation:props": { + "distributionConfig": { + "enabled": true, + "origins": [ + { + "domainName": { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + ] + } + ] + }, + "id": "integcloudfrontrestapioriginDistributionOrigin1B3EE0E72", + "originPath": { + "Fn::Join": [ + "", + [ + "/", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + "/", + { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + ] + } + ] + } + ] + ] + }, + "customOriginConfig": { + "originSslProtocols": [ + "TLSv1.2" + ], + "originProtocolPolicy": "https-only" + } + } + ], + "defaultCacheBehavior": { + "pathPattern": "*", + "targetOriginId": "integcloudfrontrestapioriginDistributionOrigin1B3EE0E72", + "cachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", + "compress": true, + "viewerProtocolPolicy": "allow-all" + }, + "httpVersion": "http2", + "ipv6Enabled": true + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CfnDistribution", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.Distribution", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.test.ts b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.test.ts new file mode 100644 index 0000000000000..cc89ec8e59976 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/rest-api-origin.test.ts @@ -0,0 +1,62 @@ +import * as apigateway from '@aws-cdk/aws-apigateway'; +import { Stack } from '@aws-cdk/core'; +import { RestApiOrigin } from '../lib'; + +let stack: Stack; + +beforeEach(() => { + stack = new Stack(); +}); + +test('Correctly renders the origin', () => { + const api = new apigateway.RestApi(stack, 'RestApi'); + api.root.addMethod('GET'); + + const origin = new RestApiOrigin(api); + const originBindConfig = origin.bind(stack, { originId: 'StackOrigin029E19582' }); + + expect(stack.resolve(originBindConfig.originProperty)).toEqual({ + id: 'StackOrigin029E19582', + domainName: { + 'Fn::Select': [2, { + 'Fn::Split': ['/', { + 'Fn::Join': ['', [ + 'https://', { Ref: 'RestApi0C43BF4B' }, + '.execute-api.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + '/', + { Ref: 'RestApiDeploymentStageprod3855DE66' }, + '/', + ]], + }], + }], + }, + originPath: { + 'Fn::Join': ['', ['/', { + 'Fn::Select': [3, { + 'Fn::Split': ['/', { + 'Fn::Join': ['', [ + 'https://', + { Ref: 'RestApi0C43BF4B' }, + '.execute-api.', + { Ref: 'AWS::Region' }, + '.', + { Ref: 'AWS::URLSuffix' }, + '/', + { Ref: 'RestApiDeploymentStageprod3855DE66' }, + '/', + ]], + }], + }], + }]], + }, + customOriginConfig: { + originProtocolPolicy: 'https-only', + originSslProtocols: [ + 'TLSv1.2', + ], + }, + }); +}); diff --git a/packages/@aws-cdk/aws-cloudfront/lib/origin.ts b/packages/@aws-cdk/aws-cloudfront/lib/origin.ts index 12672e5406abb..7addd7d4ebaca 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/origin.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/origin.ts @@ -51,17 +51,9 @@ export interface IOrigin { } /** - * Properties to define an Origin. + * Options to define an Origin. */ -export interface OriginProps { - /** - * An optional path that CloudFront appends to the origin domain name when CloudFront requests content from the origin. - * Must begin, but not end, with '/' (e.g., '/production/images'). - * - * @default '/' - */ - readonly originPath?: string; - +export interface OriginOptions { /** * The number of seconds that CloudFront waits when trying to establish a connection to the origin. * Valid values are 1-10 seconds, inclusive. @@ -94,6 +86,19 @@ export interface OriginProps { readonly originShieldRegion?: string; } +/** + * Properties to define an Origin. + */ +export interface OriginProps extends OriginOptions { + /** + * An optional path that CloudFront appends to the origin domain name when CloudFront requests content from the origin. + * Must begin, but not end, with '/' (e.g., '/production/images'). + * + * @default '/' + */ + readonly originPath?: string; +} + /** * Options passed to Origin.bind(). */ @@ -236,4 +241,4 @@ function validateCustomHeaders(customHeaders?: Record<string, string>) { if (prohibitedHeaderPrefixMatches.length !== 0) { throw new Error(`The following headers cannot be used as prefixes for custom origin headers: ${prohibitedHeaderPrefixMatches.join(', ')}`); } -} \ No newline at end of file +} From d0163f8a3d14e38f67b381c569b5bd3af92c4f51 Mon Sep 17 00:00:00 2001 From: Tejas M R <matblockgreninja@gmail.com> Date: Thu, 19 May 2022 22:24:40 +0530 Subject: [PATCH 18/29] fix(iam): AccountPrincipal accepts values which aren't account IDs (#20292) Changed the type of accountId in AccountPrincipal constructor to string from any fixes #20288 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-iam/lib/principals.ts | 3 +++ packages/@aws-cdk/aws-iam/test/principals.test.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/packages/@aws-cdk/aws-iam/lib/principals.ts b/packages/@aws-cdk/aws-iam/lib/principals.ts index 8cb94c33a0b1d..77dc3003a4ddf 100644 --- a/packages/@aws-cdk/aws-iam/lib/principals.ts +++ b/packages/@aws-cdk/aws-iam/lib/principals.ts @@ -394,6 +394,9 @@ export class AccountPrincipal extends ArnPrincipal { */ constructor(public readonly accountId: any) { super(new StackDependentToken(stack => `arn:${stack.partition}:iam::${accountId}:root`).toString()); + if (!cdk.Token.isUnresolved(accountId) && typeof accountId !== 'string') { + throw new Error('accountId should be of type string'); + } this.principalAccount = accountId; } diff --git a/packages/@aws-cdk/aws-iam/test/principals.test.ts b/packages/@aws-cdk/aws-iam/test/principals.test.ts index 34206540def53..b5c936be58a2e 100644 --- a/packages/@aws-cdk/aws-iam/test/principals.test.ts +++ b/packages/@aws-cdk/aws-iam/test/principals.test.ts @@ -294,6 +294,10 @@ test('AccountPrincipal can specify an organization', () => { }); }); +test('Passing non-string as accountId parameter in AccountPrincipal constructor should throw error', () => { + expect(() => new iam.AccountPrincipal(1234)).toThrowError('accountId should be of type string'); +}); + test('ServicePrincipal in agnostic stack generates lookup table', () => { // GIVEN const stack = new Stack(); From 01708bc7cf842eab7e1d1fc58bf42e4724624c0a Mon Sep 17 00:00:00 2001 From: Joshua Weber <57131123+daschaa@users.noreply.github.com> Date: Thu, 19 May 2022 19:38:25 +0200 Subject: [PATCH 19/29] feat(cloud9): configure Connection Type of Ec2Environment (#20250) Fixes #17027. Adds `connectionType` property to the L2 Construct `cloud9.Ec2Environment`. For the connection type an enum was implemented which is either `"CONNECT_SSH` or `CONNECT_SSM`. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-cloud9/lib/environment.ts | 24 + .../test/cloud9.environment.test.ts | 20 +- .../C9Stack.template.json | 1 + .../C9Stack.template.json | 409 ++++++++++ .../connection-type.integ.snapshot/cdk.out | 1 + .../connection-type.integ.snapshot/integ.json | 14 + .../manifest.json | 172 +++++ .../connection-type.integ.snapshot/tree.json | 704 ++++++++++++++++++ .../aws-cloud9/test/integ.connection-type.ts | 38 + 9 files changed, 1382 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/C9Stack.template.json create mode 100644 packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-cloud9/test/integ.connection-type.ts diff --git a/packages/@aws-cdk/aws-cloud9/lib/environment.ts b/packages/@aws-cdk/aws-cloud9/lib/environment.ts index 5dced968ac40e..ed269cbc64a63 100644 --- a/packages/@aws-cdk/aws-cloud9/lib/environment.ts +++ b/packages/@aws-cdk/aws-cloud9/lib/environment.ts @@ -24,6 +24,20 @@ export interface IEc2Environment extends cdk.IResource { readonly ec2EnvironmentArn: string; } +/** + * The connection type used for connecting to an Amazon EC2 environment. + */ +export enum ConnectionType { + /** + * Conect through SSH + */ + CONNECT_SSH = 'CONNECT_SSH', + /** + * Connect through AWS Systems Manager + */ + CONNECT_SSM = 'CONNECT_SSM' +} + /** * Properties for Ec2Environment */ @@ -70,6 +84,15 @@ export interface Ec2EnvironmentProps { */ // readonly clonedRepositories?: Cloud9Repository[]; readonly clonedRepositories?: CloneRepository[]; + + /** + * The connection type used for connecting to an Amazon EC2 environment. + * + * Valid values are: CONNECT_SSH (default) and CONNECT_SSM (connected through AWS Systems Manager) + * + * @default - CONNECT_SSH + */ + readonly connectionType?: ConnectionType } /** @@ -139,6 +162,7 @@ export class Ec2Environment extends cdk.Resource implements IEc2Environment { repositoryUrl: r.repositoryUrl, pathComponent: r.pathComponent, })) : undefined, + connectionType: props.connectionType ?? ConnectionType.CONNECT_SSH, }); this.environmentId = c9env.ref; this.ec2EnvironmentArn = c9env.getAtt('Arn').toString(); diff --git a/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts b/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts index 6b5f87ab6c7d8..687a93d4fb786 100644 --- a/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts +++ b/packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts @@ -1,8 +1,9 @@ -import { Template } from '@aws-cdk/assertions'; +import { Match, Template } from '@aws-cdk/assertions'; import * as codecommit from '@aws-cdk/aws-codecommit'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import * as cloud9 from '../lib'; +import { ConnectionType } from '../lib'; let stack: cdk.Stack; let vpc: ec2.IVpc; @@ -105,3 +106,20 @@ test('can use CodeCommit repositories', () => { ], }); }); + +test.each([ + [ConnectionType.CONNECT_SSH, 'CONNECT_SSH'], + [ConnectionType.CONNECT_SSM, 'CONNECT_SSM'], + [undefined, 'CONNECT_SSH'], +])('has connection type property (%s)', (connectionType, expected) => { + new cloud9.Ec2Environment(stack, 'C9Env', { + vpc, + connectionType, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Cloud9::EnvironmentEC2', { + InstanceType: Match.anyValue(), + ConnectionType: expected, + SubnetId: Match.anyValue(), + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloud9/test/cloud9.integ.snapshot/C9Stack.template.json b/packages/@aws-cdk/aws-cloud9/test/cloud9.integ.snapshot/C9Stack.template.json index e1f3e49a031e1..5fdf4618385ec 100644 --- a/packages/@aws-cdk/aws-cloud9/test/cloud9.integ.snapshot/C9Stack.template.json +++ b/packages/@aws-cdk/aws-cloud9/test/cloud9.integ.snapshot/C9Stack.template.json @@ -360,6 +360,7 @@ "C9EnvF05FC3BE": { "Type": "AWS::Cloud9::EnvironmentEC2", "Properties": { + "ConnectionType": "CONNECT_SSH", "InstanceType": "t2.micro", "Repositories": [ { diff --git a/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/C9Stack.template.json b/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/C9Stack.template.json new file mode 100644 index 0000000000000..2e3163332b034 --- /dev/null +++ b/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/C9Stack.template.json @@ -0,0 +1,409 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "C9Stack/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.0.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "C9Stack/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "C9Stack/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": "C9Stack/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "C9Stack/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.64.0/18", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "C9Stack/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "C9Stack/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.128.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "C9Stack/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "C9Stack/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": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.192.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "C9Stack/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "C9Stack/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "C9Stack/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "Repo02AC86CF": { + "Type": "AWS::CodeCommit::Repository", + "Properties": { + "RepositoryName": "foo" + } + }, + "C9EnvF05FC3BE": { + "Type": "AWS::Cloud9::EnvironmentEC2", + "Properties": { + "ConnectionType": "CONNECT_SSM", + "InstanceType": "t2.micro", + "Repositories": [ + { + "PathComponent": "/foo", + "RepositoryUrl": { + "Fn::GetAtt": [ + "Repo02AC86CF", + "CloneUrlHttp" + ] + } + } + ], + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + } + }, + "Outputs": { + "URL": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "AWS::Region" + }, + ".console.aws.amazon.com/cloud9/ide/", + { + "Ref": "C9EnvF05FC3BE" + } + ] + ] + } + }, + "ARN": { + "Value": { + "Fn::GetAtt": [ + "C9EnvF05FC3BE", + "Arn" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..90bef2e09ad39 --- /dev/null +++ b/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"17.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/integ.json b/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/integ.json new file mode 100644 index 0000000000000..a66248fe1e546 --- /dev/null +++ b/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/integ.json @@ -0,0 +1,14 @@ +{ + "version": "18.0.0", + "testCases": { + "aws-cloud9/test/integ.cloud9": { + "stacks": [ + "C9Stack" + ], + "diffAssets": false, + "stackUpdateWorkflow": true + } + }, + "synthContext": {}, + "enableLookups": false +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..1bc910cfa1616 --- /dev/null +++ b/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/manifest.json @@ -0,0 +1,172 @@ +{ + "version": "17.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "C9Stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "C9Stack.template.json", + "validateOnSynth": false + }, + "metadata": { + "/C9Stack/VPC/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCB9E5F0B4" + } + ], + "/C9Stack/VPC/PublicSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1SubnetB4246D30" + } + ], + "/C9Stack/VPC/PublicSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1RouteTableFEE4B781" + } + ], + "/C9Stack/VPC/PublicSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1RouteTableAssociation0B0896DC" + } + ], + "/C9Stack/VPC/PublicSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1DefaultRoute91CEF279" + } + ], + "/C9Stack/VPC/PublicSubnet1/EIP": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1EIP6AD938E8" + } + ], + "/C9Stack/VPC/PublicSubnet1/NATGateway": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet1NATGatewayE0556630" + } + ], + "/C9Stack/VPC/PublicSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2Subnet74179F39" + } + ], + "/C9Stack/VPC/PublicSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2RouteTable6F1A15F1" + } + ], + "/C9Stack/VPC/PublicSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2RouteTableAssociation5A808732" + } + ], + "/C9Stack/VPC/PublicSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPublicSubnet2DefaultRouteB7481BBA" + } + ], + "/C9Stack/VPC/PrivateSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1Subnet8BCA10E0" + } + ], + "/C9Stack/VPC/PrivateSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1RouteTableBE8A6027" + } + ], + "/C9Stack/VPC/PrivateSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1RouteTableAssociation347902D1" + } + ], + "/C9Stack/VPC/PrivateSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet1DefaultRouteAE1D6490" + } + ], + "/C9Stack/VPC/PrivateSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + ], + "/C9Stack/VPC/PrivateSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2RouteTable0A19E10E" + } + ], + "/C9Stack/VPC/PrivateSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2RouteTableAssociation0C73D413" + } + ], + "/C9Stack/VPC/PrivateSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCPrivateSubnet2DefaultRouteF4F5CFD2" + } + ], + "/C9Stack/VPC/IGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCIGWB7E252D3" + } + ], + "/C9Stack/VPC/VPCGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VPCVPCGW99B986DC" + } + ], + "/C9Stack/Repo/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Repo02AC86CF" + } + ], + "/C9Stack/C9Env/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "C9EnvF05FC3BE" + } + ], + "/C9Stack/URL": [ + { + "type": "aws:cdk:logicalId", + "data": "URL" + } + ], + "/C9Stack/ARN": [ + { + "type": "aws:cdk:logicalId", + "data": "ARN" + } + ] + }, + "displayName": "C9Stack" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/tree.json b/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/tree.json new file mode 100644 index 0000000000000..833dd191139cd --- /dev/null +++ b/packages/@aws-cdk/aws-cloud9/test/connection-type.integ.snapshot/tree.json @@ -0,0 +1,704 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "@aws-cdk/core.Construct", + "version": "0.0.0" + } + }, + "C9Stack": { + "id": "C9Stack", + "path": "C9Stack", + "children": { + "VPC": { + "id": "VPC", + "path": "C9Stack/VPC", + "children": { + "Resource": { + "id": "Resource", + "path": "C9Stack/VPC/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPC", + "aws:cdk:cloudformation:props": { + "cidrBlock": "10.0.0.0/16", + "enableDnsHostnames": true, + "enableDnsSupport": true, + "instanceTenancy": "default", + "tags": [ + { + "key": "Name", + "value": "C9Stack/VPC" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPC", + "version": "0.0.0" + } + }, + "PublicSubnet1": { + "id": "PublicSubnet1", + "path": "C9Stack/VPC/PublicSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "C9Stack/VPC/PublicSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.0.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "C9Stack/VPC/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "C9Stack/VPC/PublicSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "C9Stack/VPC/PublicSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "tags": [ + { + "key": "Name", + "value": "C9Stack/VPC/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "C9Stack/VPC/PublicSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "subnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "C9Stack/VPC/PublicSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + }, + "EIP": { + "id": "EIP", + "path": "C9Stack/VPC/PublicSubnet1/EIP", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EIP", + "aws:cdk:cloudformation:props": { + "domain": "vpc", + "tags": [ + { + "key": "Name", + "value": "C9Stack/VPC/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnEIP", + "version": "0.0.0" + } + }, + "NATGateway": { + "id": "NATGateway", + "path": "C9Stack/VPC/PublicSubnet1/NATGateway", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", + "aws:cdk:cloudformation:props": { + "subnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "allocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "tags": [ + { + "key": "Name", + "value": "C9Stack/VPC/PublicSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnNatGateway", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PublicSubnet2": { + "id": "PublicSubnet2", + "path": "C9Stack/VPC/PublicSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "C9Stack/VPC/PublicSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.64.0/18", + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "C9Stack/VPC/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "C9Stack/VPC/PublicSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "C9Stack/VPC/PublicSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "tags": [ + { + "key": "Name", + "value": "C9Stack/VPC/PublicSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "C9Stack/VPC/PublicSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "subnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "C9Stack/VPC/PublicSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PublicSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet1": { + "id": "PrivateSubnet1", + "path": "C9Stack/VPC/PrivateSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "C9Stack/VPC/PrivateSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.128.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "C9Stack/VPC/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "C9Stack/VPC/PrivateSubnet1/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "C9Stack/VPC/PrivateSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "tags": [ + { + "key": "Name", + "value": "C9Stack/VPC/PrivateSubnet1" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "C9Stack/VPC/PrivateSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "subnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "C9Stack/VPC/PrivateSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "PrivateSubnet2": { + "id": "PrivateSubnet2", + "path": "C9Stack/VPC/PrivateSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "C9Stack/VPC/PrivateSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.192.0/18", + "mapPublicIpOnLaunch": false, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Private" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Private" + }, + { + "key": "Name", + "value": "C9Stack/VPC/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnet", + "version": "0.0.0" + } + }, + "Acl": { + "id": "Acl", + "path": "C9Stack/VPC/PrivateSubnet2/Acl", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "C9Stack/VPC/PrivateSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "tags": [ + { + "key": "Name", + "value": "C9Stack/VPC/PrivateSubnet2" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRouteTable", + "version": "0.0.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "C9Stack/VPC/PrivateSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "subnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnSubnetRouteTableAssociation", + "version": "0.0.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "C9Stack/VPC/PrivateSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "destinationCidrBlock": "0.0.0.0/0", + "natGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnRoute", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.PrivateSubnet", + "version": "0.0.0" + } + }, + "IGW": { + "id": "IGW", + "path": "C9Stack/VPC/IGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::InternetGateway", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "C9Stack/VPC" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnInternetGateway", + "version": "0.0.0" + } + }, + "VPCGW": { + "id": "VPCGW", + "path": "C9Stack/VPC/VPCGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPCGatewayAttachment", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "VPCB9E5F0B4" + }, + "internetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.CfnVPCGatewayAttachment", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ec2.Vpc", + "version": "0.0.0" + } + }, + "Repo": { + "id": "Repo", + "path": "C9Stack/Repo", + "children": { + "Resource": { + "id": "Resource", + "path": "C9Stack/Repo/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CodeCommit::Repository", + "aws:cdk:cloudformation:props": { + "repositoryName": "foo" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codecommit.CfnRepository", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codecommit.Repository", + "version": "0.0.0" + } + }, + "C9Env": { + "id": "C9Env", + "path": "C9Stack/C9Env", + "children": { + "Resource": { + "id": "Resource", + "path": "C9Stack/C9Env/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Cloud9::EnvironmentEC2", + "aws:cdk:cloudformation:props": { + "instanceType": "t2.micro", + "repositories": [ + { + "repositoryUrl": { + "Fn::GetAtt": [ + "Repo02AC86CF", + "CloneUrlHttp" + ] + }, + "pathComponent": "/foo" + } + ], + "subnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloud9.CfnEnvironmentEC2", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloud9.Ec2Environment", + "version": "0.0.0" + } + }, + "URL": { + "id": "URL", + "path": "C9Stack/URL", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "ARN": { + "id": "ARN", + "path": "C9Stack/ARN", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloud9/test/integ.connection-type.ts b/packages/@aws-cdk/aws-cloud9/test/integ.connection-type.ts new file mode 100644 index 0000000000000..98fb65e2649f0 --- /dev/null +++ b/packages/@aws-cdk/aws-cloud9/test/integ.connection-type.ts @@ -0,0 +1,38 @@ +import * as codecommit from '@aws-cdk/aws-codecommit'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import * as constructs from 'constructs'; +import * as cloud9 from '../lib'; +import { ConnectionType } from '../lib'; + +export class Cloud9Env extends cdk.Stack { + constructor(scope: constructs.Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const vpc = new ec2.Vpc(this, 'VPC', { + maxAzs: 2, + natGateways: 1, + }); + + // create a codecommit repository to clone into the cloud9 environment + const repo = new codecommit.Repository(this, 'Repo', { + repositoryName: 'foo', + }); + + // create a cloud9 ec2 environment in a new VPC + const c9env = new cloud9.Ec2Environment(this, 'C9Env', { + vpc, + connectionType: ConnectionType.CONNECT_SSM, + // clone repositories into the environment + clonedRepositories: [ + cloud9.CloneRepository.fromCodeCommit(repo, '/foo'), + ], + }); + new cdk.CfnOutput(this, 'URL', { value: c9env.ideUrl }); + new cdk.CfnOutput(this, 'ARN', { value: c9env.ec2EnvironmentArn }); + } +} + +const app = new cdk.App(); + +new Cloud9Env(app, 'C9Stack'); From ae6418393582232c959fa956c95b71930de8f185 Mon Sep 17 00:00:00 2001 From: Christopher Rybicki <crybicki98@gmail.com> Date: Thu, 19 May 2022 11:56:29 -0700 Subject: [PATCH 20/29] chore(core): revert "allow disabling of LogicalID Metadata in case of large manifest" (#20421) aws/aws-cdk#20387 tests fail on our merge to v2, likely because CDK v2 uses the new style stack synthesis by default and generates a lot more metadata. Reverting for now to unblock the release. --- packages/@aws-cdk/core/lib/cfn-element.ts | 5 +- packages/@aws-cdk/core/test/synthesis.test.ts | 85 ++----------------- packages/@aws-cdk/cx-api/lib/app.ts | 6 -- 3 files changed, 6 insertions(+), 90 deletions(-) diff --git a/packages/@aws-cdk/core/lib/cfn-element.ts b/packages/@aws-cdk/core/lib/cfn-element.ts index 84126add4e13c..9bb08746c4a47 100644 --- a/packages/@aws-cdk/core/lib/cfn-element.ts +++ b/packages/@aws-cdk/core/lib/cfn-element.ts @@ -1,5 +1,4 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; import { Construct, Node } from 'constructs'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. @@ -65,9 +64,7 @@ export abstract class CfnElement extends CoreConstruct { displayHint: `${notTooLong(Node.of(this).path)}.LogicalID`, }); - if (!this.node.tryGetContext(cxapi.DISABLE_LOGICAL_ID_METADATA)) { - Node.of(this).addMetadata(cxschema.ArtifactMetadataEntryType.LOGICAL_ID, this.logicalId, this.constructor); - } + Node.of(this).addMetadata(cxschema.ArtifactMetadataEntryType.LOGICAL_ID, this.logicalId, this.constructor); } /** diff --git a/packages/@aws-cdk/core/test/synthesis.test.ts b/packages/@aws-cdk/core/test/synthesis.test.ts index a419a481da3f3..496fd76fdbd91 100644 --- a/packages/@aws-cdk/core/test/synthesis.test.ts +++ b/packages/@aws-cdk/core/test/synthesis.test.ts @@ -36,6 +36,7 @@ describe('synthesis', () => { }, }), }); + }); test('synthesis respects disabling tree metadata', () => { @@ -44,87 +45,7 @@ describe('synthesis', () => { }); const assembly = app.synth(); expect(list(assembly.directory)).toEqual(['cdk.out', 'manifest.json']); - }); - - test('synthesis respects disabling logicalId metadata', () => { - const app = new cdk.App({ - context: { 'aws:cdk:disable-logicalId-metadata': true }, - }); - const stack = new cdk.Stack(app, 'one-stack'); - new cdk.CfnResource(stack, 'MagicResource', { type: 'Resource::Type' }); - - // WHEN - const session = app.synth(); - - // THEN - expect(session.manifest).toEqual({ - version: cxschema.Manifest.version(), - artifacts: { - 'Tree': { - type: 'cdk:tree', - properties: { file: 'tree.json' }, - }, - 'one-stack': { - type: 'aws:cloudformation:stack', - environment: 'aws://unknown-account/unknown-region', - properties: { - templateFile: 'one-stack.template.json', - validateOnSynth: false, - }, - displayName: 'one-stack', - // no metadata, because the only entry was a logicalId - }, - }, - }); - }); - - test('synthesis respects disabling logicalId metadata, and does not disable other metadata', () => { - const app = new cdk.App({ - context: { 'aws:cdk:disable-logicalId-metadata': true }, - stackTraces: false, - }); - const stack = new cdk.Stack(app, 'one-stack', { tags: { boomTag: 'BOOM' } }); - new cdk.CfnResource(stack, 'MagicResource', { type: 'Resource::Type' }); - - // WHEN - const session = app.synth(); - // THEN - expect(session.manifest).toEqual({ - version: cxschema.Manifest.version(), - artifacts: { - 'Tree': { - type: 'cdk:tree', - properties: { file: 'tree.json' }, - }, - 'one-stack': { - type: 'aws:cloudformation:stack', - environment: 'aws://unknown-account/unknown-region', - properties: { - templateFile: 'one-stack.template.json', - validateOnSynth: false, - tags: { - boomTag: 'BOOM', - }, - }, - displayName: 'one-stack', - metadata: { - '/one-stack': [ - { - type: 'aws:cdk:stack-tags', - data: [ - { - key: 'boomTag', - value: 'BOOM', - }, - ], - }, - ], - }, - // no logicalId entry - }, - }, - }); }); test('single empty stack', () => { @@ -137,6 +58,7 @@ describe('synthesis', () => { // THEN expect(list(session.directory).includes('one-stack.template.json')).toEqual(true); + }); test('some random construct implements "synthesize"', () => { @@ -190,6 +112,7 @@ describe('synthesis', () => { }, }, }); + }); test('random construct uses addCustomSynthesis', () => { @@ -249,6 +172,7 @@ describe('synthesis', () => { }, }, }); + }); testDeprecated('it should be possible to synthesize without an app', () => { @@ -296,6 +220,7 @@ describe('synthesis', () => { expect(stack.templateFile).toEqual('hey.json'); expect(stack.parameters).toEqual({ paramId: 'paramValue', paramId2: 'paramValue2' }); expect(stack.environment).toEqual({ region: 'us-east-1', account: 'unknown-account', name: 'aws://unknown-account/us-east-1' }); + }); }); diff --git a/packages/@aws-cdk/cx-api/lib/app.ts b/packages/@aws-cdk/cx-api/lib/app.ts index c08fd5868207b..41c03f374b408 100644 --- a/packages/@aws-cdk/cx-api/lib/app.ts +++ b/packages/@aws-cdk/cx-api/lib/app.ts @@ -39,12 +39,6 @@ export const DISABLE_ASSET_STAGING_CONTEXT = 'aws:cdk:disable-asset-staging'; */ export const DISABLE_METADATA_STACK_TRACE = 'aws:cdk:disable-stack-trace'; -/** - * If this context key is set, the CDK will not store logical ID - * metadata in the manifest. - */ -export const DISABLE_LOGICAL_ID_METADATA = 'aws:cdk:disable-logicalId-metadata'; - /** * Run bundling for stacks specified in this context key */ From f9552c08b8fa0271c62871b0cf55a2087f83e3fa Mon Sep 17 00:00:00 2001 From: Christopher Rybicki <rybickic@amazon.com> Date: Thu, 19 May 2022 15:01:19 -0700 Subject: [PATCH 21/29] chore(codepipeline): revert "cannot deploy pipeline stack with crossAccountKeys twice" (#20427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR fails in CDK v2 because the added unit tests that use `testFutureBehavior` fail on the CDK v2 branch. I believe they're failing because the `testFutureBehavior` utility function was written before CDK v2 was released, and so it automatically discards all feature flags - which should not be happening for new feature flags. I'm not sure what the best fix for this is so I'm just reverting it for the time being to unblock the release. Test logs: <details> ``` FAIL test/pipeline.test.js (12.04 s) ● › cross account key alias name tests › cross account key alias is named with stack name instead of ID when feature flag is enabled Template has 1 resources with type AWS::KMS::Alias, but none match as expected. The closest result is: { "Type": "AWS::KMS::Alias", "Properties": { "AliasName": "alias/codepipeline-pipelinestackpipeline9db740af", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", "Arn" ] } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" } with the following mismatches: Expected alias/codepipeline-actual-stack-name-pipeline-0a412eb5 but received alias/codepipeline-pipelinestackpipeline9db740af at /Properties/AliasName (using objectLike matcher) 83 | const matchError = hasResourceProperties(this.template, type, props); 84 | if (matchError) { > 85 | throw new Error(matchError); | ^ 86 | } 87 | } 88 | at Template.hasResourceProperties (../assertions/lib/template.ts:85:13) at fn (test/pipeline.test.ts:500:33) at Object.<anonymous> (../../../tools/@aws-cdk/cdk-build-tools/lib/feature-flag.ts:34:35) ● › cross account key alias name tests › cross account key alias is named with generated stack name when stack name is undefined and feature flag is enabled Template has 1 resources with type AWS::KMS::Alias, but none match as expected. The closest result is: { "Type": "AWS::KMS::Alias", "Properties": { "AliasName": "alias/codepipeline-pipelinestackpipeline9db740af", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", "Arn" ] } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" } with the following mismatches: Expected alias/codepipeline-pipelinestack-pipeline-9db740af but received alias/codepipeline-pipelinestackpipeline9db740af at /Properties/AliasName (using objectLike matcher) 83 | const matchError = hasResourceProperties(this.template, type, props); 84 | if (matchError) { > 85 | throw new Error(matchError); | ^ 86 | } 87 | } 88 | at Template.hasResourceProperties (../assertions/lib/template.ts:85:13) at fn (test/pipeline.test.ts:525:33) at Object.<anonymous> (../../../tools/@aws-cdk/cdk-build-tools/lib/feature-flag.ts:34:35) ● › cross account key alias name tests › cross account key alias is named with stack name and nested stack ID when feature flag is enabled Template has 1 resources with type AWS::KMS::Alias, but none match as expected. The closest result is: { "Type": "AWS::KMS::Alias", "Properties": { "AliasName": "alias/codepipeline-toplevelstacknestedpipelinestackactualpipeline3161a537", "TargetKeyId": { "Fn::GetAtt": [ "ActualPipelineArtifactsBucketEncryptionKeyDF448A3D", "Arn" ] } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" } with the following mismatches: Expected alias/codepipeline-actual-stack-name-nestedpipelinestack-actualpipeline-23a98110 but received alias/codepipeline-toplevelstacknestedpipelinestackactualpipeline3161a537 at /Properties/AliasName (using objectLike matcher) 83 | const matchError = hasResourceProperties(this.template, type, props); 84 | if (matchError) { > 85 | throw new Error(matchError); | ^ 86 | } 87 | } 88 | at Template.hasResourceProperties (../assertions/lib/template.ts:85:13) at fn (test/pipeline.test.ts:552:46) at Object.<anonymous> (../../../tools/@aws-cdk/cdk-build-tools/lib/feature-flag.ts:34:35) ● › cross account key alias name tests › cross account key alias is named with generated stack name and nested stack ID when stack name is undefined and feature flag is enabled Template has 1 resources with type AWS::KMS::Alias, but none match as expected. The closest result is: { "Type": "AWS::KMS::Alias", "Properties": { "AliasName": "alias/codepipeline-toplevelstacknestedpipelinestackactualpipeline3161a537", "TargetKeyId": { "Fn::GetAtt": [ "ActualPipelineArtifactsBucketEncryptionKeyDF448A3D", "Arn" ] } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" } with the following mismatches: Expected alias/codepipeline-toplevelstack-nestedpipelinestack-actualpipeline-3161a537 but received alias/codepipeline-toplevelstacknestedpipelinestackactualpipeline3161a537 at /Properties/AliasName (using objectLike matcher) 83 | const matchError = hasResourceProperties(this.template, type, props); 84 | if (matchError) { > 85 | throw new Error(matchError); | ^ 86 | } 87 | } 88 | at Template.hasResourceProperties (../assertions/lib/template.ts:85:13) at fn (test/pipeline.test.ts:581:46) at Object.<anonymous> (../../../tools/@aws-cdk/cdk-build-tools/lib/feature-flag.ts:34:35) ● › cross account key alias name tests › cross account key alias is properly shortened to 256 characters when stack name is too long and feature flag is enabled Template has 1 resources with type AWS::KMS::Alias, but none match as expected. The closest result is: { "Type": "AWS::KMS::Alias", "Properties": { "AliasName": "alias/codepipeline-toolongactualpipelinewithextrasuperlongnamethatwillneedtobeshortenedduetothealsoverysuperextralongnameofthestackalsowithsomedifferentcharactersaddedtotheendc9bb503e", "TargetKeyId": { "Fn::GetAtt": [ "ActualPipelineWithExtraSuperLongNameThatWillNeedToBeShortenedDueToTheAlsoVerySuperExtraLongNameOfTheStackAlsoWithSomeDifferentCharactersAddedToTheEndArtifactsBucketEncryptionKeyABD1BD7F", "Arn" ] } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" } with the following mismatches: Expected alias/codepipeline-actual-stack-needstobeshortenedduetothelengthofthisabsurdnamethatnooneshouldusebutitstillmighthappensowemusttestfohatwillneedtobeshortenedduetothealsoverysuperextralongnameofthestack-alsowithsomedifferentcharactersaddedtotheend-384b9343 but received alias/codepipeline-toolongactualpipelinewithextrasuperlongnamethatwillneedtobeshortenedduetothealsoverysuperextralongnameofthestackalsowithsomedifferentcharactersaddedtotheendc9bb503e at /Properties/AliasName (using objectLike matcher) 83 | const matchError = hasResourceProperties(this.template, type, props); 84 | if (matchError) { > 85 | throw new Error(matchError); | ^ 86 | } 87 | } 88 | at Template.hasResourceProperties (../assertions/lib/template.ts:85:13) at fn (test/pipeline.test.ts:609:33) at Object.<anonymous> (../../../tools/@aws-cdk/cdk-build-tools/lib/feature-flag.ts:34:35) ● › cross account key alias name tests › cross account key alias names do not conflict when the stack ID is the same and pipeline ID is the same and feature flag is enabled Template has 1 resources with type AWS::KMS::Alias, but none match as expected. The closest result is: { "Type": "AWS::KMS::Alias", "Properties": { "AliasName": "alias/codepipeline-stackidpipeline32fb88b3", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", "Arn" ] } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" } with the following mismatches: Expected alias/codepipeline-actual-stack-1-pipeline-b09fefee but received alias/codepipeline-stackidpipeline32fb88b3 at /Properties/AliasName (using objectLike matcher) 83 | const matchError = hasResourceProperties(this.template, type, props); 84 | if (matchError) { > 85 | throw new Error(matchError); | ^ 86 | } 87 | } 88 | at Template.hasResourceProperties (../assertions/lib/template.ts:85:13) at fn (test/pipeline.test.ts:643:34) at Object.<anonymous> (../../../tools/@aws-cdk/cdk-build-tools/lib/feature-flag.ts:34:35) ● › cross account key alias name tests › cross account key alias names do not conflict for nested stacks when pipeline ID is the same and nested stacks have the same ID when feature flag is enabled Template has 1 resources with type AWS::KMS::Alias, but none match as expected. The closest result is: { "Type": "AWS::KMS::Alias", "Properties": { "AliasName": "alias/codepipeline-stackidnestedpipelineid3e91360a", "TargetKeyId": { "Fn::GetAtt": [ "PIPELINEIDArtifactsBucketEncryptionKeyE292C50C", "Arn" ] } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" } with the following mismatches: Expected alias/codepipeline-actual-stack-name-1-nested-pipeline-id-c8c9f252 but received alias/codepipeline-stackidnestedpipelineid3e91360a at /Properties/AliasName (using objectLike matcher) 83 | const matchError = hasResourceProperties(this.template, type, props); 84 | if (matchError) { > 85 | throw new Error(matchError); | ^ 86 | } 87 | } 88 | at Template.hasResourceProperties (../assertions/lib/template.ts:85:13) at fn (test/pipeline.test.ts:697:47) at Object.<anonymous> (../../../tools/@aws-cdk/cdk-build-tools/lib/feature-flag.ts:34:35) ● › cross account key alias name tests › cross account key alias names do not conflict for nested stacks when in the same stack but nested stacks have different IDs when feature flag is enabled Template has 1 resources with type AWS::KMS::Alias, but none match as expected. The closest result is: { "Type": "AWS::KMS::Alias", "Properties": { "AliasName": "alias/codepipeline-stackidfirstpipelineid5abca693", "TargetKeyId": { "Fn::GetAtt": [ "PIPELINEIDArtifactsBucketEncryptionKeyE292C50C", "Arn" ] } }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" } with the following mismatches: Expected alias/codepipeline-actual-stack-name-1-first-pipeline-id-3c59cb88 but received alias/codepipeline-stackidfirstpipelineid5abca693 at /Properties/AliasName (using objectLike matcher) 83 | const matchError = hasResourceProperties(this.template, type, props); 84 | if (matchError) { > 85 | throw new Error(matchError); | ^ 86 | } 87 | } 88 | at Template.hasResourceProperties (../assertions/lib/template.ts:85:13) at fn (test/pipeline.test.ts:749:46) at Object.<anonymous> (../../../tools/@aws-cdk/cdk-build-tools/lib/feature-flag.ts:34:35) ``` </details> ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- ...-codepipeline-cloudformation.template.json | 2 +- .../PipelineStack.template.json | 2 +- .../aws-cdk-codepipeline-lambda.template.json | 2 +- ...dk-codepipeline-alexa-deploy.template.json | 2 +- ...-codepipeline-cloudformation.template.json | 2 +- ...ipeline-codecommit-codebuild.template.json | 2 +- ...-cdk-codepipeline-codecommit.template.json | 2 +- ...ws-cdk-pipeline-event-target.template.json | 2 +- ...k-codepipeline-stepfunctions.template.json | 2 +- .../@aws-cdk/aws-codepipeline/lib/pipeline.ts | 19 +- .../@aws-cdk/aws-codepipeline/package.json | 3 +- .../aws-codepipeline/test/pipeline.test.ts | 348 ------------------ .../pipeline-events.template.json | 2 +- packages/@aws-cdk/core/lib/names.ts | 58 +-- .../core/lib/private/unique-resource-name.ts | 122 ------ .../test/private/unique-resource-name.test.ts | 101 ----- packages/@aws-cdk/cx-api/lib/features.ts | 13 - .../PipelineSecurityStack.template.json | 2 +- .../PipelineStack.template.json | 2 +- .../PipelineStack.template.json | 2 +- .../PipelineStack.template.json | 2 +- 21 files changed, 20 insertions(+), 672 deletions(-) delete mode 100644 packages/@aws-cdk/core/lib/private/unique-resource-name.ts delete mode 100644 packages/@aws-cdk/core/test/private/unique-resource-name.test.ts diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/cfn-template-from-repo.lit.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/cfn-template-from-repo.lit.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json index 83869967f93dc..cb837eab62aa1 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/cfn-template-from-repo.lit.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cfn-template-from-repo.lit.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json @@ -44,7 +44,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-aws-cdk-codepipeline-cloudformation-pipeline-7dbde619", + "AliasName": "alias/codepipeline-awscdkcodepipelinecloudformationpipeline7dbde619", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-deployed-through-codepipeline.lit.integ.snapshot/PipelineStack.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-deployed-through-codepipeline.lit.integ.snapshot/PipelineStack.template.json index edce01a382b91..e3da2128d9482 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-deployed-through-codepipeline.lit.integ.snapshot/PipelineStack.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-deployed-through-codepipeline.lit.integ.snapshot/PipelineStack.template.json @@ -38,7 +38,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipelinestack-pipeline-9db740af", + "AliasName": "alias/codepipeline-pipelinestackpipeline9db740af", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-pipeline.integ.snapshot/aws-cdk-codepipeline-lambda.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-pipeline.integ.snapshot/aws-cdk-codepipeline-lambda.template.json index 39ca1662ed0ec..f19a0fc010bae 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-pipeline.integ.snapshot/aws-cdk-codepipeline-lambda.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda-pipeline.integ.snapshot/aws-cdk-codepipeline-lambda.template.json @@ -38,7 +38,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-aws-cdk-codepipeline-lambda-pipeline-87a4b3d3", + "AliasName": "alias/codepipeline-awscdkcodepipelinelambdapipeline87a4b3d3", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-alexa-deploy.integ.snapshot/aws-cdk-codepipeline-alexa-deploy.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-alexa-deploy.integ.snapshot/aws-cdk-codepipeline-alexa-deploy.template.json index 10688fe9721a6..b1eb53d6c59cc 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-alexa-deploy.integ.snapshot/aws-cdk-codepipeline-alexa-deploy.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-alexa-deploy.integ.snapshot/aws-cdk-codepipeline-alexa-deploy.template.json @@ -48,7 +48,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-aws-cdk-codepipeline-alexa-deploy-pipeline-961107f5", + "AliasName": "alias/codepipeline-awscdkcodepipelinealexadeploypipeline961107f5", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-cfn.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-cfn.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json index fd3fafc21a418..b671f21665464 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-cfn.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-cfn.integ.snapshot/aws-cdk-codepipeline-cloudformation.template.json @@ -38,7 +38,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-aws-cdk-codepipeline-cloudformation-pipeline-7dbde619", + "AliasName": "alias/codepipeline-awscdkcodepipelinecloudformationpipeline7dbde619", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit-build.integ.snapshot/aws-cdk-codepipeline-codecommit-codebuild.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit-build.integ.snapshot/aws-cdk-codepipeline-codecommit-codebuild.template.json index 1c1241a8d23d6..7a9855fe36470 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit-build.integ.snapshot/aws-cdk-codepipeline-codecommit-codebuild.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit-build.integ.snapshot/aws-cdk-codepipeline-codecommit-codebuild.template.json @@ -244,7 +244,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-aws-cdk-codepipeline-codecommit-codebuild-pipeline-9540e1f5", + "AliasName": "alias/codepipeline-awscdkcodepipelinecodecommitcodebuildpipeline9540e1f5", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit.integ.snapshot/aws-cdk-codepipeline-codecommit.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit.integ.snapshot/aws-cdk-codepipeline-codecommit.template.json index 9ad15802f8bd6..cca5e3c5d5725 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit.integ.snapshot/aws-cdk-codepipeline-codecommit.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-code-commit.integ.snapshot/aws-cdk-codepipeline-codecommit.template.json @@ -109,7 +109,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-aws-cdk-codepipeline-codecommit-pipeline-f780ca18", + "AliasName": "alias/codepipeline-awscdkcodepipelinecodecommitpipelinef780ca18", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKey01D58D69", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-events.integ.snapshot/aws-cdk-pipeline-event-target.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-events.integ.snapshot/aws-cdk-pipeline-event-target.template.json index 38dec014dc488..02a0628ba357a 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-events.integ.snapshot/aws-cdk-pipeline-event-target.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-events.integ.snapshot/aws-cdk-pipeline-event-target.template.json @@ -38,7 +38,7 @@ "MyPipelineArtifactsBucketEncryptionKeyAlias9D4F8C59": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-aws-cdk-pipeline-event-target-mypipeline-4ae5d407", + "AliasName": "alias/codepipeline-awscdkpipelineeventtargetmypipeline4ae5d407", "TargetKeyId": { "Fn::GetAtt": [ "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-stepfunctions.integ.snapshot/aws-cdk-codepipeline-stepfunctions.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-stepfunctions.integ.snapshot/aws-cdk-codepipeline-stepfunctions.template.json index f8f998e639f18..f407e31285705 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-stepfunctions.integ.snapshot/aws-cdk-codepipeline-stepfunctions.template.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/pipeline-stepfunctions.integ.snapshot/aws-cdk-codepipeline-stepfunctions.template.json @@ -78,7 +78,7 @@ "MyPipelineArtifactsBucketEncryptionKeyAlias9D4F8C59": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-aws-cdk-codepipeline-stepfunctions-mypipeline-ce88aa28", + "AliasName": "alias/codepipeline-awscdkcodepipelinestepfunctionsmypipelinece88aa28", "TargetKeyId": { "Fn::GetAtt": [ "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3", diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index 004b8683ad027..6d500b3147ef2 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -7,7 +7,6 @@ import { ArnFormat, BootstraplessSynthesizer, DefaultStackSynthesizer, - FeatureFlags, IStackSynthesizer, Lazy, Names, @@ -18,7 +17,6 @@ import { Stage as CdkStage, Token, } from '@aws-cdk/core'; -import * as cxapi from '@aws-cdk/cx-api'; import { Construct } from 'constructs'; import { ActionCategory, IAction, IPipeline, IStage, PipelineNotificationEvents, PipelineNotifyOnOptions } from './action'; import { CfnPipeline } from './codepipeline.generated'; @@ -699,19 +697,10 @@ export class Pipeline extends PipelineBase { private generateNameForDefaultBucketKeyAlias(): string { const prefix = 'alias/codepipeline-'; const maxAliasLength = 256; - const maxResourceNameLength = maxAliasLength - prefix.length; - // Names.uniqueId() may have naming collisions when the IDs of resources are similar - // and/or when they are too long and sliced. We do not want to update this and - // automatically change the name of every KMS key already generated so we are putting - // this under a feature flag. - const uniqueId = FeatureFlags.of(this).isEnabled(cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID) ? - Names.uniqueResourceName(this, { - separator: '-', - maxLength: maxResourceNameLength, - allowedSpecialCharacters: '/_-', - }) : - Names.uniqueId(this).slice(-maxResourceNameLength); - return prefix + uniqueId.toLowerCase(); + const uniqueId = Names.uniqueId(this); + // take the last 256 - (prefix length) characters of uniqueId + const startIndex = Math.max(0, uniqueId.length - (maxAliasLength - prefix.length)); + return prefix + uniqueId.substring(startIndex).toLowerCase(); } /** diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index e82a1461aee6d..2137e04707bfe 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -88,6 +88,7 @@ "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.0", "jest": "^27.5.1" @@ -99,7 +100,6 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", - "@aws-cdk/cx-api": "0.0.0", "constructs": "^3.3.69" }, "homepage": "https://github.com/aws/aws-cdk", @@ -110,7 +110,6 @@ "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", - "@aws-cdk/cx-api": "0.0.0", "constructs": "^3.3.69" }, "engines": { diff --git a/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts b/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts index 985d2cf3deabc..dd4f79d040201 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts @@ -2,7 +2,6 @@ import { Match, Template } from '@aws-cdk/assertions'; 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 { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import * as codepipeline from '../lib'; @@ -486,298 +485,6 @@ describe('', () => { }); }); }); - - describe('cross account key alias name tests', () => { - const kmsAliasResource = 'AWS::KMS::Alias'; - - testFutureBehavior('cross account key alias is named with stack name instead of ID when feature flag is enabled', { [cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true }, cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'Name', - stackId: 'PipelineStack', - }); - - Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-actual-stack-name-pipeline-0a412eb5', - }); - }); - - testLegacyBehavior('cross account key alias is named with stack ID when feature flag is not enabled', cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'Name', - stackId: 'PipelineStack', - }); - - Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-pipelinestackpipeline9db740af', - }); - }); - - testFutureBehavior('cross account key alias is named with generated stack name when stack name is undefined and feature flag is enabled', { [cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true }, cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'Name', - stackId: 'PipelineStack', - undefinedStackName: true, - }); - - Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-pipelinestack-pipeline-9db740af', - }); - }); - - testLegacyBehavior('cross account key alias is named with stack ID when stack name is not present and feature flag is not enabled', cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'Name', - stackId: 'PipelineStack', - undefinedStackName: true, - }); - - Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-pipelinestackpipeline9db740af', - }); - }); - - testFutureBehavior('cross account key alias is named with stack name and nested stack ID when feature flag is enabled', { [cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true }, cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'Name', - stackId: 'TopLevelStack', - nestedStackId: 'NestedPipelineStack', - pipelineId: 'ActualPipeline', - }); - - Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-actual-stack-name-nestedpipelinestack-actualpipeline-23a98110', - }); - }); - - testLegacyBehavior('cross account key alias is named with stack ID and nested stack ID when stack name is present and feature flag is not enabled', cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'Name', - stackId: 'TopLevelStack', - nestedStackId: 'NestedPipelineStack', - pipelineId: 'ActualPipeline', - }); - - Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-toplevelstacknestedpipelinestackactualpipeline3161a537', - }); - }); - - testFutureBehavior('cross account key alias is named with generated stack name and nested stack ID when stack name is undefined and feature flag is enabled', { [cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true }, cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'Name', - stackId: 'TopLevelStack', - nestedStackId: 'NestedPipelineStack', - pipelineId: 'ActualPipeline', - undefinedStackName: true, - }); - - Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-toplevelstack-nestedpipelinestack-actualpipeline-3161a537', - }); - }); - - testLegacyBehavior('cross account key alias is named with stack ID and nested stack ID when stack name is not present and feature flag is not enabled', cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'Name', - stackId: 'TopLevelStack', - nestedStackId: 'NestedPipelineStack', - pipelineId: 'ActualPipeline', - undefinedStackName: true, - }); - - Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-toplevelstacknestedpipelinestackactualpipeline3161a537', - }); - }); - - testFutureBehavior('cross account key alias is properly shortened to 256 characters when stack name is too long and feature flag is enabled', { [cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true }, cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'NeedsToBeShortenedDueToTheLengthOfThisAbsurdNameThatNoOneShouldUseButItStillMightHappenSoWeMustTestForTheTestCase', - stackId: 'too-long', - pipelineId: 'ActualPipelineWithExtraSuperLongNameThatWillNeedToBeShortenedDueToTheAlsoVerySuperExtraLongNameOfTheStack-AlsoWithSomeDifferentCharactersAddedToTheEnd', - }); - - Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-actual-stack-needstobeshortenedduetothelengthofthisabsurdnamethatnooneshouldusebutitstillmighthappensowemusttestfohatwillneedtobeshortenedduetothealsoverysuperextralongnameofthestack-alsowithsomedifferentcharactersaddedtotheend-384b9343', - }); - }); - - testLegacyBehavior('cross account key alias is properly shortened to 256 characters when stack name is too long and feature flag is not enabled', cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'too-long', - stackId: 'NeedsToBeShortenedDueToTheLengthOfThisAbsurdNameThatNoOneShouldUseButItStillMightHappenSoWeMustTestForTheTestCase', - pipelineId: 'ActualPipelineWithExtraSuperLongNameThatWillNeedToBeShortenedDueToTheAlsoVerySuperExtraLongNameOfTheStack-AlsoWithSomeDifferentCharactersAddedToTheEnd', - }); - - Template.fromStack(stack).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-ortenedduetothelengthofthisabsurdnamethatnooneshouldusebutitstillmighthappensowemusttestforthetestcaseactualpipelinewithextrasuperlongnamethatwillneedtobeshortenedduetothealsoverysuperextralongnameofthestackalsowithsomedifferentc498e0672', - }); - }); - - testFutureBehavior('cross account key alias names do not conflict when the stack ID is the same and pipeline ID is the same and feature flag is enabled', { [cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true }, cdk.App, (app1) => { - const app2 = new cdk.App(({ context: { [cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true } })); - const stack1 = createPipelineStack({ - context: app1, - suffix: '1', - stackId: 'STACK-ID', - }); - - const stack2 = createPipelineStack({ - context: app2, - suffix: '2', - stackId: 'STACK-ID', - }); - - expect(Template.fromStack(stack1).findResources(kmsAliasResource)).not.toEqual(Template.fromStack(stack2).findResources(kmsAliasResource)); - - Template.fromStack(stack1).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-actual-stack-1-pipeline-b09fefee', - }); - - Template.fromStack(stack2).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-actual-stack-2-pipeline-f46258fe', - }); - }); - - testLegacyBehavior('cross account key alias names do conflict when the stack ID is the same and pipeline ID is the same when feature flag is not enabled', cdk.App, (app1) => { - const app2 = new cdk.App(); - const stack1 = createPipelineStack({ - context: app1, - suffix: '1', - stackId: 'STACK-ID', - }); - - const stack2 = createPipelineStack({ - context: app2, - suffix: '2', - stackId: 'STACK-ID', - }); - - expect(Template.fromStack(stack1).findResources(kmsAliasResource)).toEqual(Template.fromStack(stack2).findResources(kmsAliasResource)); - - Template.fromStack(stack1).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-stackidpipeline32fb88b3', - }); - - Template.fromStack(stack2).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-stackidpipeline32fb88b3', - }); - }); - - testFutureBehavior('cross account key alias names do not conflict for nested stacks when pipeline ID is the same and nested stacks have the same ID when feature flag is enabled', { [cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true }, cdk.App, (app1) => { - const app2 = new cdk.App(({ context: { [cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true } })); - const stack1 = createPipelineStack({ - context: app1, - suffix: 'Name-1', - stackId: 'STACK-ID', - nestedStackId: 'Nested', - pipelineId: 'PIPELINE-ID', - }); - const stack2 = createPipelineStack({ - context: app2, - suffix: 'Name-2', - stackId: 'STACK-ID', - nestedStackId: 'Nested', - pipelineId: 'PIPELINE-ID', - }); - - expect(Template.fromStack(stack1.nestedStack!).findResources(kmsAliasResource)) - .not.toEqual(Template.fromStack(stack2.nestedStack!).findResources(kmsAliasResource)); - - Template.fromStack(stack1.nestedStack!).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-actual-stack-name-1-nested-pipeline-id-c8c9f252', - }); - - Template.fromStack(stack2.nestedStack!).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-actual-stack-name-2-nested-pipeline-id-aff6dd63', - }); - }); - - testLegacyBehavior('cross account key alias names do conflict for nested stacks when pipeline ID is the same and nested stacks have the same ID when feature flag is not enabled', cdk.App, (app1) => { - const app2 = new cdk.App(); - const stack1 = createPipelineStack({ - context: app1, - suffix: '1', - stackId: 'STACK-ID', - nestedStackId: 'Nested', - pipelineId: 'PIPELINE-ID', - }); - const stack2 = createPipelineStack({ - context: app2, - suffix: '2', - stackId: 'STACK-ID', - nestedStackId: 'Nested', - pipelineId: 'PIPELINE-ID', - }); - - expect(Template.fromStack(stack1.nestedStack!).findResources(kmsAliasResource)) - .toEqual(Template.fromStack(stack2.nestedStack!).findResources(kmsAliasResource)); - - Template.fromStack(stack1.nestedStack!).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-stackidnestedpipelineid3e91360a', - }); - - Template.fromStack(stack2.nestedStack!).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-stackidnestedpipelineid3e91360a', - }); - }); - - testFutureBehavior('cross account key alias names do not conflict for nested stacks when in the same stack but nested stacks have different IDs when feature flag is enabled', { [cxapi.CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true }, cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'Name-1', - stackId: 'STACK-ID', - nestedStackId: 'First', - pipelineId: 'PIPELINE-ID', - }); - const nestedStack2 = new cdk.NestedStack(stack, 'Second'); - createPipelineWithSourceAndBuildStages(nestedStack2, 'Actual-Pipeline-Name-2', 'PIPELINE-ID'); - - expect(Template.fromStack(stack.nestedStack!).findResources(kmsAliasResource)) - .not.toEqual(Template.fromStack(nestedStack2).findResources(kmsAliasResource)); - - Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-actual-stack-name-1-first-pipeline-id-3c59cb88', - }); - - Template.fromStack(nestedStack2).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-actual-stack-name-1-second-pipeline-id-16143d12', - }); - }); - - testLegacyBehavior('cross account key alias names do not conflict for nested stacks when in the same stack but nested stacks have different IDs when feature flag is not enabled', cdk.App, (app) => { - const stack = createPipelineStack({ - context: app, - suffix: 'Name-1', - stackId: 'STACK-ID', - nestedStackId: 'First', - pipelineId: 'PIPELINE-ID', - }); - const nestedStack2 = new cdk.NestedStack(stack, 'Second'); - createPipelineWithSourceAndBuildStages(nestedStack2, 'Actual-Pipeline-Name-2', 'PIPELINE-ID'); - - expect(Template.fromStack(stack.nestedStack!).findResources(kmsAliasResource)) - .not.toEqual(Template.fromStack(nestedStack2).findResources(kmsAliasResource)); - - Template.fromStack(stack.nestedStack!).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-stackidfirstpipelineid5abca693', - }); - - Template.fromStack(nestedStack2).hasResourceProperties(kmsAliasResource, { - AliasName: 'alias/codepipeline-stackidsecondpipelineid288ce778', - }); - }); - }); }); describe('test with shared setup', () => { @@ -859,58 +566,3 @@ class ReusePipelineStack extends cdk.Stack { }); } } - -interface PipelineStackProps extends cdk.StackProps { - readonly nestedStackId?: string; - readonly pipelineName: string; - readonly pipelineId?: string; -} - -class PipelineStack extends cdk.Stack { - nestedStack?: cdk.NestedStack; - pipeline: codepipeline.Pipeline; - - constructor(scope?: Construct, id?: string, props?: PipelineStackProps) { - super (scope, id, props); - - props?.nestedStackId ? this.nestedStack = new cdk.NestedStack(this, props!.nestedStackId!) : undefined; - this.pipeline = createPipelineWithSourceAndBuildStages(this.nestedStack || this, props?.pipelineName, props?.pipelineId); - } -} - -function createPipelineWithSourceAndBuildStages(scope: Construct, pipelineName?: string, pipelineId: string = 'Pipeline') { - const artifact = new codepipeline.Artifact(); - return new codepipeline.Pipeline(scope, pipelineId, { - pipelineName, - crossAccountKeys: true, - reuseCrossRegionSupportStacks: false, - stages: [ - { - stageName: 'Source', - actions: [new FakeSourceAction({ actionName: 'Source', output: artifact })], - }, - { - stageName: 'Build', - actions: [new FakeBuildAction({ actionName: 'Build', input: artifact })], - }, - ], - }); -}; - -interface CreatePipelineStackOptions { - readonly context: cdk.App, - readonly suffix: string, - readonly stackId?: string, - readonly pipelineId?: string, - readonly undefinedStackName?: boolean, - readonly nestedStackId?: string, -} - -function createPipelineStack(options: CreatePipelineStackOptions): PipelineStack { - return new PipelineStack(options.context, options.stackId, { - stackName: options.undefinedStackName ? undefined : `Actual-Stack-${options.suffix}`, - nestedStackId: options.nestedStackId, - pipelineName: `Actual-Pipeline-${options.suffix}`.substring(0, 100), - pipelineId: options.pipelineId, - }); -}; diff --git a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline-event-target.integ.snapshot/pipeline-events.template.json b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline-event-target.integ.snapshot/pipeline-events.template.json index 2a37807987742..a11fb7f25fb7b 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline-event-target.integ.snapshot/pipeline-events.template.json +++ b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline-event-target.integ.snapshot/pipeline-events.template.json @@ -44,7 +44,7 @@ "pipelinePipeline22F2A91DArtifactsBucketEncryptionKeyAlias9530209A": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipeline-events-pipelinepipeline22f2a91d-fbb66895", + "AliasName": "alias/codepipeline-pipelineeventspipelinepipeline22f2a91dfbb66895", "TargetKeyId": { "Fn::GetAtt": [ "pipelinePipeline22F2A91DArtifactsBucketEncryptionKey87C796D2", diff --git a/packages/@aws-cdk/core/lib/names.ts b/packages/@aws-cdk/core/lib/names.ts index 0d9b9521e98e4..2d204c298d9fe 100644 --- a/packages/@aws-cdk/core/lib/names.ts +++ b/packages/@aws-cdk/core/lib/names.ts @@ -1,37 +1,6 @@ import { Construct, Node } from 'constructs'; import { ConstructNode } from './construct-compat'; -import { unresolved } from './private/encoding'; -import { makeUniqueResourceName } from './private/unique-resource-name'; import { makeUniqueId } from './private/uniqueid'; -import { Stack } from './stack'; - - -/** - * Options for creating a unique resource name. -*/ -export interface UniqueResourceNameOptions { - - /** - * The maximum length of the unique resource name. - * - * @default - 256 - */ - readonly maxLength?: number; - - /** - * The separator used between the path components. - * - * @default - none - */ - readonly separator?: string; - - /** - * Non-alphanumeric characters allowed in the unique resource name. - * - * @default - none - */ - readonly allowedSpecialCharacters?: string; -} /** * Functions for devising unique names for constructs. For example, those can be @@ -41,8 +10,7 @@ export class Names { /** * Returns a CloudFormation-compatible unique identifier for a construct based * on its path. The identifier includes a human readable portion rendered - * from the path components and a hash suffix. uniqueId is not unique if multiple - * copies of the stack are deployed. Prefer using uniqueResourceName(). + * from the path components and a hash suffix. * * @param construct The construct * @returns a unique id based on the construct path @@ -68,29 +36,5 @@ export class Names { return components.length > 0 ? makeUniqueId(components) : ''; } - /** - * Returns a CloudFormation-compatible unique identifier for a construct based - * on its path. This function finds the stackName of the parent stack (non-nested) - * to the construct, and the ids of the components in the construct path. - * - * The user can define allowed special characters, a separator between the elements, - * and the maximum length of the resource name. The name includes a human readable portion rendered - * from the path components, with or without user defined separators, and a hash suffix. - * If the resource name is longer than the maximum length, it is trimmed in the middle. - * - * @param construct The construct - * @param options Options for defining the unique resource name - * @returns a unique resource name based on the construct path - */ - public static uniqueResourceName(construct: Construct, options: UniqueResourceNameOptions) { - const node = Node.of(construct); - - const componentsPath = node.scopes.slice(node.scopes.indexOf(node.scopes.reverse() - .find(component => (Stack.isStack(component) && !unresolved(component.stackName)))!, - )).map(component => Stack.isStack(component) && !unresolved(component.stackName) ? component.stackName : Node.of(component).id); - - return makeUniqueResourceName(componentsPath, options); - } - private constructor() {} } diff --git a/packages/@aws-cdk/core/lib/private/unique-resource-name.ts b/packages/@aws-cdk/core/lib/private/unique-resource-name.ts deleted file mode 100644 index cf816dc9a5758..0000000000000 --- a/packages/@aws-cdk/core/lib/private/unique-resource-name.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { createHash } from 'crypto'; -// import { unresolved } from './encoding'; - -/** - * Options for creating a unique resource name. -*/ -interface MakeUniqueResourceNameOptions { - - /** - * The maximum length of the unique resource name. - * - * @default - 256 - */ - readonly maxLength?: number; - - /** - * The separator used between the path components. - * - * @default - none - */ - readonly separator?: string; - - /** - * Non-alphanumeric characters allowed in the unique resource name. - * - * @default - none - */ - readonly allowedSpecialCharacters?: string; -} - -/** - * Resources with this ID are hidden from humans - * - * They do not appear in the human-readable part of the logical ID, - * but they are included in the hash calculation. - */ -const HIDDEN_FROM_HUMAN_ID = 'Resource'; - -/** -* Resources with this ID are complete hidden from the logical ID calculation. -*/ -const HIDDEN_ID = 'Default'; - -const PATH_SEP = '/'; - -const MAX_LEN = 256; - -const HASH_LEN = 8; - -export function makeUniqueResourceName(components: string[], options: MakeUniqueResourceNameOptions) { - const maxLength = options.maxLength ?? 256; - const separator = options.separator ?? ''; - components = components.filter(x => x !== HIDDEN_ID); - - if (components.length === 0) { - throw new Error('Unable to calculate a unique resource name for an empty set of components'); - } - - // top-level resources will simply use the `name` as-is if the name is also short enough - // in order to support transparent migration of cloudformation templates to the CDK without the - // need to rename all resources. - if (components.length === 1) { - const topLevelResource = removeNonAllowedSpecialCharacters(components[0], separator, options.allowedSpecialCharacters); - - if (topLevelResource.length <= maxLength) { - return topLevelResource; - } - } - - // Calculate the hash from the full path, included unresolved tokens so the hash value is always unique - const hash = pathHash(components); - const human = removeDupes(components) - .filter(pathElement => pathElement !== HIDDEN_FROM_HUMAN_ID) - .map(pathElement => removeNonAllowedSpecialCharacters(pathElement, separator, options.allowedSpecialCharacters)) - .filter(pathElement => pathElement) - .join(separator) - .concat(separator); - - const maxhumanLength = maxLength - HASH_LEN; - return human.length > maxhumanLength ? `${splitInMiddle(human, maxhumanLength)}${hash}`: `${human}${hash}`; -} - -/** - * Take a hash of the given path. - * - * The hash is limited in size. - */ -function pathHash(path: string[]): string { - const md5 = createHash('md5').update(path.join(PATH_SEP)).digest('hex'); - return md5.slice(0, HASH_LEN).toUpperCase(); -} - -/** - * Removes all non-allowed special characters in a string. - */ -function removeNonAllowedSpecialCharacters(s: string, _separator: string, allowedSpecialCharacters?: string) { - const pattern = allowedSpecialCharacters ? `[^A-Za-z0-9${allowedSpecialCharacters}]` : '[^A-Za-z0-9]'; - const regex = new RegExp(pattern, 'g'); - return s.replace(regex, ''); -} - -/** - * Remove duplicate "terms" from the path list - * - * If the previous path component name ends with this component name, skip the - * current component. - */ -function removeDupes(path: string[]): string[] { - const ret = new Array<string>(); - - for (const component of path) { - if (ret.length === 0 || !ret[ret.length - 1].endsWith(component)) { - ret.push(component); - } - } - return ret; -} - -function splitInMiddle(s: string, maxLength: number = MAX_LEN - HASH_LEN) { - const half = maxLength / 2; - return s.slice(0, half) + s.slice(-half); -} \ No newline at end of file diff --git a/packages/@aws-cdk/core/test/private/unique-resource-name.test.ts b/packages/@aws-cdk/core/test/private/unique-resource-name.test.ts deleted file mode 100644 index c2dcdeff445bc..0000000000000 --- a/packages/@aws-cdk/core/test/private/unique-resource-name.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { createHash } from 'crypto'; -import { makeUniqueResourceName } from '../../lib/private/unique-resource-name'; - -const pathHash = (path: string[]): string => { - return createHash('md5').update(path.join('/')).digest('hex').slice(0, 8).toUpperCase(); -}; - -describe('makeUniqueResourceName tests', () => { - test('unique resource name is just resource name when the resource is top level, short enough, has no nonalphanumeric characters', () => { - const uniqueResourceName = makeUniqueResourceName(['toplevelresource'], {}); - expect(uniqueResourceName).toEqual('toplevelresource'); - }); - - test('unique resource name is shortened with a hash added when resource is top level and resource name is too long', () => { - const tooLongName = ['anamethatisslightlylongerthan256charactersthatisalsoatoplevelresourcesothereisonlyonevalueinthisarrayandapparentlybrevityisnotthestrongpointofthepersonwhonamedthisresourcewhichIbettheywillcometoregretlaterbutitiswhatitisanywhodlethisfunctionshouldshortenthis']; - const uniqueResourceName = makeUniqueResourceName(tooLongName, {}); - - const expectedName = `anamethatisslightlylongerthan256charactersthatisalsoatoplevelresourcesothereisonlyonevalueinthisarrayandapparentlybrevityisngpointofthepersonwhonamedthisresourcewhichIbettheywillcometoregretlaterbutitiswhatitisanywhodlethisfunctionshouldshortenthis${pathHash(tooLongName)}`; - expect(uniqueResourceName).toEqual(expectedName); - expect(uniqueResourceName.length).toEqual(256); - }); - - test('unique resource name removes special characters when resource is top level', () => { - const componentsPath = ['I-love-special-characters-¯\\\_(ツ)_/¯-for-real-though']; - const expectedName = 'Ilovespecialcharactersforrealthough'; - - expect(makeUniqueResourceName(componentsPath, {})).toEqual(expectedName); - }); - - test('unique resource name shortens from the middle and adds a hash when maxLength is defined, resource is top level, and resource name is longer than max', () => { - const componentsPath = ['ThisIsStillLongerThanTheAllowedLength']; - const expectedName = `ThisIsLength${pathHash(componentsPath)}`; - - expect(makeUniqueResourceName(componentsPath, { maxLength: 20 })).toEqual(expectedName); - }); - - test('unique resource name shortens from the middle and adds a hash when maxLength is defined, resource is top level, resource name is longer than max, and separator is provided', () => { - const componentsPath = ['ThisIsStillLongerThanTheAllowedLength']; - const expectedName = `ThisIsength-${pathHash(componentsPath)}`; - - expect(makeUniqueResourceName(componentsPath, { maxLength: 20, separator: '-' })).toEqual(expectedName); - }); - - test('unique resource name removes special characters and makes no other changes when resouce is top level and too long with special characters but proper length without', () => { - const tooLongName = ['a-name-that-is-slightly-longer-than-256-characters-that-is-also-a-top-level-resource-so-there-is-only-one-value-in-this-array-and-apparently-brevity-is-not-the-strong-point-of-the-person-who-named-this-resource-which-I-bet-they-will-come-to-regret-later-but-it-is-what-it-is']; - const expectedName = 'anamethatisslightlylongerthan256charactersthatisalsoatoplevelresourcesothereisonlyonevalueinthisarrayandapparentlybrevityisnotthestrongpointofthepersonwhonamedthisresourcewhichIbettheywillcometoregretlaterbutitiswhatitis'; - - expect(makeUniqueResourceName(tooLongName, {})).toEqual(expectedName); - }); - - test('unique resource name leaves in allowed special characters and adds no hash when resource is top level and resouce name is short enougn', () => { - const componentsPath = ['¯\\\_(ツ)_/¯-shruggie-gets-to-stay-¯\\\_(ツ)_/¯']; - const expectedName = '¯\_(ツ)_/¯shruggiegetstostay¯\_(ツ)_/¯'; - - expect(makeUniqueResourceName(componentsPath, { allowedSpecialCharacters: '¯\\\_(ツ)/', maxLength: 200 })).toEqual(expectedName); - }); - - test('unique resource name leaves in allowed special characters and adds no hash or separators when resource is top level and resouce name is short enougn', () => { - const componentsPath = ['¯\\\_(ツ)_/¯-shruggie-gets-to-stay-¯\\\_(ツ)_/¯']; - const expectedName = '¯\_(ツ)_/¯shruggiegetstostay¯\_(ツ)_/¯'; - - expect(makeUniqueResourceName(componentsPath, { allowedSpecialCharacters: '¯\\\_(ツ)/', maxLength: 200, separator: '-' })).toEqual(expectedName); - }); - - test('unique resource name is shortened with a hash and separator added when resource is top level, resource name is too long, and separator is provided', () => { - const tooLongName = ['anamethatisslightlylongerthan256charactersthatisalsoatoplevelresourcesothereisonlyonevalueinthisarrayandapparentlybrevityisnotthestrongpointofthepersonwhonamedthisresourcewhichIbettheywillcometoregretlaterbutitiswhatitisanywhodlethisfunctionshouldshortenthis']; - const uniqueResourceName = makeUniqueResourceName(tooLongName, { separator: '~' }); - - const expectedName = `anamethatisslightlylongerthan256charactersthatisalsoatoplevelresourcesothereisonlyonevalueinthisarrayandapparentlybrevityisnpointofthepersonwhonamedthisresourcewhichIbettheywillcometoregretlaterbutitiswhatitisanywhodlethisfunctionshouldshortenthis~${pathHash(tooLongName)}`; - expect(uniqueResourceName).toEqual(expectedName); - expect(uniqueResourceName.length).toEqual(256); - }); - - test('unique resource name removes special characters when they are included in the components names', () => { - const componentsPath = ['I', 'love', 'special', 'characters', '¯\\\_(ツ)_/¯', 'for', 'real', 'though']; - const expectedName = `Ilovespecialcharactersforrealthough${pathHash(componentsPath)}`; - - expect(makeUniqueResourceName(componentsPath, {})).toEqual(expectedName); - }); - - test('unique resource name removes special characters that are not allow listed and leaves the allowed ones', () => { - const componentsPath = ['I-love-special-characters-', '¯\\\_(ツ)_/¯', '-for-real-though-']; - const expectedName = `I-love-special-characters--for-real-though-${pathHash(componentsPath)}`; - - expect(makeUniqueResourceName(componentsPath, { allowedSpecialCharacters: '-' })).toEqual(expectedName); - }); - - test('unique resource name adds in separator and adds hash when separator is provided and name is not too long', () => { - const componentsPath = ['This', 'unique', 'resource', 'name', 'needs', 'a', 'separator']; - const expectedName = `This.*.unique.*.resource.*.name.*.needs.*.a.*.separator.*.${pathHash(componentsPath)}`; - - expect(makeUniqueResourceName(componentsPath, { separator: '.*.' })).toEqual(expectedName); - }); - - test('unique resource name adds in separator, adds hash, and shortens name when separator is provided and name too long', () => { - const componentsPath = ['This', 'unique', 'resource', 'name', 'is', 'longer', 'than', 'allowed']; - const expectedName = `This/unique/resourcelonger/than/allowed/${pathHash(componentsPath)}`; - - expect(makeUniqueResourceName(componentsPath, { maxLength: 48, separator: '/' })).toEqual(expectedName); - }); -}); \ 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 d465b74cdb213..ad3341853eaf2 100644 --- a/packages/@aws-cdk/cx-api/lib/features.ts +++ b/packages/@aws-cdk/cx-api/lib/features.ts @@ -233,18 +233,6 @@ export const EC2_UNIQUE_IMDSV2_LAUNCH_TEMPLATE_NAME = '@aws-cdk/aws-ec2:uniqueIm */ export const IAM_MINIMIZE_POLICIES = '@aws-cdk/aws-iam:minimizePolicies'; -/** - * Enable this feature flag to have CodePipeline generate a unique cross account key alias name using the stack name. - * - * Previously, when creating multiple pipelines with similar naming conventions and when crossAccountKeys is true, - * the KMS key alias name created for these pipelines may be the same due to how the uniqueId is generated. - * - * This new implementation creates a stack safe uniqueId for the alias name using the stack name instead of the stack ID. - * - * [PERMANENT] - */ -export const CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID = '@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeUniqueId'; - /** * Enable this feature flag to pass through the `NetworkLoadBalanced<Ec2|Fargate>ServiceProps.taskImageOptions.containerPort` * and the `NetworkMultipleTargetGroups<Ec2|Fargate>ServiceProps.targetGroups[X].containerPort` to the generated @@ -281,7 +269,6 @@ export const FUTURE_FLAGS: { [key: string]: boolean } = { [EC2_UNIQUE_IMDSV2_LAUNCH_TEMPLATE_NAME]: true, [CHECK_SECRET_USAGE]: true, [IAM_MINIMIZE_POLICIES]: true, - [CODEPIPELINE_CROSS_ACCOUNT_KEY_ALIAS_STACK_SAFE_UNIQUE_ID]: true, [ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT]: true, }; diff --git a/packages/@aws-cdk/pipelines/test/pipeline-security.integ.snapshot/PipelineSecurityStack.template.json b/packages/@aws-cdk/pipelines/test/pipeline-security.integ.snapshot/PipelineSecurityStack.template.json index e7602f6d2568c..e6b1603b70c2b 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline-security.integ.snapshot/PipelineSecurityStack.template.json +++ b/packages/@aws-cdk/pipelines/test/pipeline-security.integ.snapshot/PipelineSecurityStack.template.json @@ -212,7 +212,7 @@ "TestPipelineArtifactsBucketEncryptionKeyAliasE8D86DD3": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipelinesecuritystack-testpipeline-f7060861", + "AliasName": "alias/codepipeline-pipelinesecuritystacktestpipelinef7060861", "TargetKeyId": { "Fn::GetAtt": [ "TestPipelineArtifactsBucketEncryptionKey13258842", diff --git a/packages/@aws-cdk/pipelines/test/pipeline-with-assets-single-upload.integ.snapshot/PipelineStack.template.json b/packages/@aws-cdk/pipelines/test/pipeline-with-assets-single-upload.integ.snapshot/PipelineStack.template.json index c134fb23c8ceb..5fbba631c564d 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline-with-assets-single-upload.integ.snapshot/PipelineStack.template.json +++ b/packages/@aws-cdk/pipelines/test/pipeline-with-assets-single-upload.integ.snapshot/PipelineStack.template.json @@ -212,7 +212,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipelinestack-pipeline-e95eedaa", + "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKeyF5BF0670", diff --git a/packages/@aws-cdk/pipelines/test/pipeline-with-assets.integ.snapshot/PipelineStack.template.json b/packages/@aws-cdk/pipelines/test/pipeline-with-assets.integ.snapshot/PipelineStack.template.json index 2ad15e11acba2..b1aabb1680288 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline-with-assets.integ.snapshot/PipelineStack.template.json +++ b/packages/@aws-cdk/pipelines/test/pipeline-with-assets.integ.snapshot/PipelineStack.template.json @@ -212,7 +212,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipelinestack-pipeline-e95eedaa", + "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKeyF5BF0670", diff --git a/packages/@aws-cdk/pipelines/test/pipeline.integ.snapshot/PipelineStack.template.json b/packages/@aws-cdk/pipelines/test/pipeline.integ.snapshot/PipelineStack.template.json index 227d454457fb3..df8d293f9ae73 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline.integ.snapshot/PipelineStack.template.json +++ b/packages/@aws-cdk/pipelines/test/pipeline.integ.snapshot/PipelineStack.template.json @@ -212,7 +212,7 @@ "PipelineArtifactsBucketEncryptionKeyAlias94A07392": { "Type": "AWS::KMS::Alias", "Properties": { - "AliasName": "alias/codepipeline-pipelinestack-pipeline-e95eedaa", + "AliasName": "alias/codepipeline-pipelinestackpipelinee95eedaa", "TargetKeyId": { "Fn::GetAtt": [ "PipelineArtifactsBucketEncryptionKeyF5BF0670", From 32dfa6efc7bf836225b64183e7754778df44d668 Mon Sep 17 00:00:00 2001 From: Peter Woodworth <44349620+peterwoodworth@users.noreply.github.com> Date: Fri, 20 May 2022 02:54:51 -0700 Subject: [PATCH 22/29] docs: explain SnapshotCredentials (#20431) fixes #20388 I'm interested in why `DatabaseClusterFromSnapshot` generates an `admin` username unlike the other snapshot constructs, I'm unfamiliar with why it's be okay to generate a username for that but not an instance or serverless cluster ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-rds/README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index dcf365bb2eb8d..76290625b3d90 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -185,7 +185,7 @@ const rule = instance.onEvent('InstanceEvent', { target: new targets.LambdaFunct ## Login credentials -By default, database instances and clusters will have `admin` user with an auto-generated password. +By default, database instances and clusters (with the exception of `DatabaseInstanceFromSnapshot` and `ServerlessClusterFromSnapshot`) will have `admin` user with an auto-generated password. An alternative username (and password) may be specified for the admin user instead of the default. The following examples use a `DatabaseInstance`, but the same usage is applicable to `DatabaseCluster`. @@ -232,6 +232,27 @@ new rds.DatabaseInstance(this, 'InstanceWithCustomizedSecret', { }); ``` +### Snapshot credentials + +As noted above, Databases created with `DatabaseInstanceFromSnapshot` or `ServerlessClusterFromSnapshot` will not create user and auto-generated password by default because it's not possible to change the master username for a snapshot. Instead, they will use the existing username and password from the snapshot. You can still generate a new password - to generate a secret similarly to the other constructs, pass in credentials with `fromGeneratedSecret()` or `fromGeneratedPassword()`. + +```ts +declare const vpc: ec2.Vpc; +const engine = rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_12_3 }); +const myKey = new kms.Key(this, 'MyKey'); + +new rds.DatabaseInstanceFromSnapshot(this, 'InstanceFromSnapshotWithCustomizedSecret', { + engine, + vpc, + snapshotIdentifier: 'mySnapshot', + credentials: rds.SnapshotCredentials.fromGeneratedSecret('username', { + encryptionKey: myKey, + excludeCharacters: '!&*^#@()', + replicaRegions: [{ region: 'eu-west-1' }, { region: 'eu-west-2' }], + }), +}); +``` + ## Connecting To control who can access the cluster or instance, use the `.connections` attribute. RDS databases have From 88ea829b5d0a64f51848474b6b9f006d1f729fb4 Mon Sep 17 00:00:00 2001 From: Calvin Combs <66279577+comcalvi@users.noreply.github.com> Date: Fri, 20 May 2022 04:42:31 -0600 Subject: [PATCH 23/29] feat(core): allow disabling of LogicalID Metadata in case of large manifest (#20433) Users have encountered an error resulting from the manifest being too large to stringify. This allows users to prevent this metadata from ever being added to the manifest. Fixes the issue that caused the previous iteration of this PR to fail in the v2 pipeline. Fixes https://github.com/aws/aws-cdk/issues/20211. ---- *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/cfn-element.ts | 5 +- packages/@aws-cdk/core/test/synthesis.test.ts | 51 +++++++++++++++++-- packages/@aws-cdk/cx-api/lib/app.ts | 6 +++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/core/lib/cfn-element.ts b/packages/@aws-cdk/core/lib/cfn-element.ts index 9bb08746c4a47..84126add4e13c 100644 --- a/packages/@aws-cdk/core/lib/cfn-element.ts +++ b/packages/@aws-cdk/core/lib/cfn-element.ts @@ -1,4 +1,5 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cxapi from '@aws-cdk/cx-api'; import { Construct, Node } from 'constructs'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. @@ -64,7 +65,9 @@ export abstract class CfnElement extends CoreConstruct { displayHint: `${notTooLong(Node.of(this).path)}.LogicalID`, }); - Node.of(this).addMetadata(cxschema.ArtifactMetadataEntryType.LOGICAL_ID, this.logicalId, this.constructor); + if (!this.node.tryGetContext(cxapi.DISABLE_LOGICAL_ID_METADATA)) { + Node.of(this).addMetadata(cxschema.ArtifactMetadataEntryType.LOGICAL_ID, this.logicalId, this.constructor); + } } /** diff --git a/packages/@aws-cdk/core/test/synthesis.test.ts b/packages/@aws-cdk/core/test/synthesis.test.ts index 496fd76fdbd91..bb0a87afa0ea4 100644 --- a/packages/@aws-cdk/core/test/synthesis.test.ts +++ b/packages/@aws-cdk/core/test/synthesis.test.ts @@ -3,6 +3,7 @@ import * as os from 'os'; import * as path from 'path'; import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cxapi from '@aws-cdk/cx-api'; import * as cdk from '../lib'; function createModernApp() { @@ -36,7 +37,6 @@ describe('synthesis', () => { }, }), }); - }); test('synthesis respects disabling tree metadata', () => { @@ -45,7 +45,52 @@ describe('synthesis', () => { }); const assembly = app.synth(); expect(list(assembly.directory)).toEqual(['cdk.out', 'manifest.json']); + }); + + test('synthesis respects disabling logicalId metadata', () => { + const app = new cdk.App({ + context: { + [cxapi.DISABLE_LOGICAL_ID_METADATA]: true, + }, + }); + const stack = new cdk.Stack(app, 'one-stack'); + new cdk.CfnResource(stack, 'MagicResource', { type: 'Resource::Type' }); + + // WHEN + const session = app.synth(); + + // THEN + expect(Object.keys((session.manifest.artifacts ?? {})['one-stack'])).not.toContain('metadata'); + }); + + test('synthesis respects disabling logicalId metadata, and does not disable other metadata', () => { + const app = new cdk.App({ + context: { + [cxapi.DISABLE_LOGICAL_ID_METADATA]: true, + }, + stackTraces: false, + }); + const stack = new cdk.Stack(app, 'one-stack', { tags: { boomTag: 'BOOM' } }); + new cdk.CfnResource(stack, 'MagicResource', { type: 'Resource::Type' }); + // WHEN + const session = app.synth(); + + // THEN + expect(session.manifest.artifacts?.['one-stack'].metadata).toEqual({ + '/one-stack': [ + { + type: 'aws:cdk:stack-tags', + data: [ + { + key: 'boomTag', + value: 'BOOM', + }, + ], + }, + ], + // no logicalId entry + }); }); test('single empty stack', () => { @@ -58,7 +103,6 @@ describe('synthesis', () => { // THEN expect(list(session.directory).includes('one-stack.template.json')).toEqual(true); - }); test('some random construct implements "synthesize"', () => { @@ -112,7 +156,6 @@ describe('synthesis', () => { }, }, }); - }); test('random construct uses addCustomSynthesis', () => { @@ -172,7 +215,6 @@ describe('synthesis', () => { }, }, }); - }); testDeprecated('it should be possible to synthesize without an app', () => { @@ -220,7 +262,6 @@ describe('synthesis', () => { expect(stack.templateFile).toEqual('hey.json'); expect(stack.parameters).toEqual({ paramId: 'paramValue', paramId2: 'paramValue2' }); expect(stack.environment).toEqual({ region: 'us-east-1', account: 'unknown-account', name: 'aws://unknown-account/us-east-1' }); - }); }); diff --git a/packages/@aws-cdk/cx-api/lib/app.ts b/packages/@aws-cdk/cx-api/lib/app.ts index 41c03f374b408..c08fd5868207b 100644 --- a/packages/@aws-cdk/cx-api/lib/app.ts +++ b/packages/@aws-cdk/cx-api/lib/app.ts @@ -39,6 +39,12 @@ export const DISABLE_ASSET_STAGING_CONTEXT = 'aws:cdk:disable-asset-staging'; */ export const DISABLE_METADATA_STACK_TRACE = 'aws:cdk:disable-stack-trace'; +/** + * If this context key is set, the CDK will not store logical ID + * metadata in the manifest. + */ +export const DISABLE_LOGICAL_ID_METADATA = 'aws:cdk:disable-logicalId-metadata'; + /** * Run bundling for stacks specified in this context key */ From 484cfb21710f91aed64e2db32c35b9e3a4e1163c Mon Sep 17 00:00:00 2001 From: Rico Huijbers <rix0rrr@gmail.com> Date: Fri, 20 May 2022 16:03:14 +0200 Subject: [PATCH 24/29] chore(integ-runner): `--inspect-failures` option (#20399) When the integ runner fails to validate a snapshot, add an option to retain the tempdir with the "new actual" snapshot so that a human can go look around in it. Also make the `--verbose` flag print out a command that will reproduce the running of the CDK app directly from the command-line, so that it can be debugged more easily. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/integ-runner/lib/cli.ts | 7 ++- .../integ-runner/lib/runner/runner-base.ts | 6 ++- .../lib/runner/snapshot-test-runner.ts | 53 ++++++++++++++++--- .../integ-runner/lib/workers/common.ts | 24 +++++++++ .../lib/workers/extract/extract_worker.ts | 12 ++--- .../lib/workers/integ-snapshot-worker.ts | 10 ++-- .../test/workers/integ-worker.test.ts | 2 +- 7 files changed, 94 insertions(+), 20 deletions(-) diff --git a/packages/@aws-cdk/integ-runner/lib/cli.ts b/packages/@aws-cdk/integ-runner/lib/cli.ts index 4d10df51de312..1c7c7920375d5 100644 --- a/packages/@aws-cdk/integ-runner/lib/cli.ts +++ b/packages/@aws-cdk/integ-runner/lib/cli.ts @@ -27,7 +27,9 @@ async function main() { .options('max-workers', { type: 'number', desc: 'The max number of workerpool workers to use when running integration tests in parallel', default: 16 }) .options('exclude', { type: 'boolean', desc: 'All tests should be run, except for the list of tests provided', default: false }) .options('from-file', { type: 'string', desc: 'Import tests to include or exclude from a file' }) + .option('inspect-failures', { type: 'boolean', desc: 'Keep the integ test cloud assembly if a failure occurs for inspection', default: false }) .option('disable-update-workflow', { type: 'boolean', default: false, desc: 'If this is "true" then the stack update workflow will be disabled' }) + .strict() .argv; const pool = workerpool.pool(path.join(__dirname, '../lib/workers/extract/index.js'), { @@ -70,7 +72,10 @@ async function main() { // always run snapshot tests, but if '--force' is passed then // run integration tests on all failed tests, not just those that // failed snapshot tests - failedSnapshots = await runSnapshotTests(pool, testsFromArgs); + failedSnapshots = await runSnapshotTests(pool, testsFromArgs, { + retain: argv['inspect-failures'], + verbose: argv.verbose, + }); for (const failure of failedSnapshots) { destructiveChanges.push(...failure.destructiveChanges ?? []); } diff --git a/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts b/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts index b2aebc80905b5..eff0e68191acb 100644 --- a/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts +++ b/packages/@aws-cdk/integ-runner/lib/runner/runner-base.ts @@ -129,6 +129,8 @@ export abstract class IntegRunner { protected readonly profile?: string; + protected readonly cdkExecutable: string; + protected _destructiveChanges?: DestructiveChange[]; private legacyContext?: Record<string, any>; @@ -150,8 +152,10 @@ export abstract class IntegRunner { this.relativeSnapshotDir = `${testName}.integ.snapshot`; this.sourceFilePath = path.join(this.directory, parsed.base); this.cdkContextPath = path.join(this.directory, 'cdk.context.json'); + + this.cdkExecutable = require.resolve('aws-cdk/bin/cdk'); this.cdk = options.cdk ?? new CdkCliWrapper({ - cdkExecutable: require.resolve('aws-cdk/bin/cdk'), + cdkExecutable: this.cdkExecutable, directory: this.directory, env: { ...options.env, diff --git a/packages/@aws-cdk/integ-runner/lib/runner/snapshot-test-runner.ts b/packages/@aws-cdk/integ-runner/lib/runner/snapshot-test-runner.ts index ca521103d882e..e7c95beff0a56 100644 --- a/packages/@aws-cdk/integ-runner/lib/runner/snapshot-test-runner.ts +++ b/packages/@aws-cdk/integ-runner/lib/runner/snapshot-test-runner.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import { Writable, WritableOptions } from 'stream'; import { StringDecoder, NodeStringDecoder } from 'string_decoder'; import { diffTemplate, formatDifferences, ResourceDifference, ResourceImpact } from '@aws-cdk/cloudformation-diff'; -import { Diagnostic, DiagnosticReason, DestructiveChange } from '../workers/common'; +import { Diagnostic, DiagnosticReason, DestructiveChange, SnapshotVerificationOptions } from '../workers/common'; import { canonicalizeTemplate } from './private/canonicalize-assets'; import { AssemblyManifestReader } from './private/cloud-assembly'; import { IntegRunnerOptions, IntegRunner, DEFAULT_SYNTH_OPTIONS } from './runner-base'; @@ -22,7 +22,8 @@ export class IntegSnapshotRunner extends IntegRunner { * * @returns any diagnostics and any destructive changes */ - public testSnapshot(): { diagnostics: Diagnostic[], destructiveChanges: DestructiveChange[] } { + public testSnapshot(options: SnapshotVerificationOptions = {}): { diagnostics: Diagnostic[], destructiveChanges: DestructiveChange[] } { + let doClean = true; try { // read the existing snapshot const expectedStacks = this.readAssembly(this.snapshotDir); @@ -39,17 +40,19 @@ export class IntegSnapshotRunner extends IntegRunner { // the cdkOutDir exists already, but for some reason generateActualSnapshot // generates an incorrect snapshot and I have no idea why so synth again here // to produce the "correct" snapshot + const env = { + ...DEFAULT_SYNTH_OPTIONS.env, + CDK_CONTEXT_JSON: JSON.stringify(this.getContext()), + }; this.cdk.synthFast({ execCmd: this.cdkApp.split(' '), - env: { - ...DEFAULT_SYNTH_OPTIONS.env, - CDK_CONTEXT_JSON: JSON.stringify(this.getContext()), - }, + env, output: this.cdkOutDir, }); // read the "actual" snapshot - const actualStacks = this.readAssembly(path.join(this.directory, this.cdkOutDir)); + const actualDir = path.join(this.directory, this.cdkOutDir); + const actualStacks = this.readAssembly(actualDir); // only diff stacks that are part of the test case const actualStacksToDiff: Record<string, any> = {}; for (const [stackName, template] of Object.entries(actualStacks)) { @@ -60,11 +63,45 @@ export class IntegSnapshotRunner extends IntegRunner { // diff the existing snapshot (expected) with the integration test (actual) const diagnostics = this.diffAssembly(expectedStacksToDiff, actualStacksToDiff); + + if (diagnostics.diagnostics.length) { + // Attach additional messages to the first diagnostic + const additionalMessages: string[] = []; + + if (options.retain) { + additionalMessages.push( + `(Failure retained) Expected: ${path.relative(process.cwd(), this.snapshotDir)}`, + ` Actual: ${path.relative(process.cwd(), actualDir)}`, + ), + doClean = false; + } + + if (options.verbose) { + // Show the command necessary to repro this + const envSet = Object.entries(env) + .filter(([k, _]) => k !== 'CDK_CONTEXT_JSON') + .map(([k, v]) => `${k}='${v}'`); + const envCmd = envSet.length > 0 ? ['env', ...envSet] : []; + + additionalMessages.push( + 'Repro:', + ` ${[...envCmd, 'cdk synth', `-a '${this.cdkApp}'`, `-o '${this.cdkOutDir}'`, ...Object.entries(this.getContext()).flatMap(([k, v]) => typeof v !== 'object' ? [`-c '${k}=${v}'`] : [])].join(' ')}`, + ); + } + + diagnostics.diagnostics[0] = { + ...diagnostics.diagnostics[0], + additionalMessages, + }; + } + return diagnostics; } catch (e) { throw e; } finally { - this.cleanup(); + if (doClean) { + this.cleanup(); + } } } diff --git a/packages/@aws-cdk/integ-runner/lib/workers/common.ts b/packages/@aws-cdk/integ-runner/lib/workers/common.ts index bcd6a03e36d2b..9b10c6b195f93 100644 --- a/packages/@aws-cdk/integ-runner/lib/workers/common.ts +++ b/packages/@aws-cdk/integ-runner/lib/workers/common.ts @@ -89,6 +89,22 @@ export interface IntegRunnerMetrics { readonly profile?: string; } +export interface SnapshotVerificationOptions { + /** + * Retain failed snapshot comparisons + * + * @default false + */ + readonly retain?: boolean; + + /** + * Verbose mode + * + * @default false + */ + readonly verbose?: boolean; +} + /** * Integration test results */ @@ -208,6 +224,11 @@ export interface Diagnostic { * The reason for the diagnostic */ readonly reason: DiagnosticReason; + + /** + * Additional messages to print + */ + readonly additionalMessages?: string[]; } export function printSummary(total: number, failed: number): void { @@ -251,4 +272,7 @@ export function printResults(diagnostic: Diagnostic): void { case DiagnosticReason.ASSERTION_FAILED: logger.error(' %s - Assertions Failed! %s\n%s', diagnostic.testName, chalk.gray(`${diagnostic.duration}s`), diagnostic.message); } + for (const addl of diagnostic.additionalMessages ?? []) { + logger.print(` ${addl}`); + } } diff --git a/packages/@aws-cdk/integ-runner/lib/workers/extract/extract_worker.ts b/packages/@aws-cdk/integ-runner/lib/workers/extract/extract_worker.ts index c38d97a55856c..957341647e90f 100644 --- a/packages/@aws-cdk/integ-runner/lib/workers/extract/extract_worker.ts +++ b/packages/@aws-cdk/integ-runner/lib/workers/extract/extract_worker.ts @@ -1,7 +1,7 @@ import * as workerpool from 'workerpool'; import { IntegSnapshotRunner, IntegTestRunner } from '../../runner'; import { IntegTestConfig } from '../../runner/integration-tests'; -import { DiagnosticReason, IntegTestWorkerConfig, formatAssertionResults } from '../common'; +import { DiagnosticReason, IntegTestWorkerConfig, SnapshotVerificationOptions, Diagnostic, formatAssertionResults } from '../common'; import { IntegTestBatchRequest } from '../integ-test-worker'; /** @@ -84,7 +84,7 @@ export function integTestWorker(request: IntegTestBatchRequest): IntegTestWorker * if there is an existing snapshot, and if there is will * check if there are any changes */ -export function snapshotTestWorker(test: IntegTestConfig): IntegTestWorkerConfig[] { +export function snapshotTestWorker(test: IntegTestConfig, options: SnapshotVerificationOptions = {}): IntegTestWorkerConfig[] { const failedTests = new Array<IntegTestWorkerConfig>(); const runner = new IntegSnapshotRunner({ fileName: test.fileName, directory: test.directory }); const start = Date.now(); @@ -98,12 +98,12 @@ export function snapshotTestWorker(test: IntegTestConfig): IntegTestWorkerConfig }); failedTests.push(test); } else { - const { diagnostics, destructiveChanges } = runner.testSnapshot(); + const { diagnostics, destructiveChanges } = runner.testSnapshot(options); if (diagnostics.length > 0) { diagnostics.forEach(diagnostic => workerpool.workerEmit({ ...diagnostic, duration: (Date.now() - start) / 1000, - })); + } as Diagnostic)); failedTests.push({ fileName: test.fileName, directory: test.directory, @@ -115,7 +115,7 @@ export function snapshotTestWorker(test: IntegTestConfig): IntegTestWorkerConfig testName: runner.testName, message: 'Success', duration: (Date.now() - start) / 1000, - }); + } as Diagnostic); } } } catch (e) { @@ -125,7 +125,7 @@ export function snapshotTestWorker(test: IntegTestConfig): IntegTestWorkerConfig testName: runner.testName, reason: DiagnosticReason.SNAPSHOT_FAILED, duration: (Date.now() - start) / 1000, - }); + } as Diagnostic); } return failedTests; diff --git a/packages/@aws-cdk/integ-runner/lib/workers/integ-snapshot-worker.ts b/packages/@aws-cdk/integ-runner/lib/workers/integ-snapshot-worker.ts index 66057110a5490..1ff88e6cba5d6 100644 --- a/packages/@aws-cdk/integ-runner/lib/workers/integ-snapshot-worker.ts +++ b/packages/@aws-cdk/integ-runner/lib/workers/integ-snapshot-worker.ts @@ -2,18 +2,22 @@ import * as workerpool from 'workerpool'; import * as logger from '../logger'; import { IntegTestConfig } from '../runner/integration-tests'; import { flatten } from '../utils'; -import { printSummary, printResults, IntegTestWorkerConfig } from './common'; +import { printSummary, printResults, IntegTestWorkerConfig, SnapshotVerificationOptions } from './common'; /** * Run Snapshot tests * First batch up the tests. By default there will be 3 tests per batch. * Use a workerpool to run the batches in parallel. */ -export async function runSnapshotTests(pool: workerpool.WorkerPool, tests: IntegTestConfig[]): Promise<IntegTestWorkerConfig[]> { +export async function runSnapshotTests( + pool: workerpool.WorkerPool, + tests: IntegTestConfig[], + options: SnapshotVerificationOptions, +): Promise<IntegTestWorkerConfig[]> { logger.highlight('\nVerifying integration test snapshots...\n'); const failedTests: IntegTestWorkerConfig[][] = await Promise.all( - tests.map((test) => pool.exec('snapshotTestWorker', [test], { + tests.map((test) => pool.exec('snapshotTestWorker', [test, options], { on: printResults, })), ); diff --git a/packages/@aws-cdk/integ-runner/test/workers/integ-worker.test.ts b/packages/@aws-cdk/integ-runner/test/workers/integ-worker.test.ts index 09598f2017011..54864d2421b4a 100644 --- a/packages/@aws-cdk/integ-runner/test/workers/integ-worker.test.ts +++ b/packages/@aws-cdk/integ-runner/test/workers/integ-worker.test.ts @@ -147,7 +147,7 @@ describe('test runner', () => { ]), ])); - expect(results.length).toEqual(0); + expect(results).toEqual([]); }); test('deploy failed', () => { From 3a11317cd95cbe93d93b5aaf6fb02fbe58a8eb9a Mon Sep 17 00:00:00 2001 From: Christopher Rybicki <rybickic@amazon.com> Date: Fri, 20 May 2022 09:21:55 -0700 Subject: [PATCH 25/29] chore(ecs-patterns): revert "feature flag missing for breaking change passing container port for target group port (#20284)" (#20430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts #20284 since its tests fail to pass in CDK v2, blocking the next CDK release. The root cause of failure looks as though it may be the same as #20427 - I've included the test logs below: <details> ``` @aws-cdk/aws-ecs-patterns: FAIL test/fargate/load-balanced-fargate-service-v2.test.js (11.703 s) @aws-cdk/aws-ecs-patterns: â— When Network Load Balancer › Fargate networkloadbalanced construct uses custom Port for target group when feature flag is enabled @aws-cdk/aws-ecs-patterns: Template has 1 resources with type AWS::ElasticLoadBalancingV2::TargetGroup, but none match as expected. @aws-cdk/aws-ecs-patterns: The closest result is: @aws-cdk/aws-ecs-patterns: { @aws-cdk/aws-ecs-patterns: "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", @aws-cdk/aws-ecs-patterns: "Properties": { @aws-cdk/aws-ecs-patterns: "Port": 80, @aws-cdk/aws-ecs-patterns: "Protocol": "TCP", @aws-cdk/aws-ecs-patterns: "TargetType": "ip", @aws-cdk/aws-ecs-patterns: "VpcId": { @aws-cdk/aws-ecs-patterns: "Ref": "VPCB9E5F0B4" @aws-cdk/aws-ecs-patterns: } @aws-cdk/aws-ecs-patterns: } @aws-cdk/aws-ecs-patterns: } @aws-cdk/aws-ecs-patterns: with the following mismatches: @aws-cdk/aws-ecs-patterns: Expected 81 but received 80 at /Properties/Port (using objectLike matcher) @aws-cdk/aws-ecs-patterns: 83 | const matchError = hasResourceProperties(this.template, type, props); @aws-cdk/aws-ecs-patterns: 84 | if (matchError) { @aws-cdk/aws-ecs-patterns: > 85 | throw new Error(matchError); @aws-cdk/aws-ecs-patterns: | ^ @aws-cdk/aws-ecs-patterns: 86 | } @aws-cdk/aws-ecs-patterns: 87 | } @aws-cdk/aws-ecs-patterns: 88 | @aws-cdk/aws-ecs-patterns: at Template.hasResourceProperties (../assertions/lib/template.ts:85:13) @aws-cdk/aws-ecs-patterns: at fn (test/fargate/load-balanced-fargate-service-v2.test.ts:709:31) @aws-cdk/aws-ecs-patterns: at Object.<anonymous> (../../../tools/@aws-cdk/cdk-build-tools/lib/feature-flag.ts:34:35) @aws-cdk/aws-ecs-patterns: â— When Network Load Balancer › test Fargate multinetworkloadbalanced construct uses custom Port for target group when feature flag is enabled @aws-cdk/aws-ecs-patterns: Template has 2 resources with type AWS::ElasticLoadBalancingV2::TargetGroup, but none match as expected. @aws-cdk/aws-ecs-patterns: The closest result is: @aws-cdk/aws-ecs-patterns: { @aws-cdk/aws-ecs-patterns: "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", @aws-cdk/aws-ecs-patterns: "Properties": { @aws-cdk/aws-ecs-patterns: "Port": 80, @aws-cdk/aws-ecs-patterns: "Protocol": "TCP", @aws-cdk/aws-ecs-patterns: "TargetType": "ip", @aws-cdk/aws-ecs-patterns: "VpcId": { @aws-cdk/aws-ecs-patterns: "Ref": "VPCB9E5F0B4" @aws-cdk/aws-ecs-patterns: } @aws-cdk/aws-ecs-patterns: } @aws-cdk/aws-ecs-patterns: } @aws-cdk/aws-ecs-patterns: with the following mismatches: @aws-cdk/aws-ecs-patterns: Expected 81 but received 80 at /Properties/Port (using objectLike matcher) @aws-cdk/aws-ecs-patterns: 83 | const matchError = hasResourceProperties(this.template, type, props); @aws-cdk/aws-ecs-patterns: 84 | if (matchError) { @aws-cdk/aws-ecs-patterns: > 85 | throw new Error(matchError); @aws-cdk/aws-ecs-patterns: | ^ @aws-cdk/aws-ecs-patterns: 86 | } @aws-cdk/aws-ecs-patterns: 87 | } @aws-cdk/aws-ecs-patterns: 88 | @aws-cdk/aws-ecs-patterns: at Template.hasResourceProperties (../assertions/lib/template.ts:85:13) @aws-cdk/aws-ecs-patterns: at fn (test/fargate/load-balanced-fargate-service-v2.test.ts:823:31) @aws-cdk/aws-ecs-patterns: at Object.<anonymous> (../../../tools/@aws-cdk/cdk-build-tools/lib/feature-flag.ts:34:35) ``` </details> ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../network-load-balanced-service-base.ts | 11 +- ...ork-multiple-target-groups-service-base.ts | 5 +- .../load-balanced-fargate-service-v2.test.ts | 150 +----------------- packages/@aws-cdk/cx-api/lib/features.ts | 10 -- 4 files changed, 12 insertions(+), 164 deletions(-) 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 fc71eed3c45d1..942f13e3439aa 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 @@ -7,8 +7,7 @@ import { INetworkLoadBalancer, NetworkListener, NetworkLoadBalancer, NetworkTarg import { IRole } from '@aws-cdk/aws-iam'; import { ARecord, CnameRecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53'; import { LoadBalancerTarget } from '@aws-cdk/aws-route53-targets'; -import { CfnOutput, Duration, FeatureFlags, Stack } from '@aws-cdk/core'; -import { ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT } from '@aws-cdk/cx-api'; +import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -104,7 +103,7 @@ export interface NetworkLoadBalancedServiceBaseProps { * * @default - defaults to 60 seconds if at least one load balancer is in-use and it is not already set */ - readonly healthCheckGracePeriod?: Duration; + readonly healthCheckGracePeriod?: cdk.Duration; /** * The maximum number of tasks, specified as a percentage of the Amazon ECS @@ -348,7 +347,7 @@ export abstract class NetworkLoadBalancedServiceBase extends CoreConstruct { const loadBalancer = props.loadBalancer ?? new NetworkLoadBalancer(this, 'LB', lbProps); const listenerPort = props.listenerPort ?? 80; const targetProps = { - port: FeatureFlags.of(this).isEnabled(ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT) ? props.taskImageOptions?.containerPort ?? 80 : 80, + port: props.taskImageOptions?.containerPort ?? 80, }; this.listener = loadBalancer.addListener('PublicListener', { port: listenerPort }); @@ -385,7 +384,7 @@ export abstract class NetworkLoadBalancedServiceBase extends CoreConstruct { } if (props.loadBalancer === undefined) { - new CfnOutput(this, 'LoadBalancerDNS', { value: this.loadBalancer.loadBalancerDnsName }); + new cdk.CfnOutput(this, 'LoadBalancerDNS', { value: this.loadBalancer.loadBalancerDnsName }); } } @@ -395,7 +394,7 @@ export abstract class NetworkLoadBalancedServiceBase extends CoreConstruct { protected getDefaultCluster(scope: CoreConstruct, vpc?: IVpc): Cluster { // magic string to avoid collision with user-defined constructs const DEFAULT_CLUSTER_ID = `EcsDefaultClusterMnL3mNNYN${vpc ? vpc.node.id : ''}`; - const stack = Stack.of(scope); + const stack = cdk.Stack.of(scope); return stack.node.tryFindChild(DEFAULT_CLUSTER_ID) as Cluster || new Cluster(stack, DEFAULT_CLUSTER_ID, { vpc }); } 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 3febc79520079..677caf8c2df9f 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 @@ -7,8 +7,7 @@ import { NetworkListener, NetworkLoadBalancer, NetworkTargetGroup } from '@aws-c import { IRole } from '@aws-cdk/aws-iam'; import { ARecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53'; import { LoadBalancerTarget } from '@aws-cdk/aws-route53-targets'; -import { CfnOutput, Duration, FeatureFlags, Stack } from '@aws-cdk/core'; -import { ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT } from '@aws-cdk/cx-api'; +import { CfnOutput, Duration, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. @@ -375,7 +374,7 @@ export abstract class NetworkMultipleTargetGroupsServiceBase extends CoreConstru protected registerECSTargets(service: BaseService, container: ContainerDefinition, targets: NetworkTargetProps[]): NetworkTargetGroup { for (const targetProps of targets) { const targetGroup = this.findListener(targetProps.listener).addTargets(`ECSTargetGroup${container.containerName}${targetProps.containerPort}`, { - port: FeatureFlags.of(this).isEnabled(ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT) ? targetProps.containerPort ?? 80 : 80, + port: targetProps.containerPort ?? 80, targets: [ service.loadBalancerTarget({ containerName: container.containerName, diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts index 8ab4b8b8ab34a..b196f4b0616b1 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts @@ -3,9 +3,7 @@ import { Vpc } from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import { ContainerImage } from '@aws-cdk/aws-ecs'; import { CompositePrincipal, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; -import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools'; -import { App, Duration, Stack } from '@aws-cdk/core'; -import { ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT } from '@aws-cdk/cx-api'; +import { Duration, Stack } from '@aws-cdk/core'; import { ApplicationLoadBalancedFargateService, ApplicationMultipleTargetGroupsFargateService, NetworkLoadBalancedFargateService, NetworkMultipleTargetGroupsFargateService } from '../../lib'; describe('When Application Load Balancer', () => { @@ -665,36 +663,9 @@ describe('When Network Load Balancer', () => { }).toThrow(/You must specify one of: taskDefinition or image/); }); - testLegacyBehavior('Fargate neworkloadbalanced construct uses Port 80 for target group when feature flag is not enabled', App, (app) => { + test('test Fargate networkloadbalanced construct with custom Port', () => { // GIVEN - const stack = new Stack(app); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - new NetworkLoadBalancedFargateService(stack, 'NLBService', { - cluster: cluster, - memoryLimitMiB: 1024, - cpu: 512, - taskImageOptions: { - image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - containerPort: 81, - }, - listenerPort: 8181, - }); - - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { - Port: 80, - Protocol: 'TCP', - TargetType: 'ip', - VpcId: { - Ref: 'VPCB9E5F0B4', - }, - }); - }); - - testFutureBehavior('Fargate networkloadbalanced construct uses custom Port for target group when feature flag is enabled', { [ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT]: true }, App, (app) => { - // GIVEN - const stack = new Stack(app); + const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); @@ -719,79 +690,9 @@ describe('When Network Load Balancer', () => { }); }); - testFutureBehavior('Fargate networkloadbalanced construct uses 80 for target group when feature flag is enabled but container port is not provided', { [ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT]: true }, App, (app) => { - // GIVEN - const stack = new Stack(app); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - new NetworkLoadBalancedFargateService(stack, 'NLBService', { - cluster: cluster, - memoryLimitMiB: 1024, - cpu: 512, - taskImageOptions: { - image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - listenerPort: 8181, - }); - - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { - Port: 80, - Protocol: 'TCP', - TargetType: 'ip', - VpcId: { - Ref: 'VPCB9E5F0B4', - }, - }); - }); - - testLegacyBehavior('Fargate multinetworkloadbalanced construct uses Port 80 for target group when feature flag is not enabled', App, (app) => { - // GIVEN - const stack = new Stack(app); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - }); - - - new NetworkMultipleTargetGroupsFargateService(stack, 'NLBService', { - cluster: cluster, - memoryLimitMiB: 1024, - cpu: 512, - taskImageOptions: { - image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - loadBalancers: [ - { - name: 'lb1', - listeners: [ - { name: 'listener1', port: 8181 }, - ], - }, - ], - targetGroups: [{ - containerPort: 81, - }], - }); - - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { - Port: 80, - Protocol: 'TCP', - TargetType: 'ip', - VpcId: { - Ref: 'VPCB9E5F0B4', - }, - }); - }); - - testFutureBehavior('test Fargate multinetworkloadbalanced construct uses custom Port for target group when feature flag is enabled', { [ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT]: true }, App, (app) => { + test('test Fargate multinetworkloadbalanced construct with custom Port', () => { // GIVEN - const stack = new Stack(app); + const stack = new Stack(); const vpc = new Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); @@ -832,45 +733,4 @@ describe('When Network Load Balancer', () => { }, }); }); - - testFutureBehavior('test Fargate multinetworkloadbalanced construct uses 80 for target group when feature flag is enabled but container port is not provided', { [ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT]: true }, App, (app) => { - // GIVEN - const stack = new Stack(app); - const vpc = new Vpc(stack, 'VPC'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - - new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { - cluster, - taskImageOptions: { - image: ecs.ContainerImage.fromRegistry('test'), - }, - }); - - - new NetworkMultipleTargetGroupsFargateService(stack, 'NLBService', { - cluster: cluster, - memoryLimitMiB: 1024, - cpu: 512, - taskImageOptions: { - image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), - }, - loadBalancers: [ - { - name: 'lb1', - listeners: [ - { name: 'listener1', port: 8181 }, - ], - }, - ], - }); - - Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::TargetGroup', { - Port: 80, - Protocol: 'TCP', - TargetType: 'ip', - VpcId: { - Ref: 'VPCB9E5F0B4', - }, - }); - }); }); diff --git a/packages/@aws-cdk/cx-api/lib/features.ts b/packages/@aws-cdk/cx-api/lib/features.ts index ad3341853eaf2..21918585ae183 100644 --- a/packages/@aws-cdk/cx-api/lib/features.ts +++ b/packages/@aws-cdk/cx-api/lib/features.ts @@ -233,15 +233,6 @@ export const EC2_UNIQUE_IMDSV2_LAUNCH_TEMPLATE_NAME = '@aws-cdk/aws-ec2:uniqueIm */ export const IAM_MINIMIZE_POLICIES = '@aws-cdk/aws-iam:minimizePolicies'; -/** - * Enable this feature flag to pass through the `NetworkLoadBalanced<Ec2|Fargate>ServiceProps.taskImageOptions.containerPort` - * and the `NetworkMultipleTargetGroups<Ec2|Fargate>ServiceProps.targetGroups[X].containerPort` to the generated - * `ElasticLoadBalancingV2::TargetGroup`'s `Port` property. - * - * This is a feature flag because updating `Port` causes a replacement of the target groups, which is a breaking change. - */ -export const ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT = '@aws-cdk/aws-ecs-patterns:containerPortToTargetGroupPort'; - /** * Flag values that should apply for new projects * @@ -269,7 +260,6 @@ export const FUTURE_FLAGS: { [key: string]: boolean } = { [EC2_UNIQUE_IMDSV2_LAUNCH_TEMPLATE_NAME]: true, [CHECK_SECRET_USAGE]: true, [IAM_MINIMIZE_POLICIES]: true, - [ECS_PATTERNS_TARGET_GROUP_PORT_FROM_CONTAINER_PORT]: true, }; /** From ca71f98d3d382643fba9ed47a68bd3629ac6a884 Mon Sep 17 00:00:00 2001 From: AWS CDK Team <aws-cdk@amazon.com> Date: Fri, 20 May 2022 19:14:46 +0000 Subject: [PATCH 26/29] chore(release): 1.157.0 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ version.v1.json | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd37d1ae85b9..e09741c20e89e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,39 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.157.0](https://github.com/aws/aws-cdk/compare/v1.156.1...v1.157.0) (2022-05-20) + + +### Features + +* **cfnspec:** cloudformation spec v69.0.0 ([#20240](https://github.com/aws/aws-cdk/issues/20240)) ([e82b63f](https://github.com/aws/aws-cdk/commit/e82b63fc8880ecbd5e29d02e3e623cda3bbce1d6)) and ([#20331](https://github.com/aws/aws-cdk/issues/20331)) ([e9de4e9](https://github.com/aws/aws-cdk/commit/e9de4e9ab6bc44ff691238d91a8945c880a4d97c)) +* **cfnspec:** cloudformation spec v72.0.0 ([#20357](https://github.com/aws/aws-cdk/issues/20357)) ([c8fd84c](https://github.com/aws/aws-cdk/commit/c8fd84c12c726e216c10380f9fe7e5d55a892cdf)) +* **cli:** make ecr images immutable when created from cdk bootstrap ([#19937](https://github.com/aws/aws-cdk/issues/19937)) ([0ef4bb4](https://github.com/aws/aws-cdk/commit/0ef4bb4bf493a7e3b72b518841f676e91d014ba9)), closes [#18376](https://github.com/aws/aws-cdk/issues/18376) +* **cloud9:** configure Connection Type of Ec2Environment ([#20250](https://github.com/aws/aws-cdk/issues/20250)) ([01708bc](https://github.com/aws/aws-cdk/commit/01708bc7cf842eab7e1d1fc58bf42e4724624c0a)), closes [#17027](https://github.com/aws/aws-cdk/issues/17027) +* **cloudfront:** REST API origin ([#20335](https://github.com/aws/aws-cdk/issues/20335)) ([f7693e3](https://github.com/aws/aws-cdk/commit/f7693e3f981f60886c94fb61876a1e5e0f2c1a02)) +* **cognito:** `grant()` for user pool ([#20285](https://github.com/aws/aws-cdk/issues/20285)) ([10d13e4](https://github.com/aws/aws-cdk/commit/10d13e4bc1841721650f9ca9b6b16e18c219ea21)) +* **core:** allow disabling of LogicalID Metadata in case of large manifest ([#20433](https://github.com/aws/aws-cdk/pull/20433)) ([88ea829](https://github.com/aws/aws-cdk/commit/88ea829b5d0a64f51848474b6b9f006d1f729fb4)), closes [#20211](https://github.com/aws/aws-cdk/issues/20211) +* **ec2:** more router types ([#20151](https://github.com/aws/aws-cdk/issues/20151)) ([33b983c](https://github.com/aws/aws-cdk/commit/33b983ca76c91f182e60dcab8c6ead6be4d4712d)), closes [#19057](https://github.com/aws/aws-cdk/issues/19057) [/docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html#aws-resource-ec2](https://github.com/aws//docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html/issues/aws-resource-ec2) +* **iam:** validate role path at build time ([#16165](https://github.com/aws/aws-cdk/issues/16165)) ([65a5a46](https://github.com/aws/aws-cdk/commit/65a5a46837c42b2538837a699267ec9cc46ddc51)), closes [#13747](https://github.com/aws/aws-cdk/issues/13747) +* **integ-tests:** enhancements to integ-tests ([#20180](https://github.com/aws/aws-cdk/issues/20180)) ([3ff3fb7](https://github.com/aws/aws-cdk/commit/3ff3fb7c5ec9636022b3046036376c09a3166fb0)) +* **logs:** additional log retention periods ([#20347](https://github.com/aws/aws-cdk/issues/20347)) ([734faa5](https://github.com/aws/aws-cdk/commit/734faa5ae7489a511d5a00f255d7afd408db880c)), closes [#20346](https://github.com/aws/aws-cdk/issues/20346) +* **s3:** add `noncurrentVersionsToRetain` property to lifecycle rule ([#20348](https://github.com/aws/aws-cdk/issues/20348)) ([85604d9](https://github.com/aws/aws-cdk/commit/85604d929978aa1c645dba8959d682892278f862)), closes [#19784](https://github.com/aws/aws-cdk/issues/19784) + + +### Bug Fixes + +* **amplify:** custom headers break with tokens ([#20395](https://github.com/aws/aws-cdk/issues/20395)) ([765f441](https://github.com/aws/aws-cdk/commit/765f44177298b645c88a29587b52619e91a8757c)) +* **apigateway:** arnForExecuteApi fails on tokenized path ([#20323](https://github.com/aws/aws-cdk/issues/20323)) ([f7732a1](https://github.com/aws/aws-cdk/commit/f7732a1b06927d84e79ea1c9fb671ad184a9efea)), closes [#20252](https://github.com/aws/aws-cdk/issues/20252) +* **assets:** parallel docker image publishing fails on macOS ([#20117](https://github.com/aws/aws-cdk/issues/20117)) ([a58a803](https://github.com/aws/aws-cdk/commit/a58a8037b79636e9f973beff2483baecad73f15d)), closes [#20116](https://github.com/aws/aws-cdk/issues/20116) +* **cfn-include:** allow CFN Functions in Tags ([#19923](https://github.com/aws/aws-cdk/issues/19923)) ([4df9a4f](https://github.com/aws/aws-cdk/commit/4df9a4fa9ef24266b2bcde378ecc112c7dcaf8aa)), closes [#16889](https://github.com/aws/aws-cdk/issues/16889) +* **cli:** allow SSO profiles to be used as source profiles ([#20340](https://github.com/aws/aws-cdk/issues/20340)) ([a0b29e9](https://github.com/aws/aws-cdk/commit/a0b29e9f29775bfd94307a8975f5ba3a8faf05fa)), closes [#19897](https://github.com/aws/aws-cdk/issues/19897) +* **cloudwatch-actions:** stack partition is hardcoded 'aws' in action arn ([#20224](https://github.com/aws/aws-cdk/issues/20224)) ([0eb6c3b](https://github.com/aws/aws-cdk/commit/0eb6c3bb5853194f8727fc2cd3b1c9acb6eea20f)), closes [#19765](https://github.com/aws/aws-cdk/issues/19765) +* **eks:** Cluster.FromClusterAttributes ignores KubectlLambdaRole ([#20373](https://github.com/aws/aws-cdk/issues/20373)) ([7e824ab](https://github.com/aws/aws-cdk/commit/7e824ab40772dc888aec7986e343b12ec1032657)), closes [#20008](https://github.com/aws/aws-cdk/issues/20008) +* **iam:** AccountPrincipal accepts values which aren't account IDs ([#20292](https://github.com/aws/aws-cdk/issues/20292)) ([d0163f8](https://github.com/aws/aws-cdk/commit/d0163f8a3d14e38f67b381c569b5bd3af92c4f51)), closes [#20288](https://github.com/aws/aws-cdk/issues/20288) +* **pipelines:** specifying the Action Role for CodeBuild steps ([#18293](https://github.com/aws/aws-cdk/issues/18293)) ([719edfc](https://github.com/aws/aws-cdk/commit/719edfcb949828a423be2367b5c85b0e9a9c1c12)), closes [#18291](https://github.com/aws/aws-cdk/issues/18291) [#18291](https://github.com/aws/aws-cdk/issues/18291) +* **rds:** tokens should not be lowercased ([#20287](https://github.com/aws/aws-cdk/issues/20287)) ([5429e55](https://github.com/aws/aws-cdk/commit/5429e55126db7556dd2eb2d5e30a50976b5f6ee4)), closes [#18802](https://github.com/aws/aws-cdk/issues/18802) +* **secretsmanager:** automatic rotation cannot be disabled ([#18906](https://github.com/aws/aws-cdk/issues/18906)) ([c50d60c](https://github.com/aws/aws-cdk/commit/c50d60ca9417c771ca31cb330521e0e9f988e3fd)), closes [#18749](https://github.com/aws/aws-cdk/issues/18749) + ## [1.156.1](https://github.com/aws/aws-cdk/compare/v1.156.0...v1.156.1) (2022-05-12) ## [1.156.0](https://github.com/aws/aws-cdk/compare/v1.155.0...v1.156.0) (2022-05-11) diff --git a/version.v1.json b/version.v1.json index f30c4bf217885..e9b6f5ffd7864 100644 --- a/version.v1.json +++ b/version.v1.json @@ -1,3 +1,3 @@ { - "version": "1.156.1" + "version": "1.157.0" } \ No newline at end of file From 03896271c5e47206966c3e5234695b703c15243e Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser <jogold@users.noreply.github.com> Date: Sat, 21 May 2022 19:32:38 +0200 Subject: [PATCH 27/29] chore(rds): clarify JSDoc of `fromGeneratedPassword()` (#20448) Closes #20432 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-rds/lib/props.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index 943045d6cb4b7..ced255f32ae9d 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -356,7 +356,9 @@ export abstract class SnapshotCredentials { * * Note - The username must match the existing master username of the snapshot. * - * NOTE: use `fromGeneratedSecret()` for new Clusters and Instances. + * NOTE: use `fromGeneratedSecret()` for new Clusters and Instances. Switching from + * `fromGeneratedPassword()` to `fromGeneratedSecret()` for already deployed Clusters + * or Instances will update their master password. */ public static fromGeneratedPassword(username: string, options: SnapshotCredentialsFromGeneratedPasswordOptions = {}): SnapshotCredentials { return { From ebeaa71f9d1093ca51debf6d0387ee05f04fec93 Mon Sep 17 00:00:00 2001 From: AWS CDK Automation <43080478+aws-cdk-automation@users.noreply.github.com> Date: Mon, 23 May 2022 02:47:23 -0700 Subject: [PATCH 28/29] docs(cfnspec): update CloudFormation documentation (#20459) --- .../spec-source/cfn-docs/cfn-docs.json | 370 ++++++++++++++++-- 1 file changed, 346 insertions(+), 24 deletions(-) diff --git a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json index a7b19f7813278..addfabcb2aef8 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json +++ b/packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json @@ -2928,13 +2928,16 @@ "AWS::AppMesh::Mesh.MeshServiceDiscovery": { "attributes": {}, "description": "An object that represents the service discovery information for a service mesh.", - "properties": {} + "properties": { + "IpPreference": "The IP version to use to control traffic within the mesh." + } }, "AWS::AppMesh::Mesh.MeshSpec": { "attributes": {}, "description": "An object that represents the specification of a service mesh.", "properties": { - "EgressFilter": "The egress filter rules for the service mesh." + "EgressFilter": "The egress filter rules for the service mesh.", + "ServiceDiscovery": "An object that represents the service discovery information for a service mesh." } }, "AWS::AppMesh::Route": { @@ -3460,6 +3463,7 @@ "description": "An object that represents the AWS Cloud Map service discovery information for your virtual node.\n\n> AWS Cloud Map is not available in the eu-south-1 Region.", "properties": { "Attributes": "A string map that contains attributes with values that you can use to filter instances by any custom attribute that you specified when you registered the instance. Only instances that match all of the specified key/value pairs will be returned.", + "IpPreference": "The preferred IP version that this virtual node uses. Setting the IP preference on the virtual node only overrides the IP preference set for the mesh on this specific node.", "NamespaceName": "The name of the AWS Cloud Map namespace to use.", "ServiceName": "The name of the AWS Cloud Map service to use." } @@ -3508,6 +3512,7 @@ "description": "An object that represents the DNS service discovery information for your virtual node.", "properties": { "Hostname": "Specifies the DNS service discovery hostname for the virtual node.", + "IpPreference": "The preferred IP version that this virtual node uses. Setting the IP preference on the virtual node only overrides the IP preference set for the mesh on this specific node.", "ResponseType": "Specifies the DNS response type for the virtual node." } }, @@ -9296,6 +9301,7 @@ "CallbackURLs": "A list of allowed redirect (callback) URLs for the IdPs.\n\nA redirect URI must:\n\n- Be an absolute URI.\n- Be registered with the authorization server.\n- Not include a fragment component.\n\nSee [OAuth 2.0 - Redirection Endpoint](https://docs.aws.amazon.com/https://tools.ietf.org/html/rfc6749#section-3.1.2) .\n\nAmazon Cognito requires HTTPS over HTTP except for http://localhost for testing purposes only.\n\nApp callback URLs such as myapp://example are also supported.", "ClientName": "The client name for the user pool client you would like to create.", "DefaultRedirectURI": "The default redirect URI. Must be in the `CallbackURLs` list.\n\nA redirect URI must:\n\n- Be an absolute URI.\n- Be registered with the authorization server.\n- Not include a fragment component.\n\nSee [OAuth 2.0 - Redirection Endpoint](https://docs.aws.amazon.com/https://tools.ietf.org/html/rfc6749#section-3.1.2) .\n\nAmazon Cognito requires HTTPS over HTTP except for http://localhost for testing purposes only.\n\nApp callback URLs such as myapp://example are also supported.", + "EnablePropagateAdditionalUserContextData": "", "EnableTokenRevocation": "Activates or deactivates token revocation. For more information about revoking tokens, see [RevokeToken](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_RevokeToken.html) .\n\nIf you don't include this parameter, token revocation is automatically activated for the new user pool client.", "ExplicitAuthFlows": "The authentication flows that are supported by the user pool clients. Flow names without the `ALLOW_` prefix are no longer supported, in favor of new names with the `ALLOW_` prefix.\n\n> Values with `ALLOW_` prefix must be used only along with the `ALLOW_` prefix. \n\nValid values include:\n\n- `ALLOW_ADMIN_USER_PASSWORD_AUTH` : Enable admin based user password authentication flow `ADMIN_USER_PASSWORD_AUTH` . This setting replaces the `ADMIN_NO_SRP_AUTH` setting. With this authentication flow, Amazon Cognito receives the password in the request instead of using the Secure Remote Password (SRP) protocol to verify passwords.\n- `ALLOW_CUSTOM_AUTH` : Enable AWS Lambda trigger based authentication.\n- `ALLOW_USER_PASSWORD_AUTH` : Enable user password-based authentication. In this flow, Amazon Cognito receives the password in the request instead of using the SRP protocol to verify passwords.\n- `ALLOW_USER_SRP_AUTH` : Enable SRP-based authentication.\n- `ALLOW_REFRESH_TOKEN_AUTH` : Enable authflow to refresh tokens.\n\nIf you don't specify a value for `ExplicitAuthFlows` , your app client activates the `ALLOW_USER_SRP_AUTH` and `ALLOW_CUSTOM_AUTH` authentication flows.", "GenerateSecret": "Boolean to specify whether you want to generate a secret for the user pool client being created.", @@ -11847,6 +11853,7 @@ "Gid": "The group ID (GID) of the file's owners.\n\nDefault value: `INT_VALUE`\n\n`INT_VALUE` : Preserve the integer value of the user ID (UID) and group ID (GID) (recommended).\n\n`NAME` : Currently not supported.\n\n`NONE` : Ignore the UID and GID.", "LogLevel": "A value that determines the type of logs that DataSync publishes to a log stream in the Amazon CloudWatch log group that you provide. For more information about providing a log group for DataSync, see [CloudWatchLogGroupArn](https://docs.aws.amazon.com/datasync/latest/userguide/API_CreateTask.html#DataSync-CreateTask-request-CloudWatchLogGroupArn) . If set to `OFF` , no logs are published. `BASIC` publishes logs on errors for individual files transferred, and `TRANSFER` publishes logs for every file or object that is transferred and integrity checked.", "Mtime": "A value that indicates the last time that a file was modified (that is, a file was written to) before the PREPARING phase. This option is required for cases when you need to run the same task more than one time.\n\nDefault value: `PRESERVE`\n\n`PRESERVE` : Preserve original `Mtime` (recommended)\n\n`NONE` : Ignore `Mtime` .\n\n> If `Mtime` is set to `PRESERVE` , `Atime` must be set to `BEST_EFFORT` .\n> \n> If `Mtime` is set to `NONE` , `Atime` must also be set to `NONE` .", + "ObjectTags": "Specifies whether object tags are maintained when transferring between object storage systems. If you want your DataSync task to ignore object tags, specify the `NONE` value.\n\nDefault Value: `PRESERVE`", "OverwriteMode": "A value that determines whether files at the destination should be overwritten or preserved when copying files. If set to `NEVER` a destination file will not be replaced by a source file, even if the destination file differs from the source file. If you modify files in the destination and you sync the files, you can use this value to protect against overwriting those changes.\n\nSome storage classes have specific behaviors that can affect your S3 storage cost. For detailed information, see [Considerations when working with Amazon S3 storage classes in DataSync](https://docs.aws.amazon.com/datasync/latest/userguide/create-s3-location.html#using-storage-classes) in the *AWS DataSync User Guide* .", "PosixPermissions": "A value that determines which users or groups can access a file for a specific purpose, such as reading, writing, or execution of the file. This option should be set only for Network File System (NFS), Amazon EFS, and Amazon S3 locations. For more information about what metadata is copied by DataSync, see [Metadata Copied by DataSync](https://docs.aws.amazon.com/datasync/latest/userguide/special-files.html#metadata-copied) .\n\nDefault value: `PRESERVE`\n\n`PRESERVE` : Preserve POSIX-style permissions (recommended).\n\n`NONE` : Ignore permissions.\n\n> AWS DataSync can preserve extant permissions of a source location.", "PreserveDeletedFiles": "A value that specifies whether files in the destination that don't exist in the source file system are preserved. This option can affect your storage costs. If your task deletes objects, you might incur minimum storage duration charges for certain storage classes. For detailed information, see [Considerations when working with Amazon S3 storage classes in DataSync](https://docs.aws.amazon.com/datasync/latest/userguide/create-s3-location.html#using-storage-classes) in the *AWS DataSync User Guide* .\n\nDefault value: `PRESERVE`\n\n`PRESERVE` : Ignore destination files that aren't present in the source (recommended).\n\n`REMOVE` : Delete destination files that aren't present in the source.", @@ -13528,13 +13535,13 @@ "ElasticGpuSpecifications": "An elastic GPU to associate with the instance.", "ElasticInferenceAccelerators": "The elastic inference accelerator for the instance.", "EnclaveOptions": "Indicates whether the instance is enabled for AWS Nitro Enclaves. For more information, see [What is AWS Nitro Enclaves?](https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html) in the *AWS Nitro Enclaves User Guide* .\n\nYou can't enable AWS Nitro Enclaves and hibernation on the same instance.", - "HibernationOptions": "Indicates whether an instance is enabled for hibernation. This parameter is valid only if the instance meets the [hibernation prerequisites](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Hibernate.html#hibernating-prerequisites) . For more information, see [Hibernate your instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Hibernate.html) in the *Amazon Elastic Compute Cloud User Guide* .", + "HibernationOptions": "Indicates whether an instance is enabled for hibernation. This parameter is valid only if the instance meets the [hibernation prerequisites](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/hibernating-prerequisites.html) . For more information, see [Hibernate your instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Hibernate.html) in the *Amazon Elastic Compute Cloud User Guide* .", "IamInstanceProfile": "The name or Amazon Resource Name (ARN) of an IAM instance profile.", "ImageId": "The ID of the AMI.", "InstanceInitiatedShutdownBehavior": "Indicates whether an instance stops or terminates when you initiate shutdown from the instance (using the operating system command for system shutdown).\n\nDefault: `stop`", "InstanceMarketOptions": "The market (purchasing) option for the instances.", "InstanceRequirements": "The attributes for the instance types. When you specify instance attributes, Amazon EC2 will identify instance types with these attributes.\n\nIf you specify `InstanceRequirements` , you can't specify `InstanceTypes` .", - "InstanceType": "The instance type. For more information, see [Instance Types](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html) in the *Amazon Elastic Compute Cloud User Guide* .\n\nIf you specify `InstanceTypes` , you can't specify `InstanceRequirements` .", + "InstanceType": "The instance type. For more information, see [Instance types](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html) in the *Amazon Elastic Compute Cloud User Guide* .\n\nIf you specify `InstanceTypes` , you can't specify `InstanceRequirements` .", "KernelId": "The ID of the kernel.\n\nWe recommend that you use PV-GRUB instead of kernels and RAM disks. For more information, see [User Provided Kernels](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedkernels.html) in the *Amazon EC2 User Guide* .", "KeyName": "The name of the key pair. You can create a key pair using [CreateKeyPair](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateKeyPair.html) or [ImportKeyPair](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ImportKeyPair.html) .\n\n> If you do not specify a key pair, you can't connect to the instance unless you choose an AMI that is configured to allow users another way to log in.", "LicenseSpecifications": "The license configurations.", @@ -13543,11 +13550,11 @@ "NetworkInterfaces": "One or more network interfaces. If you specify a network interface, you must specify any security groups and subnets as part of the network interface.", "Placement": "The placement for the instance.", "PrivateDnsNameOptions": "The options for the instance hostname. The default values are inherited from the subnet.", - "RamDiskId": "The ID of the RAM disk.\n\n> We recommend that you use PV-GRUB instead of kernels and RAM disks. For more information, see [User Provided Kernels](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedkernels.html) in the *Amazon Elastic Compute Cloud User Guide* .", + "RamDiskId": "The ID of the RAM disk.\n\n> We recommend that you use PV-GRUB instead of kernels and RAM disks. For more information, see [User provided kernels](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedkernels.html) in the *Amazon Elastic Compute Cloud User Guide* .", "SecurityGroupIds": "One or more security group IDs. You can create a security group using [CreateSecurityGroup](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateSecurityGroup.html) . You cannot specify both a security group ID and security name in the same request.", "SecurityGroups": "[EC2-Classic, default VPC] One or more security group names. For a nondefault VPC, you must use security group IDs instead. You cannot specify both a security group ID and security name in the same request.", "TagSpecifications": "The tags to apply to the resources during launch. You can only tag instances and volumes on launch. The specified tags are applied to all instances or volumes that are created during launch.", - "UserData": "The user data to make available to the instance. You must provide base64-encoded text. User data is limited to 16 KB. For more information, see [Running Commands on Your Linux Instance at Launch](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html) (Linux) or [Adding User Data](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ec2-instance-metadata.html#instancedata-add-user-data) (Windows).\n\nIf you are creating the launch template for use with AWS Batch , the user data must be provided in the [MIME multi-part archive format](https://docs.aws.amazon.com/https://cloudinit.readthedocs.io/en/latest/topics/format.html#mime-multi-part-archive) . For more information, see [Amazon EC2 user data in launch templates](https://docs.aws.amazon.com/batch/latest/userguide/launch-templates.html) in the *AWS Batch User Guide* ." + "UserData": "The user data to make available to the instance. You must provide base64-encoded text. User data is limited to 16 KB. For more information, see [Run commands on your Linux instance at launch](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html) (Linux) or [Work with instance user data](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/instancedata-add-user-data.html) (Windows) in the *Amazon Elastic Compute Cloud User Guide* .\n\nIf you are creating the launch template for use with AWS Batch , the user data must be provided in the [MIME multi-part archive format](https://docs.aws.amazon.com/https://cloudinit.readthedocs.io/en/latest/topics/format.html#mime-multi-part-archive) . For more information, see [Amazon EC2 user data in launch templates](https://docs.aws.amazon.com/batch/latest/userguide/launch-templates.html) in the *AWS Batch User Guide* ." } }, "AWS::EC2::LaunchTemplate.LaunchTemplateElasticInferenceAccelerator": { @@ -17791,7 +17798,7 @@ "DomainEndpoint": "The domain-specific endpoint that's used for requests to the OpenSearch APIs, such as `search-mystack-elasti-1ab2cdefghij-ab1c2deckoyb3hofw7wpqa3cm.us-west-1.es.amazonaws.com` .", "Ref": "When the logical ID of this resource is provided to the Ref intrinsic function, Ref returns the resource name, such as `mystack-elasticsea-abc1d2efg3h4.` For more information about using the Ref function, see [Ref](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html) ." }, - "description": "The AWS::Elasticsearch::Domain resource creates an Amazon OpenSearch Service (successor to Amazon Elasticsearch Service) domain.\n\n> The `AWS::Elasticsearch::Domain` resource is being replaced by the [AWS::OpenSearchService::Domain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html) resource. While the legacy Elasticsearch resource and options are still supported, we recommend modifying your existing Cloudformation templates to use the new OpenSearch Service resource, which supports both OpenSearch and legacy Elasticsearch. For instructions to upgrade domains defined within CloudFormation from Elasticsearch to OpenSearch, see [Remarks](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html#aws-resource-opensearchservice-domain--remarks) .", + "description": "The AWS::Elasticsearch::Domain resource creates an Amazon OpenSearch Service domain.\n\n> The `AWS::Elasticsearch::Domain` resource is being replaced by the [AWS::OpenSearchService::Domain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html) resource. While the legacy Elasticsearch resource and options are still supported, we recommend modifying your existing Cloudformation templates to use the new OpenSearch Service resource, which supports both OpenSearch and legacy Elasticsearch. For instructions to upgrade domains defined within CloudFormation from Elasticsearch to OpenSearch, see [Remarks](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html#aws-resource-opensearchservice-domain--remarks) .", "properties": { "AccessPolicies": "An AWS Identity and Access Management ( IAM ) policy document that specifies who can access the OpenSearch Service domain and their permissions. For more information, see [Configuring access policies](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ac.html#ac-creating) in the *Amazon OpenSearch Service Developer Guid* e.", "AdvancedOptions": "Additional options to specify for the OpenSearch Service domain. For more information, see [Advanced cluster parameters](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/createupdatedomains.html#createdomain-configure-advanced-options) in the *Amazon OpenSearch Service Developer Guide* .", @@ -17824,9 +17831,9 @@ "description": "Configures OpenSearch Service to use Amazon Cognito authentication for OpenSearch Dashboards.\n\n> The `AWS::Elasticsearch::Domain` resource is being replaced by the [AWS::OpenSearchService::Domain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html) resource. While the legacy Elasticsearch resource and options are still supported, we recommend modifying your existing Cloudformation templates to use the new OpenSearch Service resource, which supports both OpenSearch and Elasticsearch. For more information about the service rename, see [New resource types](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html#rename-resource) in the *Amazon OpenSearch Service Developer Guide* .", "properties": { "Enabled": "Whether to enable or disable Amazon Cognito authentication for OpenSearch Dashboards. See [Amazon Cognito authentication for OpenSearch Dashboards](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/cognito-auth.html) .", - "IdentityPoolId": "The Amazon Cognito identity pool ID that you want OpenSearch Service to use for OpenSearch Dashboards authentication.", - "RoleArn": "The `AmazonESCognitoAccess` role that allows OpenSearch Service to configure your user pool and identity pool.", - "UserPoolId": "The Amazon Cognito user pool ID that you want OpenSearch Service to use for OpenSearch Dashboards authentication." + "IdentityPoolId": "The Amazon Cognito identity pool ID that you want OpenSearch Service to use for OpenSearch Dashboards authentication. Required if you enable Cognito authentication.", + "RoleArn": "The `AmazonESCognitoAccess` role that allows OpenSearch Service to configure your user pool and identity pool. Required if you enable Cognito authentication.", + "UserPoolId": "The Amazon Cognito user pool ID that you want OpenSearch Service to use for OpenSearch Dashboards authentication. Required if you enable Cognito authentication." } }, "AWS::Elasticsearch::Domain.ColdStorageOptions": { @@ -17840,8 +17847,8 @@ "attributes": {}, "description": "Specifies additional options for the domain endpoint, such as whether to require HTTPS for all traffic or whether to use a custom endpoint rather than the default endpoint.\n\n> The `AWS::Elasticsearch::Domain` resource is being replaced by the [AWS::OpenSearchService::Domain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html) resource. While the legacy Elasticsearch resource and options are still supported, we recommend modifying your existing Cloudformation templates to use the new OpenSearch Service resource, which supports both OpenSearch and Elasticsearch. For more information about the service rename, see [New resource types](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html#rename-resource) in the *Amazon OpenSearch Service Developer Guide* .", "properties": { - "CustomEndpoint": "The fully qualified URL for your custom endpoint.", - "CustomEndpointCertificateArn": "The AWS Certificate Manager ARN for your domain's SSL/TLS certificate.", + "CustomEndpoint": "The fully qualified URL for your custom endpoint. Required if you enabled a custom endpoint for the domain.", + "CustomEndpointCertificateArn": "The AWS Certificate Manager ARN for your domain's SSL/TLS certificate. Required if you enabled a custom endpoint for the domain.", "CustomEndpointEnabled": "True to enable a custom endpoint for the domain. If enabled, you must also provide values for `CustomEndpoint` and `CustomEndpointCertificateArn` .", "EnforceHTTPS": "True to require that all traffic to the domain arrive over HTTPS.", "TLSSecurityPolicy": "The minimum TLS version required for traffic to the domain. Valid values are TLS 1.0 (default) or 1.2:\n\n- `Policy-Min-TLS-1-0-2019-07`\n- `Policy-Min-TLS-1-2-2019-07`" @@ -17867,9 +17874,9 @@ "DedicatedMasterType": "The hardware configuration of the computer that hosts the dedicated master node, such as `m3.medium.elasticsearch` . If you specify this property, you must specify true for the `DedicatedMasterEnabled` property. For valid values, see [Supported instance types in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-instance-types.html) .", "InstanceCount": "The number of data nodes (instances) to use in the OpenSearch Service domain.", "InstanceType": "The instance type for your data nodes, such as `m3.medium.elasticsearch` . For valid values, see [Supported instance types in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-instance-types.html) .", - "WarmCount": "The number of warm nodes in the cluster.", + "WarmCount": "The number of warm nodes in the cluster. Required if you enable warm storage.", "WarmEnabled": "Whether to enable warm storage for the cluster.", - "WarmType": "The instance type for the cluster's warm nodes.", + "WarmType": "The instance type for the cluster's warm nodes. Required if you enable warm storage.", "ZoneAwarenessConfig": "Specifies zone awareness configuration options. Only use if `ZoneAwarenessEnabled` is `true` .", "ZoneAwarenessEnabled": "Indicates whether to enable zone awareness for the OpenSearch Service domain. When you enable zone awareness, OpenSearch Service allocates the nodes and replica index shards that belong to a cluster across two Availability Zones (AZs) in the same region to prevent data loss and minimize downtime in the event of node or data center failure. Don't enable zone awareness if your cluster has no replica index shards or is a single-node cluster. For more information, see [Configuring a multi-AZ domain in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/managedomains-multiaz.html) ." } @@ -17879,20 +17886,20 @@ "description": "Whether the domain should encrypt data at rest, and if so, the AWS Key Management Service key to use.\n\n> The `AWS::Elasticsearch::Domain` resource is being replaced by the [AWS::OpenSearchService::Domain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html) resource. While the legacy Elasticsearch resource and options are still supported, we recommend modifying your existing Cloudformation templates to use the new OpenSearch Service resource, which supports both OpenSearch and Elasticsearch. For more information about the service rename, see [New resource types](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html#rename-resource) in the *Amazon OpenSearch Service Developer Guide* .", "properties": { "Enabled": "Specify `true` to enable encryption at rest.", - "KmsKeyId": "The KMS key ID. Takes the form `1a2a3a4-1a2a-3a4a-5a6a-1a2a3a4a5a6a` ." + "KmsKeyId": "The KMS key ID. Takes the form `1a2a3a4-1a2a-3a4a-5a6a-1a2a3a4a5a6a` . Required if you enable encryption at rest." } }, "AWS::Elasticsearch::Domain.LogPublishingOption": { "attributes": {}, "description": "> The `AWS::Elasticsearch::Domain` resource is being replaced by the [AWS::OpenSearchService::Domain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html) resource. While the legacy Elasticsearch resource and options are still supported, we recommend modifying your existing Cloudformation templates to use the new OpenSearch Service resource, which supports both OpenSearch and Elasticsearch. For more information about the service rename, see [New resource types](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html#rename-resource) in the *Amazon OpenSearch Service Developer Guide* . \n\nSpecifies whether the OpenSearch Service domain publishes the Elasticsearch application, search slow logs, or index slow logs to Amazon CloudWatch. Each option must be an object of name `SEARCH_SLOW_LOGS` , `ES_APPLICATION_LOGS` , `INDEX_SLOW_LOGS` , or `AUDIT_LOGS` depending on the type of logs you want to publish.\n\nIf you enable a slow log, you still have to enable the *collection* of slow logs using the Configuration API. To learn more, see [Enabling log publishing ( AWS CLI)](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/createdomain-configure-slow-logs.html#createdomain-configure-slow-logs-cli) .", "properties": { - "CloudWatchLogsLogGroupArn": "Specifies the CloudWatch log group to publish to.", + "CloudWatchLogsLogGroupArn": "Specifies the CloudWatch log group to publish to. Required if you enable log publishing for the domain.", "Enabled": "If `true` , enables the publishing of logs to CloudWatch.\n\nDefault: `false` ." } }, "AWS::Elasticsearch::Domain.MasterUserOptions": { "attributes": {}, - "description": "Specifies information about the master user.\n\n> The `AWS::Elasticsearch::Domain` resource is being replaced by the [AWS::OpenSearchService::Domain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html) resource. While the legacy Elasticsearch resource and options are still supported, we recommend modifying your existing Cloudformation templates to use the new OpenSearch Service resource, which supports both OpenSearch and Elasticsearch. For more information about the service rename, see [New resource types](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html#rename-resource) in the *Amazon OpenSearch Service Developer Guide* .", + "description": "Specifies information about the master user. Required if you enabled the internal user database.\n\n> The `AWS::Elasticsearch::Domain` resource is being replaced by the [AWS::OpenSearchService::Domain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html) resource. While the legacy Elasticsearch resource and options are still supported, we recommend modifying your existing Cloudformation templates to use the new OpenSearch Service resource, which supports both OpenSearch and Elasticsearch. For more information about the service rename, see [New resource types](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html#rename-resource) in the *Amazon OpenSearch Service Developer Guide* .", "properties": { "MasterUserARN": "ARN for the master user. Only specify if `InternalUserDatabaseEnabled` is false in `AdvancedSecurityOptions` .", "MasterUserName": "Username for the master user. Only specify if `InternalUserDatabaseEnabled` is true in `AdvancedSecurityOptions` .", @@ -17918,7 +17925,7 @@ "description": "The virtual private cloud (VPC) configuration for the OpenSearch Service domain. For more information, see [Launching your Amazon OpenSearch Service domains using a VPC](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/vpc.html) in the *Amazon OpenSearch Service Developer Guide* .\n\n> The `AWS::Elasticsearch::Domain` resource is being replaced by the [AWS::OpenSearchService::Domain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html) resource. While the legacy Elasticsearch resource and options are still supported, we recommend modifying your existing Cloudformation templates to use the new OpenSearch Service resource, which supports both OpenSearch and Elasticsearch. For more information about the service rename, see [New resource types](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/rename.html#rename-resource) in the *Amazon OpenSearch Service Developer Guide* .", "properties": { "SecurityGroupIds": "The list of security group IDs that are associated with the VPC endpoints for the domain. If you don't provide a security group ID, OpenSearch Service uses the default security group for the VPC. To learn more, see [Security groups for your VPC](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html) in the *Amazon VPC User Guide* .", - "SubnetIds": "Provide one subnet ID for each Availability Zone that your domain uses. For example, you must specify three subnet IDs for a three Availability Zone domain. To learn more, see [VPCs and subnets](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Subnets.html) in the *Amazon VPC User Guide* ." + "SubnetIds": "Provide one subnet ID for each Availability Zone that your domain uses. For example, you must specify three subnet IDs for a three Availability Zone domain. To learn more, see [VPCs and subnets](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Subnets.html) in the *Amazon VPC User Guide* .\n\nRequired if you're creating your domain inside a VPC." } }, "AWS::Elasticsearch::Domain.ZoneAwarenessConfig": { @@ -28810,7 +28817,7 @@ "LoadBalancerArn": "The Amazon Resource Name (ARN) of the load balancer.", "Ref": "" }, - "description": "The `AWS::Lightsail::LoadBalancer` resource specifies a load balancer that can be used with Lightsail instances.\n\n> You cannot attach attach TLS certificates to a load balancer using the `AWS::Lightsail::LoadBalancer` resource type. Instead, use the `LoadBalancerTlsCertificate` resource type to create a certificate and attach it to a load balancer.", + "description": "The `AWS::Lightsail::LoadBalancer` resource specifies a load balancer that can be used with Lightsail instances.\n\n> You cannot attach a TLS certificate to a load balancer using the `AWS::Lightsail::LoadBalancer` resource type. Instead, use the `AWS::Lightsail::LoadBalancerTlsCertificate` resource type to create a certificate and attach it to a load balancer.", "properties": { "AttachedInstances": "The Lightsail instances to attach to the load balancer.", "HealthCheckPath": "The path on the attached instance where the health check will be performed. If no path is specified, the load balancer tries to make a request to the default (root) page ( `/index.html` ).", @@ -28819,7 +28826,8 @@ "LoadBalancerName": "The name of the load balancer.", "SessionStickinessEnabled": "A Boolean value indicating whether session stickiness is enabled.\n\nEnable session stickiness (also known as *session affinity* ) to bind a user's session to a specific instance. This ensures that all requests from the user during the session are sent to the same instance.", "SessionStickinessLBCookieDurationSeconds": "The time period, in seconds, after which the load balancer session stickiness cookie should be considered stale. If you do not specify this parameter, the default value is 0, which indicates that the sticky session should last for the duration of the browser session.", - "Tags": "An array of key-value pairs to apply to this resource.\n\nFor more information, see [Tag](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html) in the *AWS CloudFormation User Guide* .\n\n> The `Value` of `Tags` is optional for Lightsail resources." + "Tags": "An array of key-value pairs to apply to this resource.\n\nFor more information, see [Tag](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html) in the *AWS CloudFormation User Guide* .\n\n> The `Value` of `Tags` is optional for Lightsail resources.", + "TlsPolicyName": "The name of the TLS security policy for the load balancer." } }, "AWS::Lightsail::LoadBalancerTlsCertificate": { @@ -28833,6 +28841,7 @@ "CertificateAlternativeNames": "An array of alternative domain names and subdomain names for your SSL/TLS certificate.\n\nIn addition to the primary domain name, you can have up to nine alternative domain names. Wildcards (such as `*.example.com` ) are not supported.", "CertificateDomainName": "The domain name for the SSL/TLS certificate. For example, `example.com` or `www.example.com` .", "CertificateName": "The name of the SSL/TLS certificate.", + "HttpsRedirectionEnabled": "A Boolean value indicating whether HTTPS redirection is enabled for the load balancer that the TLS certificate is attached to.", "IsAttached": "A Boolean value indicating whether the SSL/TLS certificate is attached to a Lightsail load balancer.", "LoadBalancerName": "The name of the load balancer that the SSL/TLS certificate is attached to." } @@ -32229,7 +32238,7 @@ "description": "Specifies a MemoryDB user. For more information, see [Authenticating users with Access Contol Lists (ACLs)](https://docs.aws.amazon.com/memorydb/latest/devguide/clusters.acls.html) .", "properties": { "AccessString": "Access permissions string used for this user.", - "AuthenticationMode": "Denotes whether the user requires a password to authenticate.", + "AuthenticationMode": "Denotes whether the user requires a password to authenticate.\n\n*Example:*\n\n`mynewdbuser: Type: AWS::MemoryDB::User Properties: AccessString: on ~* &* +@all AuthenticationMode: Passwords: '1234567890123456' Type: password UserName: mynewdbuser AuthenticationMode: { \"Passwords\": [\"1234567890123456\"], \"Type\": \"Password\" }`", "Tags": "An array of key-value pairs to apply to this resource.\n\nFor more information, see [Tag](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html) .", "UserName": "The name of the user." } @@ -33107,7 +33116,7 @@ "Id": "The resource ID. For example, `123456789012/my-domain` .", "Ref": "When the logical ID of this resource is provided to the Ref intrinsic function, Ref returns the resource name, such as `mystack-abc1d2efg3h4.` For more information about using the Ref function, see [Ref](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html) ." }, - "description": "The AWS::OpenSearchService::Domain resource creates an Amazon OpenSearch Service (successor to Amazon Elasticsearch Service) domain.\n\n> The `AWS::OpenSearchService::Domain` resource replaces the legacy [AWS::Elasticsearch::Domain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticsearch-domain.html) resource. While the Elasticsearch resource and options are still supported, we recommend modifying your existing Cloudformation templates to use the new OpenSearch Service resource, which supports both OpenSearch and legacy Elasticsearch engines. For instructions to upgrade domains defined within CloudFormation from Elasticsearch to OpenSearch, see [Remarks](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html#aws-resource-opensearchservice-domain--remarks) .", + "description": "The AWS::OpenSearchService::Domain resource creates an Amazon OpenSearch Service domain.\n\n> The `AWS::OpenSearchService::Domain` resource replaces the legacy [AWS::Elasticsearch::Domain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticsearch-domain.html) resource. While the Elasticsearch resource and options are still supported, we recommend modifying your existing Cloudformation templates to use the new OpenSearch Service resource, which supports both OpenSearch and legacy Elasticsearch engines. For instructions to upgrade domains defined within CloudFormation from Elasticsearch to OpenSearch, see [Remarks](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html#aws-resource-opensearchservice-domain--remarks) .", "properties": { "AccessPolicies": "An AWS Identity and Access Management ( IAM ) policy document that specifies who can access the OpenSearch Service domain and their permissions. For more information, see [Configuring access policies](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ac.html#ac-creating) in the *Amazon OpenSearch Service Developer Guide* .", "AdvancedOptions": "Additional options to specify for the OpenSearch Service domain. For more information, see [AdvancedOptions](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/configuration-api.html#configuration-api-datatypes-advancedoptions) in the OpenSearch Service configuration API reference.", @@ -39950,6 +39959,319 @@ "Subnets": "The ID of the subnets in the VPC to which you want to connect your training job or model. For information about the availability of specific instance types, see [Supported Instance Types and Availability Zones](https://docs.aws.amazon.com/sagemaker/latest/dg/instance-types-az.html) ." } }, + "AWS::SageMaker::ModelPackage": { + "attributes": { + "CreationTime": "", + "ModelPackageArn": "", + "ModelPackageStatus": "", + "Ref": "" + }, + "description": "A versioned model that can be deployed for SageMaker inference.", + "properties": { + "AdditionalInferenceSpecificationDefinition": "", + "AdditionalInferenceSpecifications": "An array of additional Inference Specification objects.", + "AdditionalInferenceSpecificationsToAdd": "", + "ApprovalDescription": "A description provided when the model approval is set.", + "CertifyForMarketplace": "Whether the model package is to be certified to be listed on AWS Marketplace. For information about listing model packages on AWS Marketplace, see [List Your Algorithm or Model Package on AWS Marketplace](https://docs.aws.amazon.com/sagemaker/latest/dg/sagemaker-mkt-list.html) .", + "ClientToken": "", + "CreatedBy": "", + "CustomerMetadataProperties": "The metadata properties for the model package.", + "Domain": "The machine learning domain of your model package and its components. Common machine learning domains include computer vision and natural language processing.", + "DriftCheckBaselines": "Represents the drift check baselines that can be used when the model monitor is set using the model package.", + "Environment": "The environment variables to set in the Docker container. Each key and value in the `Environment` string to string map can have length of up to 1024. We support up to 16 entries in the map.", + "InferenceSpecification": "", + "LastModifiedBy": "", + "LastModifiedTime": "The last time the model package was modified.", + "MetadataProperties": "", + "ModelApprovalStatus": "The approval status of the model. This can be one of the following values.\n\n- `APPROVED` - The model is approved\n- `REJECTED` - The model is rejected.\n- `PENDING_MANUAL_APPROVAL` - The model is waiting for manual approval.", + "ModelMetrics": "Metrics for the model.", + "ModelPackageDescription": "The description of the model package.", + "ModelPackageGroupName": "The model group to which the model belongs.", + "ModelPackageName": "The name of the model.", + "ModelPackageStatusDetails": "", + "ModelPackageStatusItem": "", + "ModelPackageVersion": "The version number of a versioned model.", + "SamplePayloadUrl": "The Amazon Simple Storage Service path where the sample payload are stored. This path must point to a single gzip compressed tar archive (.tar.gz suffix).", + "SourceAlgorithmSpecification": "", + "Tag": "", + "Tags": "A list of the tags associated with the model package. For more information, see [Tagging AWS resources](https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html) in the *AWS General Reference Guide* .", + "Task": "The machine learning task your model package accomplishes. Common machine learning tasks include object detection and image classification.", + "ValidationSpecification": "" + } + }, + "AWS::SageMaker::ModelPackage.AdditionalInferenceSpecificationDefinition": { + "attributes": {}, + "description": "A structure of additional Inference Specification. Additional Inference Specification specifies details about inference jobs that can be run with models based on this model package", + "properties": { + "Containers": "The Amazon ECR registry path of the Docker image that contains the inference code.", + "Description": "A description of the additional Inference specification", + "Name": "A unique name to identify the additional inference specification. The name must be unique within the list of your additional inference specifications for a particular model package.", + "SupportedContentTypes": "The supported MIME types for the input data.", + "SupportedRealtimeInferenceInstanceTypes": "A list of the instance types that are used to generate inferences in real-time.", + "SupportedResponseMIMETypes": "The supported MIME types for the output data.", + "SupportedTransformInstanceTypes": "A list of the instance types on which a transformation job can be run or on which an endpoint can be deployed." + } + }, + "AWS::SageMaker::ModelPackage.Bias": { + "attributes": {}, + "description": "Contains bias metrics for a model.", + "properties": { + "PostTrainingReport": "", + "PreTrainingReport": "", + "Report": "The bias report for a model" + } + }, + "AWS::SageMaker::ModelPackage.CreatedBy": { + "attributes": {}, + "description": "", + "properties": {} + }, + "AWS::SageMaker::ModelPackage.DataSource": { + "attributes": {}, + "description": "Describes the location of the channel data.", + "properties": { + "S3DataSource": "The S3 location of the data source that is associated with a channel." + } + }, + "AWS::SageMaker::ModelPackage.DriftCheckBaselines": { + "attributes": {}, + "description": "Represents the drift check baselines that can be used when the model monitor is set using the model package.", + "properties": { + "Bias": "Represents the drift check bias baselines that can be used when the model monitor is set using the model package.", + "Explainability": "Represents the drift check explainability baselines that can be used when the model monitor is set using the model package.", + "ModelDataQuality": "Represents the drift check model data quality baselines that can be used when the model monitor is set using the model package.", + "ModelQuality": "Represents the drift check model quality baselines that can be used when the model monitor is set using the model package." + } + }, + "AWS::SageMaker::ModelPackage.DriftCheckBias": { + "attributes": {}, + "description": "Represents the drift check bias baselines that can be used when the model monitor is set using the model package.", + "properties": { + "ConfigFile": "The bias config file for a model.", + "PostTrainingConstraints": "", + "PreTrainingConstraints": "" + } + }, + "AWS::SageMaker::ModelPackage.DriftCheckExplainability": { + "attributes": {}, + "description": "Represents the drift check explainability baselines that can be used when the model monitor is set using the model package.", + "properties": { + "ConfigFile": "The explainability config file for the model.", + "Constraints": "" + } + }, + "AWS::SageMaker::ModelPackage.DriftCheckModelDataQuality": { + "attributes": {}, + "description": "Represents the drift check data quality baselines that can be used when the model monitor is set using the model package.", + "properties": { + "Constraints": "", + "Statistics": "" + } + }, + "AWS::SageMaker::ModelPackage.DriftCheckModelQuality": { + "attributes": {}, + "description": "Represents the drift check model quality baselines that can be used when the model monitor is set using the model package.", + "properties": { + "Constraints": "", + "Statistics": "" + } + }, + "AWS::SageMaker::ModelPackage.Environment": { + "attributes": {}, + "description": "", + "properties": {} + }, + "AWS::SageMaker::ModelPackage.Explainability": { + "attributes": {}, + "description": "Contains explainability metrics for a model.", + "properties": { + "Report": "The explainability report for a model." + } + }, + "AWS::SageMaker::ModelPackage.FileSource": { + "attributes": {}, + "description": "Contains details regarding the file source.", + "properties": { + "ContentDigest": "The digest of the file source.", + "ContentType": "The type of content stored in the file source.", + "S3Uri": "The Amazon S3 URI for the file source." + } + }, + "AWS::SageMaker::ModelPackage.InferenceSpecification": { + "attributes": {}, + "description": "Defines how to perform inference generation after a training job is run.", + "properties": { + "Containers": "The Amazon ECR registry path of the Docker image that contains the inference code.", + "SupportedContentTypes": "The supported MIME types for the input data.", + "SupportedRealtimeInferenceInstanceTypes": "A list of the instance types that are used to generate inferences in real-time.\n\nThis parameter is required for unversioned models, and optional for versioned models.", + "SupportedResponseMIMETypes": "The supported MIME types for the output data.", + "SupportedTransformInstanceTypes": "A list of the instance types on which a transformation job can be run or on which an endpoint can be deployed.\n\nThis parameter is required for unversioned models, and optional for versioned models." + } + }, + "AWS::SageMaker::ModelPackage.LastModifiedBy": { + "attributes": {}, + "description": "", + "properties": {} + }, + "AWS::SageMaker::ModelPackage.MetadataProperties": { + "attributes": {}, + "description": "Metadata properties of the tracking entity, trial, or trial component.", + "properties": { + "CommitId": "The commit ID.", + "GeneratedBy": "The entity this entity was generated by.", + "ProjectId": "The project ID.", + "Repository": "The repository." + } + }, + "AWS::SageMaker::ModelPackage.MetricsSource": { + "attributes": {}, + "description": "", + "properties": { + "ContentDigest": "", + "ContentType": "", + "S3Uri": "" + } + }, + "AWS::SageMaker::ModelPackage.ModelDataQuality": { + "attributes": {}, + "description": "Data quality constraints and statistics for a model.", + "properties": { + "Constraints": "Data quality constraints for a model.", + "Statistics": "Data quality statistics for a model." + } + }, + "AWS::SageMaker::ModelPackage.ModelMetrics": { + "attributes": {}, + "description": "Contains metrics captured from a model.", + "properties": { + "Bias": "Metrics that measure bais in a model.", + "Explainability": "Metrics that help explain a model.", + "ModelDataQuality": "Metrics that measure the quality of the input data for a model.", + "ModelQuality": "Metrics that measure the quality of a model." + } + }, + "AWS::SageMaker::ModelPackage.ModelPackageContainerDefinition": { + "attributes": {}, + "description": "Describes the Docker container for the model package.", + "properties": { + "ContainerHostname": "The DNS host name for the Docker container.", + "Environment": "The environment variables to set in the Docker container. Each key and value in the `Environment` string to string map can have length of up to 1024. We support up to 16 entries in the map.", + "Framework": "The machine learning framework of the model package container image.", + "FrameworkVersion": "The framework version of the Model Package Container Image.", + "Image": "The Amazon EC2 Container Registry (Amazon ECR) path where inference code is stored.\n\nIf you are using your own custom algorithm instead of an algorithm provided by SageMaker, the inference code must meet SageMaker requirements. SageMaker supports both `registry/repository[:tag]` and `registry/repository[@digest]` image path formats. For more information, see [Using Your Own Algorithms with Amazon SageMaker](https://docs.aws.amazon.com/sagemaker/latest/dg/your-algorithms.html) .", + "ImageDigest": "An MD5 hash of the training algorithm that identifies the Docker image used for training.", + "ModelDataUrl": "The Amazon S3 path where the model artifacts, which result from model training, are stored. This path must point to a single `gzip` compressed tar archive ( `.tar.gz` suffix).\n\n> The model artifacts must be in an S3 bucket that is in the same region as the model package.", + "ModelInput": "A structure with Model Input details.", + "NearestModelName": "The name of a pre-trained machine learning benchmarked by Amazon SageMaker Inference Recommender model that matches your model. You can find a list of benchmarked models by calling `ListModelMetadata` .", + "ProductId": "The AWS Marketplace product ID of the model package." + } + }, + "AWS::SageMaker::ModelPackage.ModelPackageStatusDetails": { + "attributes": {}, + "description": "Specifies the validation and image scan statuses of the model package.", + "properties": { + "ImageScanStatuses": "The status of the scan of the Docker image container for the model package.", + "ValidationStatuses": "The validation status of the model package." + } + }, + "AWS::SageMaker::ModelPackage.ModelPackageStatusItem": { + "attributes": {}, + "description": "Represents the overall status of a model package.", + "properties": { + "FailureReason": "if the overall status is `Failed` , the reason for the failure.", + "Name": "The name of the model package for which the overall status is being reported.", + "Status": "The current status." + } + }, + "AWS::SageMaker::ModelPackage.ModelQuality": { + "attributes": {}, + "description": "Model quality statistics and constraints.", + "properties": { + "Constraints": "Model quality constraints.", + "Statistics": "Model quality statistics." + } + }, + "AWS::SageMaker::ModelPackage.S3DataSource": { + "attributes": {}, + "description": "Describes the S3 data source.", + "properties": { + "S3DataType": "If you choose `S3Prefix` , `S3Uri` identifies a key name prefix. SageMaker uses all objects that match the specified key name prefix for model training.\n\nIf you choose `ManifestFile` , `S3Uri` identifies an object that is a manifest file containing a list of object keys that you want SageMaker to use for model training.\n\nIf you choose `AugmentedManifestFile` , S3Uri identifies an object that is an augmented manifest file in JSON lines format. This file contains the data you want to use for model training. `AugmentedManifestFile` can only be used if the Channel's input mode is `Pipe` .", + "S3Uri": "Depending on the value specified for the `S3DataType` , identifies either a key name prefix or a manifest. For example:\n\n- A key name prefix might look like this: `s3://bucketname/exampleprefix`\n- A manifest might look like this: `s3://bucketname/example.manifest`\n\nA manifest is an S3 object which is a JSON file consisting of an array of elements. The first element is a prefix which is followed by one or more suffixes. SageMaker appends the suffix elements to the prefix to get a full set of `S3Uri` . Note that the prefix must be a valid non-empty `S3Uri` that precludes users from specifying a manifest whose individual `S3Uri` is sourced from different S3 buckets.\n\nThe following code example shows a valid manifest format:\n\n`[ {\"prefix\": \"s3://customer_bucket/some/prefix/\"},`\n\n`\"relative/path/to/custdata-1\",`\n\n`\"relative/path/custdata-2\",`\n\n`...`\n\n`\"relative/path/custdata-N\"`\n\n`]`\n\nThis JSON is equivalent to the following `S3Uri` list:\n\n`s3://customer_bucket/some/prefix/relative/path/to/custdata-1`\n\n`s3://customer_bucket/some/prefix/relative/path/custdata-2`\n\n`...`\n\n`s3://customer_bucket/some/prefix/relative/path/custdata-N`\n\nThe complete set of `S3Uri` in this manifest is the input data for the channel for this data source. The object that each `S3Uri` points to must be readable by the IAM role that SageMaker uses to perform tasks on your behalf." + } + }, + "AWS::SageMaker::ModelPackage.SourceAlgorithm": { + "attributes": {}, + "description": "Specifies an algorithm that was used to create the model package. The algorithm must be either an algorithm resource in your SageMaker account or an algorithm in AWS Marketplace that you are subscribed to.", + "properties": { + "AlgorithmName": "The name of an algorithm that was used to create the model package. The algorithm must be either an algorithm resource in your SageMaker account or an algorithm in AWS Marketplace that you are subscribed to.", + "ModelDataUrl": "The Amazon S3 path where the model artifacts, which result from model training, are stored. This path must point to a single `gzip` compressed tar archive ( `.tar.gz` suffix).\n\n> The model artifacts must be in an S3 bucket that is in the same region as the algorithm." + } + }, + "AWS::SageMaker::ModelPackage.SourceAlgorithmSpecification": { + "attributes": {}, + "description": "A list of algorithms that were used to create a model package.", + "properties": { + "SourceAlgorithms": "A list of the algorithms that were used to create a model package." + } + }, + "AWS::SageMaker::ModelPackage.TransformInput": { + "attributes": {}, + "description": "Describes the input source of a transform job and the way the transform job consumes it.", + "properties": { + "CompressionType": "If your transform data is compressed, specify the compression type. Amazon SageMaker automatically decompresses the data for the transform job accordingly. The default value is `None` .", + "ContentType": "The multipurpose internet mail extension (MIME) type of the data. Amazon SageMaker uses the MIME type with each http call to transfer data to the transform job.", + "DataSource": "Describes the location of the channel data, which is, the S3 location of the input data that the model can consume.", + "SplitType": "The method to use to split the transform job's data files into smaller batches. Splitting is necessary when the total size of each object is too large to fit in a single request. You can also use data splitting to improve performance by processing multiple concurrent mini-batches. The default value for `SplitType` is `None` , which indicates that input data files are not split, and request payloads contain the entire contents of an input object. Set the value of this parameter to `Line` to split records on a newline character boundary. `SplitType` also supports a number of record-oriented binary data formats. Currently, the supported record formats are:\n\n- RecordIO\n- TFRecord\n\nWhen splitting is enabled, the size of a mini-batch depends on the values of the `BatchStrategy` and `MaxPayloadInMB` parameters. When the value of `BatchStrategy` is `MultiRecord` , Amazon SageMaker sends the maximum number of records in each request, up to the `MaxPayloadInMB` limit. If the value of `BatchStrategy` is `SingleRecord` , Amazon SageMaker sends individual records in each request.\n\n> Some data formats represent a record as a binary payload wrapped with extra padding bytes. When splitting is applied to a binary data format, padding is removed if the value of `BatchStrategy` is set to `SingleRecord` . Padding is not removed if the value of `BatchStrategy` is set to `MultiRecord` .\n> \n> For more information about `RecordIO` , see [Create a Dataset Using RecordIO](https://docs.aws.amazon.com/https://mxnet.apache.org/api/faq/recordio) in the MXNet documentation. For more information about `TFRecord` , see [Consuming TFRecord data](https://docs.aws.amazon.com/https://www.tensorflow.org/guide/data#consuming_tfrecord_data) in the TensorFlow documentation." + } + }, + "AWS::SageMaker::ModelPackage.TransformJobDefinition": { + "attributes": {}, + "description": "Defines the input needed to run a transform job using the inference specification specified in the algorithm.", + "properties": { + "BatchStrategy": "A string that determines the number of records included in a single mini-batch.\n\n`SingleRecord` means only one record is used per mini-batch. `MultiRecord` means a mini-batch is set to contain as many records that can fit within the `MaxPayloadInMB` limit.", + "Environment": "The environment variables to set in the Docker container. We support up to 16 key and values entries in the map.", + "MaxConcurrentTransforms": "The maximum number of parallel requests that can be sent to each instance in a transform job. The default value is 1.", + "MaxPayloadInMB": "The maximum payload size allowed, in MB. A payload is the data portion of a record (without metadata).", + "TransformInput": "A description of the input source and the way the transform job consumes it.", + "TransformOutput": "Identifies the Amazon S3 location where you want Amazon SageMaker to save the results from the transform job.", + "TransformResources": "Identifies the ML compute instances for the transform job." + } + }, + "AWS::SageMaker::ModelPackage.TransformOutput": { + "attributes": {}, + "description": "Describes the results of a transform job.", + "properties": { + "Accept": "The MIME type used to specify the output data. Amazon SageMaker uses the MIME type with each http call to transfer data from the transform job.", + "AssembleWith": "Defines how to assemble the results of the transform job as a single S3 object. Choose a format that is most convenient to you. To concatenate the results in binary format, specify `None` . To add a newline character at the end of every transformed record, specify `Line` .", + "KmsKeyId": "The AWS Key Management Service ( AWS KMS) key that Amazon SageMaker uses to encrypt the model artifacts at rest using Amazon S3 server-side encryption. The `KmsKeyId` can be any of the following formats:\n\n- Key ID: `1234abcd-12ab-34cd-56ef-1234567890ab`\n- Key ARN: `arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab`\n- Alias name: `alias/ExampleAlias`\n- Alias name ARN: `arn:aws:kms:us-west-2:111122223333:alias/ExampleAlias`\n\nIf you don't provide a KMS key ID, Amazon SageMaker uses the default KMS key for Amazon S3 for your role's account. For more information, see [KMS-Managed Encryption Keys](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html) in the *Amazon Simple Storage Service Developer Guide.*\n\nThe KMS key policy must grant permission to the IAM role that you specify in your [CreateModel](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_CreateModel.html) request. For more information, see [Using Key Policies in AWS KMS](https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html) in the *AWS Key Management Service Developer Guide* .", + "S3OutputPath": "The Amazon S3 path where you want Amazon SageMaker to store the results of the transform job. For example, `s3://bucket-name/key-name-prefix` .\n\nFor every S3 object used as input for the transform job, batch transform stores the transformed data with an . `out` suffix in a corresponding subfolder in the location in the output prefix. For example, for the input data stored at `s3://bucket-name/input-name-prefix/dataset01/data.csv` , batch transform stores the transformed data at `s3://bucket-name/output-name-prefix/input-name-prefix/data.csv.out` . Batch transform doesn't upload partially processed objects. For an input S3 object that contains multiple records, it creates an . `out` file only if the transform job succeeds on the entire file. When the input contains multiple S3 objects, the batch transform job processes the listed S3 objects and uploads only the output for successfully processed objects. If any object fails in the transform job batch transform marks the job as failed to prompt investigation." + } + }, + "AWS::SageMaker::ModelPackage.TransformResources": { + "attributes": {}, + "description": "Describes the resources, including ML instance types and ML instance count, to use for transform job.", + "properties": { + "InstanceCount": "The number of ML compute instances to use in the transform job. For distributed transform jobs, specify a value greater than 1. The default value is `1` .", + "InstanceType": "The ML compute instance type for the transform job. If you are using built-in algorithms to transform moderately sized datasets, we recommend using ml.m4.xlarge or `ml.m5.large` instance types.", + "VolumeKmsKeyId": "The AWS Key Management Service ( AWS KMS) key that Amazon SageMaker uses to encrypt model data on the storage volume attached to the ML compute instance(s) that run the batch transform job.\n\n> Certain Nitro-based instances include local storage, dependent on the instance type. Local storage volumes are encrypted using a hardware module on the instance. You can't request a `VolumeKmsKeyId` when using an instance type with local storage.\n> \n> For a list of instance types that support local instance storage, see [Instance Store Volumes](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#instance-store-volumes) .\n> \n> For more information about local instance storage encryption, see [SSD Instance Store Volumes](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ssd-instance-store.html) . \n\nThe `VolumeKmsKeyId` can be any of the following formats:\n\n- Key ID: `1234abcd-12ab-34cd-56ef-1234567890ab`\n- Key ARN: `arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab`\n- Alias name: `alias/ExampleAlias`\n- Alias name ARN: `arn:aws:kms:us-west-2:111122223333:alias/ExampleAlias`" + } + }, + "AWS::SageMaker::ModelPackage.ValidationProfile": { + "attributes": {}, + "description": "", + "properties": { + "ProfileName": "", + "TransformJobDefinition": "" + } + }, + "AWS::SageMaker::ModelPackage.ValidationSpecification": { + "attributes": {}, + "description": "", + "properties": { + "ValidationProfiles": "", + "ValidationRole": "" + } + }, "AWS::SageMaker::ModelPackageGroup": { "attributes": { "CreationTime": "The time when the model group was created.", @@ -41424,7 +41746,7 @@ "LoggingRole": "Specifies the Amazon Resource Name (ARN) of the AWS Identity and Access Management (IAM) role that allows a server to turn on Amazon CloudWatch logging for Amazon S3 or Amazon EFS events. When set, user activity can be viewed in your CloudWatch logs.", "PostAuthenticationLoginBanner": "Specify a string to display when users connect to a server. This string is displayed after the user authenticates.\n\n> The SFTP protocol does not support post-authentication display banners.", "PreAuthenticationLoginBanner": "Specify a string to display when users connect to a server. This string is displayed before the user authenticates. For example, the following banner displays details about using the system.\n\n`This system is for the use of authorized users only. Individuals using this computer system without authority, or in excess of their authority, are subject to having all of their activities on this system monitored and recorded by system personnel.`", - "ProtocolDetails": "The protocol settings that are configured for your server.\n\nUse the `PassiveIp` parameter to indicate passive mode (for FTP and FTPS protocols). Enter a single dotted-quad IPv4 address, such as the external IP address of a firewall, router, or load balancer.\n\nUse the `TlsSessionResumptionMode` parameter to determine whether or not your Transfer server resumes recent, negotiated sessions through a unique session ID.", + "ProtocolDetails": "The protocol settings that are configured for your server.\n\n- Use the `PassiveIp` parameter to indicate passive mode (for FTP and FTPS protocols). Enter a single dotted-quad IPv4 address, such as the external IP address of a firewall, router, or load balancer.\n- Use the `SetStatOption` to ignore the error that is generated when the client attempts to use SETSTAT on a file you are uploading to an S3 bucket. Set the value to `ENABLE_NO_OP` to have the Transfer Family server ignore the SETSTAT command, and upload files without needing to make any changes to your SFTP client. Note that with `SetStatOption` set to `ENABLE_NO_OP` , Transfer generates a log entry to CloudWatch Logs, so you can determine when the client is making a SETSTAT call.\n- Use the `TlsSessionResumptionMode` parameter to determine whether or not your Transfer server resumes recent, negotiated sessions through a unique session ID.", "Protocols": "Specifies the file transfer protocol or protocols over which your file transfer protocol client can connect to your server's endpoint. The available protocols are:\n\n- `SFTP` (Secure Shell (SSH) File Transfer Protocol): File transfer over SSH\n- `FTPS` (File Transfer Protocol Secure): File transfer with TLS encryption\n- `FTP` (File Transfer Protocol): Unencrypted file transfer\n\n> If you select `FTPS` , you must choose a certificate stored in AWS Certificate Manager (ACM) which is used to identify your server when clients connect to it over FTPS.\n> \n> If `Protocol` includes either `FTP` or `FTPS` , then the `EndpointType` must be `VPC` and the `IdentityProviderType` must be `AWS_DIRECTORY_SERVICE` or `API_GATEWAY` .\n> \n> If `Protocol` includes `FTP` , then `AddressAllocationIds` cannot be associated.\n> \n> If `Protocol` is set only to `SFTP` , the `EndpointType` can be set to `PUBLIC` and the `IdentityProviderType` can be set to `SERVICE_MANAGED` .", "SecurityPolicyName": "Specifies the name of the security policy that is attached to the server.", "Tags": "Key-value pairs that can be used to group and search for servers.", @@ -41461,7 +41783,7 @@ "attributes": {}, "description": "Protocol settings that are configured for your server.", "properties": { - "PassiveIp": "Indicates passive mode, for FTP and FTPS protocols. Enter a single dotted-quad IPv4 address, such as the external IP address of a firewall, router, or load balancer.", + "PassiveIp": "Indicates passive mode, for FTP and FTPS protocols. Enter a single dotted-quad IPv4 address, such as the external IP address of a firewall, router, or load balancer. For example:\n\n`aws transfer update-server --protocol-details PassiveIp= *0.0.0.0*`\n\nReplace `*0.0.0.0*` in the example above with the actual IP address you want to use.\n\n> If you change the `PassiveIp` value, you must stop and then restart your Transfer server for the change to take effect. For details on using Passive IP (PASV) in a NAT environment, see [Configuring your FTPS server behind a firewall or NAT with AWS Transfer Family](https://docs.aws.amazon.com/storage/configuring-your-ftps-server-behind-a-firewall-or-nat-with-aws-transfer-family/) .", "TlsSessionResumptionMode": "A property used with Transfer servers that use the FTPS protocol. TLS Session Resumption provides a mechanism to resume or share a negotiated secret key between the control and data connection for an FTPS session. `TlsSessionResumptionMode` determines whether or not the server resumes recent, negotiated sessions through a unique session ID. This property is available during `CreateServer` and `UpdateServer` calls. If a `TlsSessionResumptionMode` value is not specified during CreateServer, it is set to `ENFORCED` by default.\n\n- `DISABLED` : the server does not process TLS session resumption client requests and creates a new TLS session for each request.\n- `ENABLED` : the server processes and accepts clients that are performing TLS session resumption. The server doesn't reject client data connections that do not perform the TLS session resumption client processing.\n- `ENFORCED` : the server processes and accepts clients that are performing TLS session resumption. The server rejects client data connections that do not perform the TLS session resumption client processing. Before you set the value to `ENFORCED` , test your clients.\n\n> Not all FTPS clients perform TLS session resumption. So, if you choose to enforce TLS session resumption, you prevent any connections from FTPS clients that don't perform the protocol negotiation. To determine whether or not you can use the `ENFORCED` value, you need to test your clients." } }, From 425f519df1d25c29f0128136a5641807e65f0806 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 May 2022 11:02:17 +0000 Subject: [PATCH 29/29] chore(deps): Bump awscli from 1.24.0 to 1.24.5 in /packages/@aws-cdk/lambda-layer-awscli (#20462) Bumps [awscli](https://github.com/aws/aws-cli) from 1.24.0 to 1.24.5. <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/aws/aws-cli/blob/develop/CHANGELOG.rst">awscli's changelog</a>.</em></p> <blockquote> <h1>1.24.5</h1> <ul> <li>api-change:<code>comprehend</code>: Comprehend releases 14 new entity types for DetectPiiEntities and ContainsPiiEntities APIs.</li> <li>api-change:<code>logs</code>: Doc-only update to publish the new valid values for log retention</li> <li>enhancement:dependency: Bump upper bound of docutils to <0.17</li> </ul> <h1>1.24.4</h1> <ul> <li>api-change:<code>gamesparks</code>: This release adds an optional DeploymentResult field in the responses of GetStageDeploymentIntegrationTests and ListStageDeploymentIntegrationTests APIs.</li> <li>api-change:<code>lookoutmetrics</code>: In this release we added SnsFormat to SNSConfiguration to support human readable alert.</li> </ul> <h1>1.24.3</h1> <ul> <li>api-change:<code>quicksight</code>: API UpdatePublicSharingSettings enables IAM admins to enable/disable account level setting for public access of dashboards. When enabled, owners/co-owners for dashboards can enable public access on their dashboards. These dashboards can only be accessed through share link or embedding.</li> <li>api-change:<code>greengrassv2</code>: This release adds the new DeleteDeployment API operation that you can use to delete deployment resources. This release also adds support for discontinued AWS-provided components, so AWS can communicate when a component has any issues that you should consider before you deploy it.</li> <li>api-change:<code>transfer</code>: AWS Transfer Family now supports SetStat server configuration option, which provides the ability to ignore SetStat command issued by file transfer clients, enabling customers to upload files without any errors.</li> <li>api-change:<code>batch</code>: Documentation updates for AWS Batch.</li> <li>api-change:<code>appmesh</code>: This release updates the existing Create and Update APIs for meshes and virtual nodes by adding a new IP preference field. This new IP preference field can be used to control the IP versions being used with the mesh and allows for IPv6 support within App Mesh.</li> <li>api-change:<code>iotevents-data</code>: Introducing new API for deleting detectors: BatchDeleteDetector.</li> </ul> <h1>1.24.2</h1> <ul> <li>api-change:<code>glue</code>: This release adds a new optional parameter called codeGenNodeConfiguration to CRUD job APIs that allows users to manage visual jobs via APIs. The updated CreateJob and UpdateJob will create jobs that can be viewed in Glue Studio as a visual graph. GetJob can be used to get codeGenNodeConfiguration.</li> <li>api-change:<code>kms</code>: Add HMAC best practice tip, annual rotation of AWS managed keys.</li> </ul> <h1>1.24.1</h1> <ul> <li>api-change:<code>cloudfront</code>: Introduced a new error (TooLongCSPInResponseHeadersPolicy) that is returned when the value of the Content-Security-Policy header in a response headers policy exceeds the maximum allowed length.</li> <li>api-change:<code>rekognition</code>: Documentation updates for Amazon Rekognition.</li> <li>api-change:<code>resiliencehub</code>: In this release, we are introducing support for Amazon Elastic Container Service, Amazon Route 53, AWS Elastic Disaster Recovery, AWS Backup in addition to the existing supported Services. This release also supports Terraform file input from S3 and scheduling daily assessments</li> <li>api-change:<code>servicecatalog</code>: Updated the descriptions for the ListAcceptedPortfolioShares API description and the PortfolioShareType parameters.</li> <li>api-change:<code>discovery</code>: Add Migration Evaluator Collector details to the GetDiscoverySummary API response</li> <li>api-change:<code>sts</code>: Documentation updates for AWS Security Token Service.</li> <li>api-change:<code>workspaces-web</code>: Amazon WorkSpaces Web now supports Administrator timeout control</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/aws/aws-cli/commit/6dc3193218e1f12d100ad5d137f6a8c97a39f08c"><code>6dc3193</code></a> Merge branch 'release-1.24.5'</li> <li><a href="https://github.com/aws/aws-cli/commit/601d6b4bd993539468903a7c5333f7b52aa01382"><code>601d6b4</code></a> Bumping version to 1.24.5</li> <li><a href="https://github.com/aws/aws-cli/commit/19cdbbff021cc0e154bf8a6094c37aef4486b115"><code>19cdbbf</code></a> Update changelog based on model updates</li> <li><a href="https://github.com/aws/aws-cli/commit/610646918dedb1e8f5d3d2efaa4e8db31a155db2"><code>6106469</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/aws/aws-cli/issues/6977">#6977</a> from stealthycoin/bump-docutils</li> <li><a href="https://github.com/aws/aws-cli/commit/1bc38dc3c01b8ca8557117a15b5c87f8d8f70206"><code>1bc38dc</code></a> Bump docutils upper bound to <0.17</li> <li><a href="https://github.com/aws/aws-cli/commit/469d03ccf1b01d46176ed7bc7ac624ca5a74dfb0"><code>469d03c</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/aws/aws-cli/issues/4135">#4135</a> from aamirmushtaq/cognito-documentation-fix</li> <li><a href="https://github.com/aws/aws-cli/commit/9a0a6cc38661f782b881c6269ed8e5e35fa7faae"><code>9a0a6cc</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/aws/aws-cli/issues/6968">#6968</a> from elysahall/awsdocs-05-18-22</li> <li><a href="https://github.com/aws/aws-cli/commit/a9bd9fac765c1ccf1b934285c2cffbee735862d4"><code>a9bd9fa</code></a> Fix whitespace in cognito-idp admin-update-user-attributes example</li> <li><a href="https://github.com/aws/aws-cli/commit/2436eb8cde60da55b31e70f79f6d1747dbd2b263"><code>2436eb8</code></a> Merge branch 'release-1.24.4'</li> <li><a href="https://github.com/aws/aws-cli/commit/c5063fce7f29b28b16546d1f7605dfcc0afe8220"><code>c5063fc</code></a> Merge branch 'release-1.24.4' into develop</li> <li>Additional commits viewable in <a href="https://github.com/aws/aws-cli/compare/1.24.0...1.24.5">compare view</a></li> </ul> </details> <br /> [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=awscli&package-manager=pip&previous-version=1.24.0&new-version=1.24.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> --- packages/@aws-cdk/lambda-layer-awscli/layer/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/lambda-layer-awscli/layer/requirements.txt b/packages/@aws-cdk/lambda-layer-awscli/layer/requirements.txt index 1ae5ef2f31c04..4142bbbaacc8f 100644 --- a/packages/@aws-cdk/lambda-layer-awscli/layer/requirements.txt +++ b/packages/@aws-cdk/lambda-layer-awscli/layer/requirements.txt @@ -1 +1 @@ -awscli==1.24.0 +awscli==1.24.5