aws-cdk
Version:
CDK Toolkit, the command line tool for CDK apps
1,015 lines • 150 kB
JavaScript
"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,