😇

terraform の test コマンドを 30 分ほど触ってみました

2023/12/03に公開

はじめに

terraform の v1.6 から test というコマンドが GA されたということなので触ってみました。

https://github.com/hashicorp/terraform/blob/v1.6/CHANGELOG.md#160-october-4-2023

本記事で扱う環境は以下の通りです。

$ sw_vers
ProductName:            macOS
ProductVersion:         14.0
BuildVersion:           23A344

$ terraform version
Terraform v1.6.5
on darwin_amd64
+ provider registry.terraform.io/hashicorp/aws v5.29.0

ちなみに、この記事は YAMAP エンジニア Advent Calendar 2023 三日目の記事です。

https://qiita.com/advent-calendar/2023/yamap-engineers

触ってみる

main.tf

ドキュメントに掲載されているサンプルコードをそのまま利用させて頂きます。

以下のように main.tf を用意します。

# main.tf

provider "aws" {
    region = "eu-central-1"
}

variable "bucket_prefix" {
  type = string
}

resource "aws_s3_bucket" "bucket" {
  bucket = "${var.bucket_prefix}-bucket"
}

output "bucket_name" {
  value = aws_s3_bucket.bucket.bucket
}

S3 バケットを作成するだけのとてもシンプルなコードです。これを terraform plan すると、以下のような結果となります。

$ terraform plan -no-color
var.bucket_prefix
  Enter a value: test


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:

  # aws_s3_bucket.bucket will be created
  + resource "aws_s3_bucket" "bucket" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "test-bucket"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + 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)
    }

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

Changes to Outputs:
  + bucket_name = "test-bucket"

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

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

テストコード

続いてテストコードを用意します。デフォルトでは、同じディレクトリに .tftest.hcl 又は .tftest.json という拡張子で用意する必要があります。

以下のように valid_string_concat.tftest.hcl を用意します。

# valid_string_concat.tftest.hcl

variables {
  bucket_prefix = "test"
}

run "valid_string_concat" {

  command = plan

  assert {
    condition     = aws_s3_bucket.bucket.bucket == "test-bucket"
    error_message = "S3 bucket name did not match expected"
  }

}

このテストコードは、main.tf において、バケット名が test-bucket という名前で作成されることを検証するコードになっています。run ブロック内に検証内容を記載します。run ブロックの詳細についてはドキュメントを確認しましょう。

https://developer.hashicorp.com/terraform/language/tests#run-blocks

command にはお馴染みの planapply を指定することが出来ますが、apply を指定すると、実際に AWS 上にリソースを作成して検証を行うようです。

尚、テストコードは、テストコード専用ディレクトリを用意して置いておくことも可能とのことです。その際には、以下のようにディレクトリを指定するオプションを付与する必要があります。

$ terraform test -test-directory=path

上記の例だと、path というディレクトリ以下にテストコードを置いてある状態となります。

terraform test

terraform test コマンドを実行してみます。

$ terraform test
valid_string_concat.tftest.hcl... in progress
  run "valid_string_concat"... pass
valid_string_concat.tftest.hcl... tearing down
valid_string_concat.tftest.hcl... pass

Success! 1 passed, 0 failed.

run ブロックの commandplan なので、リソースを作成するロジックとしては問題ないと判断出来ます。試しに commandapply にしてみましょう。

# valid_string_concat.tftest.hcl

variables {
  bucket_prefix = "test"
}

run "valid_string_concat" {

  command = apply

  assert {
    condition     = aws_s3_bucket.bucket.bucket == "test-bucket"
    error_message = "S3 bucket name did not match expected"
  }

}

terraform test コマンドを実行してみます。

$ terraform test
valid_string_concat.tftest.hcl... in progress
  run "valid_string_concat"... fail
╷
│ Error: creating Amazon S3 (Simple Storage) Bucket (test-bucket): BucketAlreadyExists: The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.
│       status code: 409, request id: BVDRRB6V1W38XE5N, host id: 1V/R45wOABWWye67vXvhDP1A2jdtjgTGppfX4dFza3eHHE333lt6pXAk8d0wtGHEUZVIgcmg1tU=
│
│   with aws_s3_bucket.bucket,
│   on main.tf line 9, in resource "aws_s3_bucket" "bucket":9: resource "aws_s3_bucket" "bucket" {
│
╵
valid_string_concat.tftest.hcl... tearing down
valid_string_concat.tftest.hcl... fail

Failure! 0 passed, 1 failed.

テストが失敗しました。既に test-bucket は存在しているとのことです (S3 のバケット名は全てのユーザーで一意にする必要がありますね) のでテストの失敗は想定内です。では、バケット名が一意になるようにランダムな文字列をバケット名にしてみましょう。

# valid_string_concat.tftest.hcl

# ランダムな文字列をバケット名のプレフィックスにしてみました
variables {
  bucket_prefix = "rin9wee8teziem4a"
}

run "valid_string_concat" {

  command = apply

  assert {
    condition     = aws_s3_bucket.bucket.bucket == "test-bucket"
    error_message = "S3 bucket name did not match expected"
  }

}

テストコード内に variables ブロックを書くことが出来るので、任意の変数を指定することが出来ます。

この状態でテストを実行してみましょう。

$ terraform test
valid_string_concat.tftest.hcl... in progress
  run "valid_string_concat"... fail
╷
│ Error: Test assertion failed
│
│   on valid_string_concat.tftest.hcl line 10, in run "valid_string_concat":10:     condition     = aws_s3_bucket.bucket.bucket == "test-bucket"
│     ├────────────────
│     │ aws_s3_bucket.bucket.bucket is "rin9wee8teziem4a-bucket"
│
│ S3 bucket name did not match expected
╵
valid_string_concat.tftest.hcl... tearing down
valid_string_concat.tftest.hcl... fail

Failure! 0 passed, 1 failed.

バケット名は test-bucket を期待していますが、実際に生成されるバケット名とは異なるので S3 bucket name did not match expected というエラーメッセージが表示されました。

以上

terraform plan と terraform test は何が違うんだ? という思いから、ざくっと test コマンドを触ってみました。

plan はインフラ構成の変更を tfstate ファイルを用いてシュミレートし、意図した通りにインフラの変更が行われるかを検証するものである一方、test は実際にリソースを作って terraform コードの振る舞いそのものを検証するという違いがあることで腹落ちしました。(私の理解に誤り等ありましたらご指摘頂けると幸いです。

おまけ

commandapply にしてテストをした場合、本当にリソース作っているのかなーと気になったので、terraform test を実行した後に、愚直に aws cli を叩いて確認してみました。

本当にリソースを作っていましたw

参考

YAMAP テックブログ

Discussion