feat(core): Customizable class for L1 constructs customization
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
### Describe the feature
We have seen many cases that users are trying to customize some properties for resources generated by CDK behind L2 constructs and it's not easy to achieve that. I was thinking we probably should propose something like the [Tags](https://docs.aws.amazon.com/cdk/v2/guide/tagging.html) class that builds a Aspect under the hood and iterates the constructs tree for users without having to write the Aspect by themselves.
### Use Case
1. Customize all CustomResource [ServiceTimeout](https://github.com/aws/aws-cdk/issues/30517) behind a specific construct. This [PR](https://github.com/aws/aws-cdk/pull/30557) would allow CustomResource L2 to support ServiceTimeout but for those custom resources created by L2s behind the scene, users would not be able to customize their ServiceTimeouts.
2. Customize the LoggingConfig of all lambda.Function, including those created by CDK for custom resource providers as described in https://github.com/aws/aws-cdk/issues/30777.
### Proposed Solution
We probably could have a `Customizable` class just like the Tags class and the API experience would be like:
```ts
// make all custom resource behind this scope a 1200 seconds timeout as default
Customizable.of(this).add('AWS::CloudFormation::CustomResource', 'ServiceTimeout', 1200);
// search all custom resources behind this scope, and run user-provided callback function against all of them
Customizable.of(this).bind('AWS::CloudFormation::CustomResource', cb);
// all lambda.Function should have a default LoggingConfig defined, only apply if undefined
Customizable.of(this).add('AWS::Lambda::Function', 'LoggingConfig', myconfig, { overrideIfUndefined: true });
// iterate all lambda.Functions with a custom callback method. In this `cb` function, we can check if `LoggingConfig` is defined, and override the LoggingConfig.LogGroup when necessary.
Customizable.of(this).bind('AWS::Lambda::Function', cb);
```
### Sample
```ts
export class Customizable {
private static readonly customizations: { [key: string]: any } = {};
static of(scope: Construct): Customizable {
return new Customizable(scope);
}
private constructor(private readonly scope: Construct) {}
add(resourceType: string, propertyName: string, value: any): void {
const key = `${resourceType}/${propertyName}`;
Customizable.customizations[key] = value;
this.applyCustomizations(resourceType, propertyName);
}
private applyCustomizations(resourceType: string, propertyName: string): void {
const constructs = this.scope.node.findAll();
this.applyCustomizationsRecursively(resourceType, propertyName, constructs);
}
private applyCustomizationsRecursively(resourceType: string, propertyName: string, constructs: IConstruct[]): void {
constructs.forEach(c => {
if (CfnResource.isCfnResource(c)) {
if (c.cfnResourceType === resourceType) {
const customization = Customizable.getCustomization(c, propertyName);
if (customization) {
c.addPropertyOverride(propertyName, customization);
}
} else {
// it's a CfnResource but not the resource type we need
// console.log('skipping ' + c.cfnResourceType)
}
} else {
// if it's not a cfnResource it has to be a construct
this.applyCustomizationsRecursively(resourceType, propertyName, c.node.children);
}
});
}
static getCustomization(resource: CfnResource, propertyName: string): any {
const key = `${resource.cfnResourceType}/${propertyName}`;
return Customizable.customizations[key];
}
}
```
- override the LoggingConfig for all custom resources behind this stack:
```ts
new eks.Cluster(this, 'Cluster', {
version: eks.KubernetesVersion.V1_30,
});
// create a shared log group
this.lambdaSharedLogGroup = new logs.LogGroup(this, id, {
retention: logs.RetentionDays.NINE_YEARS,
});
Customizable.of(this).add('AWS::Lambda::Function', 'LoggingConfig', {
LogGroup: this.lambdaSharedLogGroup.logGroupName,
});
```
- override the VisibilityTimeout for all sqs queues behind this scope:
```ts
Customizable.of(this).add('AWS::SQS::Queue', 'VisibilityTimeout', 300);
```
### Other Information
_No response_
### Acknowledgements
- [ ] I may be able to implement this feature request
- [ ] This feature might incur a breaking change
### CDK version used
2.148.0
### Environment details (OS name and version, etc.)
all
Contributor guide
Research direction
Start by reviewing the existing Tags class and the Aspect behavior it uses, then compare that with the proposed Customizable.of(scope).add() and bind() entry points. Done should include an agreed API for scoped resource customization, including default and callback-based updates for the example CloudFormation resource types, with coverage for the stated use cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100