AWS CDK v2.196.0 で Property Injectors という機能が導入されました。
目次
Property Injectors とは?
App や Stack、Construct 内の、「特定の種類の L2 Construct(一部の L3 Construct も含む)」のすべてに対して、プロパティ(props)を一元的に書き換えるといった機能です。AWS CDK v2.196.0で導入されました。
詳細としては、こちらのRFCに説明があります。
※こちらのPRで追加されました。(本当はその前に別の PR で追加されたがそちらは Revert された)
例えば、スタック内のすべての S3 バケットに対して、「blockPublicAccess
をBLOCK_ALL
にしたい」というようなコンプライアンス要件があったとしましょう。
Property Injectors を使うと、以下のように実現できます。
まずは、IPropertyInjector
を implements するMyBucketPropsInjector
を作成します。
this.constructUniqueId
には対象の L2 Construct が公開するPROPERTY_INJECTION_ID
を指定し、inject
メソッドでは新しい 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, }; } }
そして、以下のようないくつかの実装方法で、適用したいスコープにMyBucketPropsInjector
を指定します。これで、指定したスコープ内のバケットのblockPublicAccess
を一括でBLOCK_ALL
にすることができます。
const app = new App(); const stack = new Stack(app, 'MyStack', { propertyInjectors: [new MyBucketPropsInjector()], });
const app = new App(); PropertyInjectors.of(app).add(new MyBucketPropsInjector());
Aspects との比較
Aspects との違い
上記のような要件を実現するために、従来は Aspects などを使用していたのではないでしょうか。
Aspects でそのようなことを行う際は、Aspects 内で対象の L1 Construct が所有するプロパティを直接、もしくはaddPropertyOverride
などを使用して書き換えていました。
今回登場した「Property Injectors」ですが、Construct のプロパティではなく、Construct の props を直接書き換えるという点で Aspects と異なります。
実際は Property Injectors の内部で新しい props を生成して適用するため、props で定義されたプロパティにreadonly
識別子がついていようと問題なく書き換えられます。
ただし Property Injectors はあくまでも L2 や L3 Construct の props を書き換えるため、Aspects と違って L1 Construct を対象にすることはできません。
また、Aspects は CDK アプリケーションのライフサイクルでは 2 番目の「Prepare フェーズ」で実行されます。
つまり Aspects は、全ての Construct の作成が終わった後に Construct ツリーを走査して実行されます。
しかし、Property Injectors では props を直接書き換えるため、Construct の生成のタイミング(ライフサイクルの 1 番目である「Construct フェーズ」)で値の書き換えが適用されます。
そのため次のように、適用したい Stack の生成の後にPropertyInjectors.of().add
によって Property Injectors を適用してしまうと、書き換え処理が実行されません。
const app = new cdk.App(); new MyStack(app, 'MyStack', props); PropertyInjectors.of(app).add(new MyBucketPropsInjector());
以下のように、Stack の生成より先に呼び出すか、もしくは App や Stack の props のpropertyInjectors
に指定しましょう。
const app = new cdk.App(); PropertyInjectors.of(app).add(new MyBucketPropsInjector()); new MyStack(app, 'MyStack', props); // Or new MyStack(app, 'MyStack', { propertyInjectors: [new MyBucketPropsInjector()], });
Aspects との使い分け
Aspects の使い分け方として、個人的な観点では以下のようになるのではと思います。(まだあまり使い慣れていないため、後で違う観点が出てくるかもしれません。)
Property Injectors が向いているケース:
- Construct の持つプロパティや CloudFormation のプロパティの粒度ではなく、L2 Construct の props の粒度で書き換えたいケース
- L2 Construct 内の組み込みバリデーションがパスするようにプロパティを書き換えたいケース
- = Aspects が実行される前にエラーが起きるケース
Aspects が向いているケース:
- L1 も含む Construct を書き換えたいケース
- CloudFormation のプロパティの粒度でプロパティを書き換えたいケース
- リソースのプロパティを一元的に検査したい(書き換えはしない)ケース
テクニック紹介
例えば Property Injectors で S3 バケットの中にserverAccessLogsBucket
(これも S3 バケットの型)を作成したいとしましょう。
これをただ先ほど紹介したような実装をしてしまうと、無限ループになってしまいます。
それを避けるには、以下のようなスキップ処理をする必要があります。
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, }; } }
最後に
L2 Construct のプロパティを一元的に書き換えられる Property Injectors の紹介でした。使い所は結構あると思います。