diff --git a/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-source.ts b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-source.ts index 7a5f08ec71b98..45a8f4e5d4dfc 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-source.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/package-sources/repo-source.ts @@ -75,14 +75,13 @@ const YARN_MONOREPO_CACHE: Record = {}; * * Cached in YARN_MONOREPO_CACHE. */ -export async function findYarnPackages(root: string): Promise> { +async function findYarnPackages(root: string): Promise> { if (!(root in YARN_MONOREPO_CACHE)) { - const outputDataString: string = JSON.parse(await shell(['yarn', 'workspaces', '--json', 'info'], { + const output: YarnWorkspacesOutput = JSON.parse(await shell(['yarn', 'workspaces', '--silent', 'info'], { captureStderr: false, cwd: root, show: 'error', - })).data; - const output: YarnWorkspacesOutput = JSON.parse(outputDataString); + })); const ret: Record = {}; for (const [k, v] of Object.entries(output)) { @@ -97,7 +96,7 @@ export async function findYarnPackages(root: string): Promise(x: A): x is NonNullable { * for Node's dependency lookup mechanism). */ export async function installNpmPackages(fixture: TestFixture, packages: Record) { - if (process.env.REPO_ROOT) { - const monoRepo = await findYarnPackages(process.env.REPO_ROOT); - - // Replace the install target with the physical location of this package - for (const key of Object.keys(packages)) { - if (key in monoRepo) { - packages[key] = monoRepo[key]; - } - } - } - fs.writeFileSync(path.join(fixture.integTestDir, 'package.json'), JSON.stringify({ name: 'cdk-integ-tests', private: true, diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js index 7e91514bb6d94..d094055795e27 100755 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js @@ -637,13 +637,6 @@ class BuiltinLambdaStack extends cdk.Stack { } } -class NotificationArnPropStack extends cdk.Stack { - constructor(parent, id, props) { - super(parent, id, props); - new sns.Topic(this, 'topic'); - } -} - const app = new cdk.App({ context: { '@aws-cdk/core:assetHashSalt': process.env.CODEBUILD_BUILD_ID, // Force all assets to be unique, but consistent in one build @@ -684,10 +677,6 @@ switch (stackSet) { new DockerStack(app, `${stackPrefix}-docker`); new DockerStackWithCustomFile(app, `${stackPrefix}-docker-with-custom-file`); - new NotificationArnPropStack(app, `${stackPrefix}-notification-arn-prop`, { - notificationArns: [`arn:aws:sns:${defaultEnv.region}:${defaultEnv.account}:${stackPrefix}-test-topic-prop`], - }); - // SSO stacks new SsoInstanceAccessControlConfig(app, `${stackPrefix}-sso-access-control`); new SsoAssignment(app, `${stackPrefix}-sso-assignment`); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts index 1ce7fe3ef7751..f323110eecfa4 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts @@ -1,7 +1,7 @@ import { promises as fs, existsSync } from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { integTest, cloneDirectory, shell, withDefaultFixture, retry, sleep, randomInteger, withSamIntegrationFixture, RESOURCES_DIR, withCDKMigrateFixture, withExtendedTimeoutFixture, randomString, withoutBootstrap } from '../../lib'; +import { integTest, cloneDirectory, shell, withDefaultFixture, retry, sleep, randomInteger, withSamIntegrationFixture, RESOURCES_DIR, withCDKMigrateFixture, withExtendedTimeoutFixture, randomString } from '../../lib'; jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime @@ -187,10 +187,7 @@ integTest('context setting', withDefaultFixture(async (fixture) => { } })); -// bootstrapping also performs synthesis. As it turns out, bootstrap-stage synthesis still causes the lookups to be cached, meaning that the lookup never -// happens when we actually call `cdk synth --no-lookups`. This results in the error never being thrown, because it never tries to lookup anything. -// Fix this by not trying to bootstrap; there's no need to bootstrap anyway, since the test never tries to deploy anything. -integTest('context in stage propagates to top', withoutBootstrap(async (fixture) => { +integTest('context in stage propagates to top', withDefaultFixture(async (fixture) => { await expect(fixture.cdkSynth({ // This will make it error to prove that the context bubbles up, and also that we can fail on command options: ['--no-lookups'], @@ -469,12 +466,11 @@ integTest('deploy with parameters multi', withDefaultFixture(async (fixture) => ); })); -integTest('deploy with notification ARN as flag', withDefaultFixture(async (fixture) => { - const topicName = `${fixture.stackNamePrefix}-test-topic-flag`; +integTest('deploy with notification ARN', withDefaultFixture(async (fixture) => { + const topicName = `${fixture.stackNamePrefix}-test-topic`; const response = await fixture.aws.sns('createTopic', { Name: topicName }); const topicArn = response.TopicArn!; - try { await fixture.cdkDeploy('test-2', { options: ['--notification-arns', topicArn], @@ -492,27 +488,6 @@ integTest('deploy with notification ARN as flag', withDefaultFixture(async (fixt } })); -integTest('deploy with notification ARN as prop', withDefaultFixture(async (fixture) => { - const topicName = `${fixture.stackNamePrefix}-test-topic-prop`; - - const response = await fixture.aws.sns('createTopic', { Name: topicName }); - const topicArn = response.TopicArn!; - - try { - await fixture.cdkDeploy('notification-arn-prop'); - - // verify that the stack we deployed has our notification ARN - const describeResponse = await fixture.aws.cloudFormation('describeStacks', { - StackName: fixture.fullStackName('notification-arn-prop'), - }); - expect(describeResponse.Stacks?.[0].NotificationARNs).toEqual([topicArn]); - } finally { - await fixture.aws.sns('deleteTopic', { - TopicArn: topicArn, - }); - } -})); - // NOTE: this doesn't currently work with modern-style synthesis, as the bootstrap // role by default will not have permission to iam:PassRole the created role. integTest('deploy with role', withDefaultFixture(async (fixture) => { diff --git a/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts b/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts index 644d110e70bd9..66872401251aa 100644 --- a/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts +++ b/packages/aws-cdk-lib/cloud-assembly-schema/lib/cloud-assembly/artifact-schema.ts @@ -55,13 +55,6 @@ export interface AwsCloudFormationStackProperties { */ readonly tags?: { [id: string]: string }; - /** - * SNS Notification ARNs that should receive CloudFormation Stack Events. - * - * @default - No notification arns - */ - readonly notificationArns?: string[]; - /** * The name to use for the CloudFormation stack. * @default - name derived from artifact ID diff --git a/packages/aws-cdk-lib/cloud-assembly-schema/schema/cloud-assembly.schema.json b/packages/aws-cdk-lib/cloud-assembly-schema/schema/cloud-assembly.schema.json index 240834b8c4830..279dfbe369073 100644 --- a/packages/aws-cdk-lib/cloud-assembly-schema/schema/cloud-assembly.schema.json +++ b/packages/aws-cdk-lib/cloud-assembly-schema/schema/cloud-assembly.schema.json @@ -345,12 +345,6 @@ "type": "string" } }, - "notificationArns": { - "type": "array", - "items": { - "type": "string" - } - }, "stackName": { "description": "The name to use for the CloudFormation stack. (Default - name derived from artifact ID)", "type": "string" diff --git a/packages/aws-cdk-lib/cloud-assembly-schema/schema/cloud-assembly.version.json b/packages/aws-cdk-lib/cloud-assembly-schema/schema/cloud-assembly.version.json index 079dd58c72d69..1f0068d32659a 100644 --- a/packages/aws-cdk-lib/cloud-assembly-schema/schema/cloud-assembly.version.json +++ b/packages/aws-cdk-lib/cloud-assembly-schema/schema/cloud-assembly.version.json @@ -1 +1 @@ -{"version":"37.0.0"} \ No newline at end of file +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/aws-cdk-lib/core/README.md b/packages/aws-cdk-lib/core/README.md index aac7abe87c167..140d8920c44de 100644 --- a/packages/aws-cdk-lib/core/README.md +++ b/packages/aws-cdk-lib/core/README.md @@ -1242,18 +1242,6 @@ const stack = new Stack(app, 'StackName', { }); ``` -### Receiving CloudFormation Stack Events - -You can add one or more SNS Topic ARNs to any Stack: - -```ts -const stack = new Stack(app, 'StackName', { - notificationArns: ['arn:aws:sns:us-east-1:23456789012:Topic'], -}); -``` - -Stack events will be sent to any SNS Topics in this list. - ### CfnJson `CfnJson` allows you to postpone the resolution of a JSON blob from diff --git a/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts b/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts index c985c538cac81..1017f172a850e 100644 --- a/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts +++ b/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts @@ -48,7 +48,6 @@ export function addStackArtifactToAssembly( terminationProtection: stack.terminationProtection, tags: nonEmptyDict(stack.tags.tagValues()), validateOnSynth: session.validateOnSynth, - notificationArns: stack._notificationArns, ...stackProps, ...stackNameProperty, }; diff --git a/packages/aws-cdk-lib/core/lib/stack.ts b/packages/aws-cdk-lib/core/lib/stack.ts index f66a571f534e1..ce3cb9c9b9fd8 100644 --- a/packages/aws-cdk-lib/core/lib/stack.ts +++ b/packages/aws-cdk-lib/core/lib/stack.ts @@ -127,13 +127,6 @@ export interface StackProps { */ readonly tags?: { [key: string]: string }; - /** - * SNS Topic ARNs that will receive stack events. - * - * @default - no notfication arns. - */ - readonly notificationArns?: string[]; - /** * Synthesis method to use while deploying this stack * @@ -371,13 +364,6 @@ export class Stack extends Construct implements ITaggable { */ public readonly _crossRegionReferences: boolean; - /** - * SNS Notification ARNs to receive stack events. - * - * @internal - */ - public readonly _notificationArns: string[]; - /** * Logical ID generation strategy */ @@ -464,7 +450,6 @@ export class Stack extends Construct implements ITaggable { throw new Error(`Stack name must be <= 128 characters. Stack name: '${this._stackName}'`); } this.tags = new TagManager(TagType.KEY_VALUE, 'aws:cdk:stack', props.tags); - this._notificationArns = props.notificationArns ?? []; if (!VALID_STACK_NAME_REGEX.test(this.stackName)) { throw new Error(`Stack name must match the regular expression: ${VALID_STACK_NAME_REGEX.toString()}, got '${this.stackName}'`); diff --git a/packages/aws-cdk-lib/core/test/stack.test.ts b/packages/aws-cdk-lib/core/test/stack.test.ts index 4846ff3b0cc05..82be67b19499b 100644 --- a/packages/aws-cdk-lib/core/test/stack.test.ts +++ b/packages/aws-cdk-lib/core/test/stack.test.ts @@ -2075,21 +2075,6 @@ describe('stack', () => { expect(asm.getStackArtifact(stack2.artifactId).tags).toEqual(expected); }); - test('stack notification arns are reflected in the stack artifact properties', () => { - // GIVEN - const NOTIFICATION_ARNS = ['arn:aws:sns:bermuda-triangle-1337:123456789012:MyTopic']; - const app = new App({ stackTraces: false }); - const stack1 = new Stack(app, 'stack1', { - notificationArns: NOTIFICATION_ARNS, - }); - - // THEN - const asm = app.synth(); - const expected = { foo: 'bar' }; - - expect(asm.getStackArtifact(stack1.artifactId).notificationArns).toEqual(NOTIFICATION_ARNS); - }); - test('Termination Protection is reflected in Cloud Assembly artifact', () => { // if the root is an app, invoke "synth" to avoid double synthesis const app = new App(); diff --git a/packages/aws-cdk-lib/cx-api/lib/artifacts/cloudformation-artifact.ts b/packages/aws-cdk-lib/cx-api/lib/artifacts/cloudformation-artifact.ts index d73e2a5b33dd7..7cf279c96d924 100644 --- a/packages/aws-cdk-lib/cx-api/lib/artifacts/cloudformation-artifact.ts +++ b/packages/aws-cdk-lib/cx-api/lib/artifacts/cloudformation-artifact.ts @@ -54,11 +54,6 @@ export class CloudFormationStackArtifact extends CloudArtifact { */ public readonly tags: { [id: string]: string }; - /** - * SNS Topics that will receive stack events. - */ - public readonly notificationArns: string[]; - /** * The physical name of this stack. */ @@ -163,7 +158,6 @@ export class CloudFormationStackArtifact extends CloudArtifact { // We get the tags from 'properties' if available (cloud assembly format >= 6.0.0), otherwise // from the stack metadata this.tags = properties.tags ?? this.tagsFromMetadata(); - this.notificationArns = properties.notificationArns ?? []; this.assumeRoleArn = properties.assumeRoleArn; this.assumeRoleExternalId = properties.assumeRoleExternalId; this.cloudFormationExecutionRoleArn = properties.cloudFormationExecutionRoleArn; diff --git a/packages/aws-cdk-lib/cx-api/test/stack-artifact.test.ts b/packages/aws-cdk-lib/cx-api/test/stack-artifact.test.ts index 81d5b4a0c3186..85009cedd7c23 100644 --- a/packages/aws-cdk-lib/cx-api/test/stack-artifact.test.ts +++ b/packages/aws-cdk-lib/cx-api/test/stack-artifact.test.ts @@ -21,24 +21,6 @@ afterEach(() => { rimraf(builder.outdir); }); -test('read notification arns from artifact properties', () => { -// GIVEN - const NOTIFICATION_ARNS = ['arn:aws:sns:bermuda-triangle-1337:123456789012:MyTopic']; - builder.addArtifact('Stack', { - ...stackBase, - properties: { - ...stackBase.properties, - notificationArns: NOTIFICATION_ARNS, - }, - }); - - // WHEN - const assembly = builder.buildAssembly(); - - // THEN - expect(assembly.getStackByName('Stack').notificationArns).toEqual(NOTIFICATION_ARNS); -}); - test('read tags from artifact properties', () => { // GIVEN builder.addArtifact('Stack', { diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index 93a57eb2e4629..28af2d39616b0 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -644,12 +644,6 @@ async function canSkipDeploy( return false; } - // Notification arns have changed - if (!arrayEquals(cloudFormationStack.notificationArns, deployStackOptions.notificationArns ?? [])) { - debug(`${deployName}: notification arns have changed`); - return false; - } - // Termination protection has been updated if (!!deployStackOptions.stack.terminationProtection !== !!cloudFormationStack.terminationProtection) { debug(`${deployName}: termination protection has been updated`); @@ -700,7 +694,3 @@ function suffixWithErrors(msg: string, errors?: string[]) { ? `${msg}: ${errors.join(', ')}` : msg; } - -function arrayEquals(a: any[], b: any[]): boolean { - return a.every(item => b.includes(item)) && b.every(item => a.includes(item)); -} diff --git a/packages/aws-cdk/lib/api/util/cloudformation.ts b/packages/aws-cdk/lib/api/util/cloudformation.ts index 2361871e2bef0..23e95f6d618e5 100644 --- a/packages/aws-cdk/lib/api/util/cloudformation.ts +++ b/packages/aws-cdk/lib/api/util/cloudformation.ts @@ -138,21 +138,12 @@ export class CloudFormationStack { /** * The stack's current tags * - * Empty list if the stack does not exist + * Empty list of the stack does not exist */ public get tags(): CloudFormation.Tags { return this.stack?.Tags || []; } - /** - * SNS Topic ARNs that will receive stack events. - * - * Empty list if the stack does not exist - */ - public get notificationArns(): CloudFormation.NotificationARNs { - return this.stack?.NotificationARNs ?? []; - } - /** * Return the names of all current parameters to the stack * diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index f876634484c46..854b7ec6419c2 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -161,6 +161,7 @@ export class CdkToolkit { let changeSet = undefined; if (options.changeSet) { + let stackExists = false; try { stackExists = await this.props.deployments.stackExists({ @@ -213,6 +214,14 @@ export class CdkToolkit { return this.watch(options); } + if (options.notificationArns) { + options.notificationArns.map( arn => { + if (!validateSnsTopicArn(arn)) { + throw new Error(`Notification arn ${arn} is not a valid arn for an SNS topic`); + } + }); + } + const startSynthTime = new Date().getTime(); const stackCollection = await this.selectStacksForDeploy(options.selector, options.exclusively, options.cacheCloudAssembly, options.ignoreNoStacks); @@ -309,17 +318,7 @@ export class CdkToolkit { } } - let notificationArns: string[] = []; - notificationArns = notificationArns.concat(options.notificationArns ?? []); - notificationArns = notificationArns.concat(stack.notificationArns); - - notificationArns.map(arn => { - if (!validateSnsTopicArn(arn)) { - throw new Error(`Notification arn ${arn} is not a valid arn for an SNS topic`); - } - }); - - const stackIndex = stacks.indexOf(stack) + 1; + const stackIndex = stacks.indexOf(stack)+1; print('%s: deploying... [%s/%s]', chalk.bold(stack.displayName), stackIndex, stackCollection.stackCount); const startDeployTime = new Date().getTime(); @@ -336,7 +335,7 @@ export class CdkToolkit { roleArn: options.roleArn, toolkitStackName: options.toolkitStackName, reuseAssets: options.reuseAssets, - notificationArns, + notificationArns: options.notificationArns, tags, execute: options.execute, changeSetName: options.changeSetName, diff --git a/packages/aws-cdk/test/api/deploy-stack.test.ts b/packages/aws-cdk/test/api/deploy-stack.test.ts index 4aec7cc9ff7d1..666d4f43410ec 100644 --- a/packages/aws-cdk/test/api/deploy-stack.test.ts +++ b/packages/aws-cdk/test/api/deploy-stack.test.ts @@ -460,42 +460,6 @@ test('deploy is not skipped if parameters are different', async () => { })); }); -test('deploy is skipped if notificationArns are the same', async () => { - // GIVEN - givenTemplateIs(FAKE_STACK.template); - givenStackExists({ - NotificationARNs: ['arn:aws:sns:bermuda-triangle-1337:123456789012:TestTopic'], - }); - - // WHEN - await deployStack({ - ...standardDeployStackArguments(), - stack: FAKE_STACK, - notificationArns: ['arn:aws:sns:bermuda-triangle-1337:123456789012:TestTopic'], - }); - - // THEN - expect(cfnMocks.createChangeSet).not.toHaveBeenCalled(); -}); - -test('deploy is not skipped if notificationArns are different', async () => { - // GIVEN - givenTemplateIs(FAKE_STACK.template); - givenStackExists({ - NotificationARNs: ['arn:aws:sns:bermuda-triangle-1337:123456789012:TestTopic'], - }); - - // WHEN - await deployStack({ - ...standardDeployStackArguments(), - stack: FAKE_STACK, - notificationArns: ['arn:aws:sns:bermuda-triangle-1337:123456789012:MagicTopic'], - }); - - // THEN - expect(cfnMocks.createChangeSet).toHaveBeenCalled(); -}); - test('if existing stack failed to create, it is deleted and recreated', async () => { // GIVEN givenStackExists( @@ -660,7 +624,7 @@ test('deploy is not skipped if stack is in a _FAILED state', async () => { await deployStack({ ...standardDeployStackArguments(), usePreviousParameters: true, - }).catch(() => { }); + }).catch(() => {}); // THEN expect(cfnMocks.createChangeSet).toHaveBeenCalled(); diff --git a/packages/aws-cdk/test/cdk-toolkit.test.ts b/packages/aws-cdk/test/cdk-toolkit.test.ts index b8d998b97ef84..7f70bbc8434de 100644 --- a/packages/aws-cdk/test/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cdk-toolkit.test.ts @@ -71,8 +71,6 @@ import { CdkToolkit, Tag } from '../lib/cdk-toolkit'; import { RequireApproval } from '../lib/diff'; import { flatten } from '../lib/util'; -process.env.CXAPI_DISABLE_SELECT_BY_ID = '1'; - let cloudExecutable: MockCloudExecutable; let bootstrapper: jest.Mocked; let stderrMock: jest.SpyInstance; @@ -292,11 +290,11 @@ describe('readCurrentTemplate', () => { // GIVEN // throw error first for the 'prepareSdkWithLookupRoleFor' call and succeed for the rest mockForEnvironment = jest.fn().mockImplementationOnce(() => { throw new Error('error'); }) - .mockImplementation(() => { return { sdk: mockCloudExecutable.sdkProvider.sdk, didAssumeRole: true }; }); + .mockImplementation(() => { return { sdk: mockCloudExecutable.sdkProvider.sdk, didAssumeRole: true };}); mockCloudExecutable.sdkProvider.forEnvironment = mockForEnvironment; mockCloudExecutable.sdkProvider.stubSSM({ getParameter() { - return {}; + return { }; }, }); const cdkToolkit = new CdkToolkit({ @@ -338,7 +336,7 @@ describe('readCurrentTemplate', () => { }); mockCloudExecutable.sdkProvider.stubSSM({ getParameter() { - return {}; + return { }; }, }); @@ -484,253 +482,108 @@ describe('deploy', () => { }); }); - describe('sns notification arns', () => { - beforeEach(() => { - cloudExecutable = new MockCloudExecutable({ - stacks: [ - MockStack.MOCK_STACK_A, - MockStack.MOCK_STACK_B, - MockStack.MOCK_STACK_WITH_NOTIFICATION_ARNS, - MockStack.MOCK_STACK_WITH_BAD_NOTIFICATION_ARNS, - ], - }); - }); - - test('with sns notification arns as options', async () => { - // GIVEN - const notificationArns = [ - 'arn:aws:sns:us-east-2:444455556666:MyTopic', - 'arn:aws:sns:eu-west-1:111155556666:my-great-topic', - ]; - const toolkit = new CdkToolkit({ - cloudExecutable, - configuration: cloudExecutable.configuration, - sdkProvider: cloudExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-A': { Foo: 'Bar' }, - }, notificationArns), - }); - - // WHEN - await toolkit.deploy({ - // Stacks should be selected by their hierarchical ID, which is their displayName, not by the stack ID. - selector: { patterns: ['Test-Stack-A-Display-Name'] }, - notificationArns, - hotswap: HotswapMode.FULL_DEPLOYMENT, - }); - }); - - test('fail with incorrect sns notification arns as options', async () => { - // GIVEN - const notificationArns = ['arn:::cfn-my-cool-topic']; - const toolkit = new CdkToolkit({ - cloudExecutable, - configuration: cloudExecutable.configuration, - sdkProvider: cloudExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-A': { Foo: 'Bar' }, - }, notificationArns), - }); - - // WHEN - await expect(() => - toolkit.deploy({ - // Stacks should be selected by their hierarchical ID, which is their displayName, not by the stack ID. - selector: { patterns: ['Test-Stack-A-Display-Name'] }, - notificationArns, - hotswap: HotswapMode.FULL_DEPLOYMENT, - }), - ).rejects.toThrow('Notification arn arn:::cfn-my-cool-topic is not a valid arn for an SNS topic'); + test('with sns notification arns', async () => { + // GIVEN + const notificationArns = [ + 'arn:aws:sns:us-east-2:444455556666:MyTopic', + 'arn:aws:sns:eu-west-1:111155556666:my-great-topic', + ]; + const toolkit = new CdkToolkit({ + cloudExecutable, + configuration: cloudExecutable.configuration, + sdkProvider: cloudExecutable.sdkProvider, + deployments: new FakeCloudFormation({ + 'Test-Stack-A': { Foo: 'Bar' }, + 'Test-Stack-B': { Baz: 'Zinga!' }, + }, notificationArns), }); - test('with sns notification arns in the executable', async () => { - // GIVEN - const expectedNotificationArns = [ - 'arn:aws:sns:bermuda-triangle-1337:123456789012:MyTopic', - ]; - const toolkit = new CdkToolkit({ - cloudExecutable, - configuration: cloudExecutable.configuration, - sdkProvider: cloudExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-Notification-Arns': { Foo: 'Bar' }, - }, expectedNotificationArns), - }); - - // WHEN - await toolkit.deploy({ - selector: { patterns: ['Test-Stack-Notification-Arns'] }, - hotswap: HotswapMode.FULL_DEPLOYMENT, - }); + // WHEN + await toolkit.deploy({ + selector: { patterns: ['Test-Stack-A', 'Test-Stack-B'] }, + notificationArns, + hotswap: HotswapMode.FULL_DEPLOYMENT, }); + }); - test('fail with incorrect sns notification arns in the executable', async () => { - // GIVEN - const toolkit = new CdkToolkit({ - cloudExecutable, - configuration: cloudExecutable.configuration, - sdkProvider: cloudExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-Bad-Notification-Arns': { Foo: 'Bar' }, - }), - }); - - // WHEN - await expect(() => - toolkit.deploy({ - selector: { patterns: ['Test-Stack-Bad-Notification-Arns'] }, - hotswap: HotswapMode.FULL_DEPLOYMENT, - }), - ).rejects.toThrow('Notification arn arn:1337:123456789012:sns:bad is not a valid arn for an SNS topic'); + test('fail with incorrect sns notification arns', async () => { + // GIVEN + const notificationArns = ['arn:::cfn-my-cool-topic']; + const toolkit = new CdkToolkit({ + cloudExecutable, + configuration: cloudExecutable.configuration, + sdkProvider: cloudExecutable.sdkProvider, + deployments: new FakeCloudFormation({ + 'Test-Stack-A': { Foo: 'Bar' }, + }, notificationArns), }); - test('with sns notification arns in the executable and as options', async () => { - // GIVEN - const notificationArns = [ - 'arn:aws:sns:us-east-2:444455556666:MyTopic', - 'arn:aws:sns:eu-west-1:111155556666:my-great-topic', - ]; - - const expectedNotificationArns = notificationArns.concat(['arn:aws:sns:bermuda-triangle-1337:123456789012:MyTopic']); - const toolkit = new CdkToolkit({ - cloudExecutable, - configuration: cloudExecutable.configuration, - sdkProvider: cloudExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-Notification-Arns': { Foo: 'Bar' }, - }, expectedNotificationArns), - }); - - // WHEN - await toolkit.deploy({ - selector: { patterns: ['Test-Stack-Notification-Arns'] }, + // WHEN + await expect(() => + toolkit.deploy({ + selector: { patterns: ['Test-Stack-A'] }, notificationArns, hotswap: HotswapMode.FULL_DEPLOYMENT, - }); - }); - - test('fail with incorrect sns notification arns in the executable and incorrect sns notification arns as options', async () => { - // GIVEN - const notificationArns = ['arn:::cfn-my-cool-topic']; - const toolkit = new CdkToolkit({ - cloudExecutable, - configuration: cloudExecutable.configuration, - sdkProvider: cloudExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-Bad-Notification-Arns': { Foo: 'Bar' }, - }, notificationArns), - }); - - // WHEN - await expect(() => - toolkit.deploy({ - selector: { patterns: ['Test-Stack-Bad-Notification-Arns'] }, - notificationArns, - hotswap: HotswapMode.FULL_DEPLOYMENT, - }), - ).rejects.toThrow('Notification arn arn:::cfn-my-cool-topic is not a valid arn for an SNS topic'); - }); - - test('fail with incorrect sns notification arns in the executable and correct sns notification arns as options', async () => { - // GIVEN - const notificationArns = ['arn:aws:sns:bermuda-triangle-1337:123456789012:MyTopic']; - const toolkit = new CdkToolkit({ - cloudExecutable, - configuration: cloudExecutable.configuration, - sdkProvider: cloudExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-Bad-Notification-Arns': { Foo: 'Bar' }, - }, notificationArns), - }); - - // WHEN - await expect(() => - toolkit.deploy({ - selector: { patterns: ['Test-Stack-Bad-Notification-Arns'] }, - notificationArns, - hotswap: HotswapMode.FULL_DEPLOYMENT, - }), - ).rejects.toThrow('Notification arn arn:1337:123456789012:sns:bad is not a valid arn for an SNS topic'); - }); + }), + ).rejects.toThrow('Notification arn arn:::cfn-my-cool-topic is not a valid arn for an SNS topic'); - test('fail with correct sns notification arns in the executable and incorrect sns notification arns as options', async () => { - // GIVEN - const notificationArns = ['arn:::cfn-my-cool-topic']; - const toolkit = new CdkToolkit({ - cloudExecutable, - configuration: cloudExecutable.configuration, - sdkProvider: cloudExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-Notification-Arns': { Foo: 'Bar' }, - }, notificationArns), - }); - - // WHEN - await expect(() => - toolkit.deploy({ - selector: { patterns: ['Test-Stack-Notification-Arns'] }, - notificationArns, - hotswap: HotswapMode.FULL_DEPLOYMENT, - }), - ).rejects.toThrow('Notification arn arn:::cfn-my-cool-topic is not a valid arn for an SNS topic'); - }); }); - }); - test('globless bootstrap uses environment without question', async () => { + test('globless bootstrap uses environment without question', async () => { // GIVEN - const toolkit = defaultToolkitSetup(); + const toolkit = defaultToolkitSetup(); - // WHEN - await toolkit.bootstrap(['aws://56789/south-pole'], bootstrapper, {}); + // WHEN + await toolkit.bootstrap(['aws://56789/south-pole'], bootstrapper, {}); - // THEN - expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledWith({ - account: '56789', - region: 'south-pole', - name: 'aws://56789/south-pole', - }, expect.anything(), expect.anything()); - expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledTimes(1); - }); + // THEN + expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledWith({ + account: '56789', + region: 'south-pole', + name: 'aws://56789/south-pole', + }, expect.anything(), expect.anything()); + expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledTimes(1); + }); - test('globby bootstrap uses whats in the stacks', async () => { - // GIVEN - const toolkit = defaultToolkitSetup(); - cloudExecutable.configuration.settings.set(['app'], 'something'); + test('globby bootstrap uses whats in the stacks', async () => { + // GIVEN + const toolkit = defaultToolkitSetup(); + cloudExecutable.configuration.settings.set(['app'], 'something'); - // WHEN - await toolkit.bootstrap(['aws://*/bermuda-triangle-1'], bootstrapper, {}); + // WHEN + await toolkit.bootstrap(['aws://*/bermuda-triangle-1'], bootstrapper, {}); - // THEN - expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledWith({ - account: '123456789012', - region: 'bermuda-triangle-1', - name: 'aws://123456789012/bermuda-triangle-1', - }, expect.anything(), expect.anything()); - expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledTimes(1); - }); + // THEN + expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledWith({ + account: '123456789012', + region: 'bermuda-triangle-1', + name: 'aws://123456789012/bermuda-triangle-1', + }, expect.anything(), expect.anything()); + expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledTimes(1); + }); - test('bootstrap can be invoked without the --app argument', async () => { - // GIVEN - cloudExecutable.configuration.settings.clear(); - const mockSynthesize = jest.fn(); - cloudExecutable.synthesize = mockSynthesize; + test('bootstrap can be invoked without the --app argument', async () => { + // GIVEN + cloudExecutable.configuration.settings.clear(); + const mockSynthesize = jest.fn(); + cloudExecutable.synthesize = mockSynthesize; - const toolkit = defaultToolkitSetup(); + const toolkit = defaultToolkitSetup(); - // WHEN - await toolkit.bootstrap(['aws://123456789012/west-pole'], bootstrapper, {}); + // WHEN + await toolkit.bootstrap(['aws://123456789012/west-pole'], bootstrapper, {}); - // THEN - expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledWith({ - account: '123456789012', - region: 'west-pole', - name: 'aws://123456789012/west-pole', - }, expect.anything(), expect.anything()); - expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledTimes(1); - - expect(cloudExecutable.hasApp).toEqual(false); - expect(mockSynthesize).not.toHaveBeenCalled(); + // THEN + expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledWith({ + account: '123456789012', + region: 'west-pole', + name: 'aws://123456789012/west-pole', + }, expect.anything(), expect.anything()); + expect(bootstrapper.bootstrapEnvironment).toHaveBeenCalledTimes(1); + + expect(cloudExecutable.hasApp).toEqual(false); + expect(mockSynthesize).not.toHaveBeenCalled(); + }); }); }); @@ -738,7 +591,7 @@ describe('destroy', () => { test('destroy correct stack', async () => { const toolkit = defaultToolkitSetup(); - expect(() => { + await expect(() => { return toolkit.destroy({ selector: { patterns: ['Test-Stack-A/Test-Stack-C'] }, exclusively: true, @@ -1001,6 +854,10 @@ describe('synth', () => { expect(mockData.mock.calls.length).toEqual(0); }); + afterEach(() => { + process.env.STACKS_TO_VALIDATE = undefined; + }); + describe('migrate', () => { const testResourcePath = [__dirname, 'commands', 'test-resources']; const templatePath = [...testResourcePath, 'templates']; @@ -1136,13 +993,13 @@ describe('synth', () => { }); }); - test('causes synth to fail if autoValidate=true', async () => { + test('causes synth to fail if autoValidate=true', async() => { const toolkit = defaultToolkitSetup(); const autoValidate = true; await expect(toolkit.synth([], false, true, autoValidate)).rejects.toBeDefined(); }); - test('causes synth to succeed if autoValidate=false', async () => { + test('causes synth to succeed if autoValidate=false', async() => { const toolkit = defaultToolkitSetup(); const autoValidate = false; await toolkit.synth([], false, true, autoValidate); @@ -1150,7 +1007,7 @@ describe('synth', () => { }); }); - test('stack has error and was explicitly selected', async () => { + test('stack has error and was explicitly selected', async() => { cloudExecutable = new MockCloudExecutable({ stacks: [ MockStack.MOCK_STACK_A, @@ -1266,8 +1123,7 @@ class MockStack { ], }, depends: [MockStack.MOCK_STACK_C.stackName], - }; - + } public static readonly MOCK_STACK_WITH_ERROR: TestStackArtifact = { stackName: 'witherrors', env: 'aws://123456789012/bermuda-triangle-1', @@ -1299,39 +1155,6 @@ class MockStack { }, }, } - public static readonly MOCK_STACK_WITH_NOTIFICATION_ARNS: TestStackArtifact = { - stackName: 'Test-Stack-Notification-Arns', - notificationArns: ['arn:aws:sns:bermuda-triangle-1337:123456789012:MyTopic'], - template: { Resources: { TemplateName: 'Test-Stack-Notification-Arns' } }, - env: 'aws://123456789012/bermuda-triangle-1337', - metadata: { - '/Test-Stack-Notification-Arns': [ - { - type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, - data: [ - { key: 'Foo', value: 'Bar' }, - ], - }, - ], - }, - } - - public static readonly MOCK_STACK_WITH_BAD_NOTIFICATION_ARNS: TestStackArtifact = { - stackName: 'Test-Stack-Bad-Notification-Arns', - notificationArns: ['arn:1337:123456789012:sns:bad'], - template: { Resources: { TemplateName: 'Test-Stack-Bad-Notification-Arns' } }, - env: 'aws://123456789012/bermuda-triangle-1337', - metadata: { - '/Test-Stack-Bad-Notification-Arns': [ - { - type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, - data: [ - { key: 'Foo', value: 'Bar' }, - ], - }, - ], - }, - } } class FakeCloudFormation extends Deployments { @@ -1349,7 +1172,9 @@ class FakeCloudFormation extends Deployments { Object.entries(tags).map(([Key, Value]) => ({ Key, Value })) .sort((l, r) => l.Key.localeCompare(r.Key)); } - this.expectedNotificationArns = expectedNotificationArns ?? []; + if (expectedNotificationArns) { + this.expectedNotificationArns = expectedNotificationArns; + } } public deployStack(options: DeployStackOptions): Promise { @@ -1357,11 +1182,7 @@ class FakeCloudFormation extends Deployments { MockStack.MOCK_STACK_A.stackName, MockStack.MOCK_STACK_B.stackName, MockStack.MOCK_STACK_C.stackName, - // MockStack.MOCK_STACK_D deliberately omitted. MockStack.MOCK_STACK_WITH_ASSET.stackName, - MockStack.MOCK_STACK_WITH_ERROR.stackName, - MockStack.MOCK_STACK_WITH_NOTIFICATION_ARNS.stackName, - MockStack.MOCK_STACK_WITH_BAD_NOTIFICATION_ARNS.stackName, ]).toContain(options.stack.stackName); if (this.expectedTags[options.stack.stackName]) { @@ -1392,12 +1213,8 @@ class FakeCloudFormation extends Deployments { return Promise.resolve({}); case MockStack.MOCK_STACK_WITH_ASSET.stackName: return Promise.resolve({}); - case MockStack.MOCK_STACK_WITH_NOTIFICATION_ARNS.stackName: - return Promise.resolve({}); - case MockStack.MOCK_STACK_WITH_BAD_NOTIFICATION_ARNS.stackName: - return Promise.resolve({}); default: - throw new Error(`not an expected mock stack: ${stack.stackName}`); + return Promise.reject(`Not an expected mock stack: ${stack.stackName}`); } } } diff --git a/packages/aws-cdk/test/util.ts b/packages/aws-cdk/test/util.ts index 1f059836d670d..879d6572f369b 100644 --- a/packages/aws-cdk/test/util.ts +++ b/packages/aws-cdk/test/util.ts @@ -16,7 +16,6 @@ export interface TestStackArtifact { env?: string; depends?: string[]; metadata?: cxapi.StackMetadata; - notificationArns?: string[]; /** Old-style assets */ assets?: cxschema.AssetMetadataEntry[]; @@ -102,7 +101,6 @@ function addAttributes(assembly: TestAssembly, builder: cxapi.CloudAssemblyBuild ...stack.properties, templateFile, terminationProtection: stack.terminationProtection, - notificationArns: stack.notificationArns, }, displayName: stack.displayName, });