概要

terraform testとは?

v1.6から、terraform testコマンドが利用可能になりました。
HashiCorpブログ
terraform testコマンドを使用すれば、これから構築するリソースについて、指定した条件に沿っているかを簡単に確認することができます。

ユースケース

構築するリソースの詳細を知るためにはterraform planコマンドを使用するのが一般的かと思います。
ただ、たくさんのリソースを一気に作成するときなどはplanの結果もかなりの行数になり、想定外の構築内容になってしまっているのを見過ごしてしまうこともあるかと思います。
terraform testコマンドを利用すれば、重要な設定値などが正しく設定されているかを特に重点的にチェックすることができます。
例えば以下のようなユースケースが考えられます。

  • 作成するインスタンスの元となるAMIのIDが合っているかの確認
  • 作成後インスタンスのIPアドレスが合っているかの確認
  • 指定のタグが正しくセットされているかの確認

(v1.6)terraform testを実際にやってみる

v1.6にてterraform testコマンドが追加され、v1.7でさらにアップデートがされましたが、まずはv1.6にてAWS構築の簡単な検証をやってみたいと思います。
その後、v1.7のモックでのテスト機能を検証します。

必要なもの

  • 構築予定のリソースが定義されたterraformコード
    • 今回はEC2インスタンス1台を構築するコードを作成しました
  • テストしたい内容を記載したテスト用ファイル
    • このファイルの名前は.tftest.hclで終わる必要があります

検証の流れ

  1. EC2構築のTerraformコードを書く
    • このとき、インスタンスのNameタグに値を設定する
  2. テスト用ファイルを書く
    • テスト用ファイルでは、インスタンスのNameタグが指定の値になっているかをチェックする処理を記載する
    • 今回はエラーの結果を見たいので、1のコードで設定したNameタグの値とは別の値かどうかを判定させる
  3. terraform testコマンドを実行し、エラーになることを確認

1.EC2構築のTerraformコードを書く

まずはEC2インスタンスを構築するためのTerraformコードを書いていきます。
色々設定していますが、今回注目すべきはtagsブロックのNameタグです。
Nameタグに末尾test01を設定していますが、ここが後のテストで引っかかるようにします。

  • ec2.tf
#####################################
# EC2 Instance
#####################################
resource "aws_instance" "test01" {
  ami                                  = var.test01.ami_test
  instance_type                        = var.common.instance_type
  subnet_id                            = aws_subnet.public_subnet_a.id
  tenancy                              = "default"
  key_name                             = aws_key_pair.test01.key_name
  instance_initiated_shutdown_behavior = "stop"
  monitoring                           = "true"
  source_dest_check                    = "true"
  disable_api_termination              = "true"
  vpc_security_group_ids               = [aws_security_group.test01.id]

  tags = {
    Name  = "${var.common.prefix}-${var.common.env}-test01"
    owner = var.common.owner
  }
}

2.テスト用ファイルを書く

続いて、テスト内容を記載したテスト用ファイルを書いていきます。
テスト用ファイルの記載方法についてはこちらを参照。

細かい記載方法は色々とありますが、代表的なものは下記の通りです。

  • 少なくとも1つのrunブロックを記載する
    • テスト用ファイルにおいて一番重要なブロック
    • Terraformはrunブロックを上から順番に実行し、runブロック内に記載されたコマンドの実行をシミュレートする
  • 必要であれば、1つのvariablesブロックを記載する
  • 必要であれば、1つまたは複数のproviderブロックを記載する

runブロックの記載方法は下記の通りです。

  • commandフィールドを記載
    • planapplyか、どちらのコマンドを使ってテストするかを指定
      • terraform plan, terraform applyと同様、planだったら実際の構築は行わずにテストし、反対にapplyだったら実際に構築処理を行ってテストした後、構築されたリソースは削除される
      • デフォルトはapply
  • assertブロックを記載
    • assertブロックはconditionerror_messageで構成される
      • conditionではどうなっていたらOKと判定するかの条件を記載
      • error_messageでは、OKでなかった場合に出力する文字列を記載

下記が今回作成したテスト用ファイルです。

  • runブロック:”valid_ec2_name_tag”を定義
  • 今回は実際の構築は行わないので、commandplanを設定
  • conditionには、「インスタンスのNameタグが”sekiguchi-test-ap01″で終わるかどうか?」を記載
    • 1で作成したコードではNameタグに別の値が設定されているので、ここでエラーになる想定
  • error_messageでは、”Nameタグの値が予期した名前と一致しませんでした”を記載

ec2-nametag-check.tftest.hcl

run "valid_ec2_name_tag" {
    command = plan
    assert {
        condition     = aws_instance.test01.tags.Name == "sekiguchi-test-ap01"
        error_message = "Nameタグの値が予期した名前と一致しませんでした"
    }
}

3. terraform testコマンドを実行し、エラーになることを確認

1,2のファイルを同じフォルダに配置し、terraform initは実行済みです。
それではterraform testコマンドを実行し、エラーになることを確認していきます。
事前にAWSアカウントの認証を実行済みです。
※docker-composeを使用したterraformコマンド実行についてはこちらを参照

% docker-compose run --rm terraform test
ec2-nametag-check.tftest.hcl... in progress
  run "valid_ec2_name_tag"... fail
╷
│ Error: Test assertion failed
│ 
│   on ec2-nametag-check.tftest.hcl line 4, in run "valid_ec2_name_tag":
│    4:         condition     = aws_instance.test01.tags.Name == "sekiguchi-test-ap01"
│     ├────────────────
│     │ aws_instance.test01.tags.Name is "sekiguchi-test-test01"
│ 
│ Nameタグの値が予期した名前と一致しませんでした
╵
ec2-nametag-check.tftest.hcl... tearing down
ec2-nametag-check.tftest.hcl... fail

