🌟

CDK for Terraform の使い方まとめ

2022/09/01に公開約35,800字

先日 CDK for Terraform が GA になりました。

https://www.hashicorp.com/blog/cdk-for-terraform-now-generally-available

この記事では CDK for Terraform の基本的な使い方をまとめます。
TypeScript を使う前提で記述するため、他の言語を利用する場合は適宜読み替えてください。

CDK for Terraform とは

https://www.terraform.io/cdktf

CDK for Terraform を使うと使い慣れたプログラミング言語を使ってインフラの定義やプロビジョニングを行うことができます。
これにより HCL を学ぶことなく Terraform エコシステム全体にアクセスでき、またテストや依存関係管理などの既存のツールチェインの力を活用することができます。

CDK for Terraform では 2022年09月01日現在、次の言語をサポートしています。

  • TypeScript
  • Python
  • Java
  • C#
  • Go

言語によっては実験的な新機能が利用できない場合もあります。

CDK for Terraform では Terraform Registry で利用可能な全ての Terraform プロバイダとモジュールを利用できます。
また、 Terraform CloudTerraform EnterpriseSentinel なども利用可能です。

準備

前提条件

  • Terraform CLI ( v1.1 以上 )
  • Node.js ( v16 以上 )

cdktf cli をインストール

CDK for Terraform は cdktf cli を使ってさまざまなオペレーションを実行します。
cdktf cli は npm でインストールすることができます。

# 安定版リリース
$ npm install --global cdktf-cli@latest

# 開発版リリース
$ npm install --global cdktf-cli@next

使い方

cdktf init - プロジェクトを作成

cdktf init でプロジェクトを作成することができます。

$ cdktf init --template=typescript --local
  • --template でテンプレートを指定します。
  • --local で tfstate をローカルで管理することを指定します。
    • Terraform Cloud を利用する場合はこのオプションは不要です。
    • AWS S3 などによるリモートステートを利用する場合もここでは --local を指定します。

実行すると対話形式でさまざまな設定をしていきます。

  • プロジェクト名
  • プロジェクトの説明
  • 既存の Terraform プロジェクトを利用するかどうか ( 2022年09月01日現在 TypeScript のみ )
    • 利用する場合、 Terraform プロジェクトへのパス
  • クラッシュレポートを送信するかどうか
? Project Name
? Project Description
? Do you want to start from an existing Terraform project?
? Please enter the path to the Terraform project
? Do you want to send crash reports to the CDKTF team? See https://www.terraform.io/cdktf/create-and-deploy/configuration-file#enable-crash-reporting-for-the-cli for more information

プロジェクトの作成が完了するとカレントディレクトリに様々なファイルが作成されます。

$ tree -aFL 1
./
├── .gitignore
├── .npmrc
├── __tests__/
├── cdktf.json
├── help
├── jest.config.js
├── main.ts
├── node_modules/
├── package-lock.json
├── package.json
├── setup.js
└── tsconfig.json

2 directories, 10 files

インフラリソースの定義は主に main.ts に記述していきます。

CDK for Terraform の設定は cdktf.json で行います。

cdktf.json の例
cdktf.json
{
  "language": "typescript",
  "app": "npx ts-node main.ts",
  "projectId": "00000000-0000-0000-0000-000000000000",
  "sendCrashReports": "false",
  "terraformProviders": [],
  "terraformModules": [],
  "context": {
    "excludeStackIdFromLogicalIds": "true",
    "allowSepCharsInLogicalIds": "true"
  }
}

cdktf.json の詳細につきましては下記ドキュメントをご参照ください。

https://www.terraform.io/cdktf/create-and-deploy/configuration-file

cdktf provider add - プロバイダを追加する

cdktf provider add でプロバイダを追加することができます。

例えば aws プロバイダを追加する場合は次のように実行します。

$ cdktf provider add "aws@~>4.0"

cdktf provider add を実行すると、まずビルド済みのパッケージ ( 後述 ) が存在するかどうかチェックされます。

ビルド済みのパッケージが見つからなかった場合は cdktf.jsonterraformProviders にプロバイダ情報が追記され、 .gen/providers/{プロバイダ名}/ ディレクトリ以下にコードが生成されます。

次の例では sops プロバイダを追加しています。

$ cdktf provider add "carlpett/sops@~>0.7"
Checking whether pre-built provider exists for the following constraints:
  provider: carlpett/sops
  version : ~>0.7
  language: typescript
Pre-built provider does not exist for the given constraints.
Adding local provider registry.terraform.io/carlpett/sops with version constraint ~>0.7 to cdktf.json
Generated typescript constructs in the output directory: .gen

cdktf.jsonterraformProvidersに sops プロバイダの情報が追記されます。

cdktf.json
{
  // ...省略
  "terraformProviders": [
    "carlpett/sops@~>0.7"
  ],
  // ...省略
}

.gen/providers/sops/ ディレクトリ以下にコードが生成されています。

$ tree -aF .gen/providers
.gen/providers/
└── sops/
    ├── data-sops-external.ts
    ├── data-sops-file.ts
    ├── index.ts
    └── sops-provider.ts

1 directory, 4 files

生成されたコードは import して使用できます。

main.ts
import { DataSopsFile } from "./.gen/providers/sops";

// ...省略

// data "sops_file" "some_file" {
//   source_file = "path/to/SOURCE_FILE"
// }
new DataSopsFile(stack, "some_file", {
  sourceFile: "path/to/SOURCE_FILE"
});

// ...省略

大きなスキーマを持つプロバイダの場合はコードを生成するために数分かかることがあります。
そのため、よく使われるプロバイダはビルド済みのパッケージとして公開されています。

全てのビルド済みパッケージは下記ページから参照できます。

https://github.com/orgs/hashicorp/repositories?q=cdktf-provider-

ビルド済みのパッケージが見つかった場合はそのパッケージがインストールされます。
次の例では aws プロバイダを追加しています。
aws プロバイダの場合はビルド済みの @cdktf/provider-aws パッケージが用意されているので、こちらがインストールされます。

$ cdktf provider add "aws@~>4.0"
Checking whether pre-built provider exists for the following constraints:
  provider: aws
  version : ~>4.0
  language: typescript
  cdktf   : 0.12.1

Found pre-built provider.
Adding package @cdktf/provider-aws @ 9.0.15
Installing package @cdktf/provider-aws @ 9.0.15 using npm.
Package installed.

この場合はパッケージがインストールされるだけで、 cdktf.json は更新されず、 .gen/providers/ ディレクトリ以下にコードが生成されることもありません。
ビルド済みのパッケージは cdktf provider add を使わずに npm install でインストールすることもできます。

$ npm install @cdktf/provider-aws

インフラリソースを定義する

main.ts にコードを記述してインフラリソースを定義していきます。

CDK for Terraform ではスタックという単位でインフラリソースを定義していきます。
プロビジョニングはスタックごとに実行することができ、 tfstate ファイルもスタックごとに分けて管理されます。

スタックを定義するには TerraformStack クラスを継承したクラスを用意します。
次の例では S3 バケットを作成するスタックをひとつだけ作成しています。

main.ts
import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AwsProvider, s3 } from "@cdktf/provider-aws";

// スタックを定義
class MyStack extends TerraformStack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    // provider "aws" {
    //   region = "us-east-1"
    // }
    new AwsProvider(this, "aws", {
      region: "us-east-1",
    });

    // resource "aws_s3_bucket" "example_bucket" {
    //   bucket_prefix = "example-"
    // }
    new s3.S3Bucket(this, "example_bucket", {
      bucketPrefix: "example-",
    });
  }
}

const app = new App();

// スタックを作成
new MyStack(app, "example-stack");

app.synth();

スタックは複数作成することもできます。
次の例では環境ごとにスタックを作成しています。

main.ts
import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AwsProvider, s3 } from "@cdktf/provider-aws";

// スタックを定義
class MyStack extends TerraformStack {
  // スタックのコンストラクタで環境を指定できるようにする
  constructor(scope: Construct, id: string, env: "dev" | "stg" | "prd") {
    super(scope, id);

    new AwsProvider(this, "aws", {
      region: "us-east-1",
    });

    new s3.S3Bucket(this, "example_bucket", {
      bucketPrefix: `example-${env}-`,
      tags: {
        environment: env,
      },
    });
  }
}

const app = new App();

// 環境ごとにスタックを作成
new MyStack(app, "example-stack-dev", "dev");
new MyStack(app, "example-stack-stg", "stg");
new MyStack(app, "example-stack-prd", "prd");

app.synth();

他にも用途ごとにスタックを作成したり、スタック間の依存関係を管理したりすることもできます。
詳しくは下記ドキュメントをご参照ください。

https://www.terraform.io/cdktf/concepts/stacks

インフラリソースをプロビジョニングする

cdktf cli を使用して定義したインフラリソースをプロビジョニングすることができます。

cdktf diff - 差分を表示

cdktf diff ( もしくは cdktf plan ) で差分を表示することができます ( = terraform plan ) 。

出力例
$ cdktf diff
example-stack  Initializing the backend...
example-stack
                Successfully configured the backend "local"! Terraform will automatically
                use this backend unless the backend configuration changes.
example-stack  Initializing provider plugins...
                - Finding hashicorp/aws versions matching "4.27.0"...
example-stack  - Using hashicorp/aws v4.27.0 from the shared cache directory
example-stack  Terraform has created a lock file .terraform.lock.hcl to record the provider
                selections it made above. Include this file in your version control repository
                so that Terraform can guarantee to make the same selections by default when
                you run "terraform init" in the future.
example-stack  Terraform has been successfully initialized!
example-stack
                You may now begin working with Terraform. Try running "terraform plan" to see
                any changes that are required for your infrastructure. All Terraform commands
                should now work.

                If you ever set or change modules or backend configuration for Terraform,
                rerun this command to reinitialize your working directory. If you forget, other
                commands will detect it and remind you to do so if necessary.
example-stack  Terraform used the selected providers to generate the following execution
                plan. Resource actions are indicated with the following symbols:
                + create

                Terraform will perform the following actions:
example-stack    # aws_s3_bucket.example_bucket (example_bucket) will be created
                  + resource "aws_s3_bucket" "example_bucket" {
                + acceleration_status         = (known after apply)
                + acl                         = (known after apply)
                + arn                         = (known after apply)
                + bucket                      = (known after apply)
                + bucket_domain_name          = (known after apply)
                + bucket_prefix               = "example-"
                + bucket_regional_domain_name = (known after apply)
                + force_destroy               = false
                + hosted_zone_id              = (known after apply)
                + id                          = (known after apply)
                + object_lock_enabled         = (known after apply)
                + policy                      = (known after apply)
                + region                      = (known after apply)
                + request_payer               = (known after apply)
                + tags_all                    = (known after apply)
                + website_domain              = (known after apply)
                + website_endpoint            = (known after apply)

                + cors_rule {
                + allowed_headers = (known after apply)
                + allowed_methods = (known after apply)
                + allowed_origins = (known after apply)
                + expose_headers  = (known after apply)
                + max_age_seconds = (known after apply)
                }

                + grant {
                + id          = (known after apply)
                + permissions = (known after apply)
                + type        = (known after apply)
                + uri         = (known after apply)
                }

                + lifecycle_rule {
                + abort_incomplete_multipart_upload_days = (known after apply)
                + enabled                                = (known after apply)
                + id                                     = (known after apply)
                + prefix                                 = (known after apply)
                + tags                                   = (known after apply)

                + expiration {
                + date                         = (known after apply)
                + days                         = (known after apply)
                + expired_object_delete_marker = (known after apply)
                }

                + noncurrent_version_expiration {
                + days = (known after apply)
                }

                + noncurrent_version_transition {
                + days          = (known after apply)
                + storage_class = (known after apply)
                }

                + transition {
                + date          = (known after apply)
                + days          = (known after apply)
                + storage_class = (known after apply)
                }
                }

                + logging {
                + target_bucket = (known after apply)
                + target_prefix = (known after apply)
                }

                + object_lock_configuration {
                + object_lock_enabled = (known after apply)

                + rule {
                + default_retention {
                + days  = (known after apply)
                + mode  = (known after apply)
                + years = (known after apply)
                }
                }
                }

                + replication_configuration {
                + role = (known after apply)

                + rules {
                + delete_marker_replication_status = (known after apply)
                + id                               = (known after apply)
                + prefix                           = (known after apply)
                + priority                         = (known after apply)
                + status                           = (known after apply)

                + destination {
                + account_id         = (known after apply)
                + bucket             = (known after apply)
                + replica_kms_key_id = (known after apply)
                + storage_class      = (known after apply)

                + access_control_translation {
                + owner = (known after apply)
                }

                + metrics {
                + minutes = (known after apply)
                + status  = (known after apply)
                }

                + replication_time {
                + minutes = (known after apply)
                + status  = (known after apply)
                }
                }

                + filter {
                + prefix = (known after apply)
                + tags   = (known after apply)
                }

                + source_selection_criteria {
                + sse_kms_encrypted_objects {
                + enabled = (known after apply)
                }
                }
                }
                }

                + server_side_encryption_configuration {
                + rule {
                + bucket_key_enabled = (known after apply)

                + apply_server_side_encryption_by_default {
                + kms_master_key_id = (known after apply)
                + sse_algorithm     = (known after apply)
                }
                }
                }

                + versioning {
                + enabled    = (known after apply)
                + mfa_delete = (known after apply)
                }

                + website {
                + error_document           = (known after apply)
                + index_document           = (known after apply)
                + redirect_all_requests_to = (known after apply)
                + routing_rules            = (known after apply)
                }
                }

                Plan: 1 to add, 0 to change, 0 to destroy.

                ─────────────────────────────────────────────────────────────────────────────
