CDK for TerraformをPythonで試してハマったことまとめ

# 概要
- CDK for TerraformをPythonで試そうとしてハマったところのまとめ
- 公式のGet StatedはTypeScriptの手順であり、Pythonの手順が無かったのであれこれ調べながら試した
- コマンド内の一部をマスク(xxxxx)しておりますが、ユーザ名をマスクしているだけです

# 参考
- https://www.hashicorp.com/blog/cdk-for-terraform-enabling-python-and-typescript-support/
- https://github.com/hashicorp/terraform-cdk/tree/master/examples/python/aws

--- 
# やろうとしたこと
- CDK for TerraformのPythonでVPC,Subnetを作る

# install
- npmでcdktf-cliをインストール

```
$ npm install -g cdktf-cli
/Users/xxxxx/.anyenv/envs/nodenv/versions/12.13.0/bin/cdktf -> /Users/xxxxx/.anyenv/envs/nodenv/versions/12.13.0/lib/node_modules/cdktf-cli/bin/cdktf
npm WARN eslint-plugin-react@7.20.6 requires a peer of eslint@^3 || ^4 || ^5 || ^6 || ^7 but none is installed. You must install peer dependencies yourself.

+ cdktf-cli@0.0.16
updated 3 packages in 19.656s


   ╭────────────────────────────────────────────────────────────────╮
   │                                                                │
   │      New minor version of npm available! 6.13.6 → 6.14.8       │
   │   Changelog: https://github.com/npm/cli/releases/tag/v6.14.8   │
   │               Run npm install -g npm to update!                │
   │                                                                │
   ╰────────────────────────────────────────────────────────────────╯
```

- バージョン確認

```
$ cdktf --version
0.0.16
```

- cdktfコマンド確認

```
$ cdktf
cdktf [コマンド]

コマンド:
  cdktf deploy [OPTIONS]   Deploy the given stack
  cdktf destroy [OPTIONS]  Destroy the given stack
  cdktf diff [OPTIONS]     Perform a diff (terraform plan) for the given stack
  cdktf get [OPTIONS]      Generate CDK Constructs for Terraform providers and modules.
  cdktf init [OPTIONS]     Create a new cdktf project from a template.
  cdktf login              Retrieves an API token to connect to Terraform Cloud.
  cdktf synth [OPTIONS]    Synthesizes Terraform code for the given app in a directory.                                                                                                                               [エイリアス: synthesize]

オプション:
  --version          バージョンを表示                                                                                                                                                                                                   [真偽]
  --disable-logging  Dont write log files. Supported using the env CDKTF_DISABLE_LOGGING.                                                                                                                            [真偽] [デフォルト: true]
  --log-level        Which log level should be written. Only supported via setting the env CDKTF_LOG_LEVEL                                                                                                                            [文字列]
  -h, --help         ヘルプを表示                                                                                                                                                                                                       [真偽]

Options can be specified via environment variables with the "CDKTF_" prefix (e.g. "CDKTF_OUTPUT")
```

# プロジェクト新規作成
- ディレクトリ作成

```
$ mkdir vpc-example
$ cd vpc-example
```

- 初期化
    - ここで以下を入力する
    - Terraform Cloud使うかどうか
    - 言語選択(Python/TypeScript)
    - プロジェクト名
    - プロジェクト説明

```
$ cdktf init
Welcome to CDK for Terraform!

By default, cdktf allows you to manage the state of your stacks using Terraform Cloud for free.
cdktf will request an API token for app.terraform.io using your browser.

If login is successful, cdktf will store the token in plain text in
the following file for use by subsequent Terraform commands:
    /Users/xxxxx/.terraform.d/credentials.tfrc.json

Note: The local storage mode isn't recommended for storing the state of your stacks.

Do you want to continue with Terraform Cloud remote state management (yes/no)? no # Terraform Cloudは今回は利用しないためno

[1] python
[2] typescript
[0] CANCEL

What template you want to use? [1, 2, 0]: 1 # python選択

Initializing a project using the python template.

We will now set up the project. Please enter the details for your project.
If you want to exit, press ^C.

Project Name: (default: 'vpc-example') vpc-example # プロジェクト名入力
Project Description: (default: 'A simple getting started project for cdktf.') # プロジェクト説明入力
Unable to find "pipenv". Install from https://pipenv.kennethreitz.org
```

## エラー① 「Unable to find "pipenv". Install from https://pipenv.kennethreitz.org」
- cdktf initで生成されるファイル(cdktf.json)内を見てみたらpipenvを利用しているので追加する

```
$ brew install pipenv
（長いので略）
```

- 再度初期化

```
$ cdktf init
Welcome to CDK for Terraform!

By default, cdktf allows you to manage the state of your stacks using Terraform Cloud for free.
cdktf will request an API token for app.terraform.io using your browser.

If login is successful, cdktf will store the token in plain text in
the following file for use by subsequent Terraform commands:
    /Users/xxxxx/.terraform.d/credentials.tfrc.json

Note: The local storage mode isn't recommended for storing the state of your stacks.

Do you want to continue with Terraform Cloud remote state management (yes/no)? no # Terraform Cloudは今回は利用しないためno

[1] python
[2] typescript
[0] CANCEL

What template you want to use? [1, 2, 0]: 1 # python選択

Initializing a project using the python template.

We will now set up the project. Please enter the details for your project.
If you want to exit, press ^C.

Project Name: (default: 'vpc-example') vpc-example # プロジェクト名入力
Project Description: (default: 'A simple getting started project for cdktf.')  # プロジェクト説明入力
Creating a virtualenv for this project…
Pipfile: /Users/xxxxx/workspace/AWS/terraform/cdk/vpc-example/Pipfile
Using /Users/xxxxx/.anyenv/envs/pyenv/versions/3.7.0/bin/python3.7m (3.7.0) to create virtualenv…
⠦ Creating virtual environment...created virtual environment CPython3.7.0.final.0-64 in 1887ms
  creator CPython3Posix(dest=/Users/xxxxx/.local/share/virtualenvs/vpc-example-krFy7qQn, clear=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/Users/xxxxx/Library/Application Support/virtualenv)
    added seed packages: pip==20.2.1, setuptools==49.2.1, wheel==0.34.2
  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator

✔ Successfully created virtual environment!
Virtualenv location: /Users/xxxxx/.local/share/virtualenvs/vpc-example-krFy7qQn
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (a65489)!
Installing dependencies from Pipfile.lock (a65489)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.
Installing cdktf~=0.0.16…
Adding cdktf to Pipfile's [packages]…
✔ Installation Succeeded
Pipfile.lock (a65489) out of date, updating to (c2187c)…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Building requirements...
Resolving dependencies...
✔ Success!
Updated Pipfile.lock (c2187c)!
Installing dependencies from Pipfile.lock (c2187c)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.
Generated python constructs in the output directory: imports
========================================================================================================

  Your cdktf Python project is ready!

  cat help                Prints this message

  Compile:
    pipenv run ./main.py  Compile and run the python code.

  Synthesize:
    cdktf synth           Synthesize Terraform resources to cdktf.out/

  Diff:
    cdktf diff            Perform a diff (terraform plan) for the given stack

  Deploy:
    cdktf deploy          Deploy the given stack

  Destroy:
    cdktf destroy         Destroy the given stack

========================================================================================================
```

# VPCを追加してみる
- main.pyを編集
    - AWS provider追加
    - VPC(10.0.0.0/16)追加
    - Subnet x3 追加

```main.py
#!/usr/bin/env python
from constructs import Construct
from cdktf import App, TerraformStack
+ from imports.aws import AwsProvider # 追加
+ from imports.terraform_aws_modules.vpc.aws import Vpc # 追加

class MyStack(TerraformStack):
    def __init__(self, scope: Construct, ns: str):
        super().__init__(scope, ns)

        # define resources here
+       AwsProvider(self, 'Aws', region='ap-northeast-1') # 追加

+       Vpc(self, 'vpc-from-cdk-for-terraform',
+           name='vpc-from-cdk-for-terraform',
+           cidr='10.0.0.0/16',
+           azs=["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"],
+           public_subnets=["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
+       ) # 追加


app = App()
MyStack(app, "vpc-example")

app.synth()

```

- deployしてみる

```
$ cdktf deploy
⠇ synthesizing ...
Traceback (most recent call last):
  File "./main.py", line 5, in <module>
    from imports.terraform_aws_modules.vpc.aws import Vpc
⠙ synthesizing ...
non-zero exit code 1
```

## エラー② 「terraform_aws_modulesが見つからない」
- cdktf.jsonにmoduleの設定がされていなかった

```cdktf.json
{
  "language": "python",
  "app": "pipenv run ./main.py",
  "terraformProviders": ["aws@~> 2.0"],
+ "terraformModules": ["terraform-aws-modules/vpc/aws"], # 追加
  "codeMakerOutput": "imports"
}
```

- moduleをゲット

```
$ cdktf get
Generated python constructs in the output directory: imports
```

