jaredready
jaredready

Reputation: 2698

How to get logical ID of resource with CDK?

I'm attempting to write some tests for a CDK Construct that validates security group rules defined as part of the construct.

The Construct looks something like the following.

export interface SampleConstructProps extends StackProps {
  srcSecurityGroupId: string;
}

export class SampleConstruct extends Construct {
  securityGroup: SecurityGroup;

  constructor(scope: Construct, id: string, props: SampleConstructProps) {
    super(scope, id, props);

    // const vpc = Vpc.fromLookup(...);
    this.securityGroup = new SecurityGroup(this, "SecurityGroup", {
      vpc: vpc,
      allowAllOutbound: true,
    });

    const srcSecurityGroupId = SecurityGroup.fromSecurityGroupId(stack, "SrcSecurityGroup", props.srcSecurityGroupId);

    this.securityGroup.addIngressRule(srcSecurityGroup, Port.tcp(22));
  }
}

And I want to write a test that looks something like the following.

test("Security group config is correct", () => {
  const stack = new Stack();
  const srcSecurityGroupId = "id-123";
  const testConstruct = new SampleConstruct(stack, "TestConstruct", {
    srcSecurityGroupId: srcSecurityGroupId
  });

  expect(stack).to(
    haveResource(
      "AWS::EC2::SecurityGroupIngress",
      {
        IpProtocol: "tcp",
        FromPort: 22,
        ToPort: 22,
        SourceSecurityGroupId: srcSecurityGroupId,
        GroupId: {
          "Fn::GetAtt": [testConstruct.securityGroup.logicalId, "GroupId"], // Can't do this
        },
      },
      undefined,
      true
    )
  );
});

The issue here is that the test is validated against the synthesized CloudFormation template, so if you want to verify that the security group created by this construct has a rule allowing access from srcSecurityGroup, you need the Logical ID of the security group that was created as part of the Construct.

You can see this in the generated CloudFormation template here.

{
  "Type": "AWS::EC2::SecurityGroupIngress",
  "Properties": {
    "IpProtocol": "tcp",
    "FromPort": 22,
    "GroupId": {
      "Fn::GetAtt": [
        "TestConstructSecurityGroup95EF3F0F", <-- This
        "GroupId"
      ]
    },
    "SourceSecurityGroupId": "id-123",
    "ToPort": 22
  }
}

That Fn::GetAtt is the crux of this issue. Since these tests really just do an object comparison, you need to be able to replicate the Fn::Get invocation, which requires the CloudFormation Logical ID.


Note that the CDK does provide a handful of identifiers for you.

Is there a straightforward way getting the logical ID of CDK resources?

Upvotes: 26

Views: 23137

Answers (3)

LiamD
LiamD

Reputation: 2028

From my testing, it seems that stack.getLogicalId will always return the original, CDK allocated logicalId, it won't change if you call overrideLogicalId, so it won't always match the synthed output.

This worked for me, even with a logicalId override set:

stack.resolve((construct.node.defaultChild as cdk.CfnElement).logicalId)

stack.resolve is necessary because .logicalId is a token.

Upvotes: 9

Sandy Chapman
Sandy Chapman

Reputation: 11341

In addition to the excellent answer from jaredready, you can also explicitly set the logical ID using resource.node.default_child.overrideLogicalId("AnyStringHere")

This may make it easier as you can set it once and use hard-coded strings rather than looking up the value for every test.

Upvotes: 3

jaredready
jaredready

Reputation: 2698

After writing up this whole post and digging through the CDK code, I stumbled on the answer I was looking for. If anybody has a better approach for getting the logical ID from a higher level CDK construct, the contribution would be much appreciated.

If you need to get the logical ID of a CDK resource you can do the following:

const stack = new Stack();
const construct = new SampleConstruct(stack, "SampleConstruct");
const logicalId = stack.getLogicalId(construct.securityGroup.node.defaultChild as CfnSecurityGroup);

Note that you you already have a CloudFormation resource (eg something that begins with with Cfn) then it's a little easier.

// Pretend construct.securityGroup is of type CfnSecurityGroup
const logicalId = stack.getLogicalId(construct.securityGroup);

Upvotes: 36

Related Questions