example-stack  Saved the plan to: plan

                To perform exactly these actions, run the following command to apply:
                terraform apply "plan"

cdktf deploy - プロビジョニングを実行

cdktf deploy ( もしくは cdktf apply ) で定義に合わせてインフラリソースをプロビジョニングします ( = terraform apply ) 。

出力例
$ cdktf deploy
example-stack  Initializing the backend...
example-stack  Initializing provider plugins...
                - Reusing previous version of hashicorp/aws from the dependency lock file
example-stack  - Using previously-installed hashicorp/aws v4.27.0

                Terraform has been successfully initialized!
example-stack
                You may now begin working with Terraform. Try running "terraform plan" to see
                any changes that are required for your infrastructure. All Terraform commands
                should now work.

                If you ever set or change modules or backend configuration for Terraform,
                rerun this command to reinitialize your working directory. If you forget, other
                commands will detect it and remind you to do so if necessary.
example-stack  Terraform used the selected providers to generate the following execution
                plan. Resource actions are indicated with the following symbols:
                + create

                Terraform will perform the following actions:
example-stack    # aws_s3_bucket.example_bucket (example_bucket) will be created
                  + resource "aws_s3_bucket" "example_bucket" {
                + acceleration_status         = (known after apply)
                + acl                         = (known after apply)
                + arn                         = (known after apply)
                + bucket                      = (known after apply)
                + bucket_domain_name          = (known after apply)
                + bucket_prefix               = "example-"
                + bucket_regional_domain_name = (known after apply)
                + force_destroy               = false
                + hosted_zone_id              = (known after apply)
                + id                          = (known after apply)
                + object_lock_enabled         = (known after apply)
                + policy                      = (known after apply)
                + region                      = (known after apply)
                + request_payer               = (known after apply)
                + tags_all                    = (known after apply)
                + website_domain              = (known after apply)
                + website_endpoint            = (known after apply)

                + cors_rule {
                + allowed_headers = (known after apply)
                + allowed_methods = (known after apply)
                + allowed_origins = (known after apply)
                + expose_headers  = (known after apply)
                + max_age_seconds = (known after apply)
                }

                + grant {
                + id          = (known after apply)
                + permissions = (known after apply)
                + type        = (known after apply)
                + uri         = (known after apply)
                }

                + lifecycle_rule {
                + abort_incomplete_multipart_upload_days = (known after apply)
                + enabled                                = (known after apply)
                + id                                     = (known after apply)
                + prefix                                 = (known after apply)
                + tags                                   = (known after apply)

                + expiration {
                + date                         = (known after apply)
                + days                         = (known after apply)
                + expired_object_delete_marker = (known after apply)
                }

                + noncurrent_version_expiration {
                + days = (known after apply)
                }

                + noncurrent_version_transition {
                + days          = (known after apply)
                + storage_class = (known after apply)
                }

                + transition {
                + date          = (known after apply)
                + days          = (known after apply)
                + storage_class = (known after apply)
                }
                }

                + logging {
                + target_bucket = (known after apply)
                + target_prefix = (known after apply)
                }

                + object_lock_configuration {
                + object_lock_enabled = (known after apply)

                + rule {
                + default_retention {
                + days  = (known after apply)
                + mode  = (known after apply)
                + years = (known after apply)
                }
                }
                }

                + replication_configuration {
                + role = (known after apply)

                + rules {
                + delete_marker_replication_status = (known after apply)
                + id                               = (known after apply)
                + prefix                           = (known after apply)
                + priority                         = (known after apply)
                + status                           = (known after apply)

                + destination {
                + account_id         = (known after apply)
                + bucket             = (known after apply)
                + replica_kms_key_id = (known after apply)
                + storage_class      = (known after apply)

                + access_control_translation {
                + owner = (known after apply)
                }

                + metrics {
                + minutes = (known after apply)
                + status  = (known after apply)
                }

                + replication_time {
                + minutes = (known after apply)
                + status  = (known after apply)
                }
                }

                + filter {
                + prefix = (known after apply)
                + tags   = (known after apply)
                }

                + source_selection_criteria {
                + sse_kms_encrypted_objects {
                + enabled = (known after apply)
                }
                }
                }
                }

                + server_side_encryption_configuration {
                + rule {
                + bucket_key_enabled = (known after apply)

                + apply_server_side_encryption_by_default {
                + kms_master_key_id = (known after apply)
                + sse_algorithm     = (known after apply)
                }
                }
                }

                + versioning {
                + enabled    = (known after apply)
                + mfa_delete = (known after apply)
                }

                + website {
                + error_document           = (known after apply)
                + index_document           = (known after apply)
                + redirect_all_requests_to = (known after apply)
                + routing_rules            = (known after apply)
                }
                }

                Plan: 1 to add, 0 to change, 0 to destroy.