- module確認

```
$ ll imports
total 0
drwxr-xr-x  5 xxxxx  1896053708  160  8 23 22:51 aws
drwxr-xr-x  3 xxxxx  1896053708   96  8 23 22:51 terraform_aws_modules
```

- 再deploy

```
$ cdktf deploy
Stack: vpc-example
Resources
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_internet_gateway.this[0]
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route.public_internet_gateway[0]
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route_table.public[0]
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route_table_association.public[0]
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route_table_association.public[1]
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route_table_association.public[2]
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_subnet.public[0]
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_subnet.public[1]
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_subnet.public[2]
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_vpc.this[0]

Diff: 10 to create, 0 to update, 0 to delete.

Do you want to perform these actions?
  CDK for Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes # yesを入力
```

- terraformでおなじみの「Enter a value:」で`yes`を入力

deploy中となり

```
⠼ Deploying Stack: vpc-example
```

完了

```
Summary: 0 created, 0 updated, 0 destroyed.
```

なぜか全部0

# AWSコンソール確認
- できていたもの
    - VPC
    - Internet Gateway
    - Public Subnet x3
    - Route Table
   
上記の`cdk deploy`実行中に表示されていたリソースができていた

# terraform plan的なものができるか確認
- subnetを3 -> 2に減らしてみる

```main.py
#!/usr/bin/env python
from constructs import Construct
from cdktf import App, TerraformStack
from imports.aws import AwsProvider
from imports.terraform_aws_modules.vpc.aws import Vpc

class MyStack(TerraformStack):
    def __init__(self, scope: Construct, ns: str):
        super().__init__(scope, ns)

        # define resources here
        AwsProvider(self, 'Aws', region='ap-northeast-1')

        Vpc(self, 'vpc-from-cdk-for-terraform',
            name='vpc-from-cdk-for-terraform',
            cidr='10.0.0.0/16',
-           azs=["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"],
+           azs=["ap-northeast-1a", "ap-northeast-1c"],
-           public_subnets=["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
+           public_subnets=["10.0.1.0/24", "10.0.2.0/24"]
        )


app = App()
MyStack(app, "vpc-example")

app.synth()

```

- 差分確認

```
$ cdktf diff
Stack: vpc-example
Resources
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route_table_association.public[2]
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_subnet.public[2]

Diff: 0 to create, 0 to update, 2 to delete.
```

- subnetを3 -> 4に増やしてみる

```main.py
#!/usr/bin/env python
from constructs import Construct
from cdktf import App, TerraformStack
from imports.aws import AwsProvider
from imports.terraform_aws_modules.vpc.aws import Vpc

class MyStack(TerraformStack):
    def __init__(self, scope: Construct, ns: str):
        super().__init__(scope, ns)

        # define resources here
        AwsProvider(self, 'Aws', region='ap-northeast-1')

        Vpc(self, 'vpc-from-cdk-for-terraform',
            name='vpc-from-cdk-for-terraform',
            cidr='10.0.0.0/16',
-           azs=["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"],
+           azs=["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d", "ap-northeast-1a"],
-           public_subnets=["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
+           public_subnets=["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24", "10.0.4.0/24"]
        )


app = App()
MyStack(app, "vpc-example")

app.synth()

```

- 差分確認

```
$ cdktf diff
Stack: vpc-example
Resources
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route_table_association.public[3]
 + module.vpcexample_vpcfromcdkforterraform_660852D9.aws_subnet.public[3]

Diff: 2 to create, 0 to update, 0 to delete.
```

# 削除

```
$ cdktf destroy
⠋ planning vpc-example...
Stack: vpc-example
Resources
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_internet_gateway.this[0]
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route.public_internet_gateway[0]
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route_table.public[0]
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route_table_association.public[0]
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route_table_association.public[1]
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_route_table_association.public[2]
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_subnet.public[0]
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_subnet.public[1]
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_subnet.public[2]
 - module.vpcexample_vpcfromcdkforterraform_660852D9.aws_vpc.this[0]

Diff: 0 to create, 0 to update, 10 to delete.

Do you want to perform these actions?
 CDK for Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

```

- Enter a value: は`yes`を入力

```
⠼ Destroying Stack: vpc-example
```

```
Summary: 0 destroyed.
```

こちらも何故か `0 destoryed`

# まとめ
- pipenvインストールが必要
- 依存moduleなどは`cdktf.json`に書いてね
- deploy/destroyの結果がまだ不安定っぽい
    - そしてdeploy/destroyを間違えそう・・・
- importとかいろいろためしてみよう