Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(ELB): Can't import 2 application listeners into the same scope #12132

Closed
JayLongJie opened this issue Dec 17, 2020 · 8 comments · Fixed by #12373
Closed

(ELB): Can't import 2 application listeners into the same scope #12132

JayLongJie opened this issue Dec 17, 2020 · 8 comments · Fixed by #12373
Assignees
Labels
@aws-cdk/aws-ec2 Related to Amazon Elastic Compute Cloud bug This issue is a bug. effort/small Small work item – less than a day of effort needs-triage This issue or PR still needs to be triaged. p1

Comments

@JayLongJie
Copy link

❓ General Issue

The Question

Having an extended AWS-CDK libarary for creating Stacks and ECS services, etc, after upgrade AWS-CDK, there are problems runing unit-test which works fine previously, below is the detail error:

There is already a Construct with name 'SecurityGroup' in Stack [testStack]

  at Node.addChild (packages/container-service/node_modules/constructs/src/construct.ts:534:13)
  at new Node (packages/container-service/node_modules/constructs/src/construct.ts:75:22)
  at new ConstructNode (packages/container-service/node_modules/@aws-cdk/core/lib/construct-compat.ts:277:24)
  at Object.createNode (packages/container-service/node_modules/@aws-cdk/core/lib/construct-compat.ts:71:11)
  at new Construct (packages/container-service/node_modules/constructs/src/construct.ts:575:26)
  at new Construct (packages/container-service/node_modules/@aws-cdk/core/lib/construct-compat.ts:68:5)
  at new Resource (packages/container-service/node_modules/@aws-cdk/core/lib/resource.ts:115:5)
  at new SecurityGroupBase (packages/container-service/node_modules/@aws-cdk/aws-ec2/lib/security-group.ts:70:5)
  at new MutableImport (packages/container-service/node_modules/@aws-cdk/aws-ec2/lib/security-group.js:248:17)
  at Function.fromSecurityGroupId (packages/container-service/node_modules/@aws-cdk/aws-ec2/lib/security-group.ts:352:9)

Below is part of surce code of unit-test:

`describe("Service", () => {
  test("Create Service", async () => {
    const app = new cdk.App();
    const stack = new cdk.Stack(app, "testStack");
    const vpc = new ec2.Vpc(stack, "testVPC");

    const image = ecs.ContainerImage.fromRegistry("inanimate/echo-server");

    const cluster = Cluster.fromStackExports(stack, "testStackName", { vpc });

    const service = new Service(stack, "testService", { image, cluster });

    service.addEndpoint("testEndpoint", {
      domainName: "test.cdk.com",
      priority: 10
    });

    const cf = app.synth();
    expect(cf.stacks[0].template).toMatchSnapshot();
  });

  test("Create Service with env", async () => {

    const app = new cdk.App();

    const stack = new cdk.Stack(app, "testStack");

    const vpc = new ec2.Vpc(stack, "testVPC");

    const image = ecs.ContainerImage.fromRegistry("inanimate/echo-server");

    const cluster = Cluster.fromStackExports(stack, "testStackName", { vpc });

    const service = new Service(stack, "testService", {
      image,
      cluster,
      environment: {
        env1: "value1",
        env2: "value2",
        env3: cluster.ecsCluster.clusterArn
      }
    });

    service.addEndpoint("testEndpoint", {
      domainName: "test.cdk.com",
      priority: 10
    });

    const cf = app.synth();
    expect(cf.stacks[0].template).toMatchSnapshot();
});`

Cluster.fromStackExports will call Function.fromSecurityGroupId as below:

const ecsCluster = ecs.Cluster.fromClusterAttributes(
      scope,
      "LookupCluster",
      {
        clusterName: importValue({
          scope,
          exportNamespace,
          name: "clusterName"
        }),
        clusterArn: importValue({ scope, exportNamespace, name: "clusterArn" }),
        vpc,
        securityGroups: [
          ec2.SecurityGroup.fromSecurityGroupId(
            scope,
            `SecurityGroup`,
            importValue({
              scope,
              exportNamespace,
              name: "clusterSecurityGroup"
            })
          )
        ]
      }
    );

Could anyone help with this issue?

Environment

  • CDK CLI Version: 1.78.0
  • Module Version:
  • Node.js Version: Node 10.x
  • OS: Windows 10
  • Language (Version): JavaScript

Other information

@JayLongJie JayLongJie added guidance Question that needs advice or information. needs-triage This issue or PR still needs to be triaged. labels Dec 17, 2020
@redbaron
Copy link
Contributor

If you change SecurityGroup to SecurityGroup2 in fromClusterAttributes and then print whole stack, do you see another SecurityGroup there and if yes could you track down where it is coming from?

@SomayaB SomayaB changed the title (EC2 SecurityGroup): There is already a Construct with name 'SecurityGroup' in Stack [xxx] (EC2): There is already a Construct with name 'SecurityGroup' in Stack [xxx] Dec 17, 2020
@github-actions github-actions bot added the @aws-cdk/aws-ec2 Related to Amazon Elastic Compute Cloud label Dec 17, 2020
@JayLongJie
Copy link
Author

If you change SecurityGroup to SecurityGroup2 in fromClusterAttributes and then print whole stack, do you see another SecurityGroup there and if yes could you track down where it is coming from?

