aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.39k stars 3.79k forks source link

(aws-cdk-lib): creating a stack and a bucket with the same construct ID works, but then fails when trying to add a role to the bucket #29625

Open Ronnie76er opened 3 months ago

Ronnie76er commented 3 months ago

Describe the bug

I created a stack and a bucket with the same value for id, some-bucket in the example. This created successfully. I then tried to add a role to the bucket. It failed with an error:

There is already a Construct with name 'some-bucket' in SampleCdkIssueStack

Expected Behavior

I expect that the stack would error out on first create, being that the stack construct ID and the bucket construct ID are the same.

Current Behavior

The stack is allowed to be created at first, but you cannot update the role afterwards, and need to do some type of migration of the bucket to fix it.

The full stack trace of the error is:

      throw new Error(`There is already a Construct with name '${childName}' in ${typeName}${name.length > 0 ? ' [' + name + ']' : ''}`);
            ^
Error: There is already a Construct with name 'some-bucket' in SampleCdkIssueStack [some-bucket]
    at Node.addChild (/workspaces/sample-cdk-issue/node_modules/constructs/src/construct.ts:447:13)
    at new Node (/workspaces/sample-cdk-issue/node_modules/constructs/src/construct.ts:71:17)
    at new Construct (/workspaces/sample-cdk-issue/node_modules/constructs/src/construct.ts:499:17)
    at new Resource (/workspaces/sample-cdk-issue/node_modules/aws-cdk-lib/core/lib/resource.js:1:1309)
    at new BucketBase (/workspaces/sample-cdk-issue/node_modules/aws-cdk-lib/aws-s3/lib/bucket.js:1:2129)
    at new Bucket (/workspaces/sample-cdk-issue/node_modules/aws-cdk-lib/aws-s3/lib/bucket.js:1:19662)
    at new SampleCdkIssueStack (/workspaces/sample-cdk-issue/lib/sample-cdk-issue-stack.ts:12:28)
    at Object.<anonymous> (/workspaces/sample-cdk-issue/bin/sample-cdk-issue.ts:7:1)
    at Module._compile (node:internal/modules/cjs/loader:1241:14)
    at Module.m._compile (/workspaces/sample-cdk-issue/node_modules/ts-node/src/index.ts:1618:23)

Reproduction Steps

bin/sample-cdk-issue.ts

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { SampleCdkIssueStack } from '../lib/sample-cdk-issue-stack';

const app = new cdk.App();
new SampleCdkIssueStack(app, `some-bucket`, {
});

lib/sample-cdk-issue-stack.ts

import { aws_s3 as s3, Stack, StackProps, Tags } from 'aws-cdk-lib';
import { Role } from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class SampleCdkIssueStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);
        const someBucket = new s3.Bucket(this, 'some-bucket', {
            bucketName: `a-bucket-${this.account}`
        });

        // Uncomment this after initial stack creation to reproduce the issue
        // const someRole = Role.fromRoleName(this, id, 'some-role');
        // someBucket.grantReadWrite(someRole);
    }
}
  1. Create the stack exactly as is in the code here
  2. Uncomment the commented out lines, and provide a valid role
  3. Try to deploy the stack again

This is the only use case I came across where it happens. I tried adding a tag to the bucket, but that did NOT recreate the issue.

Possible Solution

No response

Additional Information/Context

No response

CDK CLI Version

2.133.0 (build dcc1e75)

Framework Version

No response

Node.js Version

v20.6.1

OS

Linux 6a3d87591146 6.6.16-linuxkit #1 SMP Fri Feb 16 11:54:02 UTC 2024 aarch64 GNU/Linux

Language

TypeScript

Language Version

5.3.3

Other information

No response

khushail commented 3 months ago

Hi @Ronnie76er , thanks for reaching out. This scenario works fine for me ,given the same bucket name and stack name. I Here is the snapshot for the same -

Screenshot 2024-04-02 at 5 47 36 PM

sample code snippet for reference -

export class BucketNameIssueStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const s3Bucket = new s3.Bucket(this, "some-bucket", {
      bucketName: "my-bucket-name-009"
    })

    const somerole = new iam.Role(this, 'some-role', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
    });

    s3Bucket.grantReadWrite(somerole);
  }
}

There Cloudformation tracks resources through Logical ids described in this article. Although given the same name to stack and bucket, their logical id is different ,hence it should not cause any error.

github-actions[bot] commented 3 months ago

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.

Ronnie76er commented 3 months ago

@khushail it's very weird. So, using your code to reference the role, I get the same result as you, it works. However, referencing a role in the way I do, the error is there. You may need to reference a role that already exists in the account to get it to reproduce.

Here's the code now:

export class SampleCdkIssueStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);
        const someBucket = new s3.Bucket(this, 'some-bucket', {
            bucketName: `a-bucket-${this.account}`
        });

        // This throws the "Error: There is already a Construct with name 'some-bucket' in SampleCdkIssueStack [some-bucket]"
        // const someRole = Role.fromRoleName(this, id, 'some-role');

        // This works fine
        // const someRole = new iam.Role(this, 'some-role', {
        //     assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
        // });

        // someBucket.grantReadWrite(someRole);
    }
}

And here's the screenshot of it running, first referencing the role how I did, and then creating a role as you did (I aborted without deploying, but it seems like it would apply fine): image

Here's a screenshot of the resources in the CloudFormation, before trying to apply the role: image

Let me know if there's any other information I could provide. What I'm trying to do in my actual CloudFormation is allow an existing role in the account to readWrite to the bucket, and I'm wondering if I have to do some annoying moving of resources so that the logical ids don't conflict.

NOTE: Doing the above, I used CDK version: 2.135.0 (build d46c474)

khushail commented 21 hours ago

Hey @Ronnie76er , I used an existing role and granted the bucket read write access to the role which succeeded. Sharing the snippet -

    const s3Bucket = new s3.Bucket(this, "some-bucket", {
      bucketName: "my-bucket-name-0913"
    })

    const somerole =  iam.Role.fromRoleArn(this,"some-role-091","arn:aws:iam::12345678910:role/some-role-name-090")

    s3Bucket.grantReadWrite(somerole);

This is the policy role had-

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:Abort*",
                "s3:DeleteObject*",
                "s3:GetBucket*",
                "s3:GetObject*",
                "s3:List*",
                "s3:PutObject",
                "s3:PutObjectLegalHold",
                "s3:PutObjectRetention",
                "s3:PutObjectTagging",
                "s3:PutObjectVersionTagging"
            ],
            "Resource": [
                "arn:aws:s3:::my-bucket-name-0913",
                "arn:aws:s3:::my-bucket-name-0913/*"
            ],
            "Effect": "Allow"
        }
    ]
}

let me know if this does not work for you.