bootstrapをカスタマイズして既存のIAMロール、S3バケットを使ってAWS-CDK v2を利用する

## はじめに

AWS-CDKを使ってTypescriptの型定義の恩恵を受けつつ手軽にAWSのリソースを作りたいけど、bootstrapでIAMロールやS3が自動的に作られて開発できない・・・っていう人向けです
Typescriptで書くと型推論でサクサクかけますし、TSdocも書かれていて定数も用意されているのでプロパティの設定値に迷いません

## 前提

- Node.jsがインストール済み
- AWS-CLIが使える環境であること
- AWS-CLIで使う認証情報において以下の権限が付与されていること（他にも必要かも+CDKでコンテナのビルドを行う際はECRのPush権限も必要）
    - CloudformationのStackを作成・参照できる権限
    - 既存のS3に対してオブジェクトのPutの権限がある

## 流れ
1. bootstrapのテンプレートファイルを用意する
2. bootstrapのテンプレートファイルをカスタマイズする
3. bin/xx.tsでStackを作るところで、引数にCliCredentialsStackSynthesizerを渡す

### bootstrapのテンプレートの用意
テンプレートの雛形の用意は以下のどちらか
- [AWS-CDKのリポジトリからDL](https://github.com/aws/aws-cdk/blob/v1-main/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml)
- 以下のコマンド
```bash
> cdk bootstrap --show-template > bootstrap.yml
```

### bootstrapをカスタマイズ

最低限必要なリソースは以下の通り

```yaml:bootstrap.yml
AWSTemplateFormatVersion: 2010-09-09
Description: This stack includes resources needed to deploy AWS CDK apps into this environment

Parameters:
  Qualifier:
    Type: String
    AllowedPattern: '[A-Za-z0-9_-]{1,10}'
    ConstraintDescription: Qualifier must be an alphanumeric identifier of at most 10 characters
  # 以下2つのパラメーターは不要だが、"cdk bootstrap"コマンド時に失敗するために用意する。
  # マネジメントコンソールからデプロイする場合消してもOK
  FileAssetsBucketKmsKeyId:
    Description: This parameter is unnecessary, but it is defined because it causes a validation error in the bootstrap command.
    Default: 'UNUSED'
    Type: String
  PublicAccessBlockConfiguration:
    Description: This parameter is unnecessary, but it is defined because it causes a validation error in the bootstrap command.
    Default: 'true'
    Type: 'String'
    AllowedValues: ['true']

Resources:
  # The SSM parameter is used in pipeline-deployed templates to verify the version of the bootstrap resources.
  CdkBootstrapVersion:
    Type: AWS::SSM::Parameter
    Properties:
      Type: String
      Name: !Sub '/cdk-bootstrap/${Qualifier}/version'
      Value: '13'

Outputs:
  BucketName:
    Description: This output is defined because it is needed by the Bootstrap command.
    Value: "<一時的な置き場に使用する既存のバケット名>"
  BucketDomainName:
    Description: This output is defined because it is needed by the Bootstrap command.
    Value: "<一時的な置き場に使用する既存のバケット名>.s3.amazonaws.com"
  BootstrapVersion:
    Description: The version of the bootstrap resources that are currently mastered
      in this stack
    Value: !GetAtt CdkBootstrapVersion.Value
```

### Bootstrapを用意したテンプレートでデプロイ
以下のどちらかの方法でDeployする

- AWSマネジメントコンソールのCloudformationからデプロイ
    - Stack名はcdk.jsonの"toolkitStackName"で指定されているもの（デフォルトはCDKToolkitというStack名）
- コマンドでデプロイ
    - AWS-CLIでプロファイルを設定している場合は `--profile`オプションを付ける
```bash
> cdk bootstrap --template ./bootstrap.yml --qualifier <お好みの10文字>
```

### 実際にDeployする前に

Deploy時に一時的なファイル置き場となるS3の指定と認証情報が必要になるため、bin/xx.tsに定義しているStackにCliCredentialsStackSynthesizerを渡してAWS-CLIと同じ認証情報を使う

```typescript:cdk.ts
import * as cdk from 'aws-cdk-lib'
import { Stack } from '../lib/stack'

const app = new cdk.App()

const synthesizerProps = {
  fileAssetsBucketName: "<一時的な置き場に使用する既存のバケット名>",
  // s3://<fileAssetsBucketName>/<bucketPrefix>abcd1234.zip になるイメージ
  bucketPrefix: "<S3に置かれるファイ名のプレフィックス>",
  imageAssetsRepositoryName: "<ビルド後のイメージ保管に使用する既存のECRリポジトリ>",
  dockerTagPrefix: "<bucketPrefixと同じようにイメージのタグ付けの際にプレフィックスを付与する>",
  qualifier: "<BootstrapのStackに合わせてたほうが良いです>"
}

new Stack(app, "<スタック名>", {
  synthesizer: new cdk.CliCredentialsStackSynthesizer(synthesizerProps),
  env: { region: 'ap-northeast-1' }
})

```

## 注意事項
- CDKでCodeBuildやLambdaなどCloudwatchにログを送るリソースはIAMを勝手に作ろうとしたり、既存のRoleに権限を付与しようとする
    - Roleプロパティがあるリソースは明示的に指定する
    - 既存Roleを使う場合は以下のように明示的にmutableをfalseにする
        ```typescript
        import * as IAM from 'aws-cdk-lib/aws-iam'
        const lambdaRole = IAM.Role.fromRoleArn(this, 'LambdaRole', 'arn:aws:iam::xxxxxxxxx:role/LambdaRole', {
          mutable: false
        })
        ```


## 余談

- 実際にBootstrapで作成されるリソースは以下のページがわかりやすく解説してくれています
    - [CDK BootstrapのModern templateで何が変わるのか](https://dev.classmethod.jp/articles/cdk-bootstrap-modern-template/)

- CliCredentialsStackSynthesizer を使うと以下のメリットがあります。（本来であればCDKで使うものはCDKで構築してアカウント・環境ごとに差分をなくすのが目的だと思いますが・・・）
    - CodeBuildやEC2からも利用できる（適切なRoleをアタッチしていれば）
    - CodeBuildとCodePipelineを組み合わせたときに、CodeBuildやCodePipelineとCDKで使う権限が違うといったトラブルが起きない（同一Roleを使ってる前提）
    - AWS-CLIでプロファイルを指定して使う場合は以下のいずれかでプロファイルを指定する
        - cdk.jsonで"profile"を指定する
        - Deployコマンドのオプション引数`--profile`を指定する
    - 明示的にECRリポジトリやS3のバケットを指定できる

- ローカルではCDK使えないけど、CodeBuild上で使いたい！という場合も事前にマネジメントコンソールからBootstrapをデプロイすればOK
    - Stack名はcdk.jsonの"toolkitStackName"で指定されているもの（デフォルトはCDKToolkitというStack名）
    - `cdk synth`というコマンドを実行すると、`cdk.out`にCFnテンプレートが吐き出されるのでこのJSONファイルをマネジメントコンソールからデプロイするのも可能（別途Lambdaなどのソースコードが詰まっているasset.xxxをS3に転送する必要がある）

- CDKにはテストツールがあるが、CDKでできたCloudFormationテンプレート（JSON）のプロパティをテストするツールなので使いづらい
    - Typescript上ではプロパティ名が`role`だがCloudformationテンプレートでは`Role`になる
    - 特定のリソースが作られないというテストが書きづらい

## 参考文献

- [AWS CDK Toolkit (cdk command)](https://docs.aws.amazon.com/cdk/v2/guide/cli.html)
- [Bootstrapping](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html#bootstrapping-customizing)