Thanks for you feedback, I tried to debug the source code and found the root cause.
If I call elb.ApplicationListener.fromApplicationListenerAttributes twice with same scope ( same stack in my example ), which will call aws-elasticloadbalancingv2.ImportedApplicationListener twoice, and ImportedApplicationListener implemented as below:
The SecurityGroup is a constant value here, when call the second one, constructs moudle will tell us that we are trying to add construct with name 'SecurityGroup' already exist in the stack with this information: "There is already a Construct with name 'SecurityGroup' in Stack".
I think this should be a bug since I am just trying to import two existing different application licensers for HTTP and TLS.

aws-elasticloadbalancingv2
class ImportedApplicationListener extends ExternalApplicationListener {
    constructor(scope, id, props) {
        super(scope, id);
        this.listenerArn = props.listenerArn;
        const defaultPort = props.defaultPort !== undefined ? ec2.Port.tcp(props.defaultPort) : undefined;
        let securityGroup;
        if (props.securityGroup) {
            securityGroup = props.securityGroup;
        }
        else if (props.securityGroupId) {
            securityGroup = ec2.SecurityGroup.fromSecurityGroupId(scope, 'SecurityGroup', props.securityGroupId, {
                allowAllOutbound: props.securityGroupAllowsAllOutbound,
            });
        }
   ********

Below is my code which will call elb.ApplicationListener.fromApplicationListenerAttributes twoice to get the HTTP application licenser and TLS application licenser:

 const HTTPListener = elb.ApplicationListener.fromApplicationListenerAttributes(
    scope,
    "Listener",
    {
      listenerArn: importValue({
        scope,
        exportNamespace,
        name: "listenerArn"
      }),
      securityGroupId: importValue({
        scope,
        exportNamespace,
        name: "listenerSecurityGroup"
      })
    }
  );
  const TLSListener = elb.ApplicationListener.fromApplicationListenerAttributes(
    scope,
    "TLSListener",
    {
      listenerArn: importValue({
        scope,
        exportNamespace,
        name: "tlslistenerArn"
      }),
      securityGroupId: importValue({
        scope,
        exportNamespace,
        name: "tlslistenerSecurityGroup"
      })
    }
  );

@redbaron
Copy link
Contributor

It is indeed strange, that ImportedApplicationListener constructor doesn't use this when it instantiates nested resources, but there might be a reason for it.

As a workaround, you can try to import each SecurityGroup yourself with ec2.SecurityGroup.fromSecurityGroupId and then pass it to elb.ApplicationListener.fromApplicationListenerAttributes.

@JayLongJie
Copy link
Author

It is indeed strange, that ImportedApplicationListener constructor doesn't use this when it instantiates nested resources, but there might be a reason for it.

As a workaround, you can try to import each SecurityGroup yourself with ec2.SecurityGroup.fromSecurityGroupId and then pass it to elb.ApplicationListener.fromApplicationListenerAttributes.

Thanks, I tried to use ec2.SecurityGroup.fromSecurityGroupId and I changed to pass the id as a parameter instead of hard code with SecurityGroup, only if I pass different value for this parameter, it works. What is this parameter mean? I didn't find any specification, does this mean I import the securitygroup with this new ID in the stack?

const getSecurityGroup = ({scope,exportNamespace,name, id }) =>{
  return ec2.SecurityGroup.fromSecurityGroupId(
    scope,
    id,
    importValue({
      scope,
      exportNamespace,
      name: name
    }));
}

@redbaron
Copy link
Contributor

redbaron commented Dec 21, 2020

id parameter is used to generate CloudFormation logical id. It is described more thoroughly here: https://docs.aws.amazon.com/cdk/latest/guide/identifiers.html

But imported resources do not generate cloudformation, instead they just reference existing resources, so id resides in CDK only and used to uniquely identify resource instance.

@JayLongJie
Copy link
Author

uniquely

Thank you very much, this workaround works for me currently.

@rix0rrr rix0rrr changed the title (EC2): There is already a Construct with name 'SecurityGroup' in Stack [xxx] (ELB): Can't import 2 application listeners into the same scope Jan 6, 2021
@rix0rrr
Copy link
Contributor

rix0rrr commented Jan 6, 2021

Thanks for the debug @redbaron! Totally that.

@github-actions github-actions bot added the @aws-cdk/aws-elasticloadbalancing Related to Amazon Elastic Load Balancing label Jan 6, 2021
@rix0rrr rix0rrr added bug This issue is a bug. and removed @aws-cdk/aws-elasticloadbalancing Related to Amazon Elastic Load Balancing guidance Question that needs advice or information. labels Jan 6, 2021
rix0rrr added a commit that referenced this issue Jan 6, 2021
We were passing the wrong scope variable for the security group.

Fixes #12132.
@rix0rrr rix0rrr added effort/small Small work item – less than a day of effort p1 labels Jan 6, 2021
@mergify mergify bot closed this as completed in #12373 Jan 7, 2021
mergify bot pushed a commit that referenced this issue Jan 7, 2021
#12373)

We were passing the wrong scope variable for the security group.

Fixes #12132.

PR notes: Yes I know this should formally have a unit test. I'm trying to quickly get rid of paper cuts via small edits that are "obviously" correct through quick edits. Adding tests slows the process down a lot and I'm not convinced a test would have appreciable benefit here, so I chose not to add one.


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
@github-actions
Copy link

github-actions bot commented Jan 7, 2021

⚠️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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-ec2 Related to Amazon Elastic Compute Cloud bug This issue is a bug. effort/small Small work item – less than a day of effort needs-triage This issue or PR still needs to be triaged. p1
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants