What are Property Injectors?
Property Injectors is a feature that allows you to centrally modify properties (props) for all instances of "specific types of L2 Constructs (including some L3 Constructs)" within an App, Stack, or Construct. This feature was introduced in AWS CDK v2.196.0.
For detailed information, please refer to this RFC.
Note: This feature was added by this PR. (Actually, it was added by a different PR before this one, but that one was reverted)
For example, suppose you have a compliance requirement to set blockPublicAccess
to BLOCK_ALL
for all S3 buckets in your stack.
With Property Injectors, you can achieve this as follows.
First, create a MyBucketPropsInjector
that implements IPropertyInjector
.
Set this.constructUniqueId
to the PROPERTY_INJECTION_ID
exposed by the target L2 Construct, and implement the inject
method to return new props.
export class MyBucketPropsInjector implements IPropertyInjector {
public readonly constructUniqueId: string;
constructor() {
this.constructUniqueId = Bucket.PROPERTY_INJECTION_ID;
}
public inject(originalProps: BucketProps, _context: InjectionContext): BucketProps {
return {
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
...originalProps,
};
}
}
Then, specify MyBucketPropsInjector
for the scope where you want to apply it using one of several implementation methods as shown below. This will set blockPublicAccess
to BLOCK_ALL
for all buckets within the specified scope.
const app = new App();
const stack = new Stack(app, 'MyStack', {
propertyInjectors: [new MyBucketPropsInjector()],
});
const app = new App();
PropertyInjectors.of(app).add(new MyBucketPropsInjector());
Comparison with Aspects
Differences from Aspects
To fulfill such requirements, you might have traditionally used Aspects.
When using Aspects for such purposes, you would modify properties owned by target L1 Constructs within the Aspects.
The newly introduced "Property Injectors" differs from Aspects in that it directly modifies the Construct's props rather than the Construct's properties.
In practice, Property Injectors internally generate and apply new props, so properties can be modified without issues even if they have the readonly
modifier in the props definition.
However, since Property Injectors only modify props of L2 and L3 Constructs, they cannot target L1 Constructs unlike Aspects.
Also, Aspects are executed in the second "Prepare phase" of the CDK application lifecycle.
In other words, Aspects are executed by traversing the Construct tree after all Constructs have been created.
However, Property Injectors directly modify props, so the value modifications are applied during Construct creation (the first "Construct phase" of the lifecycle).
Therefore, if you apply Property Injectors using PropertyInjectors.of().add
after creating the Stack you want to apply them to, the modification process will not be executed.
const app = new cdk.App();
new MyStack(app, 'MyStack', props);
PropertyInjectors.of(app).add(new MyBucketPropsInjector());
Call it before creating the Stack as shown below, or specify it in the propertyInjectors
prop of the App or Stack.
const app = new cdk.App();
PropertyInjectors.of(app).add(new MyBucketPropsInjector());
new MyStack(app, 'MyStack', props);
// Or
new MyStack(app, 'MyStack', {
propertyInjectors: [new MyBucketPropsInjector()],
});
When to Use Property Injectors vs Aspects
In my opinion, the use cases might be divided as follows: (Since I'm not familiar with using Property Injectors yet, different perspectives might emerge later.)
Cases where Property Injectors are suitable:
- When you want to modify at the granularity of L2 Construct props rather than properties owned by Constructs or CloudFormation properties
- When you want to modify properties so that built-in validation within L2 Constructs passes
- = Cases where errors occur before Aspects are executed
Cases where Aspects are suitable:
- When you want to modify Constructs including L1 Constructs
- When you want to modify properties at the granularity of CloudFormation properties
- When you want to centrally inspect (but not modify) resource properties
Avoiding Infinite Loops
For example, suppose you want to create a serverAccessLogsBucket
(which is also an S3 bucket type) within an S3 bucket using Property Injectors.
If you implement this naively like the previously introduced implementation, you'll end up with an infinite loop.
To avoid this, you need to implement skip processing as follows:
export class SpecialBucketInjector implements IPropertyInjector {
public readonly constructUniqueId: string;
// this variable will track if this Injector should be skipped.
private _skip: boolean;
constructor() {
this._skip = false;
this.constructUniqueId = Bucket.PROPERTY_INJECTION_ID;
}
public inject(originalProps: BucketProps, context: InjectionContext): BucketProps {
if (this._skip) {
return originalProps;
}
let accessLogBucket = originalProps.serverAccessLogsBucket;
if (!accessLogBucket) {
// When creating a new accessLogBucket, disable further Bucket injection.
this._skip = true;
// Since injection is disabled, make sure you provide all the necessary props.
accessLogBucket = new Bucket(context.scope, 'my-access-log', {
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
removalPolicy: originalProps.removalPolicy ?? core.RemovalPolicy.RETAIN,
});
// turn on injection for Bucket again.
this._skip = false;
}
return {
serverAccessLogsBucket: accessLogBucket,
...originalProps,
};
}
}
Top comments (0)