[cdk] L2の既存リソースに、L1にしかない設定をした話

# この記事の想定読者

- AWSの基本知識がある
- CloudFormationを使った事がある
- cdkも使った事がある

# L1とL2（とL3）

スタックをコードとして管理したい時、CloudFormationではyamlかjson形式で記述します。cdkでは、TypeScriptなどのメジャーな言語で記載する事が出来ます。cdkは、内部的には一旦CloudFormationに変換します。

そのcdkでは３種類の記述方法があります。下に紹介した公式ページに詳細はお願いしますが、L1はCloudFormationと同レベルの細かい設定が必要ですが、L2は必須の情報だけ設定すればあとはcdkがよしなにデフォルト設定してくれます。

https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/constructs.html

L3では標準的な構成に関してさらにサービスをまとめてくれます。例えば`ApplicationLoadBalancedFargateService`ではALBとFargateのスタックをまとめて対応してくれます。

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ecs_patterns.ApplicationLoadBalancedFargateService.html

# L2の難点

コード上の設定箇所が少ないのでありがたいL2ですが、<b>細かい部分を指定したい場合に、L2だと対応していない時がある</b>という難点があります。事前にわかっていれば最初からL1で作成するなどの対応が可能ですが、既にL2でスタックを作成している場合にL1に切り替えるとリソースが作り直されてしまう可能性があります（※未検証。後述しますがL2からL1へのキャストも出来るぐらいなので、スタック名を同じにすれば条件によっては大丈夫かもしれません）。

# 今回の具体例

Cognitoで、ユーザーを作成した場合の招待メッセージや、認証コードを送信する際のメッセージのフォーマットを指定したいけど、[L2のドキュメント](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cognito-readme.html)を探してもではその属性が存在しませんでした。
[L1のドキュメント](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cognito.CfnUserPool.html)では「[verificationMessageTemplate](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cognito.CfnUserPool.html#verificationmessagetemplate)」、「[adminCreateUserConfig](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cognito.CfnUserPool.html#admincreateuserconfig)」の２プロパティになります。

# 対応ポイント

後述、参考にさせて頂いたページの追記版になってしまいますが、以下の通りです。

## L2オブジェクトをL1のクラスにキャストする

L2オブジェクトにある`node.defaultChild`属性で、L1のクラスにキャスト出来ます。

```typescript
import * as cognito from "@aws-cdk/aws-cognito";
  // ・・中略・・
  // L2クラスのオブジェクト作成
  const userPool = new cognito.UserPool(this, "UserPool", {
  // ・・中略・・
  // L1クラスへキャスト
  const cfnUserPool = userPool.node.defaultChild as cognito.CfnUserPool;
  // ・・後略・・
```

## [addPropertyOverride](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.CfnResource.html#addwbrpropertywbroverridepropertypath-value)メソッドを使う

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.CfnResource.html#addwbrpropertywbroverridepropertypath-value

L1形式ではクラス名はCfn****となっていますが、表題のメソッドが存在します。このメソッドによりプロパティの上書きが出来ます。

## プロパティ名および値はCloudFormationのものを使用

上記関数で指定する時、プロパティ名は値はCloudFormationを意識したものになります。
自分は躓いてしまった所なのですが、L1の設定名である`adminCreateUserConfig`では駄目で、CloudFormationの設定名である`AdminCreateUserConfig`を使う必要があります（アッパーキャメルケースを使う）。
また、指定する値に関してです。プリミティブ型なら特に気にかけませんが、オブジェクト型の場合、L1のプロパティオブジェクトでなくjson形式で指定する必要があります。
L1クラスの関数なので当たり前と言えば当たり前です（L1形式ならaddPropertyOverrideを使わずに直接指定できる）。

```typescript:成功
  cfnUserPool.addPropertyOverride('AdminCreateUserConfig', {
    InviteMessageTemplate: {
      EmailSubject: '仮パスワードをお送りします',
      EmailMessage: 'ユーザーIDは{username}です。一時パスワードは「{####}」です。 ',
      SMSMessage: 'ユーザーIDは{username}です。一時パスワードは「{####}」です。 '
    }
  });
  cfnUserPool.addPropertyOverride('VerificationMessageTemplate', {
    DefaultEmailOption: 'CONFIRM_WITH_CODE',
    EmailSubject: '認証コードをお送りします',
    EmailMessage: '認証コード「{####}」を入力してください。',
    SmsMessage: '認証コード「{####}」を入力してください。',
  });
```

```typescript:駄目なケース
  const adminCreateUserConfig: cognito.CfnUserPool.AdminCreateUserConfigProperty = {
    allowAdminCreateUserOnly: true,
    inviteMessageTemplate: {
      emailSubject: "仮パスワードをお送りします",
      emailMessage: "ユーザーIDは{username}です。一時パスワードは「{####}」です。 ",
      smsMessage: "ユーザーIDは{username}です。一時パスワードは「{####}」です。 "
    }
  };
  const verificationMessageTemplateProperty: cognito.CfnUserPool.VerificationMessageTemplateProperty = {
    defaultEmailOption: 'CONFIRM_WITH_CODE',
    emailSubject: '認証コードをお送りします',
    emailMessage: '認証コード「{####}」を入力してください。',
    smsMessage: '認証コード「{####}」を入力してください。'
  };
  cfnUserPool.addPropertyOverride('adminCreateUserConfig', adminCreateUserConfig);
  cfnUserPool.addPropertyOverride('verificationMessageTemplate', verificationMessageTemplateProperty);
```

# 感想

今までL2の難点を考えて、細かい設定する時困るからL1で書こうかなとか思う時があったのですが、この関数の存在を知ったので、安心してL2を使う事が出来ます。L3だと複数種類のスタックで構成される事から`node.defaultChild`でなく別の方法があると思います（※未調査。機会があったら調査して別記事にします）
結構常識的なナレッジかもしれませんが、自分が知らなかった事もあり、きっと誰かの役に立つだろうと信じての投稿でした。

# 参考にさせて頂いたページ

https://qiita.com/shinnoki/items/aa1424128b1cc9b05dac

https://dev.classmethod.jp/articles/aws-cdk-typescript-how-to-set-properties-not-in-construct-props-in-high-level-construct/

https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/cfn_layer.html