diff --git a/packages/@aws-cdk-testing/cli-integ/package.json b/packages/@aws-cdk-testing/cli-integ/package.json index 162353a78423c..bb22a3e5eafee 100644 --- a/packages/@aws-cdk-testing/cli-integ/package.json +++ b/packages/@aws-cdk-testing/cli-integ/package.json @@ -41,6 +41,7 @@ "@octokit/rest": "^18.12.0", "aws-sdk": "^2.1653.0", "axios": "^1.7.2", + "chalk": "^4", "fs-extra": "^9.1.0", "glob": "^7.2.3", "jest": "^29.7.0", @@ -72,4 +73,4 @@ "publishConfig": { "tag": "latest" } -} \ No newline at end of file +} 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 609cf50d297c6..2db469867234f 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,54 +1,79 @@ 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 } from '../../lib'; +import * as chalk from 'chalk'; +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 describe('ci', () => { - integTest('output to stderr', withDefaultFixture(async (fixture) => { - const deployOutput = await fixture.cdkDeploy('test-2', { captureStderr: true, onlyStderr: true }); - const diffOutput = await fixture.cdk(['diff', fixture.fullStackName('test-2')], { captureStderr: true, onlyStderr: true }); - const destroyOutput = await fixture.cdkDestroy('test-2', { captureStderr: true, onlyStderr: true }); - expect(deployOutput).not.toEqual(''); - expect(destroyOutput).not.toEqual(''); - expect(diffOutput).not.toEqual(''); - })); - describe('ci=true', () => { - integTest('output to stdout', withDefaultFixture(async (fixture) => { - - const execOptions = { + integTest( + 'output to stderr', + withDefaultFixture(async (fixture) => { + const deployOutput = await fixture.cdkDeploy('test-2', { captureStderr: true, onlyStderr: true }); + const diffOutput = await fixture.cdk(['diff', fixture.fullStackName('test-2')], { captureStderr: true, onlyStderr: true, - modEnv: { - CI: 'true', - JSII_SILENCE_WARNING_KNOWN_BROKEN_NODE_VERSION: 'true', - JSII_SILENCE_WARNING_UNTESTED_NODE_VERSION: 'true', - JSII_SILENCE_WARNING_DEPRECATED_NODE_VERSION: 'true', - }, - }; - - const deployOutput = await fixture.cdkDeploy('test-2', execOptions); - const diffOutput = await fixture.cdk(['diff', fixture.fullStackName('test-2')], execOptions); - const destroyOutput = await fixture.cdkDestroy('test-2', execOptions); - expect(deployOutput).toEqual(''); - expect(destroyOutput).toEqual(''); - expect(diffOutput).toEqual(''); - })); + }); + const destroyOutput = await fixture.cdkDestroy('test-2', { captureStderr: true, onlyStderr: true }); + expect(deployOutput).not.toEqual(''); + expect(destroyOutput).not.toEqual(''); + expect(diffOutput).not.toEqual(''); + }), + ); + describe('ci=true', () => { + integTest( + 'output to stdout', + withDefaultFixture(async (fixture) => { + const execOptions = { + captureStderr: true, + onlyStderr: true, + modEnv: { + CI: 'true', + JSII_SILENCE_WARNING_KNOWN_BROKEN_NODE_VERSION: 'true', + JSII_SILENCE_WARNING_UNTESTED_NODE_VERSION: 'true', + JSII_SILENCE_WARNING_DEPRECATED_NODE_VERSION: 'true', + }, + }; + + const deployOutput = await fixture.cdkDeploy('test-2', execOptions); + const diffOutput = await fixture.cdk(['diff', fixture.fullStackName('test-2')], execOptions); + const destroyOutput = await fixture.cdkDestroy('test-2', execOptions); + expect(deployOutput).toEqual(''); + expect(destroyOutput).toEqual(''); + expect(diffOutput).toEqual(''); + }), + ); }); }); -integTest('VPC Lookup', withDefaultFixture(async (fixture) => { - fixture.log('Making sure we are clean before starting.'); - await fixture.cdkDestroy('define-vpc', { modEnv: { ENABLE_VPC_TESTING: 'DEFINE' } }); +integTest( + 'VPC Lookup', + withDefaultFixture(async (fixture) => { + fixture.log('Making sure we are clean before starting.'); + await fixture.cdkDestroy('define-vpc', { modEnv: { ENABLE_VPC_TESTING: 'DEFINE' } }); - fixture.log('Setting up: creating a VPC with known tags'); - await fixture.cdkDeploy('define-vpc', { modEnv: { ENABLE_VPC_TESTING: 'DEFINE' } }); - fixture.log('Setup complete!'); + fixture.log('Setting up: creating a VPC with known tags'); + await fixture.cdkDeploy('define-vpc', { modEnv: { ENABLE_VPC_TESTING: 'DEFINE' } }); + fixture.log('Setup complete!'); - fixture.log('Verifying we can now import that VPC'); - await fixture.cdkDeploy('import-vpc', { modEnv: { ENABLE_VPC_TESTING: 'IMPORT' } }); -})); + fixture.log('Verifying we can now import that VPC'); + await fixture.cdkDeploy('import-vpc', { modEnv: { ENABLE_VPC_TESTING: 'IMPORT' } }); + }), +); // testing a construct with a builtin Nodejs Lambda Function. // In this case we are testing the s3.Bucket construct with the @@ -57,553 +82,645 @@ integTest('VPC Lookup', withDefaultFixture(async (fixture) => { // is bundled as part of the CDK package, we want to make sure we don't // introduce changes to the compiled code that could prevent the Lambda from // executing. If we do, this test will timeout and fail. -integTest('Construct with builtin Lambda function', withDefaultFixture(async (fixture) => { - await fixture.cdkDeploy('builtin-lambda-function'); - fixture.log('Setup complete!'); - await fixture.cdkDestroy('builtin-lambda-function'); -})); +integTest( + 'Construct with builtin Lambda function', + withDefaultFixture(async (fixture) => { + await fixture.cdkDeploy('builtin-lambda-function'); + fixture.log('Setup complete!'); + await fixture.cdkDestroy('builtin-lambda-function'); + }), +); // this is to ensure that asset bundling for apps under a stage does not break -integTest('Stage with bundled Lambda function', withDefaultFixture(async (fixture) => { - await fixture.cdkDeploy('bundling-stage/BundlingStack'); - fixture.log('Setup complete!'); - await fixture.cdkDestroy('bundling-stage/BundlingStack'); -})); - -integTest('Two ways of showing the version', withDefaultFixture(async (fixture) => { - const version1 = await fixture.cdk(['version'], { verbose: false }); - const version2 = await fixture.cdk(['--version'], { verbose: false }); - - expect(version1).toEqual(version2); -})); - -integTest('Termination protection', withDefaultFixture(async (fixture) => { - const stackName = 'termination-protection'; - await fixture.cdkDeploy(stackName); - - // Try a destroy that should fail - await expect(fixture.cdkDestroy(stackName)).rejects.toThrow('exited with error'); - - // Can update termination protection even though the change set doesn't contain changes - await fixture.cdkDeploy(stackName, { modEnv: { TERMINATION_PROTECTION: 'FALSE' } }); - await fixture.cdkDestroy(stackName); -})); - -integTest('cdk synth', withDefaultFixture(async (fixture) => { - await fixture.cdk(['synth', fixture.fullStackName('test-1')]); - expect(fixture.template('test-1')).toEqual(expect.objectContaining({ - Resources: { - topic69831491: { - Type: 'AWS::SNS::Topic', - Metadata: { - 'aws:cdk:path': `${fixture.stackNamePrefix}-test-1/topic/Resource`, +integTest( + 'Stage with bundled Lambda function', + withDefaultFixture(async (fixture) => { + await fixture.cdkDeploy('bundling-stage/BundlingStack'); + fixture.log('Setup complete!'); + await fixture.cdkDestroy('bundling-stage/BundlingStack'); + }), +); + +integTest( + 'Two ways of showing the version', + withDefaultFixture(async (fixture) => { + const version1 = await fixture.cdk(['version'], { verbose: false }); + const version2 = await fixture.cdk(['--version'], { verbose: false }); + + expect(version1).toEqual(version2); + }), +); + +integTest( + 'Termination protection', + withDefaultFixture(async (fixture) => { + const stackName = 'termination-protection'; + await fixture.cdkDeploy(stackName); + + // Try a destroy that should fail + await expect(fixture.cdkDestroy(stackName)).rejects.toThrow('exited with error'); + + // Can update termination protection even though the change set doesn't contain changes + await fixture.cdkDeploy(stackName, { modEnv: { TERMINATION_PROTECTION: 'FALSE' } }); + await fixture.cdkDestroy(stackName); + }), +); + +integTest( + 'cdk synth', + withDefaultFixture(async (fixture) => { + await fixture.cdk(['synth', fixture.fullStackName('test-1')]); + expect(fixture.template('test-1')).toEqual( + expect.objectContaining({ + Resources: { + topic69831491: { + Type: 'AWS::SNS::Topic', + Metadata: { + 'aws:cdk:path': `${fixture.stackNamePrefix}-test-1/topic/Resource`, + }, + }, }, - }, - }, - })); + }), + ); - expect(await fixture.cdkSynth({ - options: [fixture.fullStackName('test-1')], - })).not.toEqual(expect.stringContaining(` + expect( + await fixture.cdkSynth({ + options: [fixture.fullStackName('test-1')], + }), + ).not.toEqual( + expect.stringContaining(` Rules: - CheckBootstrapVersion:`)); + CheckBootstrapVersion:`), + ); - await fixture.cdk(['synth', fixture.fullStackName('test-2')], { verbose: false }); - expect(fixture.template('test-2')).toEqual(expect.objectContaining({ - Resources: { - topic152D84A37: { - Type: 'AWS::SNS::Topic', - Metadata: { - 'aws:cdk:path': `${fixture.stackNamePrefix}-test-2/topic1/Resource`, + await fixture.cdk(['synth', fixture.fullStackName('test-2')], { verbose: false }); + expect(fixture.template('test-2')).toEqual( + expect.objectContaining({ + Resources: { + topic152D84A37: { + Type: 'AWS::SNS::Topic', + Metadata: { + 'aws:cdk:path': `${fixture.stackNamePrefix}-test-2/topic1/Resource`, + }, + }, + topic2A4FB547F: { + Type: 'AWS::SNS::Topic', + Metadata: { + 'aws:cdk:path': `${fixture.stackNamePrefix}-test-2/topic2/Resource`, + }, + }, }, - }, - topic2A4FB547F: { - Type: 'AWS::SNS::Topic', - Metadata: { - 'aws:cdk:path': `${fixture.stackNamePrefix}-test-2/topic2/Resource`, + }), + ); + }), +); + +integTest( + 'ssm parameter provider error', + withDefaultFixture(async (fixture) => { + await expect( + fixture.cdk( + ['synth', fixture.fullStackName('missing-ssm-parameter'), '-c', 'test:ssm-parameter-name=/does/not/exist'], + { + allowErrExit: true, }, - }, - }, - })); -})); - -integTest('ssm parameter provider error', withDefaultFixture(async (fixture) => { - await expect(fixture.cdk(['synth', - fixture.fullStackName('missing-ssm-parameter'), - '-c', 'test:ssm-parameter-name=/does/not/exist'], { - allowErrExit: true, - })).resolves.toContain('SSM parameter not available in account'); -})); - -integTest('automatic ordering', withDefaultFixture(async (fixture) => { - // Deploy the consuming stack which will include the producing stack - await fixture.cdkDeploy('order-consuming'); - - // Destroy the providing stack which will include the consuming stack - await fixture.cdkDestroy('order-providing'); -})); - -integTest('automatic ordering with concurrency', withDefaultFixture(async (fixture) => { - // Deploy the consuming stack which will include the producing stack - await fixture.cdkDeploy('order-consuming', { options: ['--concurrency', '2'] }); - - // Destroy the providing stack which will include the consuming stack - await fixture.cdkDestroy('order-providing'); -})); - -integTest('--exclusively selects only selected stack', withDefaultFixture(async (fixture) => { - // Deploy the "depends-on-failed" stack, with --exclusively. It will NOT fail (because - // of --exclusively) and it WILL create an output we can check for to confirm that it did - // get deployed. - const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json'); - await fs.mkdir(path.dirname(outputsFile), { recursive: true }); - - await fixture.cdkDeploy('depends-on-failed', { - options: [ - '--exclusively', - '--outputs-file', outputsFile, - ], - }); - - // Verify the output to see that the stack deployed - const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString()); - expect(outputs).toEqual({ - [`${fixture.stackNamePrefix}-depends-on-failed`]: { - TopicName: `${fixture.stackNamePrefix}-depends-on-failedMyTopic`, - }, - }); -})); - -integTest('context setting', withDefaultFixture(async (fixture) => { - await fs.writeFile(path.join(fixture.integTestDir, 'cdk.context.json'), JSON.stringify({ - contextkey: 'this is the context value', - })); - try { - await expect(fixture.cdk(['context'])).resolves.toContain('this is the context value'); - - // Test that deleting the contextkey works - await fixture.cdk(['context', '--reset', 'contextkey']); - await expect(fixture.cdk(['context'])).resolves.not.toContain('this is the context value'); + ), + ).resolves.toContain('SSM parameter not available in account'); + }), +); + +integTest( + 'automatic ordering', + withDefaultFixture(async (fixture) => { + // Deploy the consuming stack which will include the producing stack + await fixture.cdkDeploy('order-consuming'); + + // Destroy the providing stack which will include the consuming stack + await fixture.cdkDestroy('order-providing'); + }), +); + +integTest( + 'automatic ordering with concurrency', + withDefaultFixture(async (fixture) => { + // Deploy the consuming stack which will include the producing stack + await fixture.cdkDeploy('order-consuming', { options: ['--concurrency', '2'] }); + + // Destroy the providing stack which will include the consuming stack + await fixture.cdkDestroy('order-providing'); + }), +); + +integTest( + '--exclusively selects only selected stack', + withDefaultFixture(async (fixture) => { + // Deploy the "depends-on-failed" stack, with --exclusively. It will NOT fail (because + // of --exclusively) and it WILL create an output we can check for to confirm that it did + // get deployed. + const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json'); + await fs.mkdir(path.dirname(outputsFile), { recursive: true }); + + await fixture.cdkDeploy('depends-on-failed', { + options: ['--exclusively', '--outputs-file', outputsFile], + }); - // Test that forced delete of the context key does not throw - await fixture.cdk(['context', '-f', '--reset', 'contextkey']); + // Verify the output to see that the stack deployed + const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString()); + expect(outputs).toEqual({ + [`${fixture.stackNamePrefix}-depends-on-failed`]: { + TopicName: `${fixture.stackNamePrefix}-depends-on-failedMyTopic`, + }, + }); + }), +); - } finally { - await fs.unlink(path.join(fixture.integTestDir, 'cdk.context.json')); - } -})); - -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'], - modEnv: { - INTEG_STACK_SET: 'stage-using-context', - }, - allowErrExit: true, - })).resolves.toContain('Context lookups have been disabled'); -})); - -integTest('deploy', withDefaultFixture(async (fixture) => { - const stackArn = await fixture.cdkDeploy('test-2', { captureStderr: false }); - - // verify the number of resources in the stack - const response = await fixture.aws.cloudFormation('describeStackResources', { - StackName: stackArn, - }); - expect(response.StackResources?.length).toEqual(2); -})); +integTest( + 'context setting', + withDefaultFixture(async (fixture) => { + await fs.writeFile( + path.join(fixture.integTestDir, 'cdk.context.json'), + JSON.stringify({ + contextkey: 'this is the context value', + }), + ); + try { + await expect(fixture.cdk(['context'])).resolves.toContain('this is the context value'); -integTest('deploy --method=direct', withDefaultFixture(async (fixture) => { - const stackArn = await fixture.cdkDeploy('test-2', { - options: ['--method=direct'], - captureStderr: false, - }); + // Test that deleting the contextkey works + await fixture.cdk(['context', '--reset', 'contextkey']); + await expect(fixture.cdk(['context'])).resolves.not.toContain('this is the context value'); - // verify the number of resources in the stack - const response = await fixture.aws.cloudFormation('describeStackResources', { - StackName: stackArn, - }); - expect(response.StackResources?.length).toBeGreaterThan(0); -})); + // Test that forced delete of the context key does not throw + await fixture.cdk(['context', '-f', '--reset', 'contextkey']); + } finally { + await fs.unlink(path.join(fixture.integTestDir, 'cdk.context.json')); + } + }), +); + +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'], + modEnv: { + INTEG_STACK_SET: 'stage-using-context', + }, + allowErrExit: true, + }), + ).resolves.toContain('Context lookups have been disabled'); + }), +); -integTest('deploy all', withDefaultFixture(async (fixture) => { - const arns = await fixture.cdkDeploy('test-*', { captureStderr: false }); +integTest( + 'deploy', + withDefaultFixture(async (fixture) => { + const stackArn = await fixture.cdkDeploy('test-2', { captureStderr: false }); - // verify that we only deployed both stacks (there are 2 ARNs in the output) - expect(arns.split('\n').length).toEqual(2); -})); + // verify the number of resources in the stack + const response = await fixture.aws.cloudFormation('describeStackResources', { + StackName: stackArn, + }); + expect(response.StackResources?.length).toEqual(2); + }), +); + +integTest( + 'deploy --method=direct', + withDefaultFixture(async (fixture) => { + const stackArn = await fixture.cdkDeploy('test-2', { + options: ['--method=direct'], + captureStderr: false, + }); -integTest('deploy all concurrently', withDefaultFixture(async (fixture) => { - const arns = await fixture.cdkDeploy('test-*', { - captureStderr: false, - options: ['--concurrency', '2'], - }); + // verify the number of resources in the stack + const response = await fixture.aws.cloudFormation('describeStackResources', { + StackName: stackArn, + }); + expect(response.StackResources?.length).toBeGreaterThan(0); + }), +); + +integTest( + 'deploy all', + withDefaultFixture(async (fixture) => { + const arns = await fixture.cdkDeploy('test-*', { captureStderr: false }); + + // verify that we only deployed both stacks (there are 2 ARNs in the output) + expect(arns.split('\n').length).toEqual(2); + }), +); + +integTest( + 'deploy all concurrently', + withDefaultFixture(async (fixture) => { + const arns = await fixture.cdkDeploy('test-*', { + captureStderr: false, + options: ['--concurrency', '2'], + }); - // verify that we only deployed both stacks (there are 2 ARNs in the output) - expect(arns.split('\n').length).toEqual(2); -})); + // verify that we only deployed both stacks (there are 2 ARNs in the output) + expect(arns.split('\n').length).toEqual(2); + }), +); + +integTest( + 'nested stack with parameters', + withDefaultFixture(async (fixture) => { + // STACK_NAME_PREFIX is used in MyTopicParam to allow multiple instances + // of this test to run in parallel, othewise they will attempt to create the same SNS topic. + const stackArn = await fixture.cdkDeploy('with-nested-stack-using-parameters', { + options: ['--parameters', `MyTopicParam=${fixture.stackNamePrefix}ThereIsNoSpoon`], + captureStderr: false, + }); -integTest('nested stack with parameters', withDefaultFixture(async (fixture) => { - // STACK_NAME_PREFIX is used in MyTopicParam to allow multiple instances - // of this test to run in parallel, othewise they will attempt to create the same SNS topic. - const stackArn = await fixture.cdkDeploy('with-nested-stack-using-parameters', { - options: ['--parameters', `MyTopicParam=${fixture.stackNamePrefix}ThereIsNoSpoon`], - captureStderr: false, - }); + // verify that we only deployed a single stack (there's a single ARN in the output) + expect(stackArn.split('\n').length).toEqual(1); - // verify that we only deployed a single stack (there's a single ARN in the output) - expect(stackArn.split('\n').length).toEqual(1); + // verify the number of resources in the stack + const response = await fixture.aws.cloudFormation('describeStackResources', { + StackName: stackArn, + }); + expect(response.StackResources?.length).toEqual(1); + }), +); + +integTest( + 'deploy without execute a named change set', + withDefaultFixture(async (fixture) => { + const changeSetName = 'custom-change-set-name'; + const stackArn = await fixture.cdkDeploy('test-2', { + options: ['--no-execute', '--change-set-name', changeSetName], + captureStderr: false, + }); + // verify that we only deployed a single stack (there's a single ARN in the output) + expect(stackArn.split('\n').length).toEqual(1); - // verify the number of resources in the stack - const response = await fixture.aws.cloudFormation('describeStackResources', { - StackName: stackArn, - }); - expect(response.StackResources?.length).toEqual(1); -})); - -integTest('deploy without execute a named change set', withDefaultFixture(async (fixture) => { - const changeSetName = 'custom-change-set-name'; - const stackArn = await fixture.cdkDeploy('test-2', { - options: ['--no-execute', '--change-set-name', changeSetName], - captureStderr: false, - }); - // verify that we only deployed a single stack (there's a single ARN in the output) - expect(stackArn.split('\n').length).toEqual(1); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + expect(response.Stacks?.[0].StackStatus).toEqual('REVIEW_IN_PROGRESS'); - const response = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); - expect(response.Stacks?.[0].StackStatus).toEqual('REVIEW_IN_PROGRESS'); + //verify a change set was created with the provided name + const changeSetResponse = await fixture.aws.cloudFormation('listChangeSets', { + StackName: stackArn, + }); + const changeSets = changeSetResponse.Summaries || []; + expect(changeSets.length).toEqual(1); + expect(changeSets[0].ChangeSetName).toEqual(changeSetName); + expect(changeSets[0].Status).toEqual('CREATE_COMPLETE'); + }), +); + +integTest( + 'security related changes without a CLI are expected to fail', + withDefaultFixture(async (fixture) => { + // redirect /dev/null to stdin, which means there will not be tty attached + // since this stack includes security-related changes, the deployment should + // immediately fail because we can't confirm the changes + const stackName = 'iam-test'; + await expect( + fixture.cdkDeploy(stackName, { + options: ['<', '/dev/null'], // H4x, this only works because I happen to know we pass shell: true. + neverRequireApproval: false, + }), + ).rejects.toThrow('exited with error'); - //verify a change set was created with the provided name - const changeSetResponse = await fixture.aws.cloudFormation('listChangeSets', { - StackName: stackArn, - }); - const changeSets = changeSetResponse.Summaries || []; - expect(changeSets.length).toEqual(1); - expect(changeSets[0].ChangeSetName).toEqual(changeSetName); - expect(changeSets[0].Status).toEqual('CREATE_COMPLETE'); -})); - -integTest('security related changes without a CLI are expected to fail', withDefaultFixture(async (fixture) => { - // redirect /dev/null to stdin, which means there will not be tty attached - // since this stack includes security-related changes, the deployment should - // immediately fail because we can't confirm the changes - const stackName = 'iam-test'; - await expect(fixture.cdkDeploy(stackName, { - options: ['<', '/dev/null'], // H4x, this only works because I happen to know we pass shell: true. - neverRequireApproval: false, - })).rejects.toThrow('exited with error'); - - // Ensure stack was not deployed - await expect(fixture.aws.cloudFormation('describeStacks', { - StackName: fixture.fullStackName(stackName), - })).rejects.toThrow('does not exist'); -})); - -integTest('deploy wildcard with outputs', withDefaultFixture(async (fixture) => { - const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json'); - await fs.mkdir(path.dirname(outputsFile), { recursive: true }); - - await fixture.cdkDeploy(['outputs-test-*'], { - options: ['--outputs-file', outputsFile], - }); + // Ensure stack was not deployed + await expect( + fixture.aws.cloudFormation('describeStacks', { + StackName: fixture.fullStackName(stackName), + }), + ).rejects.toThrow('does not exist'); + }), +); + +integTest( + 'deploy wildcard with outputs', + withDefaultFixture(async (fixture) => { + const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json'); + await fs.mkdir(path.dirname(outputsFile), { recursive: true }); + + await fixture.cdkDeploy(['outputs-test-*'], { + options: ['--outputs-file', outputsFile], + }); - const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString()); - expect(outputs).toEqual({ - [`${fixture.stackNamePrefix}-outputs-test-1`]: { - TopicName: `${fixture.stackNamePrefix}-outputs-test-1MyTopic`, - }, - [`${fixture.stackNamePrefix}-outputs-test-2`]: { - TopicName: `${fixture.stackNamePrefix}-outputs-test-2MyOtherTopic`, - }, - }); -})); - -integTest('deploy with parameters', withDefaultFixture(async (fixture) => { - const stackArn = await fixture.cdkDeploy('param-test-1', { - options: [ - '--parameters', `TopicNameParam=${fixture.stackNamePrefix}bazinga`, - ], - captureStderr: false, - }); + const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString()); + expect(outputs).toEqual({ + [`${fixture.stackNamePrefix}-outputs-test-1`]: { + TopicName: `${fixture.stackNamePrefix}-outputs-test-1MyTopic`, + }, + [`${fixture.stackNamePrefix}-outputs-test-2`]: { + TopicName: `${fixture.stackNamePrefix}-outputs-test-2MyOtherTopic`, + }, + }); + }), +); + +integTest( + 'deploy with parameters', + withDefaultFixture(async (fixture) => { + const stackArn = await fixture.cdkDeploy('param-test-1', { + options: ['--parameters', `TopicNameParam=${fixture.stackNamePrefix}bazinga`], + captureStderr: false, + }); - const response = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); - expect(response.Stacks?.[0].Parameters).toContainEqual( - { + expect(response.Stacks?.[0].Parameters).toContainEqual({ ParameterKey: 'TopicNameParam', ParameterValue: `${fixture.stackNamePrefix}bazinga`, - }, - ); -})); - -integTest('update to stack in ROLLBACK_COMPLETE state will delete stack and create a new one', withDefaultFixture(async (fixture) => { - // GIVEN - await expect(fixture.cdkDeploy('param-test-1', { - options: [ - '--parameters', `TopicNameParam=${fixture.stackNamePrefix}@aww`, - ], - captureStderr: false, - })).rejects.toThrow('exited with error'); - - const response = await fixture.aws.cloudFormation('describeStacks', { - StackName: fixture.fullStackName('param-test-1'), - }); + }); + }), +); + +integTest( + 'update to stack in ROLLBACK_COMPLETE state will delete stack and create a new one', + withDefaultFixture(async (fixture) => { + // GIVEN + await expect( + fixture.cdkDeploy('param-test-1', { + options: ['--parameters', `TopicNameParam=${fixture.stackNamePrefix}@aww`], + captureStderr: false, + }), + ).rejects.toThrow('exited with error'); - const stackArn = response.Stacks?.[0].StackId; - expect(response.Stacks?.[0].StackStatus).toEqual('ROLLBACK_COMPLETE'); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: fixture.fullStackName('param-test-1'), + }); - // WHEN - const newStackArn = await fixture.cdkDeploy('param-test-1', { - options: [ - '--parameters', `TopicNameParam=${fixture.stackNamePrefix}allgood`, - ], - captureStderr: false, - }); + const stackArn = response.Stacks?.[0].StackId; + expect(response.Stacks?.[0].StackStatus).toEqual('ROLLBACK_COMPLETE'); - const newStackResponse = await fixture.aws.cloudFormation('describeStacks', { - StackName: newStackArn, - }); + // WHEN + const newStackArn = await fixture.cdkDeploy('param-test-1', { + options: ['--parameters', `TopicNameParam=${fixture.stackNamePrefix}allgood`], + captureStderr: false, + }); - // THEN - expect(stackArn).not.toEqual(newStackArn); // new stack was created - expect(newStackResponse.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); - expect(newStackResponse.Stacks?.[0].Parameters).toContainEqual( - { + const newStackResponse = await fixture.aws.cloudFormation('describeStacks', { + StackName: newStackArn, + }); + + // THEN + expect(stackArn).not.toEqual(newStackArn); // new stack was created + expect(newStackResponse.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); + expect(newStackResponse.Stacks?.[0].Parameters).toContainEqual({ ParameterKey: 'TopicNameParam', ParameterValue: `${fixture.stackNamePrefix}allgood`, - }, - ); -})); - -integTest('stack in UPDATE_ROLLBACK_COMPLETE state can be updated', withDefaultFixture(async (fixture) => { - // GIVEN - const stackArn = await fixture.cdkDeploy('param-test-1', { - options: [ - '--parameters', `TopicNameParam=${fixture.stackNamePrefix}nice`, - ], - captureStderr: false, - }); + }); + }), +); + +integTest( + 'stack in UPDATE_ROLLBACK_COMPLETE state can be updated', + withDefaultFixture(async (fixture) => { + // GIVEN + const stackArn = await fixture.cdkDeploy('param-test-1', { + options: ['--parameters', `TopicNameParam=${fixture.stackNamePrefix}nice`], + captureStderr: false, + }); - let response = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); + let response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); - expect(response.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); + expect(response.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); - // bad parameter name with @ will put stack into UPDATE_ROLLBACK_COMPLETE - await expect(fixture.cdkDeploy('param-test-1', { - options: [ - '--parameters', `TopicNameParam=${fixture.stackNamePrefix}@aww`, - ], - captureStderr: false, - })).rejects.toThrow('exited with error');; + // bad parameter name with @ will put stack into UPDATE_ROLLBACK_COMPLETE + await expect( + fixture.cdkDeploy('param-test-1', { + options: ['--parameters', `TopicNameParam=${fixture.stackNamePrefix}@aww`], + captureStderr: false, + }), + ).rejects.toThrow('exited with error'); - response = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); + response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); - expect(response.Stacks?.[0].StackStatus).toEqual('UPDATE_ROLLBACK_COMPLETE'); + expect(response.Stacks?.[0].StackStatus).toEqual('UPDATE_ROLLBACK_COMPLETE'); - // WHEN - await fixture.cdkDeploy('param-test-1', { - options: [ - '--parameters', `TopicNameParam=${fixture.stackNamePrefix}allgood`, - ], - captureStderr: false, - }); + // WHEN + await fixture.cdkDeploy('param-test-1', { + options: ['--parameters', `TopicNameParam=${fixture.stackNamePrefix}allgood`], + captureStderr: false, + }); - response = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); + response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); - // THEN - expect(response.Stacks?.[0].StackStatus).toEqual('UPDATE_COMPLETE'); - expect(response.Stacks?.[0].Parameters).toContainEqual( - { + // THEN + expect(response.Stacks?.[0].StackStatus).toEqual('UPDATE_COMPLETE'); + expect(response.Stacks?.[0].Parameters).toContainEqual({ ParameterKey: 'TopicNameParam', ParameterValue: `${fixture.stackNamePrefix}allgood`, - }, - ); -})); - -integTest('deploy with wildcard and parameters', withDefaultFixture(async (fixture) => { - await fixture.cdkDeploy('param-test-*', { - options: [ - '--parameters', `${fixture.stackNamePrefix}-param-test-1:TopicNameParam=${fixture.stackNamePrefix}bazinga`, - '--parameters', `${fixture.stackNamePrefix}-param-test-2:OtherTopicNameParam=${fixture.stackNamePrefix}ThatsMySpot`, - '--parameters', `${fixture.stackNamePrefix}-param-test-3:DisplayNameParam=${fixture.stackNamePrefix}HeyThere`, - '--parameters', `${fixture.stackNamePrefix}-param-test-3:OtherDisplayNameParam=${fixture.stackNamePrefix}AnotherOne`, - ], - }); -})); - -integTest('deploy with parameters multi', withDefaultFixture(async (fixture) => { - const paramVal1 = `${fixture.stackNamePrefix}bazinga`; - const paramVal2 = `${fixture.stackNamePrefix}=jagshemash`; - - const stackArn = await fixture.cdkDeploy('param-test-3', { - options: [ - '--parameters', `DisplayNameParam=${paramVal1}`, - '--parameters', `OtherDisplayNameParam=${paramVal2}`, - ], - captureStderr: false, - }); + }); + }), +); + +integTest( + 'deploy with wildcard and parameters', + withDefaultFixture(async (fixture) => { + await fixture.cdkDeploy('param-test-*', { + options: [ + '--parameters', + `${fixture.stackNamePrefix}-param-test-1:TopicNameParam=${fixture.stackNamePrefix}bazinga`, + '--parameters', + `${fixture.stackNamePrefix}-param-test-2:OtherTopicNameParam=${fixture.stackNamePrefix}ThatsMySpot`, + '--parameters', + `${fixture.stackNamePrefix}-param-test-3:DisplayNameParam=${fixture.stackNamePrefix}HeyThere`, + '--parameters', + `${fixture.stackNamePrefix}-param-test-3:OtherDisplayNameParam=${fixture.stackNamePrefix}AnotherOne`, + ], + }); + }), +); - const response = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); +integTest( + 'deploy with parameters multi', + withDefaultFixture(async (fixture) => { + const paramVal1 = `${fixture.stackNamePrefix}bazinga`; + const paramVal2 = `${fixture.stackNamePrefix}=jagshemash`; + + const stackArn = await fixture.cdkDeploy('param-test-3', { + options: ['--parameters', `DisplayNameParam=${paramVal1}`, '--parameters', `OtherDisplayNameParam=${paramVal2}`], + captureStderr: false, + }); + + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); - expect(response.Stacks?.[0].Parameters).toContainEqual( - { + expect(response.Stacks?.[0].Parameters).toContainEqual({ ParameterKey: 'DisplayNameParam', ParameterValue: paramVal1, - }, - ); - expect(response.Stacks?.[0].Parameters).toContainEqual( - { + }); + expect(response.Stacks?.[0].Parameters).toContainEqual({ ParameterKey: 'OtherDisplayNameParam', ParameterValue: paramVal2, - }, - ); -})); + }); + }), +); -integTest('deploy with notification ARN', withDefaultFixture(async (fixture) => { - const topicName = `${fixture.stackNamePrefix}-test-topic`; +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], - }); + const response = await fixture.aws.sns('createTopic', { Name: topicName }); + const topicArn = response.TopicArn!; + try { + await fixture.cdkDeploy('test-2', { + options: ['--notification-arns', topicArn], + }); - // verify that the stack we deployed has our notification ARN - const describeResponse = await fixture.aws.cloudFormation('describeStacks', { - StackName: fixture.fullStackName('test-2'), - }); - expect(describeResponse.Stacks?.[0].NotificationARNs).toEqual([topicArn]); - } finally { - await fixture.aws.sns('deleteTopic', { - TopicArn: topicArn, - }); - } -})); + // verify that the stack we deployed has our notification ARN + const describeResponse = await fixture.aws.cloudFormation('describeStacks', { + StackName: fixture.fullStackName('test-2'), + }); + 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) => { - if (fixture.packages.majorVersion() !== '1') { - return; // Nothing to do - } +integTest( + 'deploy with role', + withDefaultFixture(async (fixture) => { + if (fixture.packages.majorVersion() !== '1') { + return; // Nothing to do + } - const roleName = `${fixture.stackNamePrefix}-test-role`; - - await deleteRole(); - - const createResponse = await fixture.aws.iam('createRole', { - RoleName: roleName, - AssumeRolePolicyDocument: JSON.stringify({ - Version: '2012-10-17', - Statement: [{ - Action: 'sts:AssumeRole', - Principal: { Service: 'cloudformation.amazonaws.com' }, - Effect: 'Allow', - }, { - Action: 'sts:AssumeRole', - Principal: { AWS: (await fixture.aws.sts('getCallerIdentity', {})).Arn }, - Effect: 'Allow', - }], - }), - }); - const roleArn = createResponse.Role.Arn; - try { - await fixture.aws.iam('putRolePolicy', { + const roleName = `${fixture.stackNamePrefix}-test-role`; + + await deleteRole(); + + const createResponse = await fixture.aws.iam('createRole', { RoleName: roleName, - PolicyName: 'DefaultPolicy', - PolicyDocument: JSON.stringify({ + AssumeRolePolicyDocument: JSON.stringify({ Version: '2012-10-17', - Statement: [{ - Action: '*', - Resource: '*', - Effect: 'Allow', - }], + Statement: [ + { + Action: 'sts:AssumeRole', + Principal: { Service: 'cloudformation.amazonaws.com' }, + Effect: 'Allow', + }, + { + Action: 'sts:AssumeRole', + Principal: { AWS: (await fixture.aws.sts('getCallerIdentity', {})).Arn }, + Effect: 'Allow', + }, + ], }), }); - - await retry(fixture.output, 'Trying to assume fresh role', retry.forSeconds(300), async () => { - await fixture.aws.sts('assumeRole', { - RoleArn: roleArn, - RoleSessionName: 'testing', + const roleArn = createResponse.Role.Arn; + try { + await fixture.aws.iam('putRolePolicy', { + RoleName: roleName, + PolicyName: 'DefaultPolicy', + PolicyDocument: JSON.stringify({ + Version: '2012-10-17', + Statement: [ + { + Action: '*', + Resource: '*', + Effect: 'Allow', + }, + ], + }), }); - }); - // In principle, the role has replicated from 'us-east-1' to wherever we're testing. - // Give it a little more sleep to make sure CloudFormation is not hitting a box - // that doesn't have it yet. - await sleep(5000); + await retry(fixture.output, 'Trying to assume fresh role', retry.forSeconds(300), async () => { + await fixture.aws.sts('assumeRole', { + RoleArn: roleArn, + RoleSessionName: 'testing', + }); + }); - await fixture.cdkDeploy('test-2', { - options: ['--role-arn', roleArn], - }); + // In principle, the role has replicated from 'us-east-1' to wherever we're testing. + // Give it a little more sleep to make sure CloudFormation is not hitting a box + // that doesn't have it yet. + await sleep(5000); - // Immediately delete the stack again before we delete the role. - // - // Since roles are sticky, if we delete the role before the stack, subsequent DeleteStack - // operations will fail when CloudFormation tries to assume the role that's already gone. - await fixture.cdkDestroy('test-2'); + await fixture.cdkDeploy('test-2', { + options: ['--role-arn', roleArn], + }); - } finally { - await deleteRole(); - } + // Immediately delete the stack again before we delete the role. + // + // Since roles are sticky, if we delete the role before the stack, subsequent DeleteStack + // operations will fail when CloudFormation tries to assume the role that's already gone. + await fixture.cdkDestroy('test-2'); + } finally { + await deleteRole(); + } - async function deleteRole() { - try { - for (const policyName of (await fixture.aws.iam('listRolePolicies', { RoleName: roleName })).PolicyNames) { - await fixture.aws.iam('deleteRolePolicy', { - RoleName: roleName, - PolicyName: policyName, - }); + async function deleteRole() { + try { + for (const policyName of (await fixture.aws.iam('listRolePolicies', { RoleName: roleName })).PolicyNames) { + await fixture.aws.iam('deleteRolePolicy', { + RoleName: roleName, + PolicyName: policyName, + }); + } + await fixture.aws.iam('deleteRole', { RoleName: roleName }); + } catch (e: any) { + if (e.message.indexOf('cannot be found') > -1) { + return; + } + throw e; } - await fixture.aws.iam('deleteRole', { RoleName: roleName }); - } catch (e: any) { - if (e.message.indexOf('cannot be found') > -1) { return; } - throw e; } - } -})); + }), +); // TODO add more testing that ensures the symmetry of the generated constructs to the resources. -['typescript', 'python', 'csharp', 'java'].forEach(language => { - integTest(`cdk migrate ${language} deploys successfully`, withCDKMigrateFixture(language, async (fixture) => { - if (language === 'python') { - await fixture.shell(['pip', 'install', '-r', 'requirements.txt']); - } +['typescript', 'python', 'csharp', 'java'].forEach((language) => { + integTest( + `cdk migrate ${language} deploys successfully`, + withCDKMigrateFixture(language, async (fixture) => { + if (language === 'python') { + await fixture.shell(['pip', 'install', '-r', 'requirements.txt']); + } - const stackArn = await fixture.cdkDeploy(fixture.stackNamePrefix, { neverRequireApproval: true, verbose: true, captureStderr: false }, true); - const response = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); + const stackArn = await fixture.cdkDeploy( + fixture.stackNamePrefix, + { neverRequireApproval: true, verbose: true, captureStderr: false }, + true, + ); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); - expect(response.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); - await fixture.cdkDestroy(fixture.stackNamePrefix); - })); + expect(response.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); + await fixture.cdkDestroy(fixture.stackNamePrefix); + }), + ); }); -integTest('cdk migrate generates migrate.json', withCDKMigrateFixture('typescript', async (fixture) => { - - const migrateFile = await fs.readFile(path.join(fixture.integTestDir, 'migrate.json'), 'utf8'); - const expectedFile = `{ +integTest( + 'cdk migrate generates migrate.json', + withCDKMigrateFixture('typescript', async (fixture) => { + const migrateFile = await fs.readFile(path.join(fixture.integTestDir, 'migrate.json'), 'utf8'); + const expectedFile = `{ \"//\": \"This file is generated by cdk migrate. It will be automatically deleted after the first successful deployment of this app to the environment of the original resources.\", \"Source\": \"localfile\" }`; - expect(JSON.parse(migrateFile)).toEqual(JSON.parse(expectedFile)); - await fixture.cdkDestroy(fixture.stackNamePrefix); -})); + expect(JSON.parse(migrateFile)).toEqual(JSON.parse(expectedFile)); + await fixture.cdkDestroy(fixture.stackNamePrefix); + }), +); // integTest('cdk migrate --from-scan with AND/OR filters correctly filters resources', withExtendedTimeoutFixture(async (fixture) => { // const stackName = `cdk-migrate-integ-${fixture.randomString}`; @@ -676,202 +793,240 @@ integTest('cdk migrate generates migrate.json', withCDKMigrateFixture('typescrip // } // })); -['typescript', 'python', 'csharp', 'java'].forEach(language => { - integTest(`cdk migrate --from-stack creates deployable ${language} app`, withExtendedTimeoutFixture(async (fixture) => { - const migrateStackName = fixture.fullStackName('migrate-stack'); - await fixture.aws.cloudFormation('createStack', { - StackName: migrateStackName, - TemplateBody: await fs.readFile(path.join(__dirname, '..', '..', 'resources', 'templates', 'sqs-template.json'), 'utf8'), - }); - try { - let stackStatus = 'CREATE_IN_PROGRESS'; - while (stackStatus === 'CREATE_IN_PROGRESS') { - stackStatus = await (await (fixture.aws.cloudFormation('describeStacks', { StackName: migrateStackName }))).Stacks?.[0].StackStatus!; - await sleep(1000); - } - await fixture.cdk( - ['migrate', '--stack-name', migrateStackName, '--from-stack'], - { modEnv: { MIGRATE_INTEG_TEST: '1' }, neverRequireApproval: true, verbose: true, captureStderr: false }, - ); - await fixture.shell(['cd', path.join(fixture.integTestDir, migrateStackName)]); - await fixture.cdk(['deploy', migrateStackName], { neverRequireApproval: true, verbose: true, captureStderr: false }); - const response = await fixture.aws.cloudFormation('describeStacks', { +['typescript', 'python', 'csharp', 'java'].forEach((language) => { + integTest( + `cdk migrate --from-stack creates deployable ${language} app`, + withExtendedTimeoutFixture(async (fixture) => { + const migrateStackName = fixture.fullStackName('migrate-stack'); + await fixture.aws.cloudFormation('createStack', { StackName: migrateStackName, + TemplateBody: await fs.readFile( + path.join(__dirname, '..', '..', 'resources', 'templates', 'sqs-template.json'), + 'utf8', + ), }); + try { + let stackStatus = 'CREATE_IN_PROGRESS'; + while (stackStatus === 'CREATE_IN_PROGRESS') { + stackStatus = await ( + await fixture.aws.cloudFormation('describeStacks', { StackName: migrateStackName }) + ).Stacks?.[0].StackStatus!; + await sleep(1000); + } + await fixture.cdk(['migrate', '--stack-name', migrateStackName, '--from-stack'], { + modEnv: { MIGRATE_INTEG_TEST: '1' }, + neverRequireApproval: true, + verbose: true, + captureStderr: false, + }); + await fixture.shell(['cd', path.join(fixture.integTestDir, migrateStackName)]); + await fixture.cdk(['deploy', migrateStackName], { + neverRequireApproval: true, + verbose: true, + captureStderr: false, + }); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: migrateStackName, + }); - expect(response.Stacks?.[0].StackStatus).toEqual('UPDATE_COMPLETE'); - } finally { - await fixture.cdkDestroy('migrate-stack'); - } - })); + expect(response.Stacks?.[0].StackStatus).toEqual('UPDATE_COMPLETE'); + } finally { + await fixture.cdkDestroy('migrate-stack'); + } + }), + ); }); -integTest('cdk diff', withDefaultFixture(async (fixture) => { - const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); - expect(diff1).toContain('AWS::SNS::Topic'); - - const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); - expect(diff2).toContain('AWS::SNS::Topic'); - - // We can make it fail by passing --fail - await expect(fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1')])) - .rejects.toThrow('exited with error'); -})); - -integTest('enableDiffNoFail', withDefaultFixture(async (fixture) => { - await diffShouldSucceedWith({ fail: false, enableDiffNoFail: false }); - await diffShouldSucceedWith({ fail: false, enableDiffNoFail: true }); - await diffShouldFailWith({ fail: true, enableDiffNoFail: false }); - await diffShouldFailWith({ fail: true, enableDiffNoFail: true }); - await diffShouldFailWith({ fail: undefined, enableDiffNoFail: false }); - await diffShouldSucceedWith({ fail: undefined, enableDiffNoFail: true }); - - async function diffShouldSucceedWith(props: DiffParameters) { - await expect(diff(props)).resolves.not.toThrowError(); - } - - async function diffShouldFailWith(props: DiffParameters) { - await expect(diff(props)).rejects.toThrow('exited with error'); - } - - async function diff(props: DiffParameters): Promise { - await updateContext(props.enableDiffNoFail); - const flag = props.fail != null - ? (props.fail ? '--fail' : '--no-fail') - : ''; - - return fixture.cdk(['diff', flag, fixture.fullStackName('test-1')]); - } - - async function updateContext(enableDiffNoFail: boolean) { - const cdkJson = JSON.parse(await fs.readFile(path.join(fixture.integTestDir, 'cdk.json'), 'utf8')); - cdkJson.context = { - ...cdkJson.context, - 'aws-cdk:enableDiffNoFail': enableDiffNoFail, - }; - await fs.writeFile(path.join(fixture.integTestDir, 'cdk.json'), JSON.stringify(cdkJson)); - } - - type DiffParameters = { fail?: boolean; enableDiffNoFail: boolean }; -})); - -integTest('cdk diff --fail on multiple stacks exits with error if any of the stacks contains a diff', withDefaultFixture(async (fixture) => { - // GIVEN - const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); - expect(diff1).toContain('AWS::SNS::Topic'); - - await fixture.cdkDeploy('test-2'); - const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); - expect(diff2).toContain('There were no differences'); +integTest( + 'cdk diff', + withDefaultFixture(async (fixture) => { + const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); + expect(diff1).toContain('AWS::SNS::Topic'); + + const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); + expect(diff2).toContain('AWS::SNS::Topic'); + + // We can make it fail by passing --fail + await expect(fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1')])).rejects.toThrow('exited with error'); + }), +); + +integTest( + 'enableDiffNoFail', + withDefaultFixture(async (fixture) => { + await diffShouldSucceedWith({ fail: false, enableDiffNoFail: false }); + await diffShouldSucceedWith({ fail: false, enableDiffNoFail: true }); + await diffShouldFailWith({ fail: true, enableDiffNoFail: false }); + await diffShouldFailWith({ fail: true, enableDiffNoFail: true }); + await diffShouldFailWith({ fail: undefined, enableDiffNoFail: false }); + await diffShouldSucceedWith({ fail: undefined, enableDiffNoFail: true }); + + async function diffShouldSucceedWith(props: DiffParameters) { + await expect(diff(props)).resolves.not.toThrowError(); + } - // WHEN / THEN - await expect(fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1'), fixture.fullStackName('test-2')])).rejects.toThrow('exited with error'); -})); + async function diffShouldFailWith(props: DiffParameters) { + await expect(diff(props)).rejects.toThrow('exited with error'); + } -integTest('cdk diff --fail with multiple stack exits with if any of the stacks contains a diff', withDefaultFixture(async (fixture) => { - // GIVEN - await fixture.cdkDeploy('test-1'); - const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); - expect(diff1).toContain('There were no differences'); + async function diff(props: DiffParameters): Promise { + await updateContext(props.enableDiffNoFail); + const flag = props.fail != null ? (props.fail ? '--fail' : '--no-fail') : ''; - const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); - expect(diff2).toContain('AWS::SNS::Topic'); + return fixture.cdk(['diff', flag, fixture.fullStackName('test-1')]); + } - // WHEN / THEN - await expect(fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1'), fixture.fullStackName('test-2')])).rejects.toThrow('exited with error'); -})); + async function updateContext(enableDiffNoFail: boolean) { + const cdkJson = JSON.parse(await fs.readFile(path.join(fixture.integTestDir, 'cdk.json'), 'utf8')); + cdkJson.context = { + ...cdkJson.context, + 'aws-cdk:enableDiffNoFail': enableDiffNoFail, + }; + await fs.writeFile(path.join(fixture.integTestDir, 'cdk.json'), JSON.stringify(cdkJson)); + } -integTest('cdk diff --security-only successfully outputs sso-permission-set-without-managed-policy information', withDefaultFixture(async (fixture) => { - const diff = await fixture.cdk( - ['diff', '--security-only', fixture.fullStackName('sso-perm-set-without-managed-policy')], - ); - `┌───┬──────────────────────────────────────────┬──────────────────────────────────┬────────────────────┬───────────────────────────────────┬─────────────────────────────────┐ + type DiffParameters = { fail?: boolean; enableDiffNoFail: boolean }; + }), +); + +integTest( + 'cdk diff --fail on multiple stacks exits with error if any of the stacks contains a diff', + withDefaultFixture(async (fixture) => { + // GIVEN + const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); + expect(diff1).toContain('AWS::SNS::Topic'); + + await fixture.cdkDeploy('test-2'); + const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); + expect(diff2).toContain('There were no differences'); + + // WHEN / THEN + await expect( + fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1'), fixture.fullStackName('test-2')]), + ).rejects.toThrow('exited with error'); + }), +); + +integTest( + 'cdk diff --fail with multiple stack exits with if any of the stacks contains a diff', + withDefaultFixture(async (fixture) => { + // GIVEN + await fixture.cdkDeploy('test-1'); + const diff1 = await fixture.cdk(['diff', fixture.fullStackName('test-1')]); + expect(diff1).toContain('There were no differences'); + + const diff2 = await fixture.cdk(['diff', fixture.fullStackName('test-2')]); + expect(diff2).toContain('AWS::SNS::Topic'); + + // WHEN / THEN + await expect( + fixture.cdk(['diff', '--fail', fixture.fullStackName('test-1'), fixture.fullStackName('test-2')]), + ).rejects.toThrow('exited with error'); + }), +); + +integTest( + 'cdk diff --security-only successfully outputs sso-permission-set-without-managed-policy information', + withDefaultFixture(async (fixture) => { + const diff = await fixture.cdk([ + 'diff', + '--security-only', + fixture.fullStackName('sso-perm-set-without-managed-policy'), + ]); + `┌───┬──────────────────────────────────────────┬──────────────────────────────────┬────────────────────┬───────────────────────────────────┬─────────────────────────────────┐ │ │ Resource │ InstanceArn │ PermissionSet name │ PermissionsBoundary │ CustomerManagedPolicyReferences │ ├───┼──────────────────────────────────────────┼──────────────────────────────────┼────────────────────┼───────────────────────────────────┼─────────────────────────────────┤ │ + │\${permission-set-without-managed-policy} │ arn:aws:sso:::instance/testvalue │ testName │ CustomerManagedPolicyReference: { │ │ │ │ │ │ │ Name: why, Path: /how/ │ │ │ │ │ │ │ } │ │ `; - expect(diff).toContain('Resource'); - expect(diff).toContain('permission-set-without-managed-policy'); - - expect(diff).toContain('InstanceArn'); - expect(diff).toContain('arn:aws:sso:::instance/testvalue'); - - expect(diff).toContain('PermissionSet name'); - expect(diff).toContain('testName'); - - expect(diff).toContain('PermissionsBoundary'); - expect(diff).toContain('CustomerManagedPolicyReference: {'); - expect(diff).toContain('Name: why, Path: /how/'); - expect(diff).toContain('}'); - - expect(diff).toContain('CustomerManagedPolicyReferences'); -})); - -integTest('cdk diff --security-only successfully outputs sso-permission-set-with-managed-policy information', withDefaultFixture(async (fixture) => { - const diff = await fixture.cdk( - ['diff', '--security-only', fixture.fullStackName('sso-perm-set-with-managed-policy')], - ); - `┌───┬──────────────────────────────────────────┬──────────────────────────────────┬────────────────────┬───────────────────────────────────────────────────────────────┬─────────────────────────────────┐ + expect(diff).toContain('Resource'); + expect(diff).toContain('permission-set-without-managed-policy'); + + expect(diff).toContain('InstanceArn'); + expect(diff).toContain('arn:aws:sso:::instance/testvalue'); + + expect(diff).toContain('PermissionSet name'); + expect(diff).toContain('testName'); + + expect(diff).toContain('PermissionsBoundary'); + expect(diff).toContain('CustomerManagedPolicyReference: {'); + expect(diff).toContain('Name: why, Path: /how/'); + expect(diff).toContain('}'); + + expect(diff).toContain('CustomerManagedPolicyReferences'); + }), +); + +integTest( + 'cdk diff --security-only successfully outputs sso-permission-set-with-managed-policy information', + withDefaultFixture(async (fixture) => { + const diff = await fixture.cdk([ + 'diff', + '--security-only', + fixture.fullStackName('sso-perm-set-with-managed-policy'), + ]); + `┌───┬──────────────────────────────────────────┬──────────────────────────────────┬────────────────────┬───────────────────────────────────────────────────────────────┬─────────────────────────────────┐ │ │ Resource │ InstanceArn │ PermissionSet name │ PermissionsBoundary │ CustomerManagedPolicyReferences │ ├───┼──────────────────────────────────────────┼──────────────────────────────────┼────────────────────┼───────────────────────────────────────────────────────────────┼─────────────────────────────────┤ │ + │\${permission-set-with-managed-policy} │ arn:aws:sso:::instance/testvalue │ niceWork │ ManagedPolicyArn: arn:aws:iam::aws:policy/AdministratorAccess │ Name: forSSO, Path: │ `; - expect(diff).toContain('Resource'); - expect(diff).toContain('permission-set-with-managed-policy'); + expect(diff).toContain('Resource'); + expect(diff).toContain('permission-set-with-managed-policy'); - expect(diff).toContain('InstanceArn'); - expect(diff).toContain('arn:aws:sso:::instance/testvalue'); + expect(diff).toContain('InstanceArn'); + expect(diff).toContain('arn:aws:sso:::instance/testvalue'); - expect(diff).toContain('PermissionSet name'); - expect(diff).toContain('niceWork'); + expect(diff).toContain('PermissionSet name'); + expect(diff).toContain('niceWork'); - expect(diff).toContain('PermissionsBoundary'); - expect(diff).toContain('ManagedPolicyArn: arn:aws:iam::aws:policy/AdministratorAccess'); + expect(diff).toContain('PermissionsBoundary'); + expect(diff).toContain('ManagedPolicyArn: arn:aws:iam::aws:policy/AdministratorAccess'); - expect(diff).toContain('CustomerManagedPolicyReferences'); - expect(diff).toContain('Name: forSSO, Path:'); -})); + expect(diff).toContain('CustomerManagedPolicyReferences'); + expect(diff).toContain('Name: forSSO, Path:'); + }), +); -integTest('cdk diff --security-only successfully outputs sso-assignment information', withDefaultFixture(async (fixture) => { - const diff = await fixture.cdk( - ['diff', '--security-only', fixture.fullStackName('sso-assignment')], - ); - `┌───┬───────────────┬──────────────────────────────────┬─────────────────────────┬──────────────────────────────┬───────────────┬──────────────┬─────────────┐ +integTest( + 'cdk diff --security-only successfully outputs sso-assignment information', + withDefaultFixture(async (fixture) => { + const diff = await fixture.cdk(['diff', '--security-only', fixture.fullStackName('sso-assignment')]); + `┌───┬───────────────┬──────────────────────────────────┬─────────────────────────┬──────────────────────────────┬───────────────┬──────────────┬─────────────┐ │ │ Resource │ InstanceArn │ PermissionSetArn │ PrincipalId │ PrincipalType │ TargetId │ TargetType │ ├───┼───────────────┼──────────────────────────────────┼─────────────────────────┼──────────────────────────────┼───────────────┼──────────────┼─────────────┤ │ + │\${assignment} │ arn:aws:sso:::instance/testvalue │ arn:aws:sso:::testvalue │ 11111111-2222-3333-4444-test │ USER │ 111111111111 │ AWS_ACCOUNT │ └───┴───────────────┴──────────────────────────────────┴─────────────────────────┴──────────────────────────────┴───────────────┴──────────────┴─────────────┘ `; - expect(diff).toContain('Resource'); - expect(diff).toContain('assignment'); + expect(diff).toContain('Resource'); + expect(diff).toContain('assignment'); - expect(diff).toContain('InstanceArn'); - expect(diff).toContain('arn:aws:sso:::instance/testvalue'); + expect(diff).toContain('InstanceArn'); + expect(diff).toContain('arn:aws:sso:::instance/testvalue'); - expect(diff).toContain('PermissionSetArn'); - expect(diff).toContain('arn:aws:sso:::testvalue'); + expect(diff).toContain('PermissionSetArn'); + expect(diff).toContain('arn:aws:sso:::testvalue'); - expect(diff).toContain('PrincipalId'); - expect(diff).toContain('11111111-2222-3333-4444-test'); + expect(diff).toContain('PrincipalId'); + expect(diff).toContain('11111111-2222-3333-4444-test'); - expect(diff).toContain('PrincipalType'); - expect(diff).toContain('USER'); + expect(diff).toContain('PrincipalType'); + expect(diff).toContain('USER'); - expect(diff).toContain('TargetId'); - expect(diff).toContain('111111111111'); + expect(diff).toContain('TargetId'); + expect(diff).toContain('111111111111'); - expect(diff).toContain('TargetType'); - expect(diff).toContain('AWS_ACCOUNT'); -})); + expect(diff).toContain('TargetType'); + expect(diff).toContain('AWS_ACCOUNT'); + }), +); -integTest('cdk diff --security-only successfully outputs sso-access-control information', withDefaultFixture(async (fixture) => { - const diff = await fixture.cdk( - ['diff', '--security-only', fixture.fullStackName('sso-access-control')], - ); - `┌───┬────────────────────────────────┬────────────────────────┬─────────────────────────────────┐ +integTest( + 'cdk diff --security-only successfully outputs sso-access-control information', + withDefaultFixture(async (fixture) => { + const diff = await fixture.cdk(['diff', '--security-only', fixture.fullStackName('sso-access-control')]); + `┌───┬────────────────────────────────┬────────────────────────┬─────────────────────────────────┐ │ │ Resource │ InstanceArn │ AccessControlAttributes │ ├───┼────────────────────────────────┼────────────────────────┼─────────────────────────────────┤ │ + │\${instanceAccessControlConfig} │ arn:aws:test:testvalue │ Key: first, Values: [a] │ @@ -882,125 +1037,143 @@ integTest('cdk diff --security-only successfully outputs sso-access-control info │ │ │ │ Key: sixth, Values: [f] │ └───┴────────────────────────────────┴────────────────────────┴─────────────────────────────────┘ `; - expect(diff).toContain('Resource'); - expect(diff).toContain('instanceAccessControlConfig'); - - expect(diff).toContain('InstanceArn'); - expect(diff).toContain('arn:aws:sso:::instance/testvalue'); - - expect(diff).toContain('AccessControlAttributes'); - expect(diff).toContain('Key: first, Values: [a]'); - expect(diff).toContain('Key: second, Values: [b]'); - expect(diff).toContain('Key: third, Values: [c]'); - expect(diff).toContain('Key: fourth, Values: [d]'); - expect(diff).toContain('Key: fifth, Values: [e]'); - expect(diff).toContain('Key: sixth, Values: [f]'); -})); - -integTest('cdk diff --security-only --fail exits when security diff for sso access control config', withDefaultFixture(async (fixture) => { - await expect( - fixture.cdk( - ['diff', '--security-only', '--fail', fixture.fullStackName('sso-access-control')], - ), - ).rejects - .toThrow('exited with error'); -})); - -integTest('cdk diff --security-only --fail exits when security diff for sso-perm-set-without-managed-policy', withDefaultFixture(async (fixture) => { - await expect( - fixture.cdk( - ['diff', '--security-only', '--fail', fixture.fullStackName('sso-perm-set-without-managed-policy')], - ), - ).rejects - .toThrow('exited with error'); -})); - -integTest('cdk diff --security-only --fail exits when security diff for sso-perm-set-with-managed-policy', withDefaultFixture(async (fixture) => { - await expect( - fixture.cdk( - ['diff', '--security-only', '--fail', fixture.fullStackName('sso-perm-set-with-managed-policy')], - ), - ).rejects - .toThrow('exited with error'); -})); - -integTest('cdk diff --security-only --fail exits when security diff for sso-assignment', withDefaultFixture(async (fixture) => { - await expect( - fixture.cdk( - ['diff', '--security-only', '--fail', fixture.fullStackName('sso-assignment')], - ), - ).rejects - .toThrow('exited with error'); -})); - -integTest('cdk diff --security-only --fail exits when security changes are present', withDefaultFixture(async (fixture) => { - const stackName = 'iam-test'; - await expect(fixture.cdk(['diff', '--security-only', '--fail', fixture.fullStackName(stackName)])).rejects.toThrow('exited with error'); -})); - -integTest('cdk diff --quiet does not print \'There were no differences\' message for stacks which have no differences', withDefaultFixture(async (fixture) => { - // GIVEN - await fixture.cdkDeploy('test-1'); - - // WHEN - const diff = await fixture.cdk(['diff', '--quiet', fixture.fullStackName('test-1')]); - - // THEN - expect(diff).not.toContain('Stack test-1'); - expect(diff).not.toContain('There were no differences'); -})); - -integTest('deploy stack with docker asset', withDefaultFixture(async (fixture) => { - await fixture.cdkDeploy('docker'); -})); - -integTest('deploy and test stack with lambda asset', withDefaultFixture(async (fixture) => { - const stackArn = await fixture.cdkDeploy('lambda', { captureStderr: false }); - - const response = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); - const lambdaArn = response.Stacks?.[0].Outputs?.[0].OutputValue; - if (lambdaArn === undefined) { - throw new Error('Stack did not have expected Lambda ARN output'); - } + expect(diff).toContain('Resource'); + expect(diff).toContain('instanceAccessControlConfig'); + + expect(diff).toContain('InstanceArn'); + expect(diff).toContain('arn:aws:sso:::instance/testvalue'); + + expect(diff).toContain('AccessControlAttributes'); + expect(diff).toContain('Key: first, Values: [a]'); + expect(diff).toContain('Key: second, Values: [b]'); + expect(diff).toContain('Key: third, Values: [c]'); + expect(diff).toContain('Key: fourth, Values: [d]'); + expect(diff).toContain('Key: fifth, Values: [e]'); + expect(diff).toContain('Key: sixth, Values: [f]'); + }), +); + +integTest( + 'cdk diff --security-only --fail exits when security diff for sso access control config', + withDefaultFixture(async (fixture) => { + await expect( + fixture.cdk(['diff', '--security-only', '--fail', fixture.fullStackName('sso-access-control')]), + ).rejects.toThrow('exited with error'); + }), +); + +integTest( + 'cdk diff --security-only --fail exits when security diff for sso-perm-set-without-managed-policy', + withDefaultFixture(async (fixture) => { + await expect( + fixture.cdk(['diff', '--security-only', '--fail', fixture.fullStackName('sso-perm-set-without-managed-policy')]), + ).rejects.toThrow('exited with error'); + }), +); + +integTest( + 'cdk diff --security-only --fail exits when security diff for sso-perm-set-with-managed-policy', + withDefaultFixture(async (fixture) => { + await expect( + fixture.cdk(['diff', '--security-only', '--fail', fixture.fullStackName('sso-perm-set-with-managed-policy')]), + ).rejects.toThrow('exited with error'); + }), +); + +integTest( + 'cdk diff --security-only --fail exits when security diff for sso-assignment', + withDefaultFixture(async (fixture) => { + await expect( + fixture.cdk(['diff', '--security-only', '--fail', fixture.fullStackName('sso-assignment')]), + ).rejects.toThrow('exited with error'); + }), +); + +integTest( + 'cdk diff --security-only --fail exits when security changes are present', + withDefaultFixture(async (fixture) => { + const stackName = 'iam-test'; + await expect(fixture.cdk(['diff', '--security-only', '--fail', fixture.fullStackName(stackName)])).rejects.toThrow( + 'exited with error', + ); + }), +); - const output = await fixture.aws.lambda('invoke', { - FunctionName: lambdaArn, - }); +integTest( + "cdk diff --quiet does not print 'There were no differences' message for stacks which have no differences", + withDefaultFixture(async (fixture) => { + // GIVEN + await fixture.cdkDeploy('test-1'); - expect(JSON.stringify(output.Payload)).toContain('dear asset'); -})); - -integTest('cdk ls', withDefaultFixture(async (fixture) => { - const listing = await fixture.cdk(['ls'], { captureStderr: false }); - - const expectedStacks = [ - 'conditional-resource', - 'docker', - 'docker-with-custom-file', - 'failed', - 'iam-test', - 'lambda', - 'missing-ssm-parameter', - 'order-providing', - 'outputs-test-1', - 'outputs-test-2', - 'param-test-1', - 'param-test-2', - 'param-test-3', - 'termination-protection', - 'test-1', - 'test-2', - 'with-nested-stack', - 'with-nested-stack-using-parameters', - 'order-consuming', - ]; - - for (const stack of expectedStacks) { - expect(listing).toContain(fixture.fullStackName(stack)); - } -})); + // WHEN + const diff = await fixture.cdk(['diff', '--quiet', fixture.fullStackName('test-1')]); + + // THEN + expect(diff).not.toContain('Stack test-1'); + expect(diff).not.toContain('There were no differences'); + }), +); + +integTest( + 'deploy stack with docker asset', + withDefaultFixture(async (fixture) => { + await fixture.cdkDeploy('docker'); + }), +); + +integTest( + 'deploy and test stack with lambda asset', + withDefaultFixture(async (fixture) => { + const stackArn = await fixture.cdkDeploy('lambda', { captureStderr: false }); + + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + const lambdaArn = response.Stacks?.[0].Outputs?.[0].OutputValue; + if (lambdaArn === undefined) { + throw new Error('Stack did not have expected Lambda ARN output'); + } + + const output = await fixture.aws.lambda('invoke', { + FunctionName: lambdaArn, + }); + + expect(JSON.stringify(output.Payload)).toContain('dear asset'); + }), +); + +integTest( + 'cdk ls', + withDefaultFixture(async (fixture) => { + const listing = await fixture.cdk(['ls'], { captureStderr: false }); + + const expectedStacks = [ + 'conditional-resource', + 'docker', + 'docker-with-custom-file', + 'failed', + 'iam-test', + 'lambda', + 'missing-ssm-parameter', + 'order-providing', + 'outputs-test-1', + 'outputs-test-2', + 'param-test-1', + 'param-test-2', + 'param-test-3', + 'termination-protection', + 'test-1', + 'test-2', + 'with-nested-stack', + 'with-nested-stack-using-parameters', + 'order-consuming', + ]; + + for (const stack of expectedStacks) { + expect(listing).toContain(fixture.fullStackName(stack)); + } + }), +); /** * Type to store stack dependencies recursively @@ -1015,710 +1188,731 @@ type StackDetails = { dependencies: DependencyDetails[]; }; -integTest('cdk ls --show-dependencies --json', withDefaultFixture(async (fixture) => { - const listing = await fixture.cdk(['ls --show-dependencies --json'], { captureStderr: false }); - - const expectedStacks = [ - { - id: 'test-1', - dependencies: [], - }, - { - id: 'order-providing', - dependencies: [], - }, - { - id: 'order-consuming', - dependencies: [ - { - id: 'order-providing', - dependencies: [], - }, - ], - }, - { - id: 'with-nested-stack', - dependencies: [], - }, - { - id: 'list-stacks', - dependencies: [ - { - id: 'list-stacks/DependentStack', - dependencies: [ - { - id: 'list-stacks/DependentStack/InnerDependentStack', - dependencies: [], - }, - ], - }, - ], - }, - { - id: 'list-multiple-dependent-stacks', - dependencies: [ - { - id: 'list-multiple-dependent-stacks/DependentStack1', - dependencies: [], - }, - { - id: 'list-multiple-dependent-stacks/DependentStack2', - dependencies: [], - }, - ], - }, - ]; +integTest( + 'cdk ls --show-dependencies --json', + withDefaultFixture(async (fixture) => { + const listing = await fixture.cdk(['ls --show-dependencies --json'], { captureStderr: false }); - function validateStackDependencies(stack: StackDetails) { - expect(listing).toContain(stack.id); + const expectedStacks = [ + { + id: 'test-1', + dependencies: [], + }, + { + id: 'order-providing', + dependencies: [], + }, + { + id: 'order-consuming', + dependencies: [ + { + id: 'order-providing', + dependencies: [], + }, + ], + }, + { + id: 'with-nested-stack', + dependencies: [], + }, + { + id: 'list-stacks', + dependencies: [ + { + id: 'list-stacks/DependentStack', + dependencies: [ + { + id: 'list-stacks/DependentStack/InnerDependentStack', + dependencies: [], + }, + ], + }, + ], + }, + { + id: 'list-multiple-dependent-stacks', + dependencies: [ + { + id: 'list-multiple-dependent-stacks/DependentStack1', + dependencies: [], + }, + { + id: 'list-multiple-dependent-stacks/DependentStack2', + dependencies: [], + }, + ], + }, + ]; + + function validateStackDependencies(stack: StackDetails) { + expect(listing).toContain(stack.id); - function validateDependencies(dependencies: DependencyDetails[]) { - for (const dependency of dependencies) { - expect(listing).toContain(dependency.id); - if (dependency.dependencies.length > 0) { - validateDependencies(dependency.dependencies); + function validateDependencies(dependencies: DependencyDetails[]) { + for (const dependency of dependencies) { + expect(listing).toContain(dependency.id); + if (dependency.dependencies.length > 0) { + validateDependencies(dependency.dependencies); + } } } + + if (stack.dependencies.length > 0) { + validateDependencies(stack.dependencies); + } } - if (stack.dependencies.length > 0) { - validateDependencies(stack.dependencies); + for (const stack of expectedStacks) { + validateStackDependencies(stack); } - } + }), +); + +integTest( + 'cdk ls --show-dependencies --json --long', + withDefaultFixture(async (fixture) => { + const listing = await fixture.cdk(['ls --show-dependencies --json --long'], { captureStderr: false }); + + const expectedStacks = [ + { + id: 'order-providing', + name: 'order-providing', + enviroment: { + account: 'unknown-account', + region: 'unknown-region', + name: 'aws://unknown-account/unknown-region', + }, + dependencies: [], + }, + { + id: 'order-consuming', + name: 'order-consuming', + enviroment: { + account: 'unknown-account', + region: 'unknown-region', + name: 'aws://unknown-account/unknown-region', + }, + dependencies: [ + { + id: 'order-providing', + dependencies: [], + }, + ], + }, + ]; + + for (const stack of expectedStacks) { + expect(listing).toContain(fixture.fullStackName(stack.id)); + expect(listing).toContain(fixture.fullStackName(stack.name)); + expect(listing).toContain(stack.enviroment.account); + expect(listing).toContain(stack.enviroment.name); + expect(listing).toContain(stack.enviroment.region); + for (const dependency of stack.dependencies) { + expect(listing).toContain(fixture.fullStackName(dependency.id)); + } + } + }), +); + +integTest( + 'synthing a stage with errors leads to failure', + withDefaultFixture(async (fixture) => { + const output = await fixture.cdk(['synth'], { + allowErrExit: true, + modEnv: { + INTEG_STACK_SET: 'stage-with-errors', + }, + }); - for (const stack of expectedStacks) { - validateStackDependencies(stack); - } -})); - -integTest('cdk ls --show-dependencies --json --long', withDefaultFixture(async (fixture) => { - const listing = await fixture.cdk(['ls --show-dependencies --json --long'], { captureStderr: false }); - - const expectedStacks = [ - { - id: 'order-providing', - name: 'order-providing', - enviroment: { - account: 'unknown-account', - region: 'unknown-region', - name: 'aws://unknown-account/unknown-region', + expect(output).toContain('This is an error'); + }), +); + +integTest( + 'synthing a stage with errors can be suppressed', + withDefaultFixture(async (fixture) => { + await fixture.cdk(['synth', '--no-validation'], { + modEnv: { + INTEG_STACK_SET: 'stage-with-errors', }, - dependencies: [], - }, - { - id: 'order-consuming', - name: 'order-consuming', - enviroment: { - account: 'unknown-account', - region: 'unknown-region', - name: 'aws://unknown-account/unknown-region', + }); + }), +); + +integTest( + 'synth --quiet can be specified in cdk.json', + withDefaultFixture(async (fixture) => { + let cdkJson = JSON.parse(await fs.readFile(path.join(fixture.integTestDir, 'cdk.json'), 'utf8')); + cdkJson = { + ...cdkJson, + quiet: true, + }; + await fs.writeFile(path.join(fixture.integTestDir, 'cdk.json'), JSON.stringify(cdkJson)); + const synthOutput = await fixture.cdk(['synth', fixture.fullStackName('test-2')]); + expect(synthOutput).not.toContain('topic152D84A37'); + }), +); + +integTest( + 'deploy stack without resource', + withDefaultFixture(async (fixture) => { + // Deploy the stack without resources + await fixture.cdkDeploy('conditional-resource', { modEnv: { NO_RESOURCE: 'TRUE' } }); + + // This should have succeeded but not deployed the stack. + await expect( + fixture.aws.cloudFormation('describeStacks', { StackName: fixture.fullStackName('conditional-resource') }), + ).rejects.toThrow('conditional-resource does not exist'); + + // Deploy the stack with resources + await fixture.cdkDeploy('conditional-resource'); + + // Then again WITHOUT resources (this should destroy the stack) + await fixture.cdkDeploy('conditional-resource', { modEnv: { NO_RESOURCE: 'TRUE' } }); + + await expect( + fixture.aws.cloudFormation('describeStacks', { StackName: fixture.fullStackName('conditional-resource') }), + ).rejects.toThrow('conditional-resource does not exist'); + }), +); + +integTest( + 'deploy no stacks with --ignore-no-stacks', + withDefaultFixture(async (fixture) => { + // empty array for stack names + await fixture.cdkDeploy([], { + options: ['--ignore-no-stacks'], + modEnv: { + INTEG_STACK_SET: 'stage-with-no-stacks', }, - dependencies: [ - { - id: 'order-providing', - dependencies: [], + }); + }), +); + +integTest( + 'deploy no stacks error', + withDefaultFixture(async (fixture) => { + // empty array for stack names + await expect( + fixture.cdkDeploy([], { + modEnv: { + INTEG_STACK_SET: 'stage-with-no-stacks', }, - ], - }, - ]; - - for (const stack of expectedStacks) { - expect(listing).toContain(fixture.fullStackName(stack.id)); - expect(listing).toContain(fixture.fullStackName(stack.name)); - expect(listing).toContain(stack.enviroment.account); - expect(listing).toContain(stack.enviroment.name); - expect(listing).toContain(stack.enviroment.region); - for (const dependency of stack.dependencies) { - expect(listing).toContain(fixture.fullStackName(dependency.id)); + }), + ).rejects.toThrow('exited with error'); + }), +); + +integTest( + 'IAM diff', + withDefaultFixture(async (fixture) => { + const output = await fixture.cdk(['diff', fixture.fullStackName('iam-test')]); + + // Roughly check for a table like this: + // + // ┌───┬─────────────────┬────────┬────────────────┬────────────────────────────-──┬───────────┐ + // │ │ Resource │ Effect │ Action │ Principal │ Condition │ + // ├───┼─────────────────┼────────┼────────────────┼───────────────────────────────┼───────────┤ + // │ + │ ${SomeRole.Arn} │ Allow │ sts:AssumeRole │ Service:ec2.amazonaws.com │ │ + // └───┴─────────────────┴────────┴────────────────┴───────────────────────────────┴───────────┘ + + expect(output).toContain('${SomeRole.Arn}'); + expect(output).toContain('sts:AssumeRole'); + expect(output).toContain('ec2.amazonaws.com'); + }), +); + +integTest( + 'fast deploy', + withDefaultFixture(async (fixture) => { + // we are using a stack with a nested stack because CFN will always attempt to + // update a nested stack, which will allow us to verify that updates are actually + // skipped unless --force is specified. + const stackArn = await fixture.cdkDeploy('with-nested-stack', { captureStderr: false }); + const changeSet1 = await getLatestChangeSet(); + + // Deploy the same stack again, there should be no new change set created + await fixture.cdkDeploy('with-nested-stack'); + const changeSet2 = await getLatestChangeSet(); + expect(changeSet2.ChangeSetId).toEqual(changeSet1.ChangeSetId); + + // Deploy the stack again with --force, now we should create a changeset + await fixture.cdkDeploy('with-nested-stack', { options: ['--force'] }); + const changeSet3 = await getLatestChangeSet(); + expect(changeSet3.ChangeSetId).not.toEqual(changeSet2.ChangeSetId); + + // Deploy the stack again with tags, expected to create a new changeset + // even though the resources didn't change. + await fixture.cdkDeploy('with-nested-stack', { options: ['--tags', 'key=value'] }); + const changeSet4 = await getLatestChangeSet(); + expect(changeSet4.ChangeSetId).not.toEqual(changeSet3.ChangeSetId); + + async function getLatestChangeSet() { + const response = await fixture.aws.cloudFormation('describeStacks', { StackName: stackArn }); + if (!response.Stacks?.[0]) { + throw new Error('Did not get a ChangeSet at all'); + } + fixture.log(`Found Change Set ${response.Stacks?.[0].ChangeSetId}`); + return response.Stacks?.[0]; } - } + }), +); + +integTest( + 'failed deploy does not hang', + withDefaultFixture(async (fixture) => { + // this will hang if we introduce https://github.com/aws/aws-cdk/issues/6403 again. + await expect(fixture.cdkDeploy('failed')).rejects.toThrow('exited with error'); + }), +); + +integTest( + 'can still load old assemblies', + withDefaultFixture(async (fixture) => { + const cxAsmDir = path.join(os.tmpdir(), 'cdk-integ-cx'); + + const testAssembliesDirectory = path.join(RESOURCES_DIR, 'cloud-assemblies'); + for (const asmdir of await listChildDirs(testAssembliesDirectory)) { + fixture.log(`ASSEMBLY ${asmdir}`); + await cloneDirectory(asmdir, cxAsmDir); + + // Some files in the asm directory that have a .js extension are + // actually treated as templates. Evaluate them using NodeJS. + const templates = await listChildren(cxAsmDir, (fullPath) => Promise.resolve(fullPath.endsWith('.js'))); + for (const template of templates) { + const targetName = template.replace(/.js$/, ''); + await shell([process.execPath, template, '>', targetName], { + cwd: cxAsmDir, + output: fixture.output, + modEnv: { + TEST_ACCOUNT: await fixture.aws.account(), + TEST_REGION: fixture.aws.region, + }, + }); + } -})); + // Use this directory as a Cloud Assembly + const output = await fixture.cdk(['--app', cxAsmDir, '-v', 'synth']); -integTest('synthing a stage with errors leads to failure', withDefaultFixture(async (fixture) => { - const output = await fixture.cdk(['synth'], { - allowErrExit: true, - modEnv: { - INTEG_STACK_SET: 'stage-with-errors', - }, - }); + // Assert that there was no providerError in CDK's stderr + // Because we rely on the app/framework to actually error in case the + // provider fails, we inspect the logs here. + expect(output).not.toContain('$providerError'); + } + }), +); + +integTest( + 'generating and loading assembly', + withDefaultFixture(async (fixture) => { + const asmOutputDir = `${fixture.integTestDir}-cdk-integ-asm`; + await fixture.shell(['rm', '-rf', asmOutputDir]); + + // Synthesize a Cloud Assembly tothe default directory (cdk.out) and a specific directory. + await fixture.cdk(['synth']); + await fixture.cdk(['synth', '--output', asmOutputDir]); + + // cdk.out in the current directory and the indicated --output should be the same + await fixture.shell(['diff', 'cdk.out', asmOutputDir]); + + // Check that we can 'ls' the synthesized asm. + // Change to some random directory to make sure we're not accidentally loading cdk.json + const list = await fixture.cdk(['--app', asmOutputDir, 'ls'], { cwd: os.tmpdir() }); + // Same stacks we know are in the app + expect(list).toContain(`${fixture.stackNamePrefix}-lambda`); + expect(list).toContain(`${fixture.stackNamePrefix}-test-1`); + expect(list).toContain(`${fixture.stackNamePrefix}-test-2`); + + // Check that we can use '.' and just synth ,the generated asm + const stackTemplate = await fixture.cdk(['--app', '.', 'synth', fixture.fullStackName('test-2')], { + cwd: asmOutputDir, + }); + expect(stackTemplate).toContain('topic152D84A37'); - expect(output).toContain('This is an error'); -})); + // Deploy a Lambda from the copied asm + await fixture.cdkDeploy('lambda', { options: ['-a', '.'], cwd: asmOutputDir }); -integTest('synthing a stage with errors can be suppressed', withDefaultFixture(async (fixture) => { - await fixture.cdk(['synth', '--no-validation'], { - modEnv: { - INTEG_STACK_SET: 'stage-with-errors', - }, - }); -})); - -integTest('synth --quiet can be specified in cdk.json', withDefaultFixture(async (fixture) => { - let cdkJson = JSON.parse(await fs.readFile(path.join(fixture.integTestDir, 'cdk.json'), 'utf8')); - cdkJson = { - ...cdkJson, - quiet: true, - }; - await fs.writeFile(path.join(fixture.integTestDir, 'cdk.json'), JSON.stringify(cdkJson)); - const synthOutput = await fixture.cdk(['synth', fixture.fullStackName('test-2')]); - expect(synthOutput).not.toContain('topic152D84A37'); -})); - -integTest('deploy stack without resource', withDefaultFixture(async (fixture) => { - // Deploy the stack without resources - await fixture.cdkDeploy('conditional-resource', { modEnv: { NO_RESOURCE: 'TRUE' } }); - - // This should have succeeded but not deployed the stack. - await expect(fixture.aws.cloudFormation('describeStacks', { StackName: fixture.fullStackName('conditional-resource') })) - .rejects.toThrow('conditional-resource does not exist'); - - // Deploy the stack with resources - await fixture.cdkDeploy('conditional-resource'); - - // Then again WITHOUT resources (this should destroy the stack) - await fixture.cdkDeploy('conditional-resource', { modEnv: { NO_RESOURCE: 'TRUE' } }); - - await expect(fixture.aws.cloudFormation('describeStacks', { StackName: fixture.fullStackName('conditional-resource') })) - .rejects.toThrow('conditional-resource does not exist'); -})); - -integTest('deploy no stacks with --ignore-no-stacks', withDefaultFixture(async (fixture) => { - // empty array for stack names - await fixture.cdkDeploy([], { - options: ['--ignore-no-stacks'], - modEnv: { - INTEG_STACK_SET: 'stage-with-no-stacks', - }, - }); -})); - -integTest('deploy no stacks error', withDefaultFixture(async (fixture) => { - // empty array for stack names - await expect(fixture.cdkDeploy([], { - modEnv: { - INTEG_STACK_SET: 'stage-with-no-stacks', - }, - })).rejects.toThrow('exited with error'); -})); - -integTest('IAM diff', withDefaultFixture(async (fixture) => { - const output = await fixture.cdk(['diff', fixture.fullStackName('iam-test')]); - - // Roughly check for a table like this: - // - // ┌───┬─────────────────┬────────┬────────────────┬────────────────────────────-──┬───────────┐ - // │ │ Resource │ Effect │ Action │ Principal │ Condition │ - // ├───┼─────────────────┼────────┼────────────────┼───────────────────────────────┼───────────┤ - // │ + │ ${SomeRole.Arn} │ Allow │ sts:AssumeRole │ Service:ec2.amazonaws.com │ │ - // └───┴─────────────────┴────────┴────────────────┴───────────────────────────────┴───────────┘ - - expect(output).toContain('${SomeRole.Arn}'); - expect(output).toContain('sts:AssumeRole'); - expect(output).toContain('ec2.amazonaws.com'); -})); - -integTest('fast deploy', withDefaultFixture(async (fixture) => { - // we are using a stack with a nested stack because CFN will always attempt to - // update a nested stack, which will allow us to verify that updates are actually - // skipped unless --force is specified. - const stackArn = await fixture.cdkDeploy('with-nested-stack', { captureStderr: false }); - const changeSet1 = await getLatestChangeSet(); - - // Deploy the same stack again, there should be no new change set created - await fixture.cdkDeploy('with-nested-stack'); - const changeSet2 = await getLatestChangeSet(); - expect(changeSet2.ChangeSetId).toEqual(changeSet1.ChangeSetId); - - // Deploy the stack again with --force, now we should create a changeset - await fixture.cdkDeploy('with-nested-stack', { options: ['--force'] }); - const changeSet3 = await getLatestChangeSet(); - expect(changeSet3.ChangeSetId).not.toEqual(changeSet2.ChangeSetId); - - // Deploy the stack again with tags, expected to create a new changeset - // even though the resources didn't change. - await fixture.cdkDeploy('with-nested-stack', { options: ['--tags', 'key=value'] }); - const changeSet4 = await getLatestChangeSet(); - expect(changeSet4.ChangeSetId).not.toEqual(changeSet3.ChangeSetId); - - async function getLatestChangeSet() { - const response = await fixture.aws.cloudFormation('describeStacks', { StackName: stackArn }); - if (!response.Stacks?.[0]) { throw new Error('Did not get a ChangeSet at all'); } - fixture.log(`Found Change Set ${response.Stacks?.[0].ChangeSetId}`); - return response.Stacks?.[0]; - } -})); - -integTest('failed deploy does not hang', withDefaultFixture(async (fixture) => { - // this will hang if we introduce https://github.com/aws/aws-cdk/issues/6403 again. - await expect(fixture.cdkDeploy('failed')).rejects.toThrow('exited with error'); -})); - -integTest('can still load old assemblies', withDefaultFixture(async (fixture) => { - const cxAsmDir = path.join(os.tmpdir(), 'cdk-integ-cx'); - - const testAssembliesDirectory = path.join(RESOURCES_DIR, 'cloud-assemblies'); - for (const asmdir of await listChildDirs(testAssembliesDirectory)) { - fixture.log(`ASSEMBLY ${asmdir}`); - await cloneDirectory(asmdir, cxAsmDir); - - // Some files in the asm directory that have a .js extension are - // actually treated as templates. Evaluate them using NodeJS. - const templates = await listChildren(cxAsmDir, fullPath => Promise.resolve(fullPath.endsWith('.js'))); - for (const template of templates) { - const targetName = template.replace(/.js$/, ''); - await shell([process.execPath, template, '>', targetName], { - cwd: cxAsmDir, - output: fixture.output, - modEnv: { - TEST_ACCOUNT: await fixture.aws.account(), - TEST_REGION: fixture.aws.region, - }, - }); + // Remove (rename) the original custom docker file that was used during synth. + // this verifies that the assemly has a copy of it and that the manifest uses + // relative paths to reference to it. + const customDockerFile = path.join(fixture.integTestDir, 'docker', 'Dockerfile.Custom'); + await fs.rename(customDockerFile, `${customDockerFile}~`); + try { + // deploy a docker image with custom file without synth (uses assets) + await fixture.cdkDeploy('docker-with-custom-file', { options: ['-a', '.'], cwd: asmOutputDir }); + } finally { + // Rename back to restore fixture to original state + await fs.rename(`${customDockerFile}~`, customDockerFile); } + }), +); - // Use this directory as a Cloud Assembly - const output = await fixture.cdk([ - '--app', cxAsmDir, - '-v', - 'synth', - ]); +integTest( + 'templates on disk contain metadata resource, also in nested assemblies', + withDefaultFixture(async (fixture) => { + // Synth first, and switch on version reporting because cdk.json is disabling it + await fixture.cdk(['synth', '--version-reporting=true']); - // Assert that there was no providerError in CDK's stderr - // Because we rely on the app/framework to actually error in case the - // provider fails, we inspect the logs here. - expect(output).not.toContain('$providerError'); - } -})); - -integTest('generating and loading assembly', withDefaultFixture(async (fixture) => { - const asmOutputDir = `${fixture.integTestDir}-cdk-integ-asm`; - await fixture.shell(['rm', '-rf', asmOutputDir]); - - // Synthesize a Cloud Assembly tothe default directory (cdk.out) and a specific directory. - await fixture.cdk(['synth']); - await fixture.cdk(['synth', '--output', asmOutputDir]); - - // cdk.out in the current directory and the indicated --output should be the same - await fixture.shell(['diff', 'cdk.out', asmOutputDir]); - - // Check that we can 'ls' the synthesized asm. - // Change to some random directory to make sure we're not accidentally loading cdk.json - const list = await fixture.cdk(['--app', asmOutputDir, 'ls'], { cwd: os.tmpdir() }); - // Same stacks we know are in the app - expect(list).toContain(`${fixture.stackNamePrefix}-lambda`); - expect(list).toContain(`${fixture.stackNamePrefix}-test-1`); - expect(list).toContain(`${fixture.stackNamePrefix}-test-2`); - - // Check that we can use '.' and just synth ,the generated asm - const stackTemplate = await fixture.cdk(['--app', '.', 'synth', fixture.fullStackName('test-2')], { - cwd: asmOutputDir, - }); - expect(stackTemplate).toContain('topic152D84A37'); + // Load template from disk from root assembly + const templateContents = await fixture.shell(['cat', 'cdk.out/*-lambda.template.json']); - // Deploy a Lambda from the copied asm - await fixture.cdkDeploy('lambda', { options: ['-a', '.'], cwd: asmOutputDir }); + expect(JSON.parse(templateContents).Resources.CDKMetadata).toBeTruthy(); - // Remove (rename) the original custom docker file that was used during synth. - // this verifies that the assemly has a copy of it and that the manifest uses - // relative paths to reference to it. - const customDockerFile = path.join(fixture.integTestDir, 'docker', 'Dockerfile.Custom'); - await fs.rename(customDockerFile, `${customDockerFile}~`); - try { + // Load template from nested assembly + const nestedTemplateContents = await fixture.shell([ + 'cat', + 'cdk.out/assembly-*-stage/*StackInStage*.template.json', + ]); - // deploy a docker image with custom file without synth (uses assets) - await fixture.cdkDeploy('docker-with-custom-file', { options: ['-a', '.'], cwd: asmOutputDir }); + expect(JSON.parse(nestedTemplateContents).Resources.CDKMetadata).toBeTruthy(); + }), +); + +integTest( + 'CDK synth add the metadata properties expected by sam', + withSamIntegrationFixture(async (fixture) => { + // Synth first + await fixture.cdkSynth(); + + const template = fixture.template('TestStack'); + + const expectedResources = [ + { + // Python Layer Version + id: 'PythonLayerVersion39495CEF', + cdkId: 'PythonLayerVersion', + isBundled: true, + property: 'Content', + }, + { + // Layer Version + id: 'LayerVersion3878DA3A', + cdkId: 'LayerVersion', + isBundled: false, + property: 'Content', + }, + { + // Bundled layer version + id: 'BundledLayerVersionPythonRuntime6BADBD6E', + cdkId: 'BundledLayerVersionPythonRuntime', + isBundled: true, + property: 'Content', + }, + { + // Python Function + id: 'PythonFunction0BCF77FD', + cdkId: 'PythonFunction', + isBundled: true, + property: 'Code', + }, + { + // Log Retention Function + id: 'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A', + cdkId: 'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a', + isBundled: false, + property: 'Code', + }, + { + // Function + id: 'FunctionPythonRuntime28CBDA05', + cdkId: 'FunctionPythonRuntime', + isBundled: false, + property: 'Code', + }, + { + // Bundled Function + id: 'BundledFunctionPythonRuntime4D9A0918', + cdkId: 'BundledFunctionPythonRuntime', + isBundled: true, + property: 'Code', + }, + { + // NodeJs Function + id: 'NodejsFunction09C1F20F', + cdkId: 'NodejsFunction', + isBundled: true, + property: 'Code', + }, + { + // Go Function + id: 'GoFunctionCA95FBAA', + cdkId: 'GoFunction', + isBundled: true, + property: 'Code', + }, + { + // Docker Image Function + id: 'DockerImageFunction28B773E6', + cdkId: 'DockerImageFunction', + dockerFilePath: 'Dockerfile', + property: 'Code.ImageUri', + }, + { + // Spec Rest Api + id: 'SpecRestAPI7D4B3A34', + cdkId: 'SpecRestAPI', + property: 'BodyS3Location', + }, + ]; + + for (const resource of expectedResources) { + fixture.output.write(`validate assets metadata for resource ${resource}`); + expect(resource.id in template.Resources).toBeTruthy(); + expect(template.Resources[resource.id]).toEqual( + expect.objectContaining({ + Metadata: { + 'aws:cdk:path': `${fixture.fullStackName('TestStack')}/${resource.cdkId}/Resource`, + 'aws:asset:path': expect.stringMatching(/asset\.[0-9a-zA-Z]{64}/), + 'aws:asset:is-bundled': resource.isBundled, + 'aws:asset:dockerfile-path': resource.dockerFilePath, + 'aws:asset:property': resource.property, + }, + }), + ); + } - } finally { - // Rename back to restore fixture to original state - await fs.rename(`${customDockerFile}~`, customDockerFile); - } -})); - -integTest('templates on disk contain metadata resource, also in nested assemblies', withDefaultFixture(async (fixture) => { - // Synth first, and switch on version reporting because cdk.json is disabling it - await fixture.cdk(['synth', '--version-reporting=true']); - - // Load template from disk from root assembly - const templateContents = await fixture.shell(['cat', 'cdk.out/*-lambda.template.json']); - - expect(JSON.parse(templateContents).Resources.CDKMetadata).toBeTruthy(); - - // Load template from nested assembly - const nestedTemplateContents = await fixture.shell(['cat', 'cdk.out/assembly-*-stage/*StackInStage*.template.json']); - - expect(JSON.parse(nestedTemplateContents).Resources.CDKMetadata).toBeTruthy(); -})); - -integTest('CDK synth add the metadata properties expected by sam', withSamIntegrationFixture(async (fixture) => { - // Synth first - await fixture.cdkSynth(); - - const template = fixture.template('TestStack'); - - const expectedResources = [ - { - // Python Layer Version - id: 'PythonLayerVersion39495CEF', - cdkId: 'PythonLayerVersion', - isBundled: true, - property: 'Content', - }, - { - // Layer Version - id: 'LayerVersion3878DA3A', - cdkId: 'LayerVersion', - isBundled: false, - property: 'Content', - }, - { - // Bundled layer version - id: 'BundledLayerVersionPythonRuntime6BADBD6E', - cdkId: 'BundledLayerVersionPythonRuntime', - isBundled: true, - property: 'Content', - }, - { - // Python Function - id: 'PythonFunction0BCF77FD', - cdkId: 'PythonFunction', - isBundled: true, - property: 'Code', - }, - { - // Log Retention Function - id: 'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A', - cdkId: 'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a', - isBundled: false, - property: 'Code', - }, - { - // Function - id: 'FunctionPythonRuntime28CBDA05', - cdkId: 'FunctionPythonRuntime', - isBundled: false, - property: 'Code', - }, - { - // Bundled Function - id: 'BundledFunctionPythonRuntime4D9A0918', - cdkId: 'BundledFunctionPythonRuntime', - isBundled: true, - property: 'Code', - }, - { - // NodeJs Function - id: 'NodejsFunction09C1F20F', - cdkId: 'NodejsFunction', - isBundled: true, - property: 'Code', - }, - { - // Go Function - id: 'GoFunctionCA95FBAA', - cdkId: 'GoFunction', - isBundled: true, - property: 'Code', - }, - { - // Docker Image Function - id: 'DockerImageFunction28B773E6', - cdkId: 'DockerImageFunction', - dockerFilePath: 'Dockerfile', - property: 'Code.ImageUri', - }, - { - // Spec Rest Api - id: 'SpecRestAPI7D4B3A34', - cdkId: 'SpecRestAPI', - property: 'BodyS3Location', - }, - ]; - - for (const resource of expectedResources) { - fixture.output.write(`validate assets metadata for resource ${resource}`); - expect(resource.id in template.Resources).toBeTruthy(); - expect(template.Resources[resource.id]).toEqual(expect.objectContaining({ - Metadata: { - 'aws:cdk:path': `${fixture.fullStackName('TestStack')}/${resource.cdkId}/Resource`, - 'aws:asset:path': expect.stringMatching(/asset\.[0-9a-zA-Z]{64}/), - 'aws:asset:is-bundled': resource.isBundled, - 'aws:asset:dockerfile-path': resource.dockerFilePath, - 'aws:asset:property': resource.property, + // Nested Stack + fixture.output.write('validate assets metadata for nested stack resource'); + expect('NestedStackNestedStackNestedStackNestedStackResourceB70834FD' in template.Resources).toBeTruthy(); + expect(template.Resources.NestedStackNestedStackNestedStackNestedStackResourceB70834FD).toEqual( + expect.objectContaining({ + Metadata: { + 'aws:cdk:path': `${fixture.fullStackName('TestStack')}/NestedStack.NestedStack/NestedStack.NestedStackResource`, + 'aws:asset:path': expect.stringMatching( + `${fixture.stackNamePrefix.replace(/-/, '')}TestStackNestedStack[0-9A-Z]{8}\.nested\.template\.json`, + ), + 'aws:asset:property': 'TemplateURL', + }, + }), + ); + }), +); + +integTest( + 'CDK synth bundled functions as expected', + withSamIntegrationFixture(async (fixture) => { + // Synth first + await fixture.cdkSynth(); + + const template = fixture.template('TestStack'); + + const expectedBundledAssets = [ + { + // Python Layer Version + id: 'PythonLayerVersion39495CEF', + files: [ + 'python/layer_version_dependency.py', + 'python/geonamescache/__init__.py', + 'python/geonamescache-1.3.0.dist-info', + ], }, - })); - } + { + // Layer Version + id: 'LayerVersion3878DA3A', + files: ['layer_version_dependency.py', 'requirements.txt'], + }, + { + // Bundled layer version + id: 'BundledLayerVersionPythonRuntime6BADBD6E', + files: [ + 'python/layer_version_dependency.py', + 'python/geonamescache/__init__.py', + 'python/geonamescache-1.3.0.dist-info', + ], + }, + { + // Python Function + id: 'PythonFunction0BCF77FD', + files: ['app.py', 'geonamescache/__init__.py', 'geonamescache-1.3.0.dist-info'], + }, + { + // Function + id: 'FunctionPythonRuntime28CBDA05', + files: ['app.py', 'requirements.txt'], + }, + { + // Bundled Function + id: 'BundledFunctionPythonRuntime4D9A0918', + files: ['app.py', 'geonamescache/__init__.py', 'geonamescache-1.3.0.dist-info'], + }, + { + // NodeJs Function + id: 'NodejsFunction09C1F20F', + files: ['index.js'], + }, + { + // Go Function + id: 'GoFunctionCA95FBAA', + files: ['bootstrap'], + }, + { + // Docker Image Function + id: 'DockerImageFunction28B773E6', + files: ['app.js', 'Dockerfile', 'package.json'], + }, + ]; - // Nested Stack - fixture.output.write('validate assets metadata for nested stack resource'); - expect('NestedStackNestedStackNestedStackNestedStackResourceB70834FD' in template.Resources).toBeTruthy(); - expect(template.Resources.NestedStackNestedStackNestedStackNestedStackResourceB70834FD).toEqual(expect.objectContaining({ - Metadata: { - 'aws:cdk:path': `${fixture.fullStackName('TestStack')}/NestedStack.NestedStack/NestedStack.NestedStackResource`, - 'aws:asset:path': expect.stringMatching(`${fixture.stackNamePrefix.replace(/-/, '')}TestStackNestedStack[0-9A-Z]{8}\.nested\.template\.json`), - 'aws:asset:property': 'TemplateURL', - }, - })); -})); - -integTest('CDK synth bundled functions as expected', withSamIntegrationFixture(async (fixture) => { - // Synth first - await fixture.cdkSynth(); - - const template = fixture.template('TestStack'); - - const expectedBundledAssets = [ - { - // Python Layer Version - id: 'PythonLayerVersion39495CEF', - files: [ - 'python/layer_version_dependency.py', - 'python/geonamescache/__init__.py', - 'python/geonamescache-1.3.0.dist-info', - ], - }, - { - // Layer Version - id: 'LayerVersion3878DA3A', - files: [ - 'layer_version_dependency.py', - 'requirements.txt', - ], - }, - { - // Bundled layer version - id: 'BundledLayerVersionPythonRuntime6BADBD6E', - files: [ - 'python/layer_version_dependency.py', - 'python/geonamescache/__init__.py', - 'python/geonamescache-1.3.0.dist-info', - ], - }, - { - // Python Function - id: 'PythonFunction0BCF77FD', - files: [ - 'app.py', - 'geonamescache/__init__.py', - 'geonamescache-1.3.0.dist-info', - ], - }, - { - // Function - id: 'FunctionPythonRuntime28CBDA05', - files: [ - 'app.py', - 'requirements.txt', - ], - }, - { - // Bundled Function - id: 'BundledFunctionPythonRuntime4D9A0918', - files: [ - 'app.py', - 'geonamescache/__init__.py', - 'geonamescache-1.3.0.dist-info', - ], - }, - { - // NodeJs Function - id: 'NodejsFunction09C1F20F', - files: [ - 'index.js', - ], - }, - { - // Go Function - id: 'GoFunctionCA95FBAA', - files: [ - 'bootstrap', - ], - }, - { - // Docker Image Function - id: 'DockerImageFunction28B773E6', - files: [ - 'app.js', - 'Dockerfile', - 'package.json', - ], - }, - ]; - - for (const resource of expectedBundledAssets) { - const assetPath = template.Resources[resource.id].Metadata['aws:asset:path']; - for (const file of resource.files) { - fixture.output.write(`validate Path ${file} for resource ${resource}`); - expect(existsSync(path.join(fixture.integTestDir, 'cdk.out', assetPath, file))).toBeTruthy(); + for (const resource of expectedBundledAssets) { + const assetPath = template.Resources[resource.id].Metadata['aws:asset:path']; + for (const file of resource.files) { + fixture.output.write(`validate Path ${file} for resource ${resource}`); + expect(existsSync(path.join(fixture.integTestDir, 'cdk.out', assetPath, file))).toBeTruthy(); + } } - } -})); - -integTest('sam can locally test the synthesized cdk application', withSamIntegrationFixture(async (fixture) => { - // Synth first - await fixture.cdkSynth(); - - const result = await fixture.samLocalStartApi( - 'TestStack', false, randomInteger(30000, 40000), '/restapis/spec/pythonFunction'); - expect(result.actionSucceeded).toBeTruthy(); - expect(result.actionOutput).toEqual(expect.objectContaining({ - message: 'Hello World', - })); -})); - -integTest('skips notice refresh', withDefaultFixture(async (fixture) => { - const output = await fixture.cdkSynth({ - options: ['--no-notices'], - modEnv: { - INTEG_STACK_SET: 'stage-using-context', - }, - allowErrExit: true, - }); + }), +); + +integTest( + 'sam can locally test the synthesized cdk application', + withSamIntegrationFixture(async (fixture) => { + // Synth first + await fixture.cdkSynth(); + + const result = await fixture.samLocalStartApi( + 'TestStack', + false, + randomInteger(30000, 40000), + '/restapis/spec/pythonFunction', + ); + expect(result.actionSucceeded).toBeTruthy(); + expect(result.actionOutput).toEqual( + expect.objectContaining({ + message: 'Hello World', + }), + ); + }), +); + +integTest( + 'skips notice refresh', + withDefaultFixture(async (fixture) => { + const output = await fixture.cdkSynth({ + options: ['--no-notices'], + modEnv: { + INTEG_STACK_SET: 'stage-using-context', + }, + allowErrExit: true, + }); - // Neither succeeds nor fails, but skips the refresh - await expect(output).not.toContain('Notices refreshed'); - await expect(output).not.toContain('Notices refresh failed'); -})); + // Neither succeeds nor fails, but skips the refresh + await expect(output).not.toContain('Notices refreshed'); + await expect(output).not.toContain('Notices refresh failed'); + }), +); /** * Create a queue, orphan that queue, then import the queue. * * We want to test with a large template to make sure large templates can work with import. */ -integTest('test resource import', withDefaultFixture(async (fixture) => { - // GIVEN - const randomPrefix = randomString(); - const uniqueOutputsFileName = `${randomPrefix}Outputs.json`; // other tests use the outputs file. Make sure we don't collide. - const outputsFile = path.join(fixture.integTestDir, 'outputs', uniqueOutputsFileName); - await fs.mkdir(path.dirname(outputsFile), { recursive: true }); - - // First, create a stack that includes many queues, and one queue that will be removed from the stack but NOT deleted from AWS. - await fixture.cdkDeploy('importable-stack', { - modEnv: { LARGE_TEMPLATE: '1', INCLUDE_SINGLE_QUEUE: '1', RETAIN_SINGLE_QUEUE: '1' }, - options: ['--outputs-file', outputsFile], - }); - - try { - - // Second, now the queue we will remove is in the stack and has a logicalId. We can now make the resource mapping file. - // This resource mapping file will be used to tell the import operation what queue to bring into the stack. - const fullStackName = fixture.fullStackName('importable-stack'); - const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString()); - const queueLogicalId = outputs[fullStackName].QueueLogicalId; - const queueResourceMap = { - [queueLogicalId]: { QueueUrl: outputs[fullStackName].QueueUrl }, - }; - const mappingFile = path.join(fixture.integTestDir, 'outputs', `${randomPrefix}Mapping.json`); - await fs.writeFile( - mappingFile, - JSON.stringify(queueResourceMap), - { encoding: 'utf-8' }, - ); - - // Third, remove the queue from the stack, but don't delete the queue from AWS. +integTest( + 'test resource import', + withDefaultFixture(async (fixture) => { + // GIVEN + const randomPrefix = randomString(); + const uniqueOutputsFileName = `${randomPrefix}Outputs.json`; // other tests use the outputs file. Make sure we don't collide. + const outputsFile = path.join(fixture.integTestDir, 'outputs', uniqueOutputsFileName); + await fs.mkdir(path.dirname(outputsFile), { recursive: true }); + + // First, create a stack that includes many queues, and one queue that will be removed from the stack but NOT deleted from AWS. await fixture.cdkDeploy('importable-stack', { - modEnv: { LARGE_TEMPLATE: '1', INCLUDE_SINGLE_QUEUE: '0', RETAIN_SINGLE_QUEUE: '0' }, + modEnv: { LARGE_TEMPLATE: '1', INCLUDE_SINGLE_QUEUE: '1', RETAIN_SINGLE_QUEUE: '1' }, + options: ['--outputs-file', outputsFile], }); - const cfnTemplateBeforeImport = await fixture.aws.cloudFormation('getTemplate', { StackName: fullStackName }); - expect(cfnTemplateBeforeImport.TemplateBody).not.toContain(queueLogicalId); - // WHEN - await fixture.cdk( - ['import', '--resource-mapping', mappingFile, fixture.fullStackName('importable-stack')], - { modEnv: { LARGE_TEMPLATE: '1', INCLUDE_SINGLE_QUEUE: '1', RETAIN_SINGLE_QUEUE: '0' } }, - ); - - // THEN - const describeStacksResponse = await fixture.aws.cloudFormation('describeStacks', { StackName: fullStackName }); - const cfnTemplateAfterImport = await fixture.aws.cloudFormation('getTemplate', { StackName: fullStackName }); - expect(describeStacksResponse.Stacks![0].StackStatus).toEqual('IMPORT_COMPLETE'); - expect(cfnTemplateAfterImport.TemplateBody).toContain(queueLogicalId); - } finally { - // Clean up - await fixture.cdkDestroy('importable-stack'); - } -})); + try { + // Second, now the queue we will remove is in the stack and has a logicalId. We can now make the resource mapping file. + // This resource mapping file will be used to tell the import operation what queue to bring into the stack. + const fullStackName = fixture.fullStackName('importable-stack'); + const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString()); + const queueLogicalId = outputs[fullStackName].QueueLogicalId; + const queueResourceMap = { + [queueLogicalId]: { QueueUrl: outputs[fullStackName].QueueUrl }, + }; + const mappingFile = path.join(fixture.integTestDir, 'outputs', `${randomPrefix}Mapping.json`); + await fs.writeFile(mappingFile, JSON.stringify(queueResourceMap), { encoding: 'utf-8' }); -integTest('test migrate deployment for app with localfile source in migrate.json', withDefaultFixture(async (fixture) => { - const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json'); - await fs.mkdir(path.dirname(outputsFile), { recursive: true }); + // Third, remove the queue from the stack, but don't delete the queue from AWS. + await fixture.cdkDeploy('importable-stack', { + modEnv: { LARGE_TEMPLATE: '1', INCLUDE_SINGLE_QUEUE: '0', RETAIN_SINGLE_QUEUE: '0' }, + }); + const cfnTemplateBeforeImport = await fixture.aws.cloudFormation('getTemplate', { StackName: fullStackName }); + expect(cfnTemplateBeforeImport.TemplateBody).not.toContain(queueLogicalId); - // Initial deploy - await fixture.cdkDeploy('migrate-stack', { - modEnv: { ORPHAN_TOPIC: '1' }, - options: ['--outputs-file', outputsFile], - }); + // WHEN + await fixture.cdk(['import', '--resource-mapping', mappingFile, fixture.fullStackName('importable-stack')], { + modEnv: { LARGE_TEMPLATE: '1', INCLUDE_SINGLE_QUEUE: '1', RETAIN_SINGLE_QUEUE: '0' }, + }); - const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString()); - const stackName = fixture.fullStackName('migrate-stack'); - const queueName = outputs[stackName].QueueName; - const queueUrl = outputs[stackName].QueueUrl; - const queueLogicalId = outputs[stackName].QueueLogicalId; - fixture.log(`Created queue ${queueUrl} in stack ${fixture.fullStackName}`); - - // Write the migrate file based on the ID from step one, then deploy the app with migrate - const migrateFile = path.join(fixture.integTestDir, 'migrate.json'); - await fs.writeFile( - migrateFile, JSON.stringify( - { Source: 'localfile', Resources: [{ ResourceType: 'AWS::SQS::Queue', LogicalResourceId: queueLogicalId, ResourceIdentifier: { QueueUrl: queueUrl } }] }, - ), - { encoding: 'utf-8' }, - ); + // THEN + const describeStacksResponse = await fixture.aws.cloudFormation('describeStacks', { StackName: fullStackName }); + const cfnTemplateAfterImport = await fixture.aws.cloudFormation('getTemplate', { StackName: fullStackName }); + expect(describeStacksResponse.Stacks![0].StackStatus).toEqual('IMPORT_COMPLETE'); + expect(cfnTemplateAfterImport.TemplateBody).toContain(queueLogicalId); + } finally { + // Clean up + await fixture.cdkDestroy('importable-stack'); + } + }), +); + +integTest( + 'test migrate deployment for app with localfile source in migrate.json', + withDefaultFixture(async (fixture) => { + const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json'); + await fs.mkdir(path.dirname(outputsFile), { recursive: true }); + + // Initial deploy + await fixture.cdkDeploy('migrate-stack', { + modEnv: { ORPHAN_TOPIC: '1' }, + options: ['--outputs-file', outputsFile], + }); - await fixture.cdkDestroy('migrate-stack'); - fixture.log(`Deleted stack ${fixture.fullStackName}, orphaning ${queueName}`); + const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString()); + const stackName = fixture.fullStackName('migrate-stack'); + const queueName = outputs[stackName].QueueName; + const queueUrl = outputs[stackName].QueueUrl; + const queueLogicalId = outputs[stackName].QueueLogicalId; + fixture.log(`Created queue ${queueUrl} in stack ${fixture.fullStackName}`); + + // Write the migrate file based on the ID from step one, then deploy the app with migrate + const migrateFile = path.join(fixture.integTestDir, 'migrate.json'); + await fs.writeFile( + migrateFile, + JSON.stringify({ + Source: 'localfile', + Resources: [ + { + ResourceType: 'AWS::SQS::Queue', + LogicalResourceId: queueLogicalId, + ResourceIdentifier: { QueueUrl: queueUrl }, + }, + ], + }), + { encoding: 'utf-8' }, + ); - // Create new stack from existing queue - try { - fixture.log(`Deploying new stack ${fixture.fullStackName}, migrating ${queueName} into stack`); - await fixture.cdkDeploy('migrate-stack'); - } finally { - // Cleanup await fixture.cdkDestroy('migrate-stack'); - } -})); - -integTest('hotswap deployment supports Lambda function\'s description and environment variables', withDefaultFixture(async (fixture) => { - // GIVEN - const stackArn = await fixture.cdkDeploy('lambda-hotswap', { - captureStderr: false, - modEnv: { - DYNAMIC_LAMBDA_PROPERTY_VALUE: 'original value', - }, - }); - - // WHEN - const deployOutput = await fixture.cdkDeploy('lambda-hotswap', { - options: ['--hotswap'], - captureStderr: true, - onlyStderr: true, - modEnv: { - DYNAMIC_LAMBDA_PROPERTY_VALUE: 'new value', - }, - }); - - const response = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); - const functionName = response.Stacks?.[0].Outputs?.[0].OutputValue; - - // THEN + fixture.log(`Deleted stack ${fixture.fullStackName}, orphaning ${queueName}`); - // The deployment should not trigger a full deployment, thus the stack's status must remains - // "CREATE_COMPLETE" - expect(response.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); - expect(deployOutput).toContain(`Lambda Function '${functionName}' hotswapped!`); -})); + // Create new stack from existing queue + try { + fixture.log(`Deploying new stack ${fixture.fullStackName}, migrating ${queueName} into stack`); + await fixture.cdkDeploy('migrate-stack'); + } finally { + // Cleanup + await fixture.cdkDestroy('migrate-stack'); + } + }), +); -integTest('hotswap deployment supports Fn::ImportValue intrinsic', withDefaultFixture(async (fixture) => { - // GIVEN - try { - await fixture.cdkDeploy('export-value-stack'); +integTest( + "hotswap deployment supports Lambda function's description and environment variables", + withDefaultFixture(async (fixture) => { + // GIVEN const stackArn = await fixture.cdkDeploy('lambda-hotswap', { captureStderr: false, modEnv: { DYNAMIC_LAMBDA_PROPERTY_VALUE: 'original value', - USE_IMPORT_VALUE_LAMBDA_PROPERTY: 'true', }, }); @@ -1729,7 +1923,6 @@ integTest('hotswap deployment supports Fn::ImportValue intrinsic', withDefaultFi onlyStderr: true, modEnv: { DYNAMIC_LAMBDA_PROPERTY_VALUE: 'new value', - USE_IMPORT_VALUE_LAMBDA_PROPERTY: 'true', }, }); @@ -1744,90 +1937,166 @@ integTest('hotswap deployment supports Fn::ImportValue intrinsic', withDefaultFi // "CREATE_COMPLETE" expect(response.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); expect(deployOutput).toContain(`Lambda Function '${functionName}' hotswapped!`); + }), +); - } finally { - // Ensure cleanup in reverse order due to use of import/export - await fixture.cdkDestroy('lambda-hotswap'); - await fixture.cdkDestroy('export-value-stack'); - } -})); +integTest( + 'hotswap deployment supports Fn::ImportValue intrinsic', + withDefaultFixture(async (fixture) => { + // GIVEN + try { + await fixture.cdkDeploy('export-value-stack'); + const stackArn = await fixture.cdkDeploy('lambda-hotswap', { + captureStderr: false, + modEnv: { + DYNAMIC_LAMBDA_PROPERTY_VALUE: 'original value', + USE_IMPORT_VALUE_LAMBDA_PROPERTY: 'true', + }, + }); -integTest('hotswap deployment supports ecs service', withDefaultFixture(async (fixture) => { - // GIVEN - const stackArn = await fixture.cdkDeploy('ecs-hotswap', { - captureStderr: false, - }); + // WHEN + const deployOutput = await fixture.cdkDeploy('lambda-hotswap', { + options: ['--hotswap'], + captureStderr: true, + onlyStderr: true, + modEnv: { + DYNAMIC_LAMBDA_PROPERTY_VALUE: 'new value', + USE_IMPORT_VALUE_LAMBDA_PROPERTY: 'true', + }, + }); - // WHEN - const deployOutput = await fixture.cdkDeploy('ecs-hotswap', { - options: ['--hotswap'], - captureStderr: true, - onlyStderr: true, - modEnv: { - DYNAMIC_ECS_PROPERTY_VALUE: 'new value', - }, - }); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + const functionName = response.Stacks?.[0].Outputs?.[0].OutputValue; - const response = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); - const serviceName = response.Stacks?.[0].Outputs?.find(output => output.OutputKey == 'ServiceName')?.OutputValue; + // THEN - // THEN + // The deployment should not trigger a full deployment, thus the stack's status must remains + // "CREATE_COMPLETE" + expect(response.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); + expect(deployOutput).toContain(`Lambda Function '${functionName}' hotswapped!`); + } finally { + // Ensure cleanup in reverse order due to use of import/export + await fixture.cdkDestroy('lambda-hotswap'); + await fixture.cdkDestroy('export-value-stack'); + } + }), +); + +integTest( + 'hotswap deployment supports ecs service', + withDefaultFixture(async (fixture) => { + // GIVEN + const stackArn = await fixture.cdkDeploy('ecs-hotswap', { + captureStderr: false, + }); - // The deployment should not trigger a full deployment, thus the stack's status must remains - // "CREATE_COMPLETE" - expect(response.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); - expect(deployOutput).toContain(`ECS Service '${serviceName}' hotswapped!`); -})); + // WHEN + const deployOutput = await fixture.cdkDeploy('ecs-hotswap', { + options: ['--hotswap'], + captureStderr: true, + onlyStderr: true, + modEnv: { + DYNAMIC_ECS_PROPERTY_VALUE: 'new value', + }, + }); -integTest('hotswap deployment for ecs service waits for deployment to complete', withDefaultFixture(async (fixture) => { - // GIVEN - const stackArn = await fixture.cdkDeploy('ecs-hotswap', { - captureStderr: false, - }); + const response = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + const serviceName = response.Stacks?.[0].Outputs?.find((output) => output.OutputKey == 'ServiceName')?.OutputValue; - // WHEN - await fixture.cdkDeploy('ecs-hotswap', { - options: ['--hotswap'], - modEnv: { - DYNAMIC_ECS_PROPERTY_VALUE: 'new value', - }, - }); + // THEN - const describeStacksResponse = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); - const clusterName = describeStacksResponse.Stacks?.[0].Outputs?.find(output => output.OutputKey == 'ClusterName')?.OutputValue!; - const serviceName = describeStacksResponse.Stacks?.[0].Outputs?.find(output => output.OutputKey == 'ServiceName')?.OutputValue!; + // The deployment should not trigger a full deployment, thus the stack's status must remains + // "CREATE_COMPLETE" + expect(response.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); + expect(deployOutput).toContain(`ECS Service '${serviceName}' hotswapped!`); + }), +); + +integTest( + 'hotswap deployment for ecs service waits for deployment to complete', + withDefaultFixture(async (fixture) => { + // GIVEN + const stackArn = await fixture.cdkDeploy('ecs-hotswap', { + captureStderr: false, + }); - // THEN + // WHEN + await fixture.cdkDeploy('ecs-hotswap', { + options: ['--hotswap'], + modEnv: { + DYNAMIC_ECS_PROPERTY_VALUE: 'new value', + }, + }); - const describeServicesResponse = await fixture.aws.ecs('describeServices', { - cluster: clusterName, - services: [serviceName], - }); - expect(describeServicesResponse.services?.[0].deployments).toHaveLength(1); // only one deployment present + const describeStacksResponse = await fixture.aws.cloudFormation('describeStacks', { + StackName: stackArn, + }); + const clusterName = describeStacksResponse.Stacks?.[0].Outputs?.find( + (output) => output.OutputKey == 'ClusterName', + )?.OutputValue!; + const serviceName = describeStacksResponse.Stacks?.[0].Outputs?.find( + (output) => output.OutputKey == 'ServiceName', + )?.OutputValue!; -})); + // THEN -integTest('hotswap deployment for ecs service detects failed deployment and errors', withDefaultFixture(async (fixture) => { - // GIVEN - await fixture.cdkDeploy('ecs-hotswap'); + const describeServicesResponse = await fixture.aws.ecs('describeServices', { + cluster: clusterName, + services: [serviceName], + }); + expect(describeServicesResponse.services?.[0].deployments).toHaveLength(1); // only one deployment present + }), +); - // WHEN - const deployOutput = await fixture.cdkDeploy('ecs-hotswap', { - options: ['--hotswap'], - modEnv: { - USE_INVALID_ECS_HOTSWAP_IMAGE: 'true', - }, - allowErrExit: true, - }); +integTest( + 'hotswap deployment for ecs service detects failed deployment and errors', + withDefaultFixture(async (fixture) => { + // GIVEN + await fixture.cdkDeploy('ecs-hotswap'); + + // WHEN + const deployOutput = await fixture.cdkDeploy('ecs-hotswap', { + options: ['--hotswap'], + modEnv: { + USE_INVALID_ECS_HOTSWAP_IMAGE: 'true', + }, + allowErrExit: true, + }); + + // THEN + expect(deployOutput).toContain( + `❌ ${fixture.stackNamePrefix}-ecs-hotswap failed: ResourceNotReady: Resource is not in the state deploymentCompleted`, + ); + expect(deployOutput).not.toContain('hotswapped!'); + }), +); + +integTest( + 'hotswap deployment for ecs service detects failed deployment and errors', + withDefaultFixture(async (fixture) => { + // GIVEN + await fixture.cdkDeploy('ecs-hotswap'); + + // WHEN + const deployOutput = await fixture.cdkDeploy('ecs-hotswap', { + options: ['--hotswap'], + modEnv: { + USE_INVALID_ECS_HOTSWAP_IMAGE: 'true', + }, + allowErrExit: true, + }); + + const stackName = `${fixture.stackNamePrefix}-ecs-hotswap`; + const expectedSubstring = `❌ ${chalk.bold(stackName)} failed: ResourceNotReady: Resource is not in the state deploymentCompleted`; - // THEN - expect(deployOutput).toContain(`❌ ${fixture.stackNamePrefix}-ecs-hotswap failed: ResourceNotReady: Resource is not in the state deploymentCompleted`); - expect(deployOutput).not.toContain('hotswapped!'); -})); + expect(deployOutput).toContain(expectedSubstring); + expect(deployOutput).not.toContain('hotswapped!'); + }), +); async function listChildren(parent: string, pred: (x: string) => Promise) { const ret = new Array(); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/InstanceIpv6AddressCountTestStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/InstanceIpv6AddressCountTestStack.assets.json new file mode 100644 index 0000000000000..5cc2288f69897 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/InstanceIpv6AddressCountTestStack.assets.json @@ -0,0 +1,32 @@ +{ + "version": "36.0.0", + "files": { + "bde7b5c89cb43285f884c94f0b9e17cdb0f5eb5345005114dd60342e0b8a85a1": { + "source": { + "path": "asset.bde7b5c89cb43285f884c94f0b9e17cdb0f5eb5345005114dd60342e0b8a85a1", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "bde7b5c89cb43285f884c94f0b9e17cdb0f5eb5345005114dd60342e0b8a85a1.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "6b8c723f6e8d52e03024845bdd29b361872d50fdd5f37b9f06475020cf1716dd": { + "source": { + "path": "InstanceIpv6AddressCountTestStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "6b8c723f6e8d52e03024845bdd29b361872d50fdd5f37b9f06475020cf1716dd.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/InstanceIpv6AddressCountTestStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/InstanceIpv6AddressCountTestStack.template.json new file mode 100644 index 0000000000000..90312381edf62 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/InstanceIpv6AddressCountTestStack.template.json @@ -0,0 +1,688 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "InstanceIpv6AddressCountTestStack/Vpc" + } + ] + } + }, + "Vpcipv6cidr40D3CB78": { + "Type": "AWS::EC2::VPCCidrBlock", + "Properties": { + "AmazonProvidedIpv6CidrBlock": true, + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AssignIpv6AddressOnCreation": true, + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.0.0/17", + "Ipv6CidrBlock": { + "Fn::Select": [ + 0, + { + "Fn::Cidr": [ + { + "Fn::Select": [ + 0, + { + "Fn::GetAtt": [ + "Vpc8378EB38", + "Ipv6CidrBlocks" + ] + } + ] + }, + 2, + "64" + ] + } + ] + }, + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "DependsOn": [ + "Vpcipv6cidr40D3CB78" + ] + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "DependsOn": [ + "Vpcipv6cidr40D3CB78" + ] + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + }, + "DependsOn": [ + "Vpcipv6cidr40D3CB78" + ] + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + } + }, + "DependsOn": [ + "Vpcipv6cidr40D3CB78", + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1DefaultRoute6A21265FB": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationIpv6CidrBlock": "::/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + } + }, + "DependsOn": [ + "Vpcipv6cidr40D3CB78" + ] + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AssignIpv6AddressOnCreation": true, + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "CidrBlock": "10.0.128.0/17", + "Ipv6CidrBlock": { + "Fn::Select": [ + 1, + { + "Fn::Cidr": [ + { + "Fn::Select": [ + 0, + { + "Fn::GetAtt": [ + "Vpc8378EB38", + "Ipv6CidrBlocks" + ] + } + ] + }, + 2, + "64" + ] + } + ] + }, + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "DependsOn": [ + "Vpcipv6cidr40D3CB78" + ] + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "DependsOn": [ + "Vpcipv6cidr40D3CB78" + ] + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + }, + "DependsOn": [ + "Vpcipv6cidr40D3CB78" + ] + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + } + }, + "DependsOn": [ + "Vpcipv6cidr40D3CB78", + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2DefaultRoute63E63096C": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationIpv6CidrBlock": "::/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + } + }, + "DependsOn": [ + "Vpcipv6cidr40D3CB78" + ] + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "InstanceIpv6AddressCountTestStack/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "VpcEIGW61416F369": { + "Type": "AWS::EC2::EgressOnlyInternetGateway", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "VpcRestrictDefaultSecurityGroupCustomResourceC73DA2BE": { + "Type": "Custom::VpcRestrictDefaultSG", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E", + "Arn" + ] + }, + "DefaultSecurityGroupId": { + "Fn::GetAtt": [ + "Vpc8378EB38", + "DefaultSecurityGroup" + ] + }, + "Account": { + "Ref": "AWS::AccountId" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + "ec2:RevokeSecurityGroupEgress" + ], + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ec2:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":security-group/", + { + "Fn::GetAtt": [ + "Vpc8378EB38", + "DefaultSecurityGroup" + ] + } + ] + ] + } + ] + } + ] + } + } + ] + } + }, + "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "bde7b5c89cb43285f884c94f0b9e17cdb0f5eb5345005114dd60342e0b8a85a1.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0", + "Arn" + ] + }, + "Runtime": { + "Fn::FindInMap": [ + "LatestNodeRuntimeMap", + { + "Ref": "AWS::Region" + }, + "value" + ] + }, + "Description": "Lambda function for removing all inbound/outbound rules from the VPC default security group" + }, + "DependsOn": [ + "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0" + ] + }, + "InstanceInstanceSecurityGroupF0E2D5BE": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "InstanceIpv6AddressCountTestStack/Instance/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + }, + { + "CidrIpv6": "::/0", + "Description": "Allow all outbound ipv6 traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "InstanceIpv6AddressCountTestStack/Instance" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "InstanceInstanceRoleE9785DE5": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "InstanceIpv6AddressCountTestStack/Instance" + } + ] + } + }, + "InstanceInstanceProfileAB5AEF02": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "InstanceInstanceRoleE9785DE5" + } + ] + } + }, + "InstanceC1063A87": { + "Type": "AWS::EC2::Instance", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "IamInstanceProfile": { + "Ref": "InstanceInstanceProfileAB5AEF02" + }, + "ImageId": { + "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amikernel510hvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "m5.large", + "Ipv6AddressCount": 2, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "InstanceInstanceSecurityGroupF0E2D5BE", + "GroupId" + ] + } + ], + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "InstanceIpv6AddressCountTestStack/Instance" + } + ], + "UserData": { + "Fn::Base64": "#!/bin/bash" + } + }, + "DependsOn": [ + "InstanceInstanceRoleE9785DE5" + ] + } + }, + "Mappings": { + "LatestNodeRuntimeMap": { + "af-south-1": { + "value": "nodejs20.x" + }, + "ap-east-1": { + "value": "nodejs20.x" + }, + "ap-northeast-1": { + "value": "nodejs20.x" + }, + "ap-northeast-2": { + "value": "nodejs20.x" + }, + "ap-northeast-3": { + "value": "nodejs20.x" + }, + "ap-south-1": { + "value": "nodejs20.x" + }, + "ap-south-2": { + "value": "nodejs20.x" + }, + "ap-southeast-1": { + "value": "nodejs20.x" + }, + "ap-southeast-2": { + "value": "nodejs20.x" + }, + "ap-southeast-3": { + "value": "nodejs20.x" + }, + "ap-southeast-4": { + "value": "nodejs20.x" + }, + "ap-southeast-5": { + "value": "nodejs20.x" + }, + "ap-southeast-7": { + "value": "nodejs20.x" + }, + "ca-central-1": { + "value": "nodejs20.x" + }, + "ca-west-1": { + "value": "nodejs20.x" + }, + "cn-north-1": { + "value": "nodejs18.x" + }, + "cn-northwest-1": { + "value": "nodejs18.x" + }, + "eu-central-1": { + "value": "nodejs20.x" + }, + "eu-central-2": { + "value": "nodejs20.x" + }, + "eu-isoe-west-1": { + "value": "nodejs18.x" + }, + "eu-north-1": { + "value": "nodejs20.x" + }, + "eu-south-1": { + "value": "nodejs20.x" + }, + "eu-south-2": { + "value": "nodejs20.x" + }, + "eu-west-1": { + "value": "nodejs20.x" + }, + "eu-west-2": { + "value": "nodejs20.x" + }, + "eu-west-3": { + "value": "nodejs20.x" + }, + "il-central-1": { + "value": "nodejs20.x" + }, + "me-central-1": { + "value": "nodejs20.x" + }, + "me-south-1": { + "value": "nodejs20.x" + }, + "mx-central-1": { + "value": "nodejs20.x" + }, + "sa-east-1": { + "value": "nodejs20.x" + }, + "us-east-1": { + "value": "nodejs20.x" + }, + "us-east-2": { + "value": "nodejs20.x" + }, + "us-gov-east-1": { + "value": "nodejs18.x" + }, + "us-gov-west-1": { + "value": "nodejs18.x" + }, + "us-iso-east-1": { + "value": "nodejs18.x" + }, + "us-iso-west-1": { + "value": "nodejs18.x" + }, + "us-isob-east-1": { + "value": "nodejs18.x" + }, + "us-west-1": { + "value": "nodejs20.x" + }, + "us-west-2": { + "value": "nodejs20.x" + } + } + }, + "Parameters": { + "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amikernel510hvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-kernel-5.10-hvm-x86_64-gp2" + }, + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/IntegTestDefaultTestDeployAssertE3E7D2A4.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/IntegTestDefaultTestDeployAssertE3E7D2A4.assets.json new file mode 100644 index 0000000000000..87dfbae32bf67 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/IntegTestDefaultTestDeployAssertE3E7D2A4.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "IntegTestDefaultTestDeployAssertE3E7D2A4.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/IntegTestDefaultTestDeployAssertE3E7D2A4.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/IntegTestDefaultTestDeployAssertE3E7D2A4.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/IntegTestDefaultTestDeployAssertE3E7D2A4.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/asset.bde7b5c89cb43285f884c94f0b9e17cdb0f5eb5345005114dd60342e0b8a85a1/__entrypoint__.js b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/asset.bde7b5c89cb43285f884c94f0b9e17cdb0f5eb5345005114dd60342e0b8a85a1/__entrypoint__.js new file mode 100644 index 0000000000000..02033f55cf612 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/asset.bde7b5c89cb43285f884c94f0b9e17cdb0f5eb5345005114dd60342e0b8a85a1/__entrypoint__.js @@ -0,0 +1,155 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withRetries = exports.handler = exports.external = void 0; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +exports.handler = handler; +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + const parsedUrl = url.parse(event.ResponseURL); + const loggingSafeUrl = `${parsedUrl.protocol}//${parsedUrl.hostname}/${parsedUrl.pathname}?***`; + exports.external.log('submit response to cloudformation', loggingSafeUrl, json); + const responseBody = JSON.stringify(json); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { + 'content-type': '', + 'content-length': Buffer.byteLength(responseBody, 'utf8'), + }, + }; + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); +} +async function defaultSendHttpRequest(options, requestBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, (response) => { + response.resume(); // Consume the response but don't care about it + if (!response.statusCode || response.statusCode >= 400) { + reject(new Error(`Unsuccessful HTTP response: ${response.statusCode}`)); + } + else { + resolve(); + } + }); + request.on('error', reject); + request.write(requestBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + // eslint-disable-next-line no-console + console.log(fmt, ...params); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +exports.withRetries = withRetries; +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/asset.bde7b5c89cb43285f884c94f0b9e17cdb0f5eb5345005114dd60342e0b8a85a1/index.js b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/asset.bde7b5c89cb43285f884c94f0b9e17cdb0f5eb5345005114dd60342e0b8a85a1/index.js new file mode 100644 index 0000000000000..013bcaffd8fe5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/asset.bde7b5c89cb43285f884c94f0b9e17cdb0f5eb5345005114dd60342e0b8a85a1/index.js @@ -0,0 +1 @@ +"use strict";var I=Object.create;var t=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var g=Object.getPrototypeOf,l=Object.prototype.hasOwnProperty;var G=(r,e)=>{for(var o in e)t(r,o,{get:e[o],enumerable:!0})},n=(r,e,o,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of P(e))!l.call(r,s)&&s!==o&&t(r,s,{get:()=>e[s],enumerable:!(i=y(e,s))||i.enumerable});return r};var R=(r,e,o)=>(o=r!=null?I(g(r)):{},n(e||!r||!r.__esModule?t(o,"default",{value:r,enumerable:!0}):o,r)),S=r=>n(t({},"__esModule",{value:!0}),r);var k={};G(k,{handler:()=>f});module.exports=S(k);var a=R(require("@aws-sdk/client-ec2")),u=new a.EC2({});function c(r,e){return{GroupId:r,IpPermissions:[{UserIdGroupPairs:[{GroupId:r,UserId:e}],IpProtocol:"-1"}]}}function d(r){return{GroupId:r,IpPermissions:[{IpRanges:[{CidrIp:"0.0.0.0/0"}],IpProtocol:"-1"}]}}async function f(r){let e=r.ResourceProperties.DefaultSecurityGroupId,o=r.ResourceProperties.Account;switch(r.RequestType){case"Create":return p(e,o);case"Update":return h(r);case"Delete":return m(e,o)}}async function h(r){let e=r.OldResourceProperties.DefaultSecurityGroupId,o=r.ResourceProperties.DefaultSecurityGroupId;e!==o&&(await m(e,r.ResourceProperties.Account),await p(o,r.ResourceProperties.Account))}async function p(r,e){try{await u.revokeSecurityGroupEgress(d(r))}catch(o){if(o.name!=="InvalidPermission.NotFound")throw o}try{await u.revokeSecurityGroupIngress(c(r,e))}catch(o){if(o.name!=="InvalidPermission.NotFound")throw o}}async function m(r,e){await u.authorizeSecurityGroupIngress(c(r,e)),await u.authorizeSecurityGroupEgress(d(r))}0&&(module.exports={handler}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/cdk.out new file mode 100644 index 0000000000000..1f0068d32659a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/integ.json new file mode 100644 index 0000000000000..88e40e1f71ca7 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "36.0.0", + "testCases": { + "IntegTest/DefaultTest": { + "stacks": [ + "InstanceIpv6AddressCountTestStack" + ], + "assertionStack": "IntegTest/DefaultTest/DeployAssert", + "assertionStackName": "IntegTestDefaultTestDeployAssertE3E7D2A4" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/manifest.json new file mode 100644 index 0000000000000..a4ee7b7b9c8a2 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/manifest.json @@ -0,0 +1,251 @@ +{ + "version": "36.0.0", + "artifacts": { + "InstanceIpv6AddressCountTestStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "InstanceIpv6AddressCountTestStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "InstanceIpv6AddressCountTestStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "InstanceIpv6AddressCountTestStack.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/6b8c723f6e8d52e03024845bdd29b361872d50fdd5f37b9f06475020cf1716dd.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "InstanceIpv6AddressCountTestStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "InstanceIpv6AddressCountTestStack.assets" + ], + "metadata": { + "/InstanceIpv6AddressCountTestStack/Vpc/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Vpc8378EB38" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/ipv6cidr": [ + { + "type": "aws:cdk:logicalId", + "data": "Vpcipv6cidr40D3CB78" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1Subnet5C2D37C4" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1RouteTable6C95E38E" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1RouteTableAssociation97140677" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1DefaultRoute3DA9E72A" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1/DefaultRoute6": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet1DefaultRoute6A21265FB" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2/Subnet": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2Subnet691E08A3" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2/RouteTable": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2RouteTable94F7E489" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2/RouteTableAssociation": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2RouteTableAssociationDD5762D8" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2/DefaultRoute": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2DefaultRoute97F91067" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2/DefaultRoute6": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcPublicSubnet2DefaultRoute63E63096C" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/IGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcIGWD7BA715C" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/VPCGW": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcVPCGWBF912B6E" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/EIGW6": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcEIGW61416F369" + } + ], + "/InstanceIpv6AddressCountTestStack/Vpc/RestrictDefaultSecurityGroupCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "VpcRestrictDefaultSecurityGroupCustomResourceC73DA2BE" + } + ], + "/InstanceIpv6AddressCountTestStack/LatestNodeRuntimeMap": [ + { + "type": "aws:cdk:logicalId", + "data": "LatestNodeRuntimeMap" + } + ], + "/InstanceIpv6AddressCountTestStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0" + } + ], + "/InstanceIpv6AddressCountTestStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E" + } + ], + "/InstanceIpv6AddressCountTestStack/Instance/InstanceSecurityGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "InstanceInstanceSecurityGroupF0E2D5BE" + } + ], + "/InstanceIpv6AddressCountTestStack/Instance/InstanceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "InstanceInstanceRoleE9785DE5" + } + ], + "/InstanceIpv6AddressCountTestStack/Instance/InstanceProfile": [ + { + "type": "aws:cdk:logicalId", + "data": "InstanceInstanceProfileAB5AEF02" + } + ], + "/InstanceIpv6AddressCountTestStack/Instance/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "InstanceC1063A87" + } + ], + "/InstanceIpv6AddressCountTestStack/SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-kernel-5.10-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter": [ + { + "type": "aws:cdk:logicalId", + "data": "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amikernel510hvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + } + ], + "/InstanceIpv6AddressCountTestStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/InstanceIpv6AddressCountTestStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "InstanceIpv6AddressCountTestStack" + }, + "IntegTestDefaultTestDeployAssertE3E7D2A4.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "IntegTestDefaultTestDeployAssertE3E7D2A4.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "IntegTestDefaultTestDeployAssertE3E7D2A4": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "IntegTestDefaultTestDeployAssertE3E7D2A4.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "IntegTestDefaultTestDeployAssertE3E7D2A4.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "IntegTestDefaultTestDeployAssertE3E7D2A4.assets" + ], + "metadata": { + "/IntegTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/IntegTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "IntegTest/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/tree.json new file mode 100644 index 0000000000000..0457ebedae1fc --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.js.snapshot/tree.json @@ -0,0 +1,776 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "InstanceIpv6AddressCountTestStack": { + "id": "InstanceIpv6AddressCountTestStack", + "path": "InstanceIpv6AddressCountTestStack", + "children": { + "Vpc": { + "id": "Vpc", + "path": "InstanceIpv6AddressCountTestStack/Vpc", + "children": { + "Resource": { + "id": "Resource", + "path": "InstanceIpv6AddressCountTestStack/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": "InstanceIpv6AddressCountTestStack/Vpc" + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "ipv6cidr": { + "id": "ipv6cidr", + "path": "InstanceIpv6AddressCountTestStack/Vpc/ipv6cidr", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPCCidrBlock", + "aws:cdk:cloudformation:props": { + "amazonProvidedIpv6CidrBlock": true, + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "PublicSubnet1": { + "id": "PublicSubnet1", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1", + "children": { + "Subnet": { + "id": "Subnet", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "assignIpv6AddressOnCreation": true, + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.0.0/17", + "ipv6CidrBlock": { + "Fn::Select": [ + 0, + { + "Fn::Cidr": [ + { + "Fn::Select": [ + 0, + { + "Fn::GetAtt": [ + "Vpc8378EB38", + "Ipv6CidrBlocks" + ] + } + ] + }, + 2, + "64" + ] + } + ] + }, + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Acl": { + "id": "Acl", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1/Acl", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "subnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "routeTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DefaultRoute6": { + "id": "DefaultRoute6", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet1/DefaultRoute6", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "destinationIpv6CidrBlock": "::/0", + "gatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "routeTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "PublicSubnet2": { + "id": "PublicSubnet2", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2", + "children": { + "Subnet": { + "id": "Subnet", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2/Subnet", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", + "aws:cdk:cloudformation:props": { + "assignIpv6AddressOnCreation": true, + "availabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": "" + } + ] + }, + "cidrBlock": "10.0.128.0/17", + "ipv6CidrBlock": { + "Fn::Select": [ + 1, + { + "Fn::Cidr": [ + { + "Fn::Select": [ + 0, + { + "Fn::GetAtt": [ + "Vpc8378EB38", + "Ipv6CidrBlocks" + ] + } + ] + }, + 2, + "64" + ] + } + ] + }, + "mapPublicIpOnLaunch": true, + "tags": [ + { + "key": "aws-cdk:subnet-name", + "value": "Public" + }, + { + "key": "aws-cdk:subnet-type", + "value": "Public" + }, + { + "key": "Name", + "value": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Acl": { + "id": "Acl", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2/Acl", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "RouteTable": { + "id": "RouteTable", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2/RouteTable", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "RouteTableAssociation": { + "id": "RouteTableAssociation", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2/RouteTableAssociation", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SubnetRouteTableAssociation", + "aws:cdk:cloudformation:props": { + "routeTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "subnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DefaultRoute": { + "id": "DefaultRoute", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2/DefaultRoute", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "destinationCidrBlock": "0.0.0.0/0", + "gatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "routeTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DefaultRoute6": { + "id": "DefaultRoute6", + "path": "InstanceIpv6AddressCountTestStack/Vpc/PublicSubnet2/DefaultRoute6", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Route", + "aws:cdk:cloudformation:props": { + "destinationIpv6CidrBlock": "::/0", + "gatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "routeTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "IGW": { + "id": "IGW", + "path": "InstanceIpv6AddressCountTestStack/Vpc/IGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::InternetGateway", + "aws:cdk:cloudformation:props": { + "tags": [ + { + "key": "Name", + "value": "InstanceIpv6AddressCountTestStack/Vpc" + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "VPCGW": { + "id": "VPCGW", + "path": "InstanceIpv6AddressCountTestStack/Vpc/VPCGW", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::VPCGatewayAttachment", + "aws:cdk:cloudformation:props": { + "internetGatewayId": { + "Ref": "VpcIGWD7BA715C" + }, + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "EIGW6": { + "id": "EIGW6", + "path": "InstanceIpv6AddressCountTestStack/Vpc/EIGW6", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::EgressOnlyInternetGateway", + "aws:cdk:cloudformation:props": { + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "RestrictDefaultSecurityGroupCustomResource": { + "id": "RestrictDefaultSecurityGroupCustomResource", + "path": "InstanceIpv6AddressCountTestStack/Vpc/RestrictDefaultSecurityGroupCustomResource", + "children": { + "Default": { + "id": "Default", + "path": "InstanceIpv6AddressCountTestStack/Vpc/RestrictDefaultSecurityGroupCustomResource/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "LatestNodeRuntimeMap": { + "id": "LatestNodeRuntimeMap", + "path": "InstanceIpv6AddressCountTestStack/LatestNodeRuntimeMap", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Custom::VpcRestrictDefaultSGCustomResourceProvider": { + "id": "Custom::VpcRestrictDefaultSGCustomResourceProvider", + "path": "InstanceIpv6AddressCountTestStack/Custom::VpcRestrictDefaultSGCustomResourceProvider", + "children": { + "Staging": { + "id": "Staging", + "path": "InstanceIpv6AddressCountTestStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Staging", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Role": { + "id": "Role", + "path": "InstanceIpv6AddressCountTestStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Handler": { + "id": "Handler", + "path": "InstanceIpv6AddressCountTestStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Instance": { + "id": "Instance", + "path": "InstanceIpv6AddressCountTestStack/Instance", + "children": { + "InstanceSecurityGroup": { + "id": "InstanceSecurityGroup", + "path": "InstanceIpv6AddressCountTestStack/Instance/InstanceSecurityGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "InstanceIpv6AddressCountTestStack/Instance/InstanceSecurityGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::SecurityGroup", + "aws:cdk:cloudformation:props": { + "groupDescription": "InstanceIpv6AddressCountTestStack/Instance/InstanceSecurityGroup", + "securityGroupEgress": [ + { + "cidrIp": "0.0.0.0/0", + "description": "Allow all outbound traffic by default", + "ipProtocol": "-1" + }, + { + "ipProtocol": "-1", + "cidrIpv6": "::/0", + "description": "Allow all outbound ipv6 traffic by default" + } + ], + "tags": [ + { + "key": "Name", + "value": "InstanceIpv6AddressCountTestStack/Instance" + } + ], + "vpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "InstanceRole": { + "id": "InstanceRole", + "path": "InstanceIpv6AddressCountTestStack/Instance/InstanceRole", + "children": { + "ImportInstanceRole": { + "id": "ImportInstanceRole", + "path": "InstanceIpv6AddressCountTestStack/Instance/InstanceRole/ImportInstanceRole", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Resource": { + "id": "Resource", + "path": "InstanceIpv6AddressCountTestStack/Instance/InstanceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "tags": [ + { + "key": "Name", + "value": "InstanceIpv6AddressCountTestStack/Instance" + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "InstanceProfile": { + "id": "InstanceProfile", + "path": "InstanceIpv6AddressCountTestStack/Instance/InstanceProfile", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::InstanceProfile", + "aws:cdk:cloudformation:props": { + "roles": [ + { + "Ref": "InstanceInstanceRoleE9785DE5" + } + ] + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "Resource": { + "id": "Resource", + "path": "InstanceIpv6AddressCountTestStack/Instance/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::EC2::Instance", + "aws:cdk:cloudformation:props": { + "availabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": "" + } + ] + }, + "iamInstanceProfile": { + "Ref": "InstanceInstanceProfileAB5AEF02" + }, + "imageId": { + "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amikernel510hvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "instanceType": "m5.large", + "ipv6AddressCount": 2, + "securityGroupIds": [ + { + "Fn::GetAtt": [ + "InstanceInstanceSecurityGroupF0E2D5BE", + "GroupId" + ] + } + ], + "subnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "tags": [ + { + "key": "Name", + "value": "InstanceIpv6AddressCountTestStack/Instance" + } + ], + "userData": { + "Fn::Base64": "#!/bin/bash" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-kernel-5.10-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter": { + "id": "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-kernel-5.10-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter", + "path": "InstanceIpv6AddressCountTestStack/SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-kernel-5.10-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-kernel-5.10-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118": { + "id": "SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-kernel-5.10-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118", + "path": "InstanceIpv6AddressCountTestStack/SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn2-ami-kernel-5.10-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "InstanceIpv6AddressCountTestStack/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "InstanceIpv6AddressCountTestStack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "IntegTest": { + "id": "IntegTest", + "path": "IntegTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "IntegTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "IntegTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "IntegTest/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "IntegTest/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "IntegTest/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.ts new file mode 100644 index 0000000000000..617db592e4832 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ec2/test/integ.instance-ipv6-address-count.ts @@ -0,0 +1,37 @@ +import * as cdk from 'aws-cdk-lib'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const vpc = new ec2.Vpc(this, 'Vpc', { + ipProtocol: ec2.IpProtocol.DUAL_STACK, + subnetConfiguration: [ + { + name: 'Public', + subnetType: ec2.SubnetType.PUBLIC, + mapPublicIpOnLaunch: true, + }, + ], + }); + + new ec2.Instance(this, 'Instance', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE), + machineImage: ec2.MachineImage.latestAmazonLinux2(), + vpc: vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, + allowAllIpv6Outbound: true, + ipv6AddressCount: 2, + }); + } +} + +const testCase = new TestStack(app, 'InstanceIpv6AddressCountTestStack'); + +new IntegTest(app, 'IntegTest', { + testCases: [testCase], +}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/KmsKeyMultiRegionStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/KmsKeyMultiRegionStack.assets.json new file mode 100644 index 0000000000000..d362330acdb20 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/KmsKeyMultiRegionStack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.5", + "files": { + "d9e986753b0d85d4019eeeb0bfa3943d6b3e1f7727b0ad62c1f97277cc0567ac": { + "source": { + "path": "KmsKeyMultiRegionStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "d9e986753b0d85d4019eeeb0bfa3943d6b3e1f7727b0ad62c1f97277cc0567ac.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/KmsKeyMultiRegionStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/KmsKeyMultiRegionStack.template.json new file mode 100644 index 0000000000000..d88e8944ac546 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/KmsKeyMultiRegionStack.template.json @@ -0,0 +1,74 @@ +{ + "Resources": { + "keyFEDD6EC0": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "MultiRegion": true + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/cdk.out new file mode 100644 index 0000000000000..bd5311dc372de --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.5"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/integ.json new file mode 100644 index 0000000000000..f7aae343362c5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "36.0.5", + "testCases": { + "kms-key-multi-region/DefaultTest": { + "stacks": [ + "KmsKeyMultiRegionStack" + ], + "assertionStack": "kms-key-multi-region/DefaultTest/DeployAssert", + "assertionStackName": "kmskeymultiregionDefaultTestDeployAssert5D62E49E" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/kmskeymultiregionDefaultTestDeployAssert5D62E49E.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/kmskeymultiregionDefaultTestDeployAssert5D62E49E.assets.json new file mode 100644 index 0000000000000..96989d02ba47d --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/kmskeymultiregionDefaultTestDeployAssert5D62E49E.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.5", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "kmskeymultiregionDefaultTestDeployAssert5D62E49E.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/kmskeymultiregionDefaultTestDeployAssert5D62E49E.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/kmskeymultiregionDefaultTestDeployAssert5D62E49E.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/kmskeymultiregionDefaultTestDeployAssert5D62E49E.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/manifest.json new file mode 100644 index 0000000000000..7224a06794e6b --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/manifest.json @@ -0,0 +1,113 @@ +{ + "version": "36.0.5", + "artifacts": { + "KmsKeyMultiRegionStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "KmsKeyMultiRegionStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "KmsKeyMultiRegionStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "KmsKeyMultiRegionStack.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/d9e986753b0d85d4019eeeb0bfa3943d6b3e1f7727b0ad62c1f97277cc0567ac.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "KmsKeyMultiRegionStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "KmsKeyMultiRegionStack.assets" + ], + "metadata": { + "/KmsKeyMultiRegionStack/key/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "keyFEDD6EC0" + } + ], + "/KmsKeyMultiRegionStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/KmsKeyMultiRegionStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "KmsKeyMultiRegionStack" + }, + "kmskeymultiregionDefaultTestDeployAssert5D62E49E.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "kmskeymultiregionDefaultTestDeployAssert5D62E49E.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "kmskeymultiregionDefaultTestDeployAssert5D62E49E": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "kmskeymultiregionDefaultTestDeployAssert5D62E49E.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "kmskeymultiregionDefaultTestDeployAssert5D62E49E.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "kmskeymultiregionDefaultTestDeployAssert5D62E49E.assets" + ], + "metadata": { + "/kms-key-multi-region/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/kms-key-multi-region/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "kms-key-multi-region/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/tree.json new file mode 100644 index 0000000000000..2788290326799 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.js.snapshot/tree.json @@ -0,0 +1,153 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "KmsKeyMultiRegionStack": { + "id": "KmsKeyMultiRegionStack", + "path": "KmsKeyMultiRegionStack", + "children": { + "key": { + "id": "key", + "path": "KmsKeyMultiRegionStack/key", + "children": { + "Resource": { + "id": "Resource", + "path": "KmsKeyMultiRegionStack/key/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::KMS::Key", + "aws:cdk:cloudformation:props": { + "keyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "multiRegion": true + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "KmsKeyMultiRegionStack/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "KmsKeyMultiRegionStack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "kms-key-multi-region": { + "id": "kms-key-multi-region", + "path": "kms-key-multi-region", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "kms-key-multi-region/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "kms-key-multi-region/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "kms-key-multi-region/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "kms-key-multi-region/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "kms-key-multi-region/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.ts new file mode 100644 index 0000000000000..996c11faa2b87 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-multi-region.ts @@ -0,0 +1,19 @@ +import { App, Stack } from 'aws-cdk-lib'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; + +class KmsKeyMultiRegionStack extends Stack { + constructor(scope: App) { + super(scope, 'KmsKeyMultiRegionStack'); + new kms.Key(this, 'key', { + multiRegion: true, + }); + } +} + +const app = new App(); +const stack = new KmsKeyMultiRegionStack(app); + +new IntegTest(app, 'kms-key-multi-region', { + testCases: [stack], +}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.assets.json new file mode 100644 index 0000000000000..52b45419dad0a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/cdk.out new file mode 100644 index 0000000000000..1f0068d32659a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/integ.json new file mode 100644 index 0000000000000..827da5824ceba --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "36.0.0", + "testCases": { + "DynamoDBFilterBoolean/DefaultTest": { + "stacks": [ + "lambda-event-source-filter-boolean-dynamodb" + ], + "assertionStack": "DynamoDBFilterBoolean/DefaultTest/DeployAssert", + "assertionStackName": "DynamoDBFilterBooleanDefaultTestDeployAssert8561382F" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/lambda-event-source-filter-boolean-dynamodb.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/lambda-event-source-filter-boolean-dynamodb.assets.json new file mode 100644 index 0000000000000..8b90b83d51bb6 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/lambda-event-source-filter-boolean-dynamodb.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "f60708303fa3fc942070d172c3d3b758824b55d89bee7dc151ebe2968221b264": { + "source": { + "path": "lambda-event-source-filter-boolean-dynamodb.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "f60708303fa3fc942070d172c3d3b758824b55d89bee7dc151ebe2968221b264.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/lambda-event-source-filter-boolean-dynamodb.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/lambda-event-source-filter-boolean-dynamodb.template.json new file mode 100644 index 0000000000000..e40b18f46a104 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/lambda-event-source-filter-boolean-dynamodb.template.json @@ -0,0 +1,173 @@ +{ + "Resources": { + "FServiceRole3AC82EE1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "FServiceRoleDefaultPolicy17A19BFA": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "dynamodb:ListStreams", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TD925BC7E", + "StreamArn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "FServiceRoleDefaultPolicy17A19BFA", + "Roles": [ + { + "Ref": "FServiceRole3AC82EE1" + } + ] + } + }, + "FC4345940": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function handler(event) {\n console.log('event:', JSON.stringify(event, undefined, 2));\n return { event };\n}" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FServiceRole3AC82EE1", + "Arn" + ] + }, + "Runtime": "nodejs18.x" + }, + "DependsOn": [ + "FServiceRoleDefaultPolicy17A19BFA", + "FServiceRole3AC82EE1" + ] + }, + "FDynamoDBEventSourcelambdaeventsourcefilterbooleandynamodbT2161ED824BB5B64C": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 5, + "EventSourceArn": { + "Fn::GetAtt": [ + "TD925BC7E", + "StreamArn" + ] + }, + "FilterCriteria": { + "Filters": [ + { + "Pattern": "{\"eventName\":[\"INSERT\"],\"dynamodb\":{\"NewImage\":{\"id\":{\"BOOL\":[true]}}}}" + } + ] + }, + "FunctionName": { + "Ref": "FC4345940" + }, + "StartingPosition": "LATEST" + } + }, + "TD925BC7E": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + }, + "StreamSpecification": { + "StreamViewType": "NEW_IMAGE" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/manifest.json new file mode 100644 index 0000000000000..e42c90c7abd03 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/manifest.json @@ -0,0 +1,137 @@ +{ + "version": "36.0.0", + "artifacts": { + "lambda-event-source-filter-boolean-dynamodb.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "lambda-event-source-filter-boolean-dynamodb.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "lambda-event-source-filter-boolean-dynamodb": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "lambda-event-source-filter-boolean-dynamodb.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/f60708303fa3fc942070d172c3d3b758824b55d89bee7dc151ebe2968221b264.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "lambda-event-source-filter-boolean-dynamodb.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "lambda-event-source-filter-boolean-dynamodb.assets" + ], + "metadata": { + "/lambda-event-source-filter-boolean-dynamodb/F/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FServiceRole3AC82EE1" + } + ], + "/lambda-event-source-filter-boolean-dynamodb/F/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FServiceRoleDefaultPolicy17A19BFA" + } + ], + "/lambda-event-source-filter-boolean-dynamodb/F/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FC4345940" + } + ], + "/lambda-event-source-filter-boolean-dynamodb/F/DynamoDBEventSource:lambdaeventsourcefilterbooleandynamodbT2161ED82/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FDynamoDBEventSourcelambdaeventsourcefilterbooleandynamodbT2161ED824BB5B64C" + } + ], + "/lambda-event-source-filter-boolean-dynamodb/T/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TD925BC7E" + } + ], + "/lambda-event-source-filter-boolean-dynamodb/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/lambda-event-source-filter-boolean-dynamodb/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "lambda-event-source-filter-boolean-dynamodb" + }, + "DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "DynamoDBFilterBooleanDefaultTestDeployAssert8561382F": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "DynamoDBFilterBooleanDefaultTestDeployAssert8561382F.assets" + ], + "metadata": { + "/DynamoDBFilterBoolean/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/DynamoDBFilterBoolean/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "DynamoDBFilterBoolean/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/tree.json new file mode 100644 index 0000000000000..e107666a6a5da --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.js.snapshot/tree.json @@ -0,0 +1,336 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "lambda-event-source-filter-boolean-dynamodb": { + "id": "lambda-event-source-filter-boolean-dynamodb", + "path": "lambda-event-source-filter-boolean-dynamodb", + "children": { + "F": { + "id": "F", + "path": "lambda-event-source-filter-boolean-dynamodb/F", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "lambda-event-source-filter-boolean-dynamodb/F/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "lambda-event-source-filter-boolean-dynamodb/F/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "lambda-event-source-filter-boolean-dynamodb/F/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "lambda-event-source-filter-boolean-dynamodb/F/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "lambda-event-source-filter-boolean-dynamodb/F/ServiceRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": "dynamodb:ListStreams", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TD925BC7E", + "StreamArn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "FServiceRoleDefaultPolicy17A19BFA", + "roles": [ + { + "Ref": "FServiceRole3AC82EE1" + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "lambda-event-source-filter-boolean-dynamodb/F/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "zipFile": "exports.handler = async function handler(event) {\n console.log('event:', JSON.stringify(event, undefined, 2));\n return { event };\n}" + }, + "handler": "index.handler", + "role": { + "Fn::GetAtt": [ + "FServiceRole3AC82EE1", + "Arn" + ] + }, + "runtime": "nodejs18.x" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnFunction", + "version": "0.0.0" + } + }, + "DynamoDBEventSource:lambdaeventsourcefilterbooleandynamodbT2161ED82": { + "id": "DynamoDBEventSource:lambdaeventsourcefilterbooleandynamodbT2161ED82", + "path": "lambda-event-source-filter-boolean-dynamodb/F/DynamoDBEventSource:lambdaeventsourcefilterbooleandynamodbT2161ED82", + "children": { + "Resource": { + "id": "Resource", + "path": "lambda-event-source-filter-boolean-dynamodb/F/DynamoDBEventSource:lambdaeventsourcefilterbooleandynamodbT2161ED82/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::EventSourceMapping", + "aws:cdk:cloudformation:props": { + "batchSize": 5, + "eventSourceArn": { + "Fn::GetAtt": [ + "TD925BC7E", + "StreamArn" + ] + }, + "filterCriteria": { + "filters": [ + { + "pattern": "{\"eventName\":[\"INSERT\"],\"dynamodb\":{\"NewImage\":{\"id\":{\"BOOL\":[true]}}}}" + } + ] + }, + "functionName": { + "Ref": "FC4345940" + }, + "startingPosition": "LATEST" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnEventSourceMapping", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.EventSourceMapping", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.Function", + "version": "0.0.0" + } + }, + "T": { + "id": "T", + "path": "lambda-event-source-filter-boolean-dynamodb/T", + "children": { + "Resource": { + "id": "Resource", + "path": "lambda-event-source-filter-boolean-dynamodb/T/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::DynamoDB::Table", + "aws:cdk:cloudformation:props": { + "attributeDefinitions": [ + { + "attributeName": "id", + "attributeType": "S" + } + ], + "keySchema": [ + { + "attributeName": "id", + "keyType": "HASH" + } + ], + "provisionedThroughput": { + "readCapacityUnits": 5, + "writeCapacityUnits": 5 + }, + "streamSpecification": { + "streamViewType": "NEW_IMAGE" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.CfnTable", + "version": "0.0.0" + } + }, + "ScalingRole": { + "id": "ScalingRole", + "path": "lambda-event-source-filter-boolean-dynamodb/T/ScalingRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.Table", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "lambda-event-source-filter-boolean-dynamodb/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "lambda-event-source-filter-boolean-dynamodb/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "DynamoDBFilterBoolean": { + "id": "DynamoDBFilterBoolean", + "path": "DynamoDBFilterBoolean", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "DynamoDBFilterBoolean/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "DynamoDBFilterBoolean/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "DynamoDBFilterBoolean/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "DynamoDBFilterBoolean/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "DynamoDBFilterBoolean/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.ts new file mode 100644 index 0000000000000..2de0fea3e09df --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda-event-sources/test/integ.dynamodb-with-boolean-filter.ts @@ -0,0 +1,41 @@ +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as cdk from 'aws-cdk-lib'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import { TestFunction } from './test-function'; +import { DynamoEventSource } from 'aws-cdk-lib/aws-lambda-event-sources'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'lambda-event-source-filter-boolean-dynamodb'); + +const fn = new TestFunction(stack, 'F'); +const table = new dynamodb.Table(stack, 'T', { + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING, + }, + stream: dynamodb.StreamViewType.NEW_IMAGE, + removalPolicy: cdk.RemovalPolicy.DESTROY, +}); + +fn.addEventSource(new DynamoEventSource(table, { + batchSize: 5, + startingPosition: lambda.StartingPosition.LATEST, + filters: [ + lambda.FilterCriteria.filter({ + eventName: lambda.FilterRule.isEqual('INSERT'), + dynamodb: { + NewImage: { + id: { BOOL: lambda.FilterRule.isEqual(true) }, + }, + }, + }), + ], +})); + +new integ.IntegTest(app, 'DynamoDBFilterBoolean', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/@aws-cdk/custom-resource-handlers/lib/aws-eks/cluster-resource-handler/cluster.ts b/packages/@aws-cdk/custom-resource-handlers/lib/aws-eks/cluster-resource-handler/cluster.ts index 93a5131ef4707..8afce971b41a1 100644 --- a/packages/@aws-cdk/custom-resource-handlers/lib/aws-eks/cluster-resource-handler/cluster.ts +++ b/packages/@aws-cdk/custom-resource-handlers/lib/aws-eks/cluster-resource-handler/cluster.ts @@ -247,6 +247,17 @@ export class ClusterResourceHandler extends ResourceHandler { this.newProps.accessConfig?.authenticationMode === 'API') { throw new Error('Cannot update from CONFIG_MAP to API'); } + // update-authmode will fail if we try to update to the same mode, + // so skip in this case. + try { + const cluster = (await this.eks.describeCluster({ name: this.clusterName })).cluster; + if (cluster?.accessConfig?.authenticationMode === this.newProps.accessConfig?.authenticationMode) { + console.log(`cluster already at ${cluster?.accessConfig?.authenticationMode}, skipping authMode update`); + return; + } + } catch (e: any) { + throw e; + } config.accessConfig = this.newProps.accessConfig; }; diff --git a/packages/aws-cdk-lib/aws-ec2/README.md b/packages/aws-cdk-lib/aws-ec2/README.md index 686eb78ed2a3f..6e014062f87d1 100644 --- a/packages/aws-cdk-lib/aws-ec2/README.md +++ b/packages/aws-cdk-lib/aws-ec2/README.md @@ -1882,6 +1882,23 @@ Note to set `mapPublicIpOnLaunch` to true in the `subnetConfiguration`. Additionally, IPv6 support varies by instance type. Most instance types have IPv6 support with exception of m1-m3, c1, g2, and t1.micro. A full list can be found here: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html#AvailableIpPerENI. +#### Specifying the IPv6 Address + +If you want to specify [the number of IPv6 addresses](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/MultipleIP.html#assign-multiple-ipv6) to assign to the instance, you can use the `ipv6AddresseCount` property: + +```ts +// dual stack VPC +declare const vpc: ec2.Vpc; + +const instance = new ec2.Instance(this, 'MyInstance', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE), + machineImage: ec2.MachineImage.latestAmazonLinux2(), + vpc: vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, + // Assign 2 IPv6 addresses to the instance + ipv6AddressCount: 2, +}); +``` ### Credit configuration modes for burstable instances diff --git a/packages/aws-cdk-lib/aws-ec2/lib/instance.ts b/packages/aws-cdk-lib/aws-ec2/lib/instance.ts index 01aa0f18ab89e..2eb98ad26e6fa 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/instance.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/instance.ts @@ -15,7 +15,7 @@ import { UserData } from './user-data'; import { BlockDevice } from './volume'; import { IVpc, Subnet, SubnetSelection } from './vpc'; import * as iam from '../../aws-iam'; -import { Annotations, Aspects, Duration, Fn, IResource, Lazy, Resource, Stack, Tags } from '../../core'; +import { Annotations, Aspects, Duration, Fn, IResource, Lazy, Resource, Stack, Tags, Token } from '../../core'; import { md5hash } from '../../core/lib/helpers-internal'; /** @@ -294,6 +294,8 @@ export interface InstanceProps { /** * Whether to associate a public IP address to the primary network interface attached to this instance. * + * You cannot specify this property and `ipv6AddressCount` at the same time. + * * @default - public IP address is automatically assigned based on default behavior */ readonly associatePublicIpAddress?: boolean; @@ -359,6 +361,17 @@ export interface InstanceProps { * @default - false */ readonly hibernationEnabled?: boolean; + + /** + * The number of IPv6 addresses to associate with the primary network interface. + * + * Amazon EC2 chooses the IPv6 addresses from the range of your subnet. + * + * You cannot specify this property and `associatePublicIpAddress` at the same time. + * + * @default - For instances associated with an IPv6 subnet, use 1; otherwise, use 0. + */ + readonly ipv6AddressCount?: number; } /** @@ -514,6 +527,20 @@ export class Instance extends Resource implements IInstance { throw new Error('You can\'t set both `enclaveEnabled` and `hibernationEnabled` to true on the same instance'); } + if ( + props.ipv6AddressCount !== undefined && + !Token.isUnresolved(props.ipv6AddressCount) && + (props.ipv6AddressCount < 0 || !Number.isInteger(props.ipv6AddressCount)) + ) { + throw new Error(`\'ipv6AddressCount\' must be a non-negative integer, got: ${props.ipv6AddressCount}`); + } + + if ( + props.ipv6AddressCount !== undefined && + props.associatePublicIpAddress !== undefined) { + throw new Error('You can\'t set both \'ipv6AddressCount\' and \'associatePublicIpAddress\''); + } + // if network interfaces array is configured then subnetId, securityGroupIds, // and privateIpAddress are configured on the network interface level and // there is no need to configure them on the instance level @@ -538,6 +565,7 @@ export class Instance extends Resource implements IInstance { placementGroupName: props.placementGroup?.placementGroupName, enclaveOptions: props.enclaveEnabled !== undefined ? { enabled: props.enclaveEnabled } : undefined, hibernationOptions: props.hibernationEnabled !== undefined ? { configured: props.hibernationEnabled } : undefined, + ipv6AddressCount: props.ipv6AddressCount, }); this.instance.node.addDependency(this.role); diff --git a/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts b/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts index 3947bd5481293..54b25d09c2d6e 100644 --- a/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts +++ b/packages/aws-cdk-lib/aws-ec2/test/instance.test.ts @@ -1024,3 +1024,44 @@ test('throw if both enclaveEnabled and hibernationEnabled are set to true', () = }); }).toThrow('You can\'t set both `enclaveEnabled` and `hibernationEnabled` to true on the same instance'); }); + +test('instance with ipv6 address count', () => { + // WHEN + new Instance(stack, 'Instance', { + vpc, + machineImage: new AmazonLinuxImage(), + instanceType: new InstanceType('t2.micro'), + ipv6AddressCount: 2, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::EC2::Instance', { + InstanceType: 't2.micro', + Ipv6AddressCount: 2, + }); +}); + +test.each([-1, 0.1, 1.1])('throws if ipv6AddressCount is not a positive integer', (ipv6AddressCount: number) => { + // THEN + expect(() => { + new Instance(stack, 'Instance', { + vpc, + machineImage: new AmazonLinuxImage(), + instanceType: new InstanceType('t2.micro'), + ipv6AddressCount: ipv6AddressCount, + }); + }).toThrow(`\'ipv6AddressCount\' must be a non-negative integer, got: ${ipv6AddressCount}`); +}); + +test.each([true, false])('throw error for specifying ipv6AddressCount with associatePublicIpAddress', (associatePublicIpAddress) => { + // THEN + expect(() => { + new Instance(stack, 'Instance', { + vpc, + machineImage: new AmazonLinuxImage(), + instanceType: new InstanceType('t2.micro'), + ipv6AddressCount: 2, + associatePublicIpAddress, + }); + }).toThrow('You can\'t set both \'ipv6AddressCount\' and \'associatePublicIpAddress\''); +}); diff --git a/packages/aws-cdk-lib/aws-kms/README.md b/packages/aws-cdk-lib/aws-kms/README.md index 5bf8c6366087d..ae78310e96917 100644 --- a/packages/aws-cdk-lib/aws-kms/README.md +++ b/packages/aws-cdk-lib/aws-kms/README.md @@ -41,6 +41,15 @@ const key = new kms.Key(this, 'MyKey', { }); ``` + +Create a multi-Region primary key: + +```ts +const key = new kms.Key(this, 'MyKey', { + multiRegion: true, // Default is false +}); +``` + ## Sharing keys between stacks To use a KMS key in a different stack in the same CDK application, diff --git a/packages/aws-cdk-lib/aws-kms/lib/key.ts b/packages/aws-cdk-lib/aws-kms/lib/key.ts index fec01eaade57d..7cdd14fc546e0 100644 --- a/packages/aws-cdk-lib/aws-kms/lib/key.ts +++ b/packages/aws-cdk-lib/aws-kms/lib/key.ts @@ -468,6 +468,20 @@ export interface KeyProps { */ readonly keyUsage?: KeyUsage; + /** + * Creates a multi-Region primary key that you can replicate in other AWS Regions. + * + * You can't change the `multiRegion` value after the KMS key is created. + * + * IMPORTANT: If you change the value of the `multiRegion` property on an existing KMS key, the update request fails, + * regardless of the value of the UpdateReplacePolicy attribute. + * This prevents you from accidentally deleting a KMS key by changing an immutable property value. + * + * @default false + * @see https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html + */ + readonly multiRegion?: boolean; + /** * Custom policy document to attach to the KMS key. * @@ -783,6 +797,7 @@ export class Key extends KeyBase { keySpec: props.keySpec, keyUsage: props.keyUsage, keyPolicy: this.policy, + multiRegion: props.multiRegion, pendingWindowInDays: pendingWindowInDays, }); diff --git a/packages/aws-cdk-lib/aws-kms/test/key.test.ts b/packages/aws-cdk-lib/aws-kms/test/key.test.ts index 298a82730dc45..14f2df628f0b1 100644 --- a/packages/aws-cdk-lib/aws-kms/test/key.test.ts +++ b/packages/aws-cdk-lib/aws-kms/test/key.test.ts @@ -646,6 +646,17 @@ test('fails if key policy has no IAM principals', () => { expect(() => app.synth()).toThrow(/A PolicyStatement used in a resource-based policy must specify at least one IAM principal/); }); +test('multi-region primary key', () => { + const stack = new cdk.Stack(); + new kms.Key(stack, 'MyKey', { + multiRegion: true, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', { + MultiRegion: true, + }); +}); + describe('imported keys', () => { test('throw an error when providing something that is not a valid key ARN', () => { const stack = new cdk.Stack(); diff --git a/packages/aws-cdk-lib/aws-lambda-event-sources/README.md b/packages/aws-cdk-lib/aws-lambda-event-sources/README.md index 8c88ae99bc8b8..6ad5ebe73df0e 100644 --- a/packages/aws-cdk-lib/aws-lambda-event-sources/README.md +++ b/packages/aws-cdk-lib/aws-lambda-event-sources/README.md @@ -169,6 +169,7 @@ and add it to your Lambda function. The following parameters will impact Amazon * __startingPosition__: Will determine where to being consumption, either at the most recent ('LATEST') record or the oldest record ('TRIM_HORIZON'). 'TRIM_HORIZON' will ensure you process all available data, while 'LATEST' will ignore all records that arrived prior to attaching the event source. * __tumblingWindow__: The duration in seconds of a processing window when using streams. * __enabled__: If the DynamoDB Streams event source mapping should be enabled. The default is true. +* __filters__: Filters to apply before sending a change event from a DynamoDB table to a Lambda function. Events that are filtered out are not sent to the Lambda function. ```ts import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; @@ -188,6 +189,30 @@ fn.addEventSource(new DynamoEventSource(table, { })); ``` +The following code sets up a Lambda function with a DynamoDB event source. A filter is applied to only send DynamoDB events to +the Lambda function when the `id` column is a boolean that equals `true`. + +```ts +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import { DynamoEventSource } from 'aws-cdk-lib/aws-lambda-event-sources'; + +declare const table: dynamodb.Table; + +declare const fn: lambda.Function; +fn.addEventSource(new DynamoEventSource(table, { + startingPosition: lambda.StartingPosition.LATEST, + filters: [ + lambda.FilterCriteria.filter({ + eventName: lambda.FilterRule.isEqual('INSERT'), + dynamodb: { + NewImage: { + id: { BOOL: lambda.FilterRule.isEqual(true) }, + }, + }, + }), + ], +})); +``` ## Kinesis You can write Lambda functions to process streaming data in Amazon Kinesis Streams. For more information about Amazon Kinesis, see [Amazon Kinesis diff --git a/packages/aws-cdk-lib/aws-lambda-event-sources/test/dynamo.test.ts b/packages/aws-cdk-lib/aws-lambda-event-sources/test/dynamo.test.ts index eb331b0de8972..92185d565598b 100644 --- a/packages/aws-cdk-lib/aws-lambda-event-sources/test/dynamo.test.ts +++ b/packages/aws-cdk-lib/aws-lambda-event-sources/test/dynamo.test.ts @@ -949,4 +949,53 @@ describe('DynamoEventSource', () => { }).toThrowError('S3 onFailure Destination is not supported for this event source'); }); + + test('filter on boolean', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const table = new dynamodb.Table(stack, 'T', { + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING, + }, + stream: dynamodb.StreamViewType.NEW_IMAGE, + }); + + // WHEN + fn.addEventSource(new sources.DynamoEventSource(table, { + startingPosition: lambda.StartingPosition.LATEST, + filters: [ + lambda.FilterCriteria.filter({ + eventName: lambda.FilterRule.isEqual('INSERT'), + dynamodb: { + NewImage: { + id: { BOOL: lambda.FilterRule.isEqual(true) }, + }, + }, + }), + ], + })); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::EventSourceMapping', { + 'EventSourceArn': { + 'Fn::GetAtt': [ + 'TD925BC7E', + 'StreamArn', + ], + }, + 'FunctionName': { + 'Ref': 'Fn9270CBC0', + }, + 'FilterCriteria': { + 'Filters': [ + { + 'Pattern': '{"eventName":["INSERT"],"dynamodb":{"NewImage":{"id":{"BOOL":[true]}}}}', + }, + ], + }, + 'StartingPosition': 'LATEST', + }); + }); }); diff --git a/packages/aws-cdk-lib/aws-lambda/README.md b/packages/aws-cdk-lib/aws-lambda/README.md index 3a10e7aa3c369..8e707f04a255b 100644 --- a/packages/aws-cdk-lib/aws-lambda/README.md +++ b/packages/aws-cdk-lib/aws-lambda/README.md @@ -78,6 +78,10 @@ configurations as well as choosing a specific tag or digest. See their docs for To deploy a `DockerImageFunction` on Lambda `arm64` architecture, specify `Architecture.ARM_64` in `architecture`. This will bundle docker image assets for `arm64` architecture with `--platform linux/arm64` even if build within an `x86_64` host. +With that being said, if you are bundling `DockerImageFunction` for Lambda `amd64` architecture from a `arm64` machine like a Macbook with `arm64` CPU, you would +need to specify `architecture: lambda.Architecture.X86_64` as well. This ensures the `--platform` argument is passed to the image assets +bundling process so you can bundle up `X86_64` images from the `arm64` machine. + ```ts new lambda.DockerImageFunction(this, 'AssetFunction', { code: lambda.DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-arm64-handler')), diff --git a/packages/aws-cdk-lib/aws-lambda/lib/event-source-filter.ts b/packages/aws-cdk-lib/aws-lambda/lib/event-source-filter.ts index 50bce839123ba..a20bcedf4d82a 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/event-source-filter.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/event-source-filter.ts @@ -19,7 +19,7 @@ export class FilterRule { /** * Equals comparison operator */ - public static isEqual(item: string | number): any { + public static isEqual(item: string | number | boolean): any { if (typeof item === 'number') { return [{ numeric: ['=', item] }]; }