UNPKG

aws-cdk

Version:

CDK Toolkit, the command line tool for CDK apps

1,015 lines 150 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const client_lambda_1 = require("@aws-sdk/client-lambda"); const setup = require("./hotswap-test-setup"); const common_1 = require("../../../lib/api/hotswap/common"); const util_1 = require("../../util"); const mock_sdk_1 = require("../../util/mock-sdk"); const silent_1 = require("../../util/silent"); let hotswapMockSdkProvider; describe.each([common_1.HotswapMode.FALL_BACK, common_1.HotswapMode.HOTSWAP_ONLY])('%p mode', (hotswapMode) => { (0, silent_1.silentTest)('can hotswap a lambda function in a 1-level nested stack', async () => { // GIVEN hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('LambdaRoot'); const oldRootStack = (0, util_1.testStack)({ stackName: 'LambdaRoot', template: { Resources: { NestedStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.amazoff.com', }, Metadata: { 'aws:asset:path': 'one-lambda-stack.nested.template.json', }, }, }, }, }); setup.addTemplateToCloudFormationLookupMock(oldRootStack); setup.addTemplateToCloudFormationLookupMock((0, util_1.testStack)({ stackName: 'NestedStack', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'my-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, })); setup.pushNestedStackResourceSummaries('LambdaRoot', setup.stackSummaryOf('NestedStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/NestedStack/abcd')); // WHEN oldRootStack.template.Resources.NestedStack.Properties.TemplateURL = 'https://www.amazon.com'; const newRootStack = (0, util_1.testStack)({ stackName: 'LambdaRoot', template: oldRootStack.template }); const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'my-function', S3Bucket: 'current-bucket', S3Key: 'new-key', }); }); (0, silent_1.silentTest)('hotswappable changes do not override hotswappable changes in their ancestors', async () => { // GIVEN hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('TwoLevelLambdaRoot'); const oldRootStack = (0, util_1.testStack)({ stackName: 'TwoLevelLambdaRoot', template: { Resources: { ChildStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.amazoff.com', }, Metadata: { 'aws:asset:path': 'one-lambda-one-stack-stack.nested.template.json', }, }, }, }, }); setup.addTemplateToCloudFormationLookupMock(oldRootStack); const oldChildStack = (0, util_1.testStack)({ stackName: 'ChildStack', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'child-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, GrandChildStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.amazoff.com', }, Metadata: { 'aws:asset:path': 'one-lambda-stack.nested.template.json', }, }, }, }, }); setup.addTemplateToCloudFormationLookupMock(oldChildStack); setup.addTemplateToCloudFormationLookupMock((0, util_1.testStack)({ stackName: 'GrandChildStack', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'my-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, })); setup.pushNestedStackResourceSummaries('TwoLevelLambdaRoot', setup.stackSummaryOf('ChildStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/ChildStack/abcd')); setup.pushNestedStackResourceSummaries('ChildStack', setup.stackSummaryOf('GrandChildStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/GrandChildStack/abcd')); // WHEN oldRootStack.template.Resources.ChildStack.Properties.TemplateURL = 'https://www.amazon.com'; oldChildStack.template.Resources.GrandChildStack.Properties.TemplateURL = 'https://www.amazon.com'; // write the new templates to disk const newRootStack = (0, util_1.testStack)({ stackName: oldRootStack.stackName, template: oldRootStack.template }); (0, util_1.testStack)({ stackName: oldChildStack.stackName, template: oldChildStack.template }); const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'child-function', S3Bucket: 'new-bucket', S3Key: 'current-key', }); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'my-function', S3Bucket: 'current-bucket', S3Key: 'new-key', }); }); (0, silent_1.silentTest)('hotswappable changes in nested stacks do not override hotswappable changes in their parent stack', async () => { // GIVEN hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('SiblingLambdaRoot'); const oldRootStack = (0, util_1.testStack)({ stackName: 'SiblingLambdaRoot', template: { Resources: { NestedStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', }, Metadata: { 'aws:asset:path': 'one-lambda-stack.nested.template.json', }, }, Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'root-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, }); setup.addTemplateToCloudFormationLookupMock(oldRootStack); setup.addTemplateToCloudFormationLookupMock((0, util_1.testStack)({ stackName: 'NestedStack', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'my-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, })); setup.pushNestedStackResourceSummaries('SiblingLambdaRoot', setup.stackSummaryOf('NestedStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/NestedStack/abcd')); // WHEN oldRootStack.template.Resources.Func.Properties.Code.S3Bucket = 'new-bucket'; oldRootStack.template.Resources.NestedStack.Properties.TemplateURL = 'https://www.amazon.com'; // write the updated templates to disk const newRootStack = (0, util_1.testStack)({ stackName: oldRootStack.stackName, template: oldRootStack.template }); const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'root-function', S3Bucket: 'new-bucket', S3Key: 'current-key', }); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'my-function', S3Bucket: 'current-bucket', S3Key: 'new-key', }); }); (0, silent_1.silentTest)(`non-hotswappable changes in nested stacks result in a full deployment, even if their parent contains a hotswappable change in CLASSIC mode, but perform a hotswap deployment in HOTSWAP_ONLY`, async () => { // GIVEN hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('NonHotswappableRoot'); const oldRootStack = (0, util_1.testStack)({ stackName: 'NonHotswappableRoot', template: { Resources: { NestedStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', }, Metadata: { 'aws:asset:path': 'one-lambda-stack.nested.template.json', }, }, Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'root-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, }); setup.addTemplateToCloudFormationLookupMock(oldRootStack); setup.addTemplateToCloudFormationLookupMock((0, util_1.testStack)({ stackName: 'NestedStack', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, PackageType: 'Image', FunctionName: 'my-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, })); setup.pushNestedStackResourceSummaries('NonHotswappableRoot', setup.stackSummaryOf('NestedStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/NestedStack/abcd')); oldRootStack.template.Resources.Func.Properties.Code.S3Bucket = 'new-bucket'; oldRootStack.template.Resources.NestedStack.Properties.TemplateURL = 'https://www.amazon.com'; const newRootStack = (0, util_1.testStack)({ stackName: oldRootStack.stackName, template: oldRootStack.template }); if (hotswapMode === common_1.HotswapMode.FALL_BACK) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).not.toHaveReceivedCommand(client_lambda_1.UpdateFunctionCodeCommand); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'root-function', S3Bucket: 'new-bucket', S3Key: 'current-key', }); } }); (0, silent_1.silentTest)(`deleting a nested stack results in a full deployment in CLASSIC mode, even if their parent contains a hotswappable change, but results in a hotswap deployment in HOTSWAP_ONLY mode`, async () => { // GIVEN hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('NestedStackDeletionRoot'); const oldRootStack = (0, util_1.testStack)({ stackName: 'NestedStackDeletionRoot', template: { Resources: { NestedStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', }, Metadata: { 'aws:asset:path': 'one-lambda-stack.nested.template.json', }, }, Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'root-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, }); setup.addTemplateToCloudFormationLookupMock(oldRootStack); setup.addTemplateToCloudFormationLookupMock((0, util_1.testStack)({ stackName: 'NestedStack', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'my-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, })); setup.pushNestedStackResourceSummaries('NestedStackDeletionRoot', setup.stackSummaryOf('NestedStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/NestedStack/abcd')); oldRootStack.template.Resources.Func.Properties.Code.S3Bucket = 'new-bucket'; delete oldRootStack.template.Resources.NestedStack; const newRootStack = (0, util_1.testStack)({ stackName: oldRootStack.stackName, template: oldRootStack.template }); if (hotswapMode === common_1.HotswapMode.FALL_BACK) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).not.toHaveReceivedCommand(client_lambda_1.UpdateFunctionCodeCommand); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'root-function', S3Bucket: 'new-bucket', S3Key: 'current-key', }); } }); (0, silent_1.silentTest)(`creating a nested stack results in a full deployment in CLASSIC mode, even if their parent contains a hotswappable change, but results in a hotswap deployment in HOTSWAP_ONLY mode`, async () => { // GIVEN hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('NestedStackCreationRoot'); const oldRootStack = (0, util_1.testStack)({ stackName: 'NestedStackCreationRoot', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'root-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, }); setup.addTemplateToCloudFormationLookupMock(oldRootStack); oldRootStack.template.Resources.Func.Properties.Code.S3Bucket = 'new-bucket'; oldRootStack.template.Resources.NestedStack = { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.amazon.com', }, Metadata: { 'aws:asset:path': 'one-lambda-stack.nested.template.json', }, }; // we need this because testStack() immediately writes the template to disk, so changing the template afterwards is not going to update the file. const newRootStack = (0, util_1.testStack)({ stackName: oldRootStack.stackName, template: oldRootStack.template }); if (hotswapMode === common_1.HotswapMode.FALL_BACK) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).not.toHaveReceivedCommand(client_lambda_1.UpdateFunctionCodeCommand); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'root-function', S3Bucket: 'new-bucket', S3Key: 'current-key', }); } }); (0, silent_1.silentTest)(`attempting to hotswap a newly created nested stack with the same logical ID as a resource with a different type results in a full deployment in CLASSIC mode and a hotswap deployment in HOTSWAP_ONLY mode`, async () => { // GIVEN hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('NestedStackTypeChangeRoot'); const oldRootStack = (0, util_1.testStack)({ stackName: 'NestedStackTypeChangeRoot', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'root-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, FutureNestedStack: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'new-key', }, FunctionName: 'spooky-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, }); setup.addTemplateToCloudFormationLookupMock(oldRootStack); oldRootStack.template.Resources.Func.Properties.Code.S3Bucket = 'new-bucket'; oldRootStack.template.Resources.FutureNestedStack = { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.amazon.com', }, Metadata: { 'aws:asset:path': 'one-lambda-stack.nested.template.json', }, }; // write the updated template to disk const newRootStack = (0, util_1.testStack)({ stackName: oldRootStack.stackName, template: oldRootStack.template }); if (hotswapMode === common_1.HotswapMode.FALL_BACK) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).not.toHaveReceivedCommand(client_lambda_1.UpdateFunctionCodeCommand); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'root-function', S3Bucket: 'new-bucket', S3Key: 'current-key', }); } }); (0, silent_1.silentTest)('multi-sibling + 3-layer nested stack structure is hotswappable', async () => { // GIVEN hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('MultiLayerRoot'); const lambdaFunctionResource = { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }; const oldRootStack = (0, util_1.testStack)({ stackName: 'MultiLayerRoot', template: { Resources: { ChildStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', }, Metadata: { 'aws:asset:path': 'one-unnamed-lambda-two-stacks-stack.nested.template.json', }, }, Func: lambdaFunctionResource, }, }, }); setup.addTemplateToCloudFormationLookupMock(oldRootStack); const oldChildStack = (0, util_1.testStack)({ stackName: 'ChildStack', template: { Resources: { GrandChildStackA: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', }, Metadata: { 'aws:asset:path': 'one-unnamed-lambda-stack.nested.template.json', }, }, GrandChildStackB: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', }, Metadata: { 'aws:asset:path': 'one-unnamed-lambda-stack.nested.template.json', }, }, Func: lambdaFunctionResource, }, }, }); setup.addTemplateToCloudFormationLookupMock(oldChildStack); setup.addTemplateToCloudFormationLookupMock((0, util_1.testStack)({ stackName: 'GrandChildStackA', template: { Resources: { Func: lambdaFunctionResource, }, }, })); setup.addTemplateToCloudFormationLookupMock((0, util_1.testStack)({ stackName: 'GrandChildStackB', template: { Resources: { Func: lambdaFunctionResource, }, }, })); setup.pushNestedStackResourceSummaries('MultiLayerRoot', setup.stackSummaryOf('ChildStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/ChildStack/abcd'), setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'root-function')); setup.pushNestedStackResourceSummaries('ChildStack', setup.stackSummaryOf('GrandChildStackA', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/GrandChildStackA/abcd'), setup.stackSummaryOf('GrandChildStackB', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/GrandChildStackB/abcd'), setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'child-function')); setup.pushNestedStackResourceSummaries('GrandChildStackA', setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'grandchild-A-function')); setup.pushNestedStackResourceSummaries('GrandChildStackB', setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'grandchild-B-function')); // WHEN oldRootStack.template.Resources.Func.Properties.Code.S3Key = 'new-key'; oldRootStack.template.Resources.ChildStack.Properties.TemplateURL = 'https://www.amazon.com'; oldChildStack.template.Resources.GrandChildStackA.Properties.TemplateURL = 'https://www.amazon.com'; oldChildStack.template.Resources.GrandChildStackB.Properties.TemplateURL = 'https://www.amazon.com'; const newRootStack = (0, util_1.testStack)({ stackName: oldRootStack.stackName, template: oldRootStack.template }); //testStack({ stackName: oldChildStack.stackName, template: oldChildStack.template }); const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'root-function', S3Bucket: 'current-bucket', S3Key: 'new-key', }); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'child-function', S3Bucket: 'current-bucket', S3Key: 'new-key', }); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'grandchild-A-function', S3Bucket: 'current-bucket', S3Key: 'new-key', }); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'grandchild-B-function', S3Bucket: 'current-bucket', S3Key: 'new-key', }); }); (0, silent_1.silentTest)('can hotswap a lambda function in a 1-level nested stack with asset parameters', async () => { // GIVEN hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('LambdaRoot'); const rootStack = (0, util_1.testStack)({ stackName: 'LambdaRoot', template: { Resources: { NestedStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', Parameters: { referencetoS3BucketParam: { Ref: 'S3BucketParam', }, referencetoS3KeyParam: { Ref: 'S3KeyParam', }, }, }, Metadata: { 'aws:asset:path': 'one-lambda-stack-with-asset-parameters.nested.template.json', }, }, }, Parameters: { S3BucketParam: { Type: 'String', Description: 'S3 bucket for asset', }, S3KeyParam: { Type: 'String', Description: 'S3 bucket for asset', }, }, }, }); setup.addTemplateToCloudFormationLookupMock(rootStack); setup.addTemplateToCloudFormationLookupMock((0, util_1.testStack)({ stackName: 'NestedStack', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'my-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, })); setup.pushNestedStackResourceSummaries('LambdaRoot', setup.stackSummaryOf('NestedStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/NestedStack/abcd')); rootStack.template.Resources.NestedStack.Properties.TemplateURL = 'https://www.amazon.com'; // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, rootStack, { S3BucketParam: 'bucket-param-value', S3KeyParam: 'key-param-value', }); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'my-function', S3Bucket: 'bucket-param-value', S3Key: 'key-param-value', }); }); (0, silent_1.silentTest)('can hotswap a lambda function in a 2-level nested stack with dependency on an output of 2nd level sibling stack', async () => { // GIVEN: RootStack has one child stack `FirstLevelNestedStack` which further has two child stacks // `NestedLambdaStack` and `NestedSiblingStack`. `NestedLambdaStack` takes two parameters s3Key // and s3Bucket and use them for a Lambda function. // RootStack resolves s3Bucket from a root template parameter and passed to FirstLevelRootStack which // resolves s3Key through output of `NestedSiblingStack` hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('RootStack'); const oldRootStack = (0, util_1.testStack)({ stackName: 'RootStack', template: { Resources: { FirstLevelNestedStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', Parameters: { S3BucketParam: { Ref: 'S3BucketParam', }, }, }, Metadata: { 'aws:asset:path': 'one-stack-with-two-nested-stacks-stack.template.json', }, }, }, Parameters: { S3BucketParam: { Type: 'String', Description: 'S3 bucket for asset', }, }, }, }); const oldFirstLevelNestedStack = (0, util_1.testStack)({ stackName: 'FirstLevelNestedStack', template: { Resources: { NestedLambdaStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', Parameters: { referenceToS3BucketParam: { Ref: 'S3BucketParam', }, referenceToS3StackKeyOutput: { 'Fn::GetAtt': ['NestedSiblingStack', 'Outputs.NestedOutput'], }, }, }, Metadata: { 'aws:asset:path': 'one-lambda-stack-with-dependency-on-sibling-stack-output.nested.template.json', }, }, NestedSiblingStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', }, Metadata: { 'aws:asset:path': 'one-output-stack.nested.template.json', }, }, }, Parameters: { S3BucketParam: { Type: 'String', Description: 'S3 bucket for asset', }, }, }, }); const nestedLambdaStack = (0, util_1.testStack)({ stackName: 'NestedLambdaStack', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'my-function', }, }, }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }); const nestedSiblingStack = (0, util_1.testStack)({ stackName: 'NestedSiblingStack', template: { Outputs: { NestedOutput: { Value: 's3-key-value-from-output' }, }, }, }); setup.addTemplateToCloudFormationLookupMock(oldRootStack); setup.addTemplateToCloudFormationLookupMock(oldFirstLevelNestedStack); setup.addTemplateToCloudFormationLookupMock(nestedLambdaStack); setup.addTemplateToCloudFormationLookupMock(nestedSiblingStack); setup.pushNestedStackResourceSummaries(oldRootStack.stackName, setup.stackSummaryOf(oldFirstLevelNestedStack.stackName, 'AWS::CloudFormation::Stack', `arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/${oldFirstLevelNestedStack.stackName}/abcd`)); setup.pushNestedStackResourceSummaries(oldFirstLevelNestedStack.stackName, setup.stackSummaryOf(nestedLambdaStack.stackName, 'AWS::CloudFormation::Stack', `arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/${nestedLambdaStack.stackName}/abcd`), setup.stackSummaryOf(nestedSiblingStack.stackName, 'AWS::CloudFormation::Stack', `arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/${nestedSiblingStack.stackName}/abcd`)); setup.pushNestedStackResourceSummaries(nestedLambdaStack.stackName, setup.stackSummaryOf('Func', 'AWS::Lambda::Function', 'nested-lambda-function')); setup.pushNestedStackResourceSummaries(nestedSiblingStack.stackName); oldRootStack.template.Resources.FirstLevelNestedStack.Properties.TemplateURL = 'https://www.amazon.com'; oldFirstLevelNestedStack.template.Resources.NestedLambdaStack.Properties.TemplateURL = 'https://www.amazon.com'; oldFirstLevelNestedStack.template.Resources.NestedSiblingStack.Properties.TemplateURL = 'https://www.amazon.com'; const newRootStack = (0, util_1.testStack)({ stackName: oldRootStack.stackName, template: oldRootStack.template }); (0, util_1.testStack)({ stackName: oldFirstLevelNestedStack.stackName, template: oldFirstLevelNestedStack.template }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, newRootStack, { S3BucketParam: 'new-bucket', }); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'my-function', S3Bucket: 'new-bucket', S3Key: 's3-key-value-from-output', }); }); (0, silent_1.silentTest)('can hotswap a lambda function in a 1-level nested stack and read default parameters value if not provided', async () => { // GIVEN: RootStack has one child stack `NestedStack`. `NestedStack` takes two // parameters s3Key and s3Bucket and use them for a Lambda function. // RootStack resolves both parameters from root template parameters. Current/old change // has hardcoded resolved values and the new change doesn't provide parameters through // root stack forcing the evaluation of default parameter values. hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('LambdaRoot'); const rootStack = (0, util_1.testStack)({ stackName: 'LambdaRoot', template: { Resources: { NestedStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', Parameters: { referencetoS3BucketParam: { Ref: 'S3BucketParam', }, referencetoS3KeyParam: { Ref: 'S3KeyParam', }, }, }, Metadata: { 'aws:asset:path': 'one-lambda-stack-with-asset-parameters.nested.template.json', }, }, }, Parameters: { S3BucketParam: { Type: 'String', Description: 'S3 bucket for asset', Default: 'default-s3-bucket', }, S3KeyParam: { Type: 'String', Description: 'S3 bucket for asset', Default: 'default-s3-key', }, }, }, }); setup.addTemplateToCloudFormationLookupMock(rootStack); setup.addTemplateToCloudFormationLookupMock((0, util_1.testStack)({ stackName: 'NestedStack', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'my-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, })); setup.pushNestedStackResourceSummaries('LambdaRoot', setup.stackSummaryOf('NestedStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/NestedStack/abcd')); rootStack.template.Resources.NestedStack.Properties.TemplateURL = 'https://www.amazon.com'; // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, rootStack); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'my-function', S3Bucket: 'default-s3-bucket', S3Key: 'default-s3-key', }); }); (0, silent_1.silentTest)('can hotswap a lambda function in a 2-level nested stack with asset parameters', async () => { // GIVEN hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('LambdaRoot'); const rootStack = (0, util_1.testStack)({ stackName: 'LambdaRoot', template: { Resources: { ChildStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', Parameters: { referencetoGrandChildS3BucketParam: { Ref: 'GrandChildS3BucketParam', }, referencetoGrandChildS3KeyParam: { Ref: 'GrandChildS3KeyParam', }, referencetoChildS3BucketParam: { Ref: 'ChildS3BucketParam', }, referencetoChildS3KeyParam: { Ref: 'ChildS3KeyParam', }, }, }, Metadata: { 'aws:asset:path': 'one-lambda-one-stack-stack-with-asset-parameters.nested.template.json', }, }, }, Parameters: { GrandChildS3BucketParam: { Type: 'String', Description: 'S3 bucket for asset', }, GrandChildS3KeyParam: { Type: 'String', Description: 'S3 bucket for asset', }, ChildS3BucketParam: { Type: 'String', Description: 'S3 bucket for asset', }, ChildS3KeyParam: { Type: 'String', Description: 'S3 bucket for asset', }, }, }, }); setup.addTemplateToCloudFormationLookupMock(rootStack); const childStack = (0, util_1.testStack)({ stackName: 'ChildStack', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'my-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, GrandChildStack: { Type: 'AWS::CloudFormation::Stack', Properties: { TemplateURL: 'https://www.magic-url.com', }, Metadata: { 'aws:asset:path': 'one-lambda-stack-with-asset-parameters.nested.template.json', }, }, }, }, }); setup.addTemplateToCloudFormationLookupMock(childStack); setup.addTemplateToCloudFormationLookupMock((0, util_1.testStack)({ stackName: 'GrandChildStack', template: { Resources: { Func: { Type: 'AWS::Lambda::Function', Properties: { Code: { S3Bucket: 'current-bucket', S3Key: 'current-key', }, FunctionName: 'my-function', }, Metadata: { 'aws:asset:path': 'old-lambda-path', }, }, }, }, })); setup.pushNestedStackResourceSummaries('LambdaRoot', setup.stackSummaryOf('ChildStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/ChildStack/abcd')); setup.pushNestedStackResourceSummaries('ChildStack', setup.stackSummaryOf('GrandChildStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/GrandChildStack/abcd')); rootStack.template.Resources.ChildStack.Properties.TemplateURL = 'https://www.amazon.com'; childStack.template.Resources.GrandChildStack.Properties.TemplateURL = 'https://www.amazon.com'; // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, rootStack, { GrandChildS3BucketParam: 'child-bucket-param-value', GrandChildS3KeyParam: 'child-key-param-value', ChildS3BucketParam: 'bucket-param-value', ChildS3KeyParam: 'key-param-value', }); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand, { FunctionName: 'my-function', S3Bucket: 'bucket-param-value', S3Key: 'key-param-value', }); expect(mock_sdk_1.mockLambdaClient).toHaveReceivedCommandWith(client_lambda_1.UpdateFunctionCodeCommand,