以下の内容はhttps://kazuhira-r.hatenablog.com/entry/2023/10/08/221443より取得しました。


Terraform 1.6で追加されたTerraform testing framework(terraform test)を試す

これは、なにをしたくて書いたもの?

Terraform 1.6で、Terraformのtesting framework(terraform testコマンド)が追加されたようなので、試してみようかなと。

Terraform 1.6 adds a test framework for enhanced code validation

今までTerraformのテストといえばlintとTerratestだったと思いますが、選択肢が広がりそうですね。

Terraform testing framework

Terraform 1.6に関するリリースブログはこちら。

Terraform 1.6 adds a test framework for enhanced code validation

この中に、Terraform test frameworkという見出しがあります。
これは、過去に提供してきた実験的機能を置き換え、テストフレームワークを導入するもののようです。

実験的機能というのは、0.15で追加されたterraform testのことのようです。

Module Testing Experiment - Configuration Language | Terraform | HashiCorp Developer

また、Terraformには以前から以下のような機能がありました。

とはいえ、これらではテストのニーズを満たすことができません。そこで登場するのがTerraform test frameworkです、と。

Terraform testing frameworkは、ざっくり以下の感じのもののようです。

  • terraform testで実行する
  • テストファイルは、ファイル名の末尾が.tftest.hclとなる
  • runブロックで定義されたテストに基づいて、構成をプロビジョニングする
  • カスタムアサーションを評価
  • テスト終了時には、プロビジョニングしたリソースを破棄

つまり、実際にリソースを作成して、検証して破棄するという流れになります。

Terraform testing frameworkに関するドキュメントは、このあたりですね。

まず、CLIについて追ってみましょう。概要から。

Testing Terraform - Terraform CLI | Terraform | HashiCorp Developer

testing frameworkは、単体テストおよびインテグレーションテストをターゲットにしています。

terraform testコマンドで実行し、構成ディレクトリ内でテストファイルを見つけてテストを行います(ざっくり〜と書いた内容と同じ)。

テストファイルは、独自の構文で書くことになります。

Tests - Configuration Language | Terraform | HashiCorp Developer

コマンド自体の説明を見てみます。

Command: test | Terraform | HashiCorp Developer

terraform testは、以下の動作を行うようです。

  • 現在のディレクトリと指定されたテストディレクトリ(デフォルトでtests)でテストファイルを検索し、テストを実行する
  • テストファイルの仕様に従って、terraform planおよびterraform applyを行う
  • テストファイルの仕様に従って、プランファイルとステートファイルの検証を行う
  • ステートはメモリー上で保持し、空から開始して既存のステートに影響を与えない
  • テスト用のインフラリソースを作成し、テストの終了時に破棄する。破棄できなかった場合は、対象のリソースのリストをレポートする

使いそうなオプションとしては、テストファイルを限定する-filter、テストディレクトリを指定する-test-directory(デフォルトではtests)、
テストファイル内のrunに基づいて各ブロックのプランおよびステートを出力する-verboseあたりでしょうか。

あとは、チュートリアルを見てみましょう。

Write Terraform Tests | Terraform | HashiCorp Developer

チュートリアルを見ていると、テスト内でテストのヘルパーモジュール(通常の.tfファイル)を作成することもあるようですね。

ドキュメントを眺めるのはこれくらいにして、実際に使っていってみましょう。

環境

今回の環境は、こちら。

$ terraform version
Terraform v1.6.0
on linux_amd64

テスト対象のリソース作成には、LocalStackを使うことにします。

$ python3 -V
Python 3.10.12


$ localstack --version
2.3.2

起動。

$ localstack start

AWS CLI(LocalStack)も使いましょう。

$ awslocal --version
aws-cli/2.13.25 Python/3.11.5 Linux/5.15.0-86-generic exe/x86_64.ubuntu.22 prompt/off

テスト対象のモジュールを作成する

まずはテスト対象のルートモジュールを作成します。今回は、Amazon S3バケットを作ることにしましょう。

versions.tf

terraform {
  required_version = "1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.20.0"
    }
  }
}

main.tf

