From 207be2700c9dee8a2ef600c5b38d362565ac3501 Mon Sep 17 00:00:00 2001 From: Praveen Gupta Date: Tue, 21 Nov 2023 02:57:44 +0100 Subject: [PATCH 1/2] fix: evaluate all nested stacks during GetAtt evaluation --- .../lib/api/evaluate-cloudformation-template.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk/lib/api/evaluate-cloudformation-template.ts b/packages/aws-cdk/lib/api/evaluate-cloudformation-template.ts index 258b64829e62a..329bf2ec9ba59 100644 --- a/packages/aws-cdk/lib/api/evaluate-cloudformation-template.ts +++ b/packages/aws-cdk/lib/api/evaluate-cloudformation-template.ts @@ -387,7 +387,7 @@ export class EvaluateCloudFormationTemplate { if (foundResource.ResourceType == 'AWS::CloudFormation::Stack' && attribute?.startsWith('Outputs.')) { // need to resolve attributes from another stack's Output section - const dependantStackName = this.nestedStackNames[logicalId]?.nestedStackPhysicalName; + const dependantStackName = this.findNestedStack(logicalId, this.nestedStackNames); if (!dependantStackName) { //this is a newly created nested stack and cannot be hotswapped return undefined; @@ -406,6 +406,19 @@ export class EvaluateCloudFormationTemplate { return this.formatResourceAttribute(foundResource, attribute); } + private findNestedStack(logicalId: string, nestedStackNames: { + [nestedStackLogicalId: string]: NestedStackNames; + }): string | undefined { + for (const [nestedStackLogicalId, { nestedChildStackNames, nestedStackPhysicalName }] of Object.entries(nestedStackNames)) { + if (nestedStackLogicalId === logicalId) { + return nestedStackPhysicalName; + } + const checkInNestedChildStacks = this.findNestedStack(logicalId, nestedChildStackNames); + if (checkInNestedChildStacks) return checkInNestedChildStacks; + } + return undefined; + } + private formatResourceAttribute(resource: AWS.CloudFormation.StackResourceSummary, attribute: string | undefined): string | undefined { const physicalId = resource.PhysicalResourceId; From 4d9930963cef9f35a41bef5f7c5ed28da63af401 Mon Sep 17 00:00:00 2001 From: Praveen Gupta Date: Tue, 21 Nov 2023 04:16:04 +0100 Subject: [PATCH 2/2] update tests --- .../api/hotswap/nested-stacks-hotswap.test.ts | 55 ++++++++++++++++--- ...with-two-nested-stacks-stack.template.json | 38 +++++++++++++ 2 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 packages/aws-cdk/test/nested-stack-templates/one-stack-with-two-nested-stacks-stack.template.json diff --git a/packages/aws-cdk/test/api/hotswap/nested-stacks-hotswap.test.ts b/packages/aws-cdk/test/api/hotswap/nested-stacks-hotswap.test.ts index 77d159074c8da..cb499f24bd3b7 100644 --- a/packages/aws-cdk/test/api/hotswap/nested-stacks-hotswap.test.ts +++ b/packages/aws-cdk/test/api/hotswap/nested-stacks-hotswap.test.ts @@ -826,10 +826,12 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot }); }); - test('can hotswap a lambda function in a 1-level nested stack with dependency on a output of sibling stack', async () => { - // GIVEN: RootStack 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 s3Key through output of `NestedSiblingStack` + test('can hotswap a lambda function in a 2-level nested stack with dependency on a output of 2nd level sibling stack', async () => { + // GIVEN: RootStack has one child stack `FirstLevelRootStack` 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'); mockUpdateLambdaCode = jest.fn().mockReturnValue({}); hotswapMockSdkProvider.stubLambda({ @@ -838,6 +840,34 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot const rootStack = testStack({ stackName: 'RootStack', + template: { + Resources: { + FirstLevelRootStack: { + 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 firstLevelRootStack = testStack({ + stackName: 'FirstLevelRootStack', template: { Resources: { NestedLambdaStack: { @@ -869,11 +899,11 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot 'aws:asset:path': 'one-output-stack.nested.template.json', }, }, - Parameters: { - S3BucketParam: { - Type: 'String', - Description: 'S3 bucket for asset', - }, + }, + Parameters: { + S3BucketParam: { + Type: 'String', + Description: 'S3 bucket for asset', }, }, }, @@ -913,10 +943,17 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot }); setup.addTemplateToCloudFormationLookupMock(rootStack); + setup.addTemplateToCloudFormationLookupMock(firstLevelRootStack); setup.addTemplateToCloudFormationLookupMock(nestedLambdaStack); setup.addTemplateToCloudFormationLookupMock(nestedSiblingStack); setup.pushNestedStackResourceSummaries('RootStack', + setup.stackSummaryOf('FirstLevelRootStack', 'AWS::CloudFormation::Stack', + 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/FirstLevelRootStack/abcd', + ), + ); + + setup.pushNestedStackResourceSummaries('FirstLevelRootStack', setup.stackSummaryOf('NestedLambdaStack', 'AWS::CloudFormation::Stack', 'arn:aws:cloudformation:bermuda-triangle-1337:123456789012:stack/NestedLambdaStack/abcd', ), diff --git a/packages/aws-cdk/test/nested-stack-templates/one-stack-with-two-nested-stacks-stack.template.json b/packages/aws-cdk/test/nested-stack-templates/one-stack-with-two-nested-stacks-stack.template.json new file mode 100644 index 0000000000000..bdf9ed5cb2c51 --- /dev/null +++ b/packages/aws-cdk/test/nested-stack-templates/one-stack-with-two-nested-stacks-stack.template.json @@ -0,0 +1,38 @@ +{ + "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" + } + } +}