问题
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
returnsTestStackTestConstructSecurityGroup10D493A7
whereas the CloudFormation template displaysTestConstructSecurityGroup95EF3F0F
. You can note the differences are theuniqueId
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 andTestConstructSecurityGroup95EF3F0F
as the logical ID in the synthesized template.
Is there a straightforward way getting the logical ID of CDK resources?
回答1:
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);
回答2:
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.
来源:https://stackoverflow.com/questions/61803090/how-to-get-logical-id-of-resource-with-cdk