Failure! 0 passed, 1 failed.

テストファイルのconditionにて設定した条件に合っていないことを検知しFailure!となったこと、そしてerror_messageで設定したメッセージが出力されることを確認できました。

(v1.7)モックでのterraform testをやってみる

v1.7で追加されたモックでのテスト機能について

モックについての詳細はこちらを参照

Terraform v1.6で追加されたテストフレームワークでは、planまたはapply操作を使って実際にプロバイダー(今回はAWS)を呼び出すことで実行されていました。
v1.7からプロバイダー呼び出しのモック、つまり実際の呼び出しとは別に模擬的に呼びだすことができるようになり、実際のインフラを作成したりプロバイダー認証情報を要求したりすることなくテストができるようになりました。
このモックテストは、モック・プロバイダーとオーバーライドという2つの主要な機能によって可能となっています。

モック・プロバイダー

  • フェイクのプロバイダーを意味しており、実際の構築時のプロバイダーとは別で定義が可能
  • モックプロバイダーと実際の構築のためのプロバイダーを一緒に使うことで柔軟にテストが可能
  • mock_provider ブロックにてモックプロバイダーを定義し、このブロック内でリソースやデータソースの値を指定できる

オーバーライド

  • プロバイダーをモックした上で、特定のリソースをオーバーライドすることができる機能
  • ユースケース
    • プロビジョニングに時間がかかるリソースのテスト実行時間短縮
    • 出力のシミュレーションのみ実行したい場合
    • いろんなシナリオ用に、データソースの参照を多様化したい場合

必要なもの

v1.6で作成したものをそのまま利用します。

  • 構築予定のリソースが定義されたterraformコード
    • EC2インスタンス1台を構築するコード
  • テストしたい内容を記載したテスト用ファイル

検証の流れ

v1.6のテストと同様の内容を、モック・プロバイダーを使用して実行していきます。
v1.6のテスト実行時はAWSアカウント認証を実行した上でterraform testコマンドを実行しましたが、モック・プロバイダーは模擬的にプロバイダー呼び出しを行うため、AWS認証は不要です。
そのため、AWS認証を実行していない状態でコマンドを打ってみて、テストが実行できるかを試してみたいと思います。
下記の流れで検証していきます。

  1. EC2構築のTerraformコードを書く
    • 作成済み
  2. テスト用ファイルを書く
    • テスト用ファイルでは、インスタンスのNameタグが指定の値になっているかをチェックする処理を記載する
    • 今回はエラーの結果を見たいので、1のコードで設定したNameタグの値とは別の値かどうかを判定させる
  3. terraform testコマンドを実行し、エラーになることを確認

コード作成

テスト用ファイルを今回の検証用に修正していきます。
上記の作成時からの変更点は下記の通りです。

  • 冒頭にmock_provider "aws" {}の1行を追加
    • この1行を記載することにより、このテストファイル内の処理はモックにて実行することが可能
  • runブロックからcommandフィールドを削除
    • HashiCorpのサイトに記載のある通り、mock_providerでのテスト時はリソースは実際に作成される
      • これらのリソースはterraform testが作成するTerraformステートファイルに格納され、テストを実行する間メモリに保持される
    • そのためcommandフィールドでの指定が不要

ec2-nametag-check.tftest.hcl

mock_provider "aws" {}

run "valid_ec2_name_tag" {
    assert {
        condition     = aws_instance.test01.tags.Name == "sekiguchi-test-ap01"
        error_message = "Nameタグの値が予期した名前と一致しませんでした"
    }
}

terraform testコマンドを実行し、エラーになることを確認

それではterraform testコマンドを実行し、エラーになることを確認していきます。
AWSの認証を実行していない状態でterraform testコマンドを実行します。

terraform % docker-compose run --rm terraform test
ec2-nametag-check.tftest.hcl... in progress
  run "valid_ec2_name_tag"... fail
╷
│ Error: Test assertion failed
│ 
│   on ec2-nametag-check.tftest.hcl line 5, in run "valid_ec2_name_tag":
│    5:         condition     = aws_instance.test01.tags.Name == "sekiguchi-test-ap01"
│     ├────────────────
│     │ aws_instance.test01.tags.Name is "sekiguchi-test-test01"
│ 
│ Nameタグの値が予期した名前と一致しませんでした
╵
ec2-nametag-check.tftest.hcl... tearing down
ec2-nametag-check.tftest.hcl... fail

Failure! 0 passed, 1 failed.

先ほどと同様に、想定通りのエラーとなりました。
注目すべき観点としては、エラーの結果が返ってくるのがかなり早いということです。
モックを使用しない場合のテストでは裏でplanを実行しているためか、結果が返ってくるまでに30〜60秒ほどかかりましたが、モックを使用したテストの場合10秒とかからず結果が出力されます。
作成に時間がかかるインスタンスについてテストしたい場合に役に立ちそうだと思いました。

最後に

今回のテストを実行してみて、v1.7で追加されたモックでのテストはお手軽かつ結果がすぐ確認できるのが良いと思いました。
まだリリースされたばかりで有効な使い方が思いついていませんが、インフラ開発においてうまく使えると結構役に立つと思います。
例えば同一の環境を複数作成する時のチェック項目をテストファイルにまとめておき、そのテストファイルをプロジェクト内で共有することで各自で簡単にテストを実行できる…とかでしょうか。

Terraformでの開発時はデプロイ後の確認が結構大変なので、うまく活用していきたいと思いました。