Skip to content

diff: Duplicate logical ids in nested stack causes incorrect path to show in diffs. #952

@Sinua

Description

@Sinua

Describe the bug

CDK diff shows nested stack resource paths for changes related to parent stack resource paths.

Regression Issue

  • Select this option if this issue appears to be a regression.

Last Known Working CDK Version

No response

Expected Behavior

If a stack that has a defined nested stack, where both stacks have a resource that share an id, then running cdk diff should show the parent resource's path for changes related to it, and the child resource's path for changes related to it.

Current Behavior

If a stack that has a defined nested stack, where both stacks have a resource that share an id, then running cdk diff will show the child resource's path for all changes related to the parent and child resource.

Reproduction Steps

from aws_cdk import (
    Stack,
    NestedStack,
    aws_s3 as s3,
    RemovalPolicy,
)
from constructs import Construct


class TestNestedStack(NestedStack):

    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        **kwargs,
    ) -> None:
        super().__init__(scope, construct_id, **kwargs)

        self.test_bucket = s3.Bucket(
            self,
            "TestBucket",
            bucket_name=None,
            removal_policy=RemovalPolicy.DESTROY,
            auto_delete_objects=True,
        )


class TestStack(Stack):
    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        **kwargs,
    ) -> None:
        super().__init__(scope, construct_id, **kwargs)

        self.test_bucket = s3.Bucket(
            self,
            "TestBucket",
            bucket_name=None,
            removal_policy=RemovalPolicy.DESTROY,
            auto_delete_objects=True,
        )

        self.nested_stack = TestNestedStack(
            self,
            "TestNestedStack",
        )

Running CDK diff on this stack when it has never been deployed produces the following:

Stack TestStack
IAM Statement Changes
┌───┬───────────────────────────────────────────────────────────────────────────────┬────────┬────────────────────┬───────────────────────────────────────────────────────────────────────────────────┬───────────┐
│   │ Resource                                                                      │ Effect │ Action             │ Principal                                                                         │ Condition │
├───┼───────────────────────────────────────────────────────────────────────────────┼────────┼────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼───────────┤
│ + │ ${TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role.Arn} │ Allow  │ sts:AssumeRole     │ Service:lambda.amazonaws.com                                                      │           │
├───┼───────────────────────────────────────────────────────────────────────────────┼────────┼────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼───────────┤
│ + │ ${TestNestedStack/TestBucket.Arn}                                             │ Allow  │ s3:DeleteObject*   │ AWS:${TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role.Arn} │           │
│   │ ${TestNestedStack/TestBucket.Arn}/*                                           │        │ s3:GetBucket*      │                                                                                   │           │
│   │                                                                               │        │ s3:List*           │                                                                                   │           │
│   │                                                                               │        │ s3:PutBucketPolicy │                                                                                   │           │
└───┴───────────────────────────────────────────────────────────────────────────────┴────────┴────────────────────┴───────────────────────────────────────────────────────────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬───────────────────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                                                  │ Managed Policy ARN                                                                           │
├───┼───────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role} │ {"Fn::Sub":"arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"} │
└───┴───────────────────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Parameters
[+] Parameter BootstrapVersion BootstrapVersion: {"Type":"AWS::SSM::Parameter::Value<String>","Default":"/cdk-bootstrap/hnb659fds/version","Description":"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"}

Resources
[+] AWS::S3::Bucket TestNestedStack/TestBucket TestBucket560B80BC
[+] AWS::S3::BucketPolicy TestNestedStack/TestBucket/Policy TestBucketPolicyBA12ED38
[+] Custom::S3AutoDeleteObjects TestNestedStack/TestBucket/AutoDeleteObjectsCustomResource TestBucketAutoDeleteObjectsCustomResource8FEAABD5
[+] AWS::IAM::Role TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092
[+] AWS::Lambda::Function TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F
[+] AWS::CloudFormation::Stack TestNestedStack.NestedStack/TestNestedStack.NestedStackResource TestNestedStackNestedStackTestNestedStackNestedStackResource68E05791

Stack TestNestedStackNestedStackTestNestedStackNestedStackResource68E05791
IAM Statement Changes
┌───┬───────────────────────────────────────────────────────────────────────────────┬────────┬────────────────────┬───────────────────────────────────────────────────────────────────────────────────┬───────────┐
│   │ Resource                                                                      │ Effect │ Action             │ Principal                                                                         │ Condition │
├───┼───────────────────────────────────────────────────────────────────────────────┼────────┼────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼───────────┤
│ + │ ${TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role.Arn} │ Allow  │ sts:AssumeRole     │ Service:lambda.amazonaws.com                                                      │           │
├───┼───────────────────────────────────────────────────────────────────────────────┼────────┼────────────────────┼───────────────────────────────────────────────────────────────────────────────────┼───────────┤
│ + │ ${TestNestedStack/TestBucket.Arn}                                             │ Allow  │ s3:DeleteObject*   │ AWS:${TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role.Arn} │           │
│   │ ${TestNestedStack/TestBucket.Arn}/*                                           │        │ s3:GetBucket*      │                                                                                   │           │
│   │                                                                               │        │ s3:List*           │                                                                                   │           │
│   │                                                                               │        │ s3:PutBucketPolicy │                                                                                   │           │
└───┴───────────────────────────────────────────────────────────────────────────────┴────────┴────────────────────┴───────────────────────────────────────────────────────────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬───────────────────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                                                  │ Managed Policy ARN                                                                           │
├───┼───────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role} │ {"Fn::Sub":"arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"} │
└───┴───────────────────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[+] AWS::S3::Bucket TestNestedStack/TestBucket TestBucket560B80BC
[+] AWS::S3::BucketPolicy TestNestedStack/TestBucket/Policy TestBucketPolicyBA12ED38
[+] Custom::S3AutoDeleteObjects TestNestedStack/TestBucket/AutoDeleteObjectsCustomResource TestBucketAutoDeleteObjectsCustomResource8FEAABD5
[+] AWS::IAM::Role TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092
[+] AWS::Lambda::Function TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F

In the table shown above, even for the parent stack, all paths related to the created buckets points to the child resource, even when they should be pointing to the parent resource.

Possible Solution

The reason the bug happens is because logical ids for these resources is created using their route to their parent stack (but excluding the parent stack) here, both parent and child resources share the same id, therefore, they have the same logical id. When the buildLogicalToPathMap method is called, it iterates over all resources in the stack to setup the map, but when the stack and the nested stack have a logical id collision, the path value for the parent resource is overridden.

I added a log to that method before it sets the map value, here is the result of diffing:

-STACK TestStack LOGID TestBucket560B80BC PATH /TestStack/TestBucket/Resource
STACK TestStack LOGID TestBucketPolicyBA12ED38 PATH /TestStack/TestBucket/Policy/Resource
STACK TestStack LOGID TestBucketAutoDeleteObjectsCustomResource8FEAABD5 PATH /TestStack/TestBucket/AutoDeleteObjectsCustomResource/Default
STACK TestStack LOGID CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092 PATH /TestStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role
STACK TestStack LOGID CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F PATH /TestStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler
STACK TestStack LOGID TestBucket560B80BC PATH /TestStack/TestNestedStack/TestBucket/Resource
STACK TestStack LOGID TestBucketPolicyBA12ED38 PATH /TestStack/TestNestedStack/TestBucket/Policy/Resource
STACK TestStack LOGID TestBucketAutoDeleteObjectsCustomResource8FEAABD5 PATH /TestStack/TestNestedStack/TestBucket/AutoDeleteObjectsCustomResource/Default
STACK TestStack LOGID CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092 PATH /TestStack/TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role
STACK TestStack LOGID CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F PATH /TestStack/TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler
STACK TestStack LOGID CDKMetadata PATH /TestStack/TestNestedStack/CDKMetadata/Default
STACK TestStack LOGID TestNestedStackNestedStackTestNestedStackNestedStackResource68E05791 PATH /TestStack/TestNestedStack.NestedStack/TestNestedStack.NestedStackResource
STACK TestStack LOGID CDKMetadata PATH /TestStack/CDKMetadata/Default
STACK TestStack LOGID BootstrapVersion PATH /TestStack/BootstrapVersion
STACK TestStack LOGID CheckBootstrapVersion PATH /TestStack/CheckBootstrapVersion
STACK TestStack LOGID TestBucket560B80BC PATH /TestStack/TestBucket/Resource
STACK TestStack LOGID TestBucketPolicyBA12ED38 PATH /TestStack/TestBucket/Policy/Resource
STACK TestStack LOGID TestBucketAutoDeleteObjectsCustomResource8FEAABD5 PATH /TestStack/TestBucket/AutoDeleteObjectsCustomResource/Default
STACK TestStack LOGID CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092 PATH /TestStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role
STACK TestStack LOGID CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F PATH /TestStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler
-STACK TestStack LOGID TestBucket560B80BC PATH /TestStack/TestNestedStack/TestBucket/Resource
STACK TestStack LOGID TestBucketPolicyBA12ED38 PATH /TestStack/TestNestedStack/TestBucket/Policy/Resource
STACK TestStack LOGID TestBucketAutoDeleteObjectsCustomResource8FEAABD5 PATH /TestStack/TestNestedStack/TestBucket/AutoDeleteObjectsCustomResource/Default
STACK TestStack LOGID CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092 PATH /TestStack/TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role
STACK TestStack LOGID CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F PATH /TestStack/TestNestedStack/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler
STACK TestStack LOGID CDKMetadata PATH /TestStack/TestNestedStack/CDKMetadata/Default
STACK TestStack LOGID TestNestedStackNestedStackTestNestedStackNestedStackResource68E05791 PATH /TestStack/TestNestedStack.NestedStack/TestNestedStack.NestedStackResource
STACK TestStack LOGID CDKMetadata PATH /TestStack/CDKMetadata/Default
STACK TestStack LOGID BootstrapVersion PATH /TestStack/BootstrapVersion
STACK TestStack LOGID CheckBootstrapVersion PATH /TestStack/CheckBootstrapVersion

the highlighted lines show the collision clearly.

Additional Information/Context

No response

CDK CLI Version

2.1033.0 (build 1ec3310)

Framework Version

No response

Node.js Version

v25.2.1

OS

MacOS

Language

TypeScript, Python

Language Version

No response

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions