AWS CDKで既存リソースをCDK管理下に置く方法

:::note warn
**注意**
CDK v2.20.0, v1.152.0より `cdk import` 機能がリリースされています。 [使い方](https://github.com/aws/aws-cdk/pull/17666#issuecomment-1094156659)
原理的にはこの記事で紹介する方法と同じですが、より簡易な手順でインポートできますので、ぜひお試しください。
:::

CloudFormationには [Resource importing機能](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/resource-import-existing-stack.html) があります。
これを利用して、CDKで既存のリソースを管理できないかなと思いました。
実験してみたらできたので、その方法を共有します。

なお、これは `fromArn` 系のメソッドで既存リソースを _参照する_ 方法ではなく、CDKネイティブのリソースとして既存リソースを扱うことができる方法です。
参照: https://github.com/aws/aws-cdk-rfcs/issues/52

## 今回対象にするケース
Toy example として、既存のSQSキューをCDK管理下にインポートする方法を紹介します。

前提として `import_test_queue` という名前のSQSキューがすでに作成されているとします（下図。）

このいわば野良SQSキューをCDK管理下に置くことを、この記事での目標にします。

![スクリーンショット 2020-10-11 22.38.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127392/998bc26a-c2b1-7f66-cd2b-94c064d9ffec.png)


## 手順
### 1. CDKでインポートしたいリソースを定義するコードを書く

CFnのResource importingを使うには、CFnテンプレートを用意する必要があります。
このテンプレートを生成するためのCDK Stackを定義します。
このコードは、これらの手順が完了した後同リソースを管理するためにそのまま利用することができます。

```ts:import-test-stack.ts
import * as sqs from '@aws-cdk/aws-sqs';
import * as cdk from '@aws-cdk/core';

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

    new sqs.Queue(this, 'queue', {
      queueName: 'import_test_queue',
    });
  }
}
```

```ts:cdk.ts
#!/usr/bin/env node
import * as cdk from '@aws-cdk/core';
import { ImportTestStack } from '../lib/import-test-stack';

const app = new cdk.App();

new ImportTestStack(app, 'import-test-stack', {
    stackName: 'import-test-stack',
});
```

注意点として、 `queueName` は既存リソースと合わせてください。（今回は  `import_test_queue`）
また、Stack名についても今後作成するStackの名前と同一にする必要があります。（今回は `import-test-stack`）


### 2. テンプレートを生成する
上記のCDKコードから `cdk synth` でCFnテンプレートを作成します。

```sh
npm run build
npm run cdk synth
```

`cdk.out` ディレクトリには下記の `import-test-stack.template.json` が生成されるはずです。


```json:import-test-stack.template.json
{
  "Resources": {
    "queue276F7297": {
      "Type": "AWS::SQS::Queue",
      "Properties": {
        "QueueName": "import_test_queue"
      },
      "Metadata": {
        "aws:cdk:path": "import-test-stack/queue/Resource"
      }
    },
    "CDKMetadata": {
      "Type": "AWS::CDK::Metadata",
      "Properties": {
        "Modules": "aws-cdk=1.64.1,@aws-cdk/assets=1.64.1,@aws-cdk/aws-applicationautoscaling=1.64.1,@aws-cdk/aws-autoscaling=1.64.1,@aws-cdk/aws-autoscaling-common=1.64.1,@aws-cdk/aws-cloudwatch=1.64.1,@aws-cdk/aws-codeguruprofiler=1.64.1,@aws-cdk/aws-ec2=1.64.1,@aws-cdk/aws-eks=1.64.1,@aws-cdk/aws-elasticloadbalancingv2=1.64.1,@aws-cdk/aws-events=1.64.1,@aws-cdk/aws-iam=1.64.1,@aws-cdk/aws-kms=1.64.1,@aws-cdk/aws-lambda=1.64.1,@aws-cdk/aws-logs=1.64.1,@aws-cdk/aws-s3=1.64.1,@aws-cdk/aws-s3-assets=1.64.1,@aws-cdk/aws-sqs=1.64.1,@aws-cdk/aws-ssm=1.64.1,@aws-cdk/cloud-assembly-schema=1.64.1,@aws-cdk/core=1.64.1,@aws-cdk/custom-resources=1.64.1,@aws-cdk/cx-api=1.64.1,@aws-cdk/region-info=1.64.1,jsii-runtime=node.js/v14.13.0"
      }
    }
  }
}
```

### 3. 生成されたテンプレートをResource importing用に加工する
CFnのResource importingを利用するには、CFnテンプレートがいくつかの制約を満たしている必要があります。
このうち、上記のテンプレートが問題になるのは下記です:

* `DeletionPolicy` が指定されている必要がある
* `AWS::CDK::Metadata` リソースを含んではならない

この条件を満たせるように、先のテンプレートを手で編集します。
編集後のテンプレートは下記です。

```json:import-test-stack.template.json
{
  "Resources": {
    "queue276F7297": {
      "Type": "AWS::SQS::Queue",
      "DeletionPolicy": "Retain",
      "Properties": {
        "QueueName": "import_test_queue"
      },
      "Metadata": {
        "aws:cdk:path": "import-test-stack/queue/Resource"
      }
    }
  }
}
```

なお、これらの制約ですが、最初からすべて予見して編集するよりは、実際に何度かインポートを試してみてエラー文を見ながら編集するのが良いと思います。

### 4. CFnのResource Importing機能でStackを作成する
ウェブコンソールから、下記の手順でStackを作成します。

`スタックの作成` → `既存のリソースを使用` をクリックし、Resouce importingのウィザードを開始します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127392/cd66d0bb-078e-bec0-740f-2792dfbe0158.png)

先程作成したテンプレートを指定してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127392/d824b9fe-5d7a-6b6f-2afa-a31675368a23.png)

インポートしたいキューの `QueueUrl` をSQSコンソールからコピーします。
![スクリーンショット 2020-10-11 22.58.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127392/3ec64cc4-ffeb-1d4b-9476-8e1bfc33108c.png)

スタックの名前は、 `cdk.ts` で指定したものと同一にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127392/7bcf170c-5585-69da-d63d-38cd2bf7020e.png)

これまで正しく実行できていれば、インポートできます。
`リソースをインポート` ボタンをクリックしてください。
![スクリーンショット 2020-10-11 23.00.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127392/2d5fc697-4fd6-b5a3-b7ce-c71c859ec5f3.png)

インポート完了です！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127392/41de2460-844e-0c00-91a7-fde160dfde52.png)

### 5. cdkから同じスタックをデプロイする
これで、CDKからStackを操作できるようになりました。
試しに `cdk diff` してみてください

```sh:diff
$ npm run cdk diff import-test-stack

Stack import-test-stack
Resources
[~] AWS::SQS::Queue queue queue276F7297 
 └─ [-] DeletionPolicy
     └─ Retain
```

先程手で追加したDeletionPolicyを削除するようなdiffが表示されています。
想定通りですね。

cdk deploy してみましょう。

```sh
$ npm run cdk deploy import-test-stack

import-test-stack: deploying...
import-test-stack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (3/2)

 ✅  import-test-stack
```

こちらもうまくいきました。

コンソールからStackを見ると、CDK Metadataリソースが合わせて作成されていることが分かります。
![スクリーンショット 2020-10-11 23.18.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127392/43c2f3eb-331e-c2b7-31eb-79538bff6013.png)

以上で、手順は完了です。


CDKのコードを変更することで、CDKネイティブのStackと全く同様にリソースを変更できます。
これは `fromArn` などによる参照を取得する機能では実現できなかったものです。

```sh
$ npm run build && npm run cdk diff import-test-stack

Stack import-test-stack
Resources
[~] AWS::SQS::Queue queue queue276F7297 
 └─ [+] MessageRetentionPeriod
     └─ 1200
```

## まとめ
* CDKでも間接的にCFnのResource importingを使うことができます
* 多くのサービスはResource importingが[対応されている](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import-supported-resources.html)ので、使うと良いでしょう
* `fromArn` などを使うよりは、こちらのほうがCDKのメリットを享受しやすいです