example-stack
                ─────────────────────────────────────────────────────────────────────────────

                Saved the plan to: plan

                To perform exactly these actions, run the following command to apply:
                terraform apply "plan"
example-stack  aws_s3_bucket.example_bucket (example_bucket): Creating...
example-stack  aws_s3_bucket.example_bucket (example_bucket): Creation complete after 5s [id=example-20220828121939889900000001]
example-stack
                Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

No outputs found.

実行すると ApproveDismissStop の選択肢が表示されます。
方向キーで選択し、 Enter キーで決定します。

Please review the diff output above for {スタック名}
❯ Approve
  Dismiss
  Stop
選択肢 説明
Approve 現在のスタックのプロビジョニングを実行する。
Dismiss 現在のスタックのプロビジョニングを中止し、次のスタックの確認に進む。また、中止したスタックに依存しているスタックのプロビジョニングも中止される。
Stop 以降の全てのスタックのプロビジョニングを中止する。

--auto-approve フラグを指定すると確認をスキップして全てのスタックのプロビジョニングが実行されます。

cdktf destroy - リソースを削除

cdktf destroy でインフラリソースを削除します ( = terraform destroy ) 。

出力例
$ cdktf destroy --auto-approve
example-stack  Initializing the backend...
example-stack  Initializing provider plugins...
                - Reusing previous version of hashicorp/aws from the dependency lock file
example-stack  - Using previously-installed hashicorp/aws v4.27.0
example-stack  Terraform has been successfully initialized!

                You may now begin working with Terraform. Try running "terraform plan" to see
                any changes that are required for your infrastructure. All Terraform commands
                should now work.

                If you ever set or change modules or backend configuration for Terraform,
                rerun this command to reinitialize your working directory. If you forget, other
                commands will detect it and remind you to do so if necessary.
example-stack  aws_s3_bucket.example_bucket (example_bucket): Refreshing state... [id=example-20220828121939889900000001]
example-stack  Terraform used the selected providers to generate the following execution
                plan. Resource actions are indicated with the following symbols:
                - destroy

                Terraform will perform the following actions:
example-stack    # aws_s3_bucket.example_bucket (example_bucket) will be destroyed
                  - resource "aws_s3_bucket" "example_bucket" {
                - arn                         = "arn:aws:s3:::example-20220828121939889900000001" -> null
                - bucket                      = "example-20220828121939889900000001" -> null
                - bucket_domain_name          = "example-20220828121939889900000001.s3.amazonaws.com" -> null
                - bucket_prefix               = "example-" -> null
                - bucket_regional_domain_name = "example-20220828121939889900000001.s3.amazonaws.com" -> null
                - force_destroy               = false -> null
                - hosted_zone_id              = "**************" -> null
                - id                          = "example-20220828121939889900000001" -> null
                - object_lock_enabled         = false -> null
                - region                      = "us-east-1" -> null
                - request_payer               = "BucketOwner" -> null
                - tags                        = {} -> null
                - tags_all                    = {} -> null

                - grant {
                - id          = "****************************************************************" -> null
                - permissions = [
                - "FULL_CONTROL",
                ] -> null
                - type        = "CanonicalUser" -> null
                }

                - versioning {
                - enabled    = false -> null
                - mfa_delete = false -> null
                }
                }

                Plan: 0 to add, 0 to change, 1 to destroy.

                ─────────────────────────────────────────────────────────────────────────────

                Saved the plan to: plan

                To perform exactly these actions, run the following command to apply:
                terraform apply "plan"
example-stack  aws_s3_bucket.example_bucket (example_bucket): Refreshing state... [id=example-20220828121939889900000001]
example-stack  Terraform used the selected providers to generate the following execution
                plan. Resource actions are indicated with the following symbols:
                - destroy

                Terraform will perform the following actions:
example-stack    # aws_s3_bucket.example_bucket (example_bucket) will be destroyed
                  - resource "aws_s3_bucket" "example_bucket" {
                - arn                         = "arn:aws:s3:::example-20220828121939889900000001" -> null
                - bucket                      = "example-20220828121939889900000001" -> null
                - bucket_domain_name          = "example-20220828121939889900000001.s3.amazonaws.com" -> null
                - bucket_prefix               = "example-" -> null
                - bucket_regional_domain_name = "example-20220828121939889900000001.s3.amazonaws.com" -> null
                - force_destroy               = false -> null
                - hosted_zone_id              = "**************" -> null
                - id                          = "example-20220828121939889900000001" -> null
                - object_lock_enabled         = false -> null
                - region                      = "us-east-1" -> null
                - request_payer               = "BucketOwner" -> null
                - tags                        = {} -> null
                - tags_all                    = {} -> null

                - grant {
                - id          = "****************************************************************" -> null
                - permissions = [
                - "FULL_CONTROL",
                ] -> null
                - type        = "CanonicalUser" -> null
                }

                - versioning {
                - enabled    = false -> null
                - mfa_delete = false -> null
                }
                }

                Plan: 0 to add, 0 to change, 1 to destroy.

example-stack  aws_s3_bucket.example_bucket (example_bucket): Destroying... [id=example-20220828121939889900000001]
example-stack  aws_s3_bucket.example_bucket (example_bucket): Destruction complete after 1s
example-stack
                Destroy complete! Resources: 1 destroyed.

こちらも cdktf deploy と同様に ApproveDismissStop の選択肢が表示され、 --auto-approve フラグでそれらをスキップすることができます。

Terraform Module を利用する

CDK for Terraform では、 Terraform RegistryGitHub にある Terraform Module を使うこともできます。

まず cdktf.jsonterraformModules に利用するモジュール情報を追記します。
次の例では、 aws の vpc モジュールとローカルに存在する local-module モジュールを追記しています。

cdktf.json
{
  // ...省略
  "terraformModules": [
    // https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws
    {
      "name": "vpc",
      "source": "terraform-aws-modules/vpc/aws",
      "version": "~> 3.0"
    },
    // ローカルのモジュールを指定することも可能
    {
      "name": "local-module",
      "source": "./path/to/local-module"
    }
  ],
  // ...省略
}

この状態で cdktf get コマンドを実行すると .gen/modules/ ディレクトリ以下にコードが生成されます。

$ cdktf get
Generated typescript constructs in the output directory: .gen

$ tree -F .gen/modules
.gen/modules/
├── local-module.ts
└── vpc.ts

0 directories, 2 files

生成されたコードは import して利用することができます。

main.ts
import { Vpc } from "./.gen/modules/vpc";

// ...省略

new Vpc(stack, "example_vpc", {
  name: "my-vpc",
  cidr: "10.0.0.0/16",
  azs: ["us-west-2a", "us-west-2b", "us-west-2c"],
  privateSubnets: ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"],
  publicSubnets: ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"],
  enableNatGateway: true,
});

// ...省略

cdktf get によるコード生成を利用せずに TerraformHclModule を使用してモジュールを参照することもできます。
ただし、この場合は引数の型ヒントが利用できないことに注意してください。

main.ts
import { TerraformHclModule } from "cdktf";
import { AwsProvider } from "@cdktf/provider-aws";

// ...省略

const provider = new AwsProvider(stack, "provider", {
  region: "us-east-1",
});

const module = new TerraformHclModule(stack, "example_vpc", {
  source: "terraform-aws-modules/vpc/aws",
  variables: {
    name: "my-vpc",
    cidr: "10.0.0.0/16",
    azs: ["us-west-2a", "us-west-2b", "us-west-2c"],
    privateSubnets: ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"],
    publicSubnets: ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"],
    enableNatGateway: true,
  },
  providers: [provider],
});

// ...省略

ユニットテスト

CDK for Terraform ではユニットテスト用のライブラリが提供されています。
こんな感じでテストが書けます。

__tests__/main-test.ts
// 引用: https://www.terraform.io/cdktf/test/unit-tests#write-assertions
import { Testing } from "cdktf";
import { Image, Container } from "../.gen/providers/docker";
import MyApplicationsAbstraction from "../app"; // Could be a class extending from Construct

describe("Unit testing using assertions", () => {
  it("should contain a container", () => {
    expect(
      Testing.synthScope((scope) => {
        new MyApplicationsAbstraction(scope, "my-app", {});
      })
    ).toHaveResource(Container);
  });

  it("should use an ubuntu image", () => {
    expect(
      Testing.synthScope((scope) => {
        new MyApplicationsAbstraction(scope, "my-app", {});
      })
    ).toHaveResourceWithProperties(Image, { name: "ubuntu:latest" });
  });
});

詳しくは下記ドキュメントをご参照ください。

https://www.terraform.io/cdktf/test/unit-tests

cdktf synth - Terraform CLI から利用できる設定ファイルを生成する

cdktf synth は Terraform CLI から利用できる JSON 形式の設定ファイルを生成します。

実行すると ./cdktf.out/stacks/{スタック名}/cdk.tf.json という名前で JSON ファイルが生成されます。

$ cdktf synth

Generated Terraform code for the stacks: cdktf-aws-example

$ ls cdktf.out/stacks/{スタック名}/
cdk.tf.json
cdk.tf.json の例
{
  "//": {
    "metadata": {
      "backend": "local",
      "stackName": "example-stack",
      "version": "0.12.2"
    },
    "outputs": {
    }
  },
  "provider": {
    "aws": [
      {
        "region": "us-east-1"
      }
    ]
  },
  "resource": {
    "aws_s3_bucket": {
      "example_bucket": {
        "//": {
          "metadata": {
            "path": "example-stack/example_bucket",
            "uniqueId": "example_bucket"
          }
        },
        "bucket_prefix": "example-"
      }
    }
  },
  "terraform": {
    "backend": {
      "local": {
        "path": "/path/to/terraform.example-stack.tfstate"
      }
    },
    "required_providers": {
      "aws": {
        "source": "aws",
        "version": "4.27.0"
      }
    }
  }
}

cdk.tf.json があるディレクトリ内で Terraform CLI を実行することができます。

$ cd ./cdktf.out/stacks/{スタック名}/
$ terraform init
$ terraform apply

cdktf convert - HCL を CDK for Terraform のコードに変換する

cdktf convert で HCL で書かれた tf コードを CDK for Terraform のコードに変換することができます。
( 2022年09月01日現在 Go では使えません )

例えば次のような s3.tf ファイルが存在する場合、

s3.tf
resource "aws_s3_bucket" "example_bucket" {
  bucket_prefix = "example"
}

次のように実行することで標準出力に変換後のコードが出力されます。

# `--language` で言語を指定することができる
$ cat s3.tf | cdktf convert --language typescript
出力例
/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";

/*The following providers are missing schema information and might need manual adjustments to synthesize correctly: aws.
For a more precise conversion please use the --provider flag in convert.*/
new aws.s3.S3Bucket(this, "example_bucket", {
  bucket_prefix: "example",
});

公式サンプル

下記ページで言語ごとに様々なサンプルが用意されています。

https://www.terraform.io/cdktf/examples

おまけ

こちらの記事では Go で書いた例を紹介しているので興味があればご参照ください。

https://zenn.dev/kou_pg_0131/articles/gogogo-introduction

まとめ

Terraform に慣れていればそれほど苦労せずにキャッチアップできると思います。
出力されるログや実行される処理は結局 Terraform なので、むしろ慣れていないと難しいかも?

類似のツールとしては Pulumi があります。こちらも機会があれば触ってみたいです。

https://www.pulumi.com/

Discussion

ログインするとコメントできます