Skip to content

Commit 8fa6bdd

Browse files
Amplifiyerrix0rrr
authored andcommitted
fix(cli): hotswap cannot evaluate nested stacks within nested stacks
1 parent c6471f2 commit 8fa6bdd

File tree

3 files changed

+98
-10
lines changed

3 files changed

+98
-10
lines changed

packages/aws-cdk/lib/api/evaluate-cloudformation-template.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ export class EvaluateCloudFormationTemplate {
387387

388388
if (foundResource.ResourceType == 'AWS::CloudFormation::Stack' && attribute?.startsWith('Outputs.')) {
389389
// need to resolve attributes from another stack's Output section
390-
const dependantStackName = this.nestedStackNames[logicalId]?.nestedStackPhysicalName;
390+
const dependantStackName = this.findNestedStack(logicalId, this.nestedStackNames);
391391
if (!dependantStackName) {
392392
//this is a newly created nested stack and cannot be hotswapped
393393
return undefined;
@@ -406,6 +406,19 @@ export class EvaluateCloudFormationTemplate {
406406
return this.formatResourceAttribute(foundResource, attribute);
407407
}
408408

409+
private findNestedStack(logicalId: string, nestedStackNames: {
410+
[nestedStackLogicalId: string]: NestedStackNames;
411+
}): string | undefined {
412+
for (const [nestedStackLogicalId, { nestedChildStackNames, nestedStackPhysicalName }] of Object.entries(nestedStackNames)) {
413+
if (nestedStackLogicalId === logicalId) {
414+
return nestedStackPhysicalName;
415+
}
416+
const checkInNestedChildStacks = this.findNestedStack(logicalId, nestedChildStackNames);
417+
if (checkInNestedChildStacks) return checkInNestedChildStacks;
418+
}
419+
return undefined;
420+
}
421+
409422
private formatResourceAttribute(resource: AWS.CloudFormation.StackResourceSummary, attribute: string | undefined): string | undefined {
410423
const physicalId = resource.PhysicalResourceId;
411424

packages/aws-cdk/test/api/hotswap/nested-stacks-hotswap.test.ts

+46-9
Original file line numberDiff line numberDiff line change
@@ -826,10 +826,12 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot
826826
});
827827
});
828828

829-
test('can hotswap a lambda function in a 1-level nested stack with dependency on a output of sibling stack', async () => {
830-
// GIVEN: RootStack has two child stacks `NestedLambdaStack` and `NestedSiblingStack`. `NestedLambdaStack`
831-
// takes two parameters s3Key and s3Bucket and use them for a Lambda function.
832-
// RootStack resolves s3Bucket from a root template parameter and s3Key through output of `NestedSiblingStack`
829+
test('can hotswap a lambda function in a 2-level nested stack with dependency on a output of 2nd level sibling stack', async () => {
830+
// GIVEN: RootStack has one child stack `FirstLevelRootStack` which further has two child stacks
831+
// `NestedLambdaStack` and `NestedSiblingStack`. `NestedLambdaStack` takes two parameters s3Key
832+
// and s3Bucket and use them for a Lambda function.
833+
// RootStack resolves s3Bucket from a root template parameter and passed to FirstLevelRootStack which
834+
// resolves s3Key through output of `NestedSiblingStack`
833835
hotswapMockSdkProvider = setup.setupHotswapNestedStackTests('RootStack');
834836
mockUpdateLambdaCode = jest.fn().mockReturnValue({});
835837
hotswapMockSdkProvider.stubLambda({
@@ -838,6 +840,34 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot
838840

839841
const rootStack = testStack({
840842
stackName: 'RootStack',
843+
template: {
844+
Resources: {
845+
FirstLevelRootStack: {
846+
Type: 'AWS::CloudFormation::Stack',
847+
Properties: {
848+
TemplateURL: 'https://www.magic-url.com',
849+
Parameters: {
850+
S3BucketParam: {
851+
Ref: 'S3BucketParam',
852+
},
853+
},
854+
},
855+
Metadata: {
856+
'aws:asset:path': 'one-stack-with-two-nested-stacks-stack.template.json',
857+
},
858+
},
859+
},
860+
Parameters: {
861+
S3BucketParam: {
862+
Type: 'String',
863+
Description: 'S3 bucket for asset',
864+
},
865+
},
866+
},
867+
});
868+
869+
const firstLevelRootStack = testStack({
870+
stackName: 'FirstLevelRootStack',
841871
template: {
842872
Resources: {
843873
NestedLambdaStack: {
@@ -869,11 +899,11 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot
869899
'aws:asset:path': 'one-output-stack.nested.template.json',
870900
},
871901
},
872-
Parameters: {
873-
S3BucketParam: {
874-
Type: 'String',
875-
Description: 'S3 bucket for asset',
876-
},
902+
},
903+
Parameters: {
904+
S3BucketParam: {
905+
Type: 'String',
906+
Description: 'S3 bucket for asset',
877907
},
878908
},
879909
},
@@ -913,10 +943,17 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot
913943
});
914944

915945
setup.addTemplateToCloudFormationLookupMock(rootStack);
946+
setup.addTemplateToCloudFormationLookupMock(firstLevelRootStack);
916947
setup.addTemplateToCloudFormationLookupMock(nestedLambdaStack);
917948
setup.addTemplateToCloudFormationLookupMock(nestedSiblingStack);
918949

919950
setup.pushNestedStackResourceSummaries('RootStack',
951+
setup.stackSummaryOf('FirstLevelRootStack', 'AWS::CloudFormation::Stack',
952+
'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/FirstLevelRootStack/abcd',
953+
),
954+
);
955+
956+
setup.pushNestedStackResourceSummaries('FirstLevelRootStack',
920957
setup.stackSummaryOf('NestedLambdaStack', 'AWS::CloudFormation::Stack',
921958
'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/NestedLambdaStack/abcd',
922959
),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"Resources": {
3+
"NestedLambdaStack": {
4+
"Type": "AWS::CloudFormation::Stack",
5+
"Properties": {
6+
"TemplateURL": "https://www.magic-url.com",
7+
"Parameters": {
8+
"referenceToS3BucketParam": {
9+
"Ref": "S3BucketParam"
10+
},
11+
"referenceToS3StackKeyOutput": {
12+
"Fn::GetAtt": [
13+
"NestedSiblingStack",
14+
"Outputs.NestedOutput"
15+
]
16+
}
17+
}
18+
},
19+
"Metadata": {
20+
"aws:asset:path": "one-lambda-stack-with-dependency-on-sibling-stack-output.nested.template.json"
21+
}
22+
},
23+
"NestedSiblingStack": {
24+
"Type": "AWS::CloudFormation::Stack",
25+
"Properties": {
26+
"TemplateURL": "https://www.magic-url.com"
27+
},
28+
"Metadata": {
29+
"aws:asset:path": "one-output-stack.nested.template.json"
30+
}
31+
}
32+
},
33+
"Parameters": {
34+
"S3BucketParam": {
35+
"Type": "String"
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)