-
Notifications
You must be signed in to change notification settings - Fork 4.5k
(aws-cdk-lib): Property overrides in CDK Aspects and CfnParameter #19447
Description
General Issue
CDK Aspects used to modify properties will lead to odd behaviour when the original value was a CfnReference
The Question
Consider the following CDK Aspect, a simplified version we use to modify names of resources according to a naming convention:
import {
CfnResource,
IAspect,
Stack,
Token,
} from 'aws-cdk-lib';
import { CfnRole } from 'aws-cdk-lib/aws-iam';
import { IConstruct } from 'constructs';
function hasOwnProperty<X extends {}, Y extends PropertyKey>
(obj X, prop: Y): obj is X & Record<Y, unknown> {
return obj.hasOwnProperty(prop)
}
function isRef(name: string | object) {
return typeof name === 'object' && this.hasOwnProperty(name, 'Ref');
}
function isJoin(name: string | object) {
return typeof name === 'object' && this.hasOwnProperty(name, 'Fn::Join');
}
export class MyAspect implements IAspect {
public visit(node: IConstruct): void {
if (CfnResource.isCfnResource(node) && node.cfnResourceType == CfnRole.CFN_RESOURCE_TYPE_NAME) {
const role = node as CfnRole;
const name = Stack.of(node).resolve(role.roleName) || node.logicalId;
const newName = [
'my',
this.isRef(name) || this.isJoin(name) ? Token.asString(name) : name,
].join('-');
node.addPropertyOverride('RoleName', newName);
}
}
}
And the following stack we apply the aspect on:
import { CfnParameter, Fn, Stack, StackProps } from 'aws-cdk-lib';
import { Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
export class AspectsReferencesStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const parameter = new CfnParameter(this, 'MyParameter', {
type: 'String',
}).valueAsString;
new Role(this, 'MyRole1', {
assumedBy: new ServicePrincipal('codebuild.amazonaws.com'),
});
new Role(this, 'MyRole2', {
assumedBy: new ServicePrincipal('codebuild.amazonaws.com'),
roleName: 'a-role',
});
new Role(this, 'MyRole3', {
assumedBy: new ServicePrincipal('codebuild.amazonaws.com'),
roleName: parameter,
});
new Role(this, 'MyRole4', {
assumedBy: new ServicePrincipal('codebuild.amazonaws.com'),
roleName: `a-${parameter}`,
});
}
}
What you will see is that most results are as expected and produce valid CloudFormation. However, the third role will contain invalid code, and the RoleName property will be:
RoleName:
Ref: MyParameter
Fn::Join:
- ""
- - my-
- Ref: MyParameter
The Ref key will always be present, which is odd considering the override of the property. I've tried various tricks, such as deletion overrides on RoleName.Ref and more, but this additional field always seems to persist and just sit alongside the Fn::Join resulting from the interpolation in the aspect. I realize this is a fringe case, and the reason why I'm even considering a CfnParameter is because we use this to in combination with the ProductStack of the service catalog module to provide input fields when provisioning.
What happens under the hood in addPropertyOverride? Does it simply do some sort of object merge?
CDK CLI Version
2.10.0 (build e5b301f)
Framework Version
No response
Node.js Version
v16.13.0
OS
Fedora release 35 (Thirty Five)
Language
Typescript
Language Version
TypeScript (3.9.7)
Other information
No response