LoginSignup
17
9

More than 1 year has passed since last update.

[AWS CDK] L2 Constructから直接アクセスできないプロパティにアクセスする

Last updated at Posted at 2021-04-12

AWS CDK Tipsシリーズの記事です。

はじめに

AWS CDKのL2 Constructは、CloudFormationのリソースを抽象化した概念です。
複雑なインフラを簡単に定義できるため非常に便利な存在ですが、時に抽象化が原因でリソースを扱いづらくなる場合があります。
例えば、L2 Constructの props では設定できないCloudFormationの設定値が存在するような場合です。

本記事では、上記のような場合に、CDKの機能を用いてそれらの設定値を変更する方法を紹介します。

問題となるケース

例として、 aws-apigatewayv2.WebSocketApi というL2 Constructを使い、Lambda Authorizerが設定されたWebSocket APIを定義する事を考えます。

WebSocketApi Constructは、現状 props など直接的なインターフェースからAuthorizerを設定することができません。 Issue #13869
CloudFormationで設定できることは確認できています
これを解決する最高の方法は、上記のIssueを解決するPull Requestを送る方法ですが、それほど時間もないので簡単な方法で一旦間に合わせたいです。
このような時、どうすれば良いでしょうか?

上記はかなり特有の状況ですが、CDKを使っていると似たような状況に出くわした方も多いのではと思います。
一般化すると、CDKのL2 Constructから直接設定できないがL1 Constructでは設定できる値をどう設定するか? という問題になります。

以下では、そのようなケースに普遍的に適用可能な方法を紹介します。

対処方法

CDKでは、findChild などのメソッドを使うことで、L2 Constructの内部で定義されているL1 Constructにアクセスできます。
下記に例を示します。

import * as cdk from "@aws-cdk/core";
import * as agw from "@aws-cdk/aws-apigatewayv2";

export class ExampleStack extends cdk.Stack {
    constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);

        // WebSocketApiの定義(通常通り)
        const api = new agw.WebSocketApi(this, `WebSocket`);

        // Construct treeからCfnRoute (L1 Construct)を取得
        const connectRoute = api.node.findChild("$connect-Route").node.defaultChild as agw.CfnRoute;
        // 取得したCfnRouteの設定値を編集
        connectRoute.authorizationType = "CUSTOM";
        connectRoute.authorizerId = createAuthorizer().ref;
    }

    // Authorizerを返す関数 (省略)
    createAuthorizer(): agw.CfnAuthorizer {
        ...
    }
}

これにより、本来 WebSocketApi からは設定できなかった $connect ルートの authorizationTypeauthorizerId を設定しています。

解説

上記のコードで何をやっているのかを解説します。

Constructツリー

CDKでは、Constructはツリーを形成します
Constructを定義する際は、下記のように 第1引数で scope を指定します。
この scope で指定したConstructを親となり、定義したConstructが子としてツリーに追加されていきます。

Constructクラスのコンストラクタ
class Construct {
    constructor(scope: Construct, id: string);
}

CDKのアプリケーションで定義されるConstructは、全てこのツリーの中に入っています。
rootノードは App (よく new cdk.App() と書くやつです)で、そこから Stack があり、その中の Construct と続きます。

L2 ConstructはL1 Constructを束ねるツリーとみなせます。
このため、あるL2 Constructの子ノードを見れば、その中で定義されているL1 Constructが必ず存在するわけです。

ツリーのノードとしてのインターフェースは、Constructの node プロパティ で取得できます。

下記のコードでは、findChild 関数によりこのツリーを辿って所望のL1 Constructを取得しています。

上記コード例の抜粋
const connectRoute = api.node.findChild("$connect-Route").node.defaultChild as agw.CfnRoute;

defaultChild プロパティは、あるConstructに最大で1つ存在する特別なノードです。そのノードの idResource または Default のノードが defaultChild で取得されます。id については次の節でまとめます。L2 Constructに直に対応するL1 Constructが割り当てられる場合が多いようです。(WebSocketRouteCfnRouteBucketCfnBucket など)

defaultChild プロパティから取得できるという意味では特別ですが、その他の扱いは通常のノードと変わりません。

ツリー内ノードのID

上記で findChild 関数や defaultChild プロパティの意味を説明しました。
それでは、 findChild 関数の引数に渡している $connect-Route とは何でしょうか?

これは、Constructを定義する際に渡している、第2引数 id です。

Constructクラスのコンストラクタ
class Construct {
    constructor(scope: Construct, id: string);
}

このIDはツリーのあるノードの直接の子の間でユニークなため、あるConstructに対して id を指定して findChild すると、一意のConstructを取得することができます。

所望のConstructに設定されている id を知るためには、コードを読むのが正攻法です。
(例えば、上記WebSocketの例だと この辺りを読むとわかります。)

より簡単な方法として、実際に生成されたテンプレートのMetadataを見るという方法もあります。

生成されたテンプレート
  websocketapiconnectRoute50EA91CF:
    Type: AWS::ApiGatewayV2::Route
    Properties:
    ...
    Metadata:
      aws:cdk:path: ExampleStack/WebSocket/$connect-Route/Resource

この場合、下記のように対応します

  • ExampleStack: Stackの id
  • WebSocket: WebSocketApi Constructの id
  • $connect-Route: WebSocketApi Constructの中の WebSocketRoute Constructの id
  • Resource: WebSocketRoute Constructの中の CfnRoute Constructの id

ツリーと実際のリソースの対応

先程でてきたコードを上の対応に沿って書き直すと、下記のようになります。

上記コード例の抜粋
const api = new agw.WebSocketApi(this, `WebSocket`);
const connectRouteL2 = api.node.findChild("$connect-Route") as agw.WebSocketRoute;
const connectRoute = connectRouteL2.node.defaultChild as agw.CfnRoute;

defaultChildidResourceDefault のノードになることに注意してください。

取り出してきたchildの型は IConstruct になるため、 as で必要な型にキャストすると良いでしょう。

addOverride メソッド

今回は以下のコードでL1 Constructのプロパティを編集しましたが、まれにこれでは変更が反映されない場合があります。L2 Construct内で、この変更の後に更にプロパティが上書きされる場合があるためです。

   connectRoute.authorizationType = "CUSTOM";
   connectRoute.authorizerId = createAuthorizer().ref;

そのような場合は、 addOverride 系メソッド を利用しましょう。このメソッドを使うと、CloudFormationのプロパティ名を指定して任意のプロパティを上書きできます。上記の方法とは異なりTypeScriptの補完は効かないため、プロパティ名を間違えないように注意しましょう。

   connectRoute.addOverride("Properties.AuthorizationType", "CUSTOM");
   // Properties用のバージョンもある
   connectRoute.addPropertyOverride("AuthorizationType", "CUSTOM");

まとめ

上記の方法で、任意のL2 Constructから、所望のConstructを取り出すことができます。
これにより、L2 Constructの抽象性とL1 Constructの柔軟性、どちらのメリットも享受することが可能です。
抽象化を恐れることなくCDKを使っていきましょう!

17
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
9