Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

  • Unique ID provides something very close, but it's not same identifier used in the CloudFormation stack. For example, securityGroup.uniqueId returns TestStackTestConstructSecurityGroup10D493A7 whereas the CloudFormation template displays TestConstructSecurityGroup95EF3F0F. You can note the differences are the uniqueId prepends the Construct ID to the logical identifier and the appended hash is different in each.
  • Construct ID is just the identifier that you provide when instantiating a construct. It is not the logical ID either, though it is used as part of the logical ID. I also have not seen a way of programmatically retrieving this ID from the construct directly. You can of course define the ID somewhere and just reuse it, but this still doesn't solve the problem of it not fully matching the logical ID. In this case it's a difference of SecurityGroup as the construct ID and TestConstructSecurityGroup95EF3F0F as the logical ID in the synthesized template.

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

like image 699
jaredready Avatar asked May 14 '20 16:05

jaredready


People also ask

What is logical ID in CDK?

Logical IDs Unique IDs serve as the logical identifiers, which are sometimes called logical names, of resources in the generated AWS CloudFormation templates for those constructs that represent AWS resources.

What is logical ID AWS?

Logical ID Use the logical name to reference the resource in other parts of the template. For example, if you want to map an Amazon Elastic Block Store volume to an Amazon EC2 instance, you reference the logical IDs to associate the block stores with the instance.

What is CDK metadata?

What is Metadata # Metadata is a CloudFormation feature that allows us to specify additional details about our template using json or yaml. Metadata is used by the CDK team in order to collect analytics in regards to how developers use the CDK service.


2 Answers

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);
like image 191
jaredready Avatar answered Sep 16 '22 18:09

jaredready


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.

like image 38
LiamD Avatar answered Sep 16 '22 18:09

LiamD