aws / aws/aws-cdk

feat(core): Customizable class for L1 constructs customization

Open
#30,800 1 comment 2 reactions 0 assignees View on GitHub
effort/medium feature-request p2
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.