provider "aws" {
  access_key                  = "mock_access_key"
  secret_key                  = "mock_secret_key"
  region                      = "us-east-1"
  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    apigateway     = "http://localhost:4566"
    cloudformation = "http://localhost:4566"
    cloudwatch     = "http://localhost:4566"
    dynamodb       = "http://localhost:4566"
    es             = "http://localhost:4566"
    firehose       = "http://localhost:4566"
    iam            = "http://localhost:4566"
    kinesis        = "http://localhost:4566"
    lambda         = "http://localhost:4566"
    route53        = "http://localhost:4566"
    redshift       = "http://localhost:4566"
    s3             = "http://localhost:4566"
    secretsmanager = "http://localhost:4566"
    ses            = "http://localhost:4566"
    sns            = "http://localhost:4566"
    sqs            = "http://localhost:4566"
    ssm            = "http://localhost:4566"
    stepfunctions  = "http://localhost:4566"
    sts            = "http://localhost:4566"
  }
}

resource "aws_s3_bucket" "standalone_bucket" {
  bucket = var.standalone_bucket_name
}

resource "aws_s3_bucket" "with_content_bucket" {
  bucket = var.with_content_bucket_name
}

resource "aws_s3_object" "content" {
  bucket  = aws_s3_bucket.with_content_bucket.bucket
  key     = var.content_key
  content = var.content_value
}

variables.tf

variable "standalone_bucket_name" {
  type = string
}

variable "with_content_bucket_name" {
  type = string
}

variable "content_key" {
  type    = string
  default = "test.txt"
}

variable "content_value" {
  type    = string
  default = "Hello World"
}

outputs.tf

output "standalone_bucket_name" {
  value = aws_s3_bucket.standalone_bucket.bucket
}

output "with_content_bucket_name" {
  value = aws_s3_bucket.with_content_bucket.bucket
}

output "content_key" {
  value = aws_s3_object.content.key
}

とりあえず、標準モジュール構成に習ってみました。

.
├── main.tf
├── outputs.tf
├── variables.tf
└── versions.tf

0 directories, 4 files

Amazon S3バケット名は、必須の変数としています。

確認しておきましょう。

$ terraform init
$ terraform plan -var 'standalone_bucket_name=my-bucket' -var 'with_content_bucket_name=my-with-content-bucket'
$ terraform apply -var 'standalone_bucket_name=my-bucket' -var 'with_content_bucket_name=my-with-content-bucket'

OKですね。

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

content_key = "test.txt"
standalone_bucket_name = "my-bucket"
with_content_bucket_name = "my-with-content-bucket"

AWS CLIでも確認しておきます。

$ awslocal s3 ls
2023-10-08 21:07:29 my-with-content-bucket
2023-10-08 21:07:29 my-bucket


$ awslocal s3 ls my-with-content-bucket/
2023-10-08 21:07:29         11 test.txt


$ awslocal s3 cp s3://my-with-content-bucket/test.txt -
Hello World

大丈夫そうなので、リソースを破棄しておきます。

$ terraform destroy -var 'standalone_bucket_name=my-bucket' -var 'with_content_bucket_name=my-with-content-bucket'

テストを書いてみる

それでは、テストを書いてみます。テストはデフォルトではtestsディレクトリに置くらしいので、今回はこれに習うことにします。

$ mkdir tests

こんなテストを作成。テストはHCL(HashiCorp Configuration Language)で記述します。

tests/create_s3_buckets.tftest.hcl

run "create_buckets" {
  assert {
    condition     = aws_s3_bucket.standalone_bucket.bucket == "my-bucket"
    error_message = "standalone S3 bucket name did not match expected"
  }

  assert {
    condition     = aws_s3_bucket.with_content_bucket.bucket == "my-with-content-bucket"
    error_message = "with content S3 bucket name did not match expected"
  }

  assert {
    condition     = aws_s3_object.content.key == "test.txt"
    error_message = "content key did not match expected"
  }
}

terraform testを実行してみます。

$ terraform test

すると、変数の指定がないと怒られます。

tests/create_s3_buckets.tftest.hcl... in progress
  run "create_buckets"... fail
