CDK for Terraformが発表されたので試してみた！

# はじめに

2020/7/16にCDK for Terraformが発表されました！

https://aws.amazon.com/jp/blogs/developer/introducing-the-cloud-development-kit-for-terraform-preview/
https://www.hashicorp.com/blog/cdk-for-terraform-enabling-python-and-typescript-support/

Python と TypeScript がサポートのようなので、TypeScriptの環境を構築してみました。

## 実行バージョン

- node version: v12.18.2
- npm version: 6.14.6

# インストール

```sh
npm i -g cdktf-cli
```

# 初期化

```sh
mkdir vpc-example
cd vpc-example
cdktf init --template=typescript --local
```

今回は検証のためTerraform Cloudを使わずにいきます。

実行状態みてみると、ローカルにインストールしていたyarnが使われましたね。

# 生成ディレクトリの確認

```
├── .gen
│   └── providers
│       └── aws
│        ├── accessanalyzer-analyzer.ts
│        ├── # omitted for clarity
│        └── xray-sampling-rule.ts
│   └── modules
├── .terraform
├── cdktf.json
├── help
├── main.d.ts
├── main.js
├── main.ts
├── package.json
└── tsconfig.json
```

.genの中にリソースなどのクラスファイルが生成されています。
例えば `.gen/providers/aws/vpc.ts` を確認してみると以下のような構造になっていました。

```ts
import { Construct } from 'constructs';
import { TerraformResource } from 'cdktf';
import { TerraformMetaArguments } from 'cdktf';

export interface VpcConfig extends TerraformMetaArguments {
  readonly assignGeneratedIpv6CidrBlock?: boolean;
  readonly cidrBlock: string;
  readonly enableClassiclink?: boolean;
  readonly enableClassiclinkDnsSupport?: boolean;
  readonly enableDnsHostnames?: boolean;
  readonly enableDnsSupport?: boolean;
  readonly instanceTenancy?: string;
  readonly tags?: { [key: string]: string };
}

export class Vpc extends TerraformResource {

  // ===========
  // INITIALIZER
  // ===========

  public constructor(scope: Construct, id: string, config: VpcConfig) {
    super(scope, id, {
      terraformResourceType: 'aws_vpc',
      terraformGeneratorMetadata: {
        providerName: 'aws'
      },
      provider: config.provider,
      dependsOn: config.dependsOn,
      count: config.count,
      lifecycle: config.lifecycle
    });
    this._assignGeneratedIpv6CidrBlock = config.assignGeneratedIpv6CidrBlock;
    this._cidrBlock = config.cidrBlock;
    this._enableClassiclink = config.enableClassiclink;
    this._enableClassiclinkDnsSupport = config.enableClassiclinkDnsSupport;
    this._enableDnsHostnames = config.enableDnsHostnames;
    this._enableDnsSupport = config.enableDnsSupport;
    this._instanceTenancy = config.instanceTenancy;
    this._tags = config.tags;
  }

  // methods
}
```

なるほど、Vpcのコンストラクタで、

- scope
- id
- config

を渡す感じですね。

configはVpcConfig interfaceになっているので、エディタで補完できますね。これは楽そうです。

# 実装

早速公式を参考に実装してみましょう。

```ts
import { Construct } from 'constructs';
import { App, TerraformStack, Token } from 'cdktf';
import { AwsProvider, Subnet, Vpc } from './.gen/providers/aws';

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new AwsProvider(this, 'aws', {
      region: 'ap-northeast-1'
    });

    const vpc = new Vpc(this, 'my-vpc', {
      cidrBlock: '10.0.0.0/16'
    })

    new Subnet(this, 'my-subnet', {
      vpcId: Token.asString(vpc.id),
      cidrBlock: '10.0.0.0/24'
    })
  }
}

const app = new App();
new MyStack(app, 'vpc-example');
app.synth();

```

```sh
npm run synth
```
でjsonを生成してくれます。

`./cdktf.out/cdk.tf.json`


# 環境を増やす

環境を増やしたい場合は愚直に新しいインスタンスを作成すればその分jsonへ吐き出されます。

TerraformStackやAppを増やしても、最後に定義したものしか生成されないようでした。

```ts
import { Construct } from 'constructs';
import { App, TerraformStack, Token } from 'cdktf';
import { AwsProvider, Subnet, Vpc } from './.gen/providers/aws';

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new AwsProvider(this, 'aws', {
      region: 'ap-northeast-1'
    });

    const vpcProduction = new Vpc(this, 'production-vpc', {
      cidrBlock: '10.0.0.0/16'
    })

    new Subnet(this, 'production-subnet', {
      vpcId: Token.asString(vpcProduction.id),
      cidrBlock: '10.0.0.0/24'
    })

    const vpcStaging = new Vpc(this, 'staging-vpc', {
      cidrBlock: '10.1.0.0/16'
    })

    new Subnet(this, 'staging-subnet', {
      vpcId: Token.asString(vpcStaging.id),
      cidrBlock: '10.1.0.0/24'
    })
  }
}

const app = new App();
new MyStack(app, 'vpc-example');

app.synth()
```

TypeScriptでかけるので、例えばVpcとSubnetの組み合わせを出力する関数作ったりとかできそうですね。

こんな風にしてみたり。


```ts
import { Construct } from 'constructs';
import { App, TerraformStack, Token } from 'cdktf';
import { AwsProvider, Subnet, Vpc } from './.gen/providers/aws';

function createVpcAndSubnet(scope: Construct, vpcCidr: string, baseName: string) {
  const vpc = new Vpc(scope, `${baseName}-vpc`, {
    cidrBlock: vpcCidr
  })

  const subnetBits: number = 24
  const subnetNetwork: string = vpcCidr.split('/')[0]

  new Subnet(scope, `${baseName}-subnet`, {
    vpcId: Token.asString(vpc.id),
    cidrBlock: subnetNetwork + '/' + subnetBits
  })
}

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new AwsProvider(this, 'aws', {
      region: 'ap-northeast-1'
    });

    createVpcAndSubnet(this, '10.0.0.0/16', 'production')
    createVpcAndSubnet(this, '10.1.0.0/16', 'staging')
  }
}

const app = new App();
new MyStack(app, 'vpc-example');

app.synth()

```

tfを直接書くよりめちゃくちゃ楽そうです。


# 反映

`./cdktf.out/cdk.tf.json` は Terraform JSON configuration fileになっているので

```sh
cd cdktf.out
terrafrom init
terraform plan
terraform apply
```

とかしていけば反映できますね！  
今回はAWS環境を用意していないので試しません。

cdkを使って直接デプロイもできるようです。

```sh
cdktf deploy
```

# まとめ

もとからTypeScriptかける方でしたら、導入コストは低く、TypeScriptの恩恵を得られそうです。  
すでに動いているものを移植するのは結構苦労しそうですね。。。