╷
│ Error: No value for required variable
│
│   on variables.tf line 1:
│    1: variable "standalone_bucket_name" {
│
│ The module under test for run block "create_buckets" has a required variable "standalone_bucket_name" with no set value. Use a -var or -var-file command line argument or add this variable
│ into a "variables" block within the test file or run block.
╵
╷
│ Error: No value for required variable
│
│   on variables.tf line 5:
│    5: variable "with_content_bucket_name" {
│
│ The module under test for run block "create_buckets" has a required variable "with_content_bucket_name" with no set value. Use a -var or -var-file command line argument or add this
│ variable into a "variables" block within the test file or run block.
╵
tests/create_s3_buckets.tftest.hcl... tearing down
tests/create_s3_buckets.tftest.hcl... fail

Failure! 0 passed, 1 failed.

では、variablesを追加して指定。

run "create_buckets" {
  variables {
    standalone_bucket_name   = "my-bucket"
    with_content_bucket_name = "my-with-content-bucket"
  }

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

  assert {
    condition     = aws_s3_bucket.with_content_bucket.bucket == "my-with-content-bucket"
    error_message = "with content S3 bucket name did not match expected"
  }

  assert {
    condition     = aws_s3_object.content.key == "test.txt"
    error_message = "content key did not match expected"
  }
}

実行。

$ terraform test

今度はOKでした。

tests/create_s3_buckets.tftest.hcl... pass

Success! 1 passed, 0 failed.

書いたテストを見返してみましょう。

テストファイルに記述できる内容は、こちらです。

Tests - Configuration Language | Terraform | HashiCorp Developer

runブロックには、Terraformのコマンドをどのように実行するのかと、変数やアサーションを定義します。

アサーションの定義はCustom Conditionと同じです。

Custom Conditions - Configuration Language | Terraform | HashiCorp Developer

conditionには確認する内容をboolで評価できるもので指定し、失敗した場合はerror_messageに指定した内容が表示されます。

失敗するアサーションにしてみましょう。

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


  assert {
    condition     = aws_s3_object.content.key == "test"
    error_message = "content key did not match expected"
  }

確認。

$ terraform test

すると、このような結果になりました。

│ Error: Test assertion failed
│
│   on tests/create_s3_buckets.tftest.hcl line 8, in run "create_buckets":
│    8:     condition     = aws_s3_bucket.standalone_bucket.bucket == "bucket"
│     ├────────────────
│     │ aws_s3_bucket.standalone_bucket.bucket is "my-bucket"
│
│ standalone S3 bucket name did not match expected
╵
╷
│ Error: Test assertion failed
│
│   on tests/create_s3_buckets.tftest.hcl line 18, in run "create_buckets":
│   18:     condition     = aws_s3_object.content.key == "test"
│     ├────────────────
│     │ aws_s3_object.content.key is "test.txt"
│
│ content key did not match expected

テストも失敗ですね。

Failure! 0 passed, 1 failed.

また、テストのステップを見返してみます。

tests/create_s3_buckets.tftest.hcl... in progress
  run "create_buckets"... pass
tests/create_s3_buckets.tftest.hcl... tearing down
tests/create_s3_buckets.tftest.hcl... pass

「in progress」と出力されているところで、実際にリソースが作成されます。この時に実際のリソースを見てみると、リソースが作成されて
いることが確認できると思います。そして「tearing down」で削除されます。

なので、実際にリソースが作成されることは把握しておくことが大切ですね。

variablesは、runと同じ並びに書くこともできます。

tests/create_s3_buckets2.tftest.hcl

variables {
  standalone_bucket_name   = "my-bucket"
  with_content_bucket_name = "my-with-content-bucket"
}

run "create_buckets" {

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

  assert {
    condition     = aws_s3_bucket.with_content_bucket.bucket == "my-with-content-bucket"
    error_message = "with content S3 bucket name did not match expected"
  }

  assert {
    condition     = aws_s3_object.content.key == "test.txt"
    error_message = "content key did not match expected"
  }
}

こうすると、runブロックが複数ある場合はトップレベルで指定したvariablesの内容が渡されることになります。また、runブロック内で
variablesを定義して、トップレベルで定義した内容を上書きすることもできます。

outputの内容を参照することもできます。

tests/create_s3_buckets_condition_outputs.tftest.hcl

run "create_buckets" {
  variables {
    standalone_bucket_name   = "my-bucket"
    with_content_bucket_name = "my-with-content-bucket"
  }

  assert {
    condition     = output.standalone_bucket_name == "my-bucket"
    error_message = "standalone S3 bucket name did not match expected"
  }

  assert {
    condition     = output.with_content_bucket_name == "my-with-content-bucket"
    error_message = "with content S3 bucket name did not match expected"
  }

  assert {
    condition     = output.content_key == "test.txt"
    error_message = "content key did not match expected"
  }
}

今回は、例示がリソースの属性を見ていることが多いので、リソースの属性を見ることにします。

planとapplyでテストしてみる

runブロックではTerraformのコマンドが実行されると書きましたが、デフォルトではterraform applyが行われます。

Tests / Run blocks

これをterraform planに変更することもできます。またrunブロックにはterraform plan用のオプションもあるようですね。

こんな感じにしてみました。

tests/create_s3_buckets_plan_and_apply.tftest.hcl

variables {
  standalone_bucket_name   = "my-bucket"
  with_content_bucket_name = "my-with-content-bucket"
}

run "create_buckets_plan" {
  command = plan

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

  # Condition expression could not be evaluated at this time. This means you have executed a `run` block with `command = plan` and one of the values your condition depended on is not known
  # until after the plan has been applied. Either remove this value from your condition, or execute an `apply` command from this `run` block.
  # assert {
  #   condition     = aws_s3_bucket.standalone_bucket.arn == "arn:aws:s3:::my-bucket"
  #   error_message = "standalone S3 bucket arn did not match expected"
  # }

  assert {
    condition     = aws_s3_bucket.with_content_bucket.bucket == "my-with-content-bucket"
    error_message = "with content S3 bucket name did not match expected"
  }

  # Condition expression could not be evaluated at this time. This means you have executed a `run` block with `command = plan` and one of the values your condition depended on is not known
  # until after the plan has been applied. Either remove this value from your condition, or execute an `apply` command from this `run` block.
  # assert {
  #   condition     = aws_s3_bucket.with_content_bucket.arn == "arn:aws:s3:::my-with-content-bucket"
  #   error_message = "with content S3 bucket arn did not match expected"
  # }

  assert {
    condition     = aws_s3_object.content.key == "test.txt"
    error_message = "content key did not match expected"
  }
}

run "create_buckets_apply" {
  command = apply

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

  assert {
    condition     = aws_s3_bucket.standalone_bucket.arn == "arn:aws:s3:::my-bucket"
    error_message = "standalone S3 bucket arn did not match expected"
  }

  assert {
    condition     = aws_s3_bucket.with_content_bucket.bucket == "my-with-content-bucket"
    error_message = "with content S3 bucket name did not match expected"
  }

  assert {
    condition     = aws_s3_bucket.with_content_bucket.arn == "arn:aws:s3:::my-with-content-bucket"
    error_message = "with content S3 bucket arn did not match expected"
  }

  assert {
    condition     = aws_s3_object.content.key == "test.txt"
    error_message = "content key did not match expected"
  }
}

ポイントは、以下の指定ですね。

  command = plan

一見、planapplyで差がないように見えますが、以下のようにplanでは確認できないものもあります。

  # Condition expression could not be evaluated at this time. This means you have executed a `run` block with `command = plan` and one of the values your condition depended on is not known
  # until after the plan has been applied. Either remove this value from your condition, or execute an `apply` command from this `run` block.
  # assert {
  #   condition     = aws_s3_bucket.standalone_bucket.arn == "arn:aws:s3:::my-bucket"
  #   error_message = "standalone S3 bucket arn did not match expected"
  # }


  # Condition expression could not be evaluated at this time. This means you have executed a `run` block with `command = plan` and one of the values your condition depended on is not known
  # until after the plan has been applied. Either remove this value from your condition, or execute an `apply` command from this `run` block.
  # assert {
  #   condition     = aws_s3_bucket.with_content_bucket.arn == "arn:aws:s3:::my-with-content-bucket"
  #   error_message = "with content S3 bucket arn did not match expected"
  # }

これはエラーメッセージとterraform planの結果を実際に見ると想像がつきますが、「known after apply」つまりterraform apply後でないと
わからない値については、command = planを指定した時にアサーションすることができません。

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

コメントにも書いていますが、ムリに使っても以下のようなエラーメッセージが返ってきます。

│ Error: Unknown condition value
│
│   on tests/create_s3_buckets_plan_and_apply.tftest.hcl line 17, in run "create_buckets_plan":
│   17:     condition     = aws_s3_bucket.standalone_bucket.arn == "arn:aws:s3:::my-bucket"
│     ├────────────────
│     │ aws_s3_bucket.standalone_bucket.arn is a string
│
│ Condition expression could not be evaluated at this time. This means you have executed a `run` block with `command = plan` and one of the values your condition depended on is not known
│ until after the plan has been applied. Either remove this value from your condition, or execute an `apply` command from this `run` block.

モジュールを使う

テスト内でモジュールを使うこともできます。

たとえば、今回のテストでは最初にterraform applyした時と同じ値をvariablesに指定しています。

よって、terraform applyして

$ terraform plan -var 'standalone_bucket_name=my-bucket' -var 'with_content_bucket_name=my-with-content-bucket'

テストを実行すると(今回はひとつのテストファイルに絞っています)

$ terraform test -filter=tests/create_s3_buckets.tftest.hcl

以下のようにリソースが競合するので失敗します。

│ Error: creating Amazon S3 (Simple Storage) Bucket (my-bucket): bucket already exists
│
│   with aws_s3_bucket.standalone_bucket,
│   on main.tf line 33, in resource "aws_s3_bucket" "standalone_bucket":
│   33: resource "aws_s3_bucket" "standalone_bucket" {
│
╵
╷
│ Error: creating Amazon S3 (Simple Storage) Bucket (my-with-content-bucket): bucket already exists
│
│   with aws_s3_bucket.with_content_bucket,
│   on main.tf line 37, in resource "aws_s3_bucket" "with_content_bucket":
│   37: resource "aws_s3_bucket" "with_content_bucket" {
│
╵

この後、クリーンアップが動きますがすでに作成済みのリソースは削除されません。テスト内で、作成に成功したリソースのみを
削除するようです。

では、テストで作成するリソースが既存のものと競合しないようにvariablesを設定すればいいわけですが、今回はこれをランダムに
してみましょう。

5桁のランダム整数を生成するモジュールを作成。

tests/setup/random/main.tf

terraform {
  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "3.5.1"
    }
  }
}

resource "random_integer" "bucket_prefix" {
  min = 10000
  max = 99999
}

output "bucket_prefix" {
  value = random_integer.bucket_prefix.id
}

このモジュールを実行するrunブロックを含む、テストファイルを作成。

tests/create_s3_buckets_with_module.tftest.hcl

run "generate_random" {
  module {
    source = "./tests/setup/random"
  }
}

run "create_buckets" {
  variables {
    standalone_bucket_name   = "${run.generate_random.bucket_prefix}-my-bucket"
    with_content_bucket_name = "${run.generate_random.bucket_prefix}-my-with-content-bucket"
  }

  assert {
    condition     = aws_s3_bucket.standalone_bucket.bucket == "${run.generate_random.bucket_prefix}-my-bucket"
    error_message = "standalone S3 bucket name did not match expected"
  }

  assert {
    condition     = aws_s3_bucket.standalone_bucket.arn == "arn:aws:s3:::${run.generate_random.bucket_prefix}-my-bucket"
    error_message = "standalone S3 bucket arn did not match expected"
  }

  assert {
    condition     = aws_s3_bucket.with_content_bucket.bucket == "${run.generate_random.bucket_prefix}-my-with-content-bucket"
    error_message = "with content S3 bucket name did not match expected"
  }

  assert {
    condition     = aws_s3_bucket.with_content_bucket.arn == "arn:aws:s3:::${run.generate_random.bucket_prefix}-my-with-content-bucket"
    error_message = "with content S3 bucket arn did not match expected"
  }

  assert {
    condition     = aws_s3_object.content.key == "test.txt"
    error_message = "content key did not match expected"
  }
}

ポイントはこちらですね。

run "generate_random" {
  module {
    source = "./tests/setup/random"
  }
}

modulesourceには、terraformコマンドを実行する位置からのパスを指定する必要があります。このファイルからの相対パスでは
ありません。

また、モジュールのoutputは以下のようにrun.[ブロック名].[output名]で参照できます。

run "create_buckets" {
  variables {
    standalone_bucket_name   = "${run.generate_random.bucket_prefix}-my-bucket"
    with_content_bucket_name = "${run.generate_random.bucket_prefix}-my-with-content-bucket"
  }

  assert {
    condition     = aws_s3_bucket.standalone_bucket.bucket == "${run.generate_random.bucket_prefix}-my-bucket"
    error_message = "standalone S3 bucket name did not match expected"
  }

では、実行。

$ terraform test

すると、モジュールがインストールされていないと言われるので

╷
│ Error: Module not installed
│
│   on tests/create_s3_buckets_with_module.tftest.hcl line 2, in run "generate_random":
│    2:   module {
│
│ This module is not yet installed. Run "terraform init" to install all modules required by this configuration.
╵

terraform initを実行します。testsディレクトリ内や、モジュールのあるディレクトリに移動する必要はありません。

$ terraform init

今度はテストに成功します。

tests/create_s3_buckets_with_module.tftest.hcl... in progress
  run "generate_random"... pass
  run "create_buckets"... pass

tests/create_s3_buckets_with_module.tftest.hcl... tearing down

tests/create_s3_buckets_with_module.tftest.hcl... pass

アサーションを工夫する

アサーションは、boolで指定すればいいものを書けばよいのでした。

Custom Conditions / Condition Expressions

Terraformのドキュメントでは単純に値を等値比較しているものが多いのですが、関数なども使えるのかなと。

というわけで、先ほどのモジュールを使ったランダム整数の箇所を正規表現で確認するようにしてみました。

tests/create_s3_buckets_assertion.tftest.hcl

run "generate_random" {
  module {
    source = "./tests/setup/random"
  }
}

run "create_buckets" {
  variables {
    standalone_bucket_name   = "${run.generate_random.bucket_prefix}-my-bucket"
    with_content_bucket_name = "${run.generate_random.bucket_prefix}-my-with-content-bucket"
  }

  assert {
    condition     = regex("\\d{5}-my-bucket", aws_s3_bucket.standalone_bucket.bucket) == aws_s3_bucket.standalone_bucket.bucket
    error_message = "standalone S3 bucket name did not match expected"
  }

  assert {
    condition     = regex("\\d{5}-my-with-content-bucket", aws_s3_bucket.with_content_bucket.bucket) == aws_s3_bucket.with_content_bucket.bucket
    error_message = "with content S3 bucket name did not match expected"
  }

  assert {
    condition     = aws_s3_object.content.key == "test.txt"
    error_message = "content key did not match expected"
  }
}

結局、最後は等値比較しているのですが。正規表現あたりは、使いたくなるのでは?という気がしたので。

文字列内に${}を埋め込む以外にもいろいろやりたくなることはあると思うので、関数を使うことも考えておくとよいのかなと。

オマケ

使わなかったもの

今回、触れていない要素としてproviderexpect_failuresがあります。

providerを使うと、メインとなる構成ファイルで使用しているTerraform Providerをオーバーライドできるようです。aliasも考慮される
ようです。

expect_failuresは、メインとなる構成ファイルでcheckを使用している場合で、かつテストでそれが失敗することがわかっている場合は
抑止できる機能です。

Custom Conditions / Checks with Assertions

実行するテストを絞り込む

terraform testは、terraform applyを行ってリソースを作成するので、時間がかかるリソースを作成する場合はテストにも時間がかかります。

こういう時にterraform testで一括して実行すると時間がかかるので、-filterオプションで実行するテストを指定すると便利かもしれません。

$ terraform test -filter=tests/create_s3_buckets.tftest.hcl

とはいえ、そういう状況ではテストをたくさん作らない方がいい気はしますが…。

デバッグする

各モジュールがどのような動作をしているか確認したい場合は、-verboseオプションを付けて実行するとよいでしょう。
-filterは実行するテストを絞っているだけです

$ terraform test -filter=tests/create_s3_buckets_with_module.tftest.hcl -verbose

すると、こんな感じで作成されたリソースの結果などを確認できます。

$ terraform test -filter=tests/create_s3_buckets_with_module.tftest.hcl -verbose
tests/create_s3_buckets_with_module.tftest.hcl... in progress
  run "generate_random"... pass

# random_integer.bucket_prefix:
resource "random_integer" "bucket_prefix" {
    id     = "41773"
    max    = 99999
    min    = 10000
    result = 41773
}


Outputs:

bucket_prefix = "41773"

  run "create_buckets"... pass

# aws_s3_bucket.standalone_bucket:
resource "aws_s3_bucket" "standalone_bucket" {
    arn                         = "arn:aws:s3:::41773-my-bucket"
    bucket                      = "41773-my-bucket"
    bucket_domain_name          = "41773-my-bucket.s3.amazonaws.com"
    bucket_regional_domain_name = "41773-my-bucket.s3.us-east-1.amazonaws.com"
    force_destroy               = false
    hosted_zone_id              = "Z3AQBSTGFYJSTF"
    id                          = "41773-my-bucket"
    object_lock_enabled         = false
    region                      = "us-east-1"
    request_payer               = "BucketOwner"
    tags_all                    = {}

    grant {
        id          = "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
        permissions = [
            "FULL_CONTROL",
        ]
        type        = "CanonicalUser"
    }

    versioning {
        enabled    = false
        mfa_delete = false
    }
}

# aws_s3_bucket.with_content_bucket:
resource "aws_s3_bucket" "with_content_bucket" {
    arn                         = "arn:aws:s3:::41773-my-with-content-bucket"
    bucket                      = "41773-my-with-content-bucket"
    bucket_domain_name          = "41773-my-with-content-bucket.s3.amazonaws.com"
    bucket_regional_domain_name = "41773-my-with-content-bucket.s3.us-east-1.amazonaws.com"
    force_destroy               = false
    hosted_zone_id              = "Z3AQBSTGFYJSTF"
    id                          = "41773-my-with-content-bucket"
    object_lock_enabled         = false
    region                      = "us-east-1"
    request_payer               = "BucketOwner"
    tags_all                    = {}

    grant {
        id          = "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
        permissions = [
            "FULL_CONTROL",
        ]
        type        = "CanonicalUser"
    }

    versioning {
        enabled    = false
        mfa_delete = false
    }
}

# aws_s3_object.content:
resource "aws_s3_object" "content" {
    bucket             = "41773-my-with-content-bucket"
    bucket_key_enabled = false
    content            = "Hello World"
    content_type       = "application/octet-stream"
    etag               = "b10a8db164e0754105b7a99be72e3fe5"
    force_destroy      = false
    id                 = "test.txt"
    key                = "test.txt"
    storage_class      = "STANDARD"
    tags_all           = {}
}


Outputs:

content_key = "test.txt"
standalone_bucket_name = "41773-my-bucket"
with_content_bucket_name = "41773-my-with-content-bucket"

╷
│ Warning: AWS account ID not found for provider
│
│   with provider["registry.terraform.io/hashicorp/aws"],
│   on main.tf line 1, in provider "aws":
│    1: provider "aws" {
│
│ See https://www.terraform.io/docs/providers/aws/index.html#skip_requesting_account_id for implications.
│
│ (and one more similar warning elsewhere)
╵

tests/create_s3_buckets_with_module.tftest.hcl... tearing down
╷
│ Warning: AWS account ID not found for provider
│
│   with provider["registry.terraform.io/hashicorp/aws"],
│   on main.tf line 1, in provider "aws":
│    1: provider "aws" {
│
│ See https://www.terraform.io/docs/providers/aws/index.html#skip_requesting_account_id for implications.
│
│ (and one more similar warning elsewhere)
╵
tests/create_s3_buckets_with_module.tftest.hcl... pass

Success! 2 passed, 0 failed.

こちらの警告は今まで端折っていたのですが、LocalStackを使っている関係で出ているので今回は無視で

╷
│ Warning: AWS account ID not found for provider
│
│   with provider["registry.terraform.io/hashicorp/aws"],
│   on main.tf line 1, in provider "aws":
│    1: provider "aws" {
│
│ See https://www.terraform.io/docs/providers/aws/index.html#skip_requesting_account_id for implications.
│
│ (and one more similar warning elsewhere)
╵

こんなところでしょうか。

おわりに

Terraform 1.6で追加された、Terraform testing frameworkを試してみました。

実際にリソースを作るのが良い時もあればそうでない時もある気はしますが、Terratestとは異なりHCLでテストを書けるのが良いですね。
使えるところでは使っていきましょう。




以上の内容はhttps://kazuhira-r.hatenablog.com/entry/2023/10/08/221443より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14