インフラコードのテストツール Terratest を触ってみた

Kouhei Nagase

2022.6.27

Terratest の概要

公式HP: https://terratest.gruntwork.io/

Githubリポジトリ: https://github.com/gruntwork-io/terratest

インフラコードに対してテストを書くオープンソースの Go ライブラリで、以下に対応しています。

  • Terraform
  • Docker
  • Packer
  • kubernetes

開発元は Gruntwork です。

パッケージと概要

  • aws: AWS API を使った機能を提供
    ・EC2 インスタンスの IP を取得
    ・リージョンと VPC ID を指定して、含まれているサブネット情報を取得
  • collections: Slice や string に対してのいくつかの機能を提供
    ・指定の Slice に指定の文字列が含まれているかどうかを確認
  • docker: docker コマンド、docker-compose コマンドを実行する機能を提供
  • files: ファイルやディレクトリに関する機能を提供
    ・ファイルの存在確認
    ・ディレクトリの存在確認
  • http-helper: http リクエストに関する機能を提供
    ・http リクエストを送信
    ・http サーバをローカルに構築
  • ssh: サーバに ssh する機能を提供
    ・ssh 接続後にコマンド実行
    ・scp でファイルのダウンロード
  • packer: packer コマンドを実行する機能を提供
  • k8s: Kubernetes に関する機能を提供
    ・Node や Pod、Namespace などを取得
    ・Pod が立ち上がるまで待機
  • Terraform: Terraform コマンドを実行する機能を提供

Terratest の機能

  • tfstate に出された output の値を見てアサーション
    tfstate を S3 などのオブジェクトストレージで管理している場合でも、追従してくれます。
  • リソースの構築・破棄
    構築 → テスト → 破棄 のようなテストの流れを行うこともできます。
  • http リクエストを送信したりダミーサーバのためのヘルパー
    実際に構築したリソースに対してリクエストを送るようなテストも書けます。
  • 公式が出している terratest_log_parser というツールを使うとテスト結果を JUnit ライクの xml で出力可能
    https://github.com/gruntwork-io/terratest/releases
    パイプラインに組み込む際はこのツールを挟む使い方になる認識

Terraformにおけるテストのサンプル実行してみた

AWS に EC2 インスタンスを立てて、簡単な HTTP サーバを立てる Terraform のテストです。https://terratest.gruntwork.io/

サンプル Terraform(AWS)

terraform {
  # This module is now only being tested with Terraform 0.13.x. However, to make upgrading easier, we are setting
  # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it
  # forwards compatible with 0.13.x code.
  required_version = ">= 0.12.26"
}

provider "aws" {
  region = "us-east-2"
}

# website::tag::1:: Deploy an EC2 Instance.
resource "aws_instance" "example" {
  # website::tag::2:: Run an Ubuntu 18.04 AMI on the EC2 instance.
  ami                    = "ami-******"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.instance.id]

  # website::tag::3:: When the instance boots, start a web server on port 8080 that responds with "Hello, World!".
  user_data = <<EOF
#!/bin/bash
echo "Hello, World!" > index.html
nohup busybox httpd -f -p 8080 &
EOF
}

# website::tag::4:: Allow the instance to receive requests on port 8080.
resource "aws_security_group" "instance" {
  ingress {
    from_port   = 8080
    to_port     = 8080
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# website::tag::5:: Output the instance's public IP address.
output "public_ip" {
  value = aws_instance.example.public_ip
}

テストコード

テストは動的なものです。 すなわち、実際に terraform apply でリソースを作成し、それらに対して想定通りの設定となっているかをテストします。

以下コードにコメントで注釈を入れています。

package test

import (
	"fmt"
	"testing"
	"time"

	http_helper "github.com/gruntwork-io/terratest/modules/http-helper"

	"github.com/gruntwork-io/terratest/modules/terraform"
)

func TestTerraformAwsHelloWorldExample(t *testing.T) {
	t.Parallel()

	terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
		// terraformコマンドを実行するディレクトリを指定
		TerraformDir: "../examples/terraform-aws-hello-world-example",
	})

	// テスト終了後、リソースを消去する
	defer terraform.Destroy(t, terraformOptions)

	// init&applyを実行してリソースを作成する
	terraform.InitAndApply(t, terraformOptions)

	// 指定keyのoutputを取得する
	publicIp := terraform.Output(t, terraformOptions, "public_ip")

	// urlにリクエストを送って、ステータスコードとボディをアサーションする
	url := fmt.Sprintf("http://%s:8080", publicIp)
	http_helper.HttpGetWithRetry(t, url, nil, 200, "Hello, World!", 30, 5*time.Second)
}

テスト実行(抜粋)

実行ログには terraform コマンドの実行結果も出力されます。
tee コマンドでログをファイル出力していますが、後述する JUnit 形式でのテスト結果出力の際に使用します。

$ go test -v terraform_aws_hello_world_example_test.go | tee test_output.log
... (中略) ...
=== RUN   TestTerraformAwsHelloWorldExample
=== PAUSE TestTerraformAwsHelloWorldExample
=== CONT  TestTerraformAwsHelloWorldExample
... (中略) ...
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:   + create
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66: Terraform will perform the following actions:
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:   # aws_instance.example will be created
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:   + resource "aws_instance" "example" {
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + ami                                  = "ami-******"
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + arn                                  = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + associate_public_ip_address          = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + availability_zone                    = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + cpu_core_count                       = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + cpu_threads_per_core                 = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + get_password_data                    = false
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + host_id                              = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + id                                   = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + instance_initiated_shutdown_behavior = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + instance_state                       = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + instance_type                        = "t2.micro"
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + ipv6_address_count                   = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + ipv6_addresses                       = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + key_name                             = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + outpost_arn                          = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + password_data                        = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + placement_group                      = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + primary_network_interface_id         = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + private_dns                          = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + private_ip                           = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + public_dns                           = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + public_ip                            = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + secondary_private_ips                = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + security_groups                      = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + source_dest_check                    = true
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + subnet_id                            = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + tags_all                             = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + tenancy                              = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + user_data                            = "93faf098a13b043bb03f57bf2e1ec8960655de4e"
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + vpc_security_group_ids               = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + capacity_reservation_specification {
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + capacity_reservation_preference = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + capacity_reservation_target {
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:               + capacity_reservation_id = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:             }
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + ebs_block_device {
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + delete_on_termination = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + device_name           = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + encrypted             = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + iops                  = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + kms_key_id            = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + snapshot_id           = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + tags                  = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + throughput            = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + volume_id             = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + volume_size           = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + volume_type           = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + enclave_options {
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + enabled = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + ephemeral_block_device {
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + device_name  = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + no_device    = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + virtual_name = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + metadata_options {
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + http_endpoint               = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + http_put_response_hop_limit = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + http_tokens                 = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + network_interface {
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + delete_on_termination = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + device_index          = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + network_interface_id  = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + root_block_device {
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + delete_on_termination = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + device_name           = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + encrypted             = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + iops                  = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + kms_key_id            = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + tags                  = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + throughput            = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + volume_id             = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + volume_size           = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + volume_type           = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:     }
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:   # aws_security_group.instance will be created
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:   + resource "aws_security_group" "instance" {
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + arn                    = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + description            = "Managed by Terraform"
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + egress                 = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + id                     = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + ingress                = [
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:           + {
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:               + cidr_blocks      = [
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:                   + "0.0.0.0/0",
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:                 ]
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:               + description      = ""
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:               + from_port        = 8080
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:               + ipv6_cidr_blocks = []
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:               + prefix_list_ids  = []
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:               + protocol         = "tcp"
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:               + security_groups  = []
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:               + self             = false
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:               + to_port          = 8080
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:             },
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:         ]
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + name                   = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + name_prefix            = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + owner_id               = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + revoke_rules_on_delete = false
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + tags_all               = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:       + vpc_id                 = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:     }
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66: Plan: 2 to add, 0 to change, 0 to destroy.
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66: Changes to Outputs:
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:32+09:00 logger.go:66:   + public_ip = (known after apply)
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:35+09:00 logger.go:66: aws_security_group.instance: Creating...
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:42+09:00 logger.go:66: aws_security_group.instance: Creation complete after 6s [id=sg-******]
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:42+09:00 logger.go:66: aws_instance.example: Creating...
TestTerraformAwsHelloWorldExample 2021-06-24T11:26:52+09:00 logger.go:66: aws_instance.example: Still creating... [10s elapsed]
TestTerraformAwsHelloWorldExample 2021-06-24T11:27:02+09:00 logger.go:66: aws_instance.example: Still creating... [20s elapsed]
TestTerraformAwsHelloWorldExample 2021-06-24T11:27:12+09:00 logger.go:66: aws_instance.example: Creation complete after 30s [id=i-******]
TestTerraformAwsHelloWorldExample 2021-06-24T11:27:12+09:00 logger.go:66: Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
TestTerraformAwsHelloWorldExample 2021-06-24T11:27:12+09:00 logger.go:66: Outputs:
TestTerraformAwsHelloWorldExample 2021-06-24T11:27:12+09:00 logger.go:66: public_ip = "18.117.228.107"
TestTerraformAwsHelloWorldExample 2021-06-24T11:27:12+09:00 retry.go:91: terraform [output -no-color -json public_ip]
TestTerraformAwsHelloWorldExample 2021-06-24T11:27:12+09:00 logger.go:66: Running command terraform with args [output -no-color -json public_ip]
TestTerraformAwsHelloWorldExample 2021-06-24T11:27:12+09:00 logger.go:66: "18.117.228.107"
TestTerraformAwsHelloWorldExample 2021-06-24T11:27:12+09:00 retry.go:91: HTTP GET to URL http://18.117.228.107:8080
TestTerraformAwsHelloWorldExample 2021-06-24T11:27:12+09:00 http_helper.go:32: Making an HTTP GET call to URL http://18.117.228.107:8080
TestTerraformAwsHelloWorldExample 2021-06-24T11:27:15+09:00 retry.go:103: HTTP GET to URL http://18.117.228.107:8080 returned an error: Get "http://18.117.228.107:8080": dial tcp 18.117.228.107:8080: connect: connection refused. Sleeping for 5s and will try again.
... (中略) ...
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:20+09:00 retry.go:91: terraform [destroy -auto-approve -input=false -lock=false]
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:20+09:00 logger.go:66: Running command terraform with args [destroy -auto-approve -input=false -lock=false]
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:24+09:00 logger.go:66: aws_security_group.instance: Refreshing state... [id=sg-******]
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:25+09:00 logger.go:66: aws_instance.example: Refreshing state... [id=i-******]
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: 
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: Note: Objects have changed outside of Terraform
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: Terraform detected the following changes made outside of Terraform since the
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: last "terraform apply":
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:   # aws_security_group.instance has been changed
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:   ~ resource "aws_security_group" "instance" {
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         id                     = "sg-******"
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         name                   = "terraform-20210624022635773400000001"
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       + tags                   = {}
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         # (9 unchanged attributes hidden)
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:     }
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:   # aws_instance.example has been changed
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:   ~ resource "aws_instance" "example" {
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         id                                   = "i-******"
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       + tags                                 = {}
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         # (29 unchanged attributes hidden)
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         # (5 unchanged blocks hidden)
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:     }
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: Unless you have made equivalent changes to your configuration, or ignored the
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: relevant attributes using ignore_changes, the following plan may include
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: actions to undo or respond to these changes.
─────────────────────────────────────────────────────────────────────────────
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: Terraform used the selected providers to generate the following execution
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: plan. Resource actions are indicated with the following symbols:
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:   - destroy
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: Terraform will perform the following actions:
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:   # aws_instance.example will be destroyed
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:   - resource "aws_instance" "example" {
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - ami                                  = "ami-******" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - arn                                  = "arn:aws:ec2:us-east-2:******:instance/i-******" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - associate_public_ip_address          = true -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - availability_zone                    = "us-east-2b" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - cpu_core_count                       = 1 -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - cpu_threads_per_core                 = 1 -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - disable_api_termination              = false -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - ebs_optimized                        = false -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - get_password_data                    = false -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - hibernation                          = false -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - id                                   = "i-******" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - instance_initiated_shutdown_behavior = "stop" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - instance_state                       = "running" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - instance_type                        = "t2.micro" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - ipv6_address_count                   = 0 -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - ipv6_addresses                       = [] -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - monitoring                           = false -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - primary_network_interface_id         = "eni-******" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - private_dns                          = "ip-172-31-21-146.us-east-2.compute.internal" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - private_ip                           = "172.31.21.146" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - public_dns                           = "ec2-18-117-228-107.us-east-2.compute.amazonaws.com" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - public_ip                            = "18.117.228.107" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - secondary_private_ips                = [] -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - security_groups                      = [
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - "terraform-20210624022635773400000001",
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         ] -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - source_dest_check                    = true -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - subnet_id                            = "subnet-******" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - tags                                 = {} -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - tags_all                             = {} -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - tenancy                              = "default" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - user_data                            = "93faf098a13b043bb03f57bf2e1ec8960655de4e" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - vpc_security_group_ids               = [
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - "sg-******",
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         ] -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: 
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - capacity_reservation_specification {
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - capacity_reservation_preference = "open" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - credit_specification {
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - cpu_credits = "standard" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - enclave_options {
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - enabled = false -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - metadata_options {
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - http_endpoint               = "enabled" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - http_put_response_hop_limit = 1 -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - http_tokens                 = "optional" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - root_block_device {
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - delete_on_termination = true -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - device_name           = "/dev/sda1" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - encrypted             = false -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - iops                  = 100 -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - tags                  = {} -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - throughput            = 0 -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - volume_id             = "vol-******" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - volume_size           = 8 -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - volume_type           = "gp2" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         }
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:     }
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:   # aws_security_group.instance will be destroyed
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:   - resource "aws_security_group" "instance" {
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - arn                    = "arn:aws:ec2:us-east-2:************:security-group/sg-******" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - description            = "Managed by Terraform" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - egress                 = [] -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - id                     = "sg-******" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - ingress                = [
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:           - {
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:               - cidr_blocks      = [
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:                   - "0.0.0.0/0",
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:                 ]
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:               - description      = ""
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:               - from_port        = 8080
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:               - ipv6_cidr_blocks = []
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:               - prefix_list_ids  = []
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:               - protocol         = "tcp"
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:               - security_groups  = []
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:               - self             = false
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:               - to_port          = 8080
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:             },
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:         ] -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - name                   = "terraform-20210624022635773400000001" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - name_prefix            = "terraform-" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - owner_id               = "************" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - revoke_rules_on_delete = false -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - tags                   = {} -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - tags_all               = {} -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:       - vpc_id                 = "vpc-******" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:     }
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: 
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: Plan: 0 to add, 0 to change, 2 to destroy.
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: 
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66: Changes to Outputs:
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:32+09:00 logger.go:66:   - public_ip = "18.117.228.107" -> null
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:35+09:00 logger.go:66: aws_instance.example: Destroying... [id=i-******]
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:45+09:00 logger.go:66: aws_instance.example: Still destroying... [id=i-******, 10s elapsed]
TestTerraformAwsHelloWorldExample 2021-06-24T11:29:55+09:00 logger.go:66: aws_instance.example: Still destroying... [id=i-******, 20s elapsed]
TestTerraformAwsHelloWorldExample 2021-06-24T11:30:05+09:00 logger.go:66: aws_instance.example: Still destroying... [id=i-******, 30s elapsed]
TestTerraformAwsHelloWorldExample 2021-06-24T11:30:15+09:00 logger.go:66: aws_instance.example: Still destroying... [id=i-******, 40s elapsed]
TestTerraformAwsHelloWorldExample 2021-06-24T11:30:25+09:00 logger.go:66: aws_instance.example: Still destroying... [id=i-******, 50s elapsed]
TestTerraformAwsHelloWorldExample 2021-06-24T11:30:31+09:00 logger.go:66: aws_instance.example: Destruction complete after 57s
TestTerraformAwsHelloWorldExample 2021-06-24T11:30:31+09:00 logger.go:66: aws_security_group.instance: Destroying... [id=sg-******]
TestTerraformAwsHelloWorldExample 2021-06-24T11:30:33+09:00 logger.go:66: aws_security_group.instance: Destruction complete after 2s
TestTerraformAwsHelloWorldExample 2021-06-24T11:30:34+09:00 logger.go:66: Destroy complete! Resources: 2 destroyed.
--- PASS: TestTerraformAwsHelloWorldExample (249.54s)
PASS
ok      command-line-arguments  250.728s

JUnit形式での出力

JUnit 形式 (xml) でテスト結果を出す際は、terratest_log_parser という CLI ツールを別途使用します。

$ terratest_log_parser --testlog=./test_output.log --outputdir=./test_output
[terratest_log_parser] INFO[2021-06-24T11:38:19+09:00] reading from file 
[terratest_log_parser] INFO[2021-06-24T11:38:19+09:00] Creating directory /Users/k-nagase/workspace/go/src/github.com/gruntwork-io/terratest/test/test_output 
[terratest_log_parser] INFO[2021-06-24T11:38:26+09:00] Directory /Users/k-nagase/workspace/go/src/github.com/gruntwork-io/terratest/test/test_output already exists 
[terratest_log_parser] INFO[2021-06-24T11:38:26+09:00] Closing all the files in log writer
[terratest_log_parser] INFO[2021-06-24T11:38:26+09:00] Directory /Users/k-nagase/workspace/go/src/github.com/gruntwork-io/terratest/test/test_output already exists
$ cat ./test_output/report.xml
<!--?xml version="1.0" encoding="UTF-8"?-->
<testsuites>
        <testsuite tests="1" failures="0" time="250.728" name="command-line-arguments">
                <properties>
                        <property name="go.version" value="go1.16.3"></property>
                </properties>
                <testcase classname="command-line-arguments" name="TestTerraformAwsHelloWorldExample" time="249.540"></testcase>
        </testsuite>
</testsuites>

ベストプラクティス

https://terratest.gruntwork.io/docs/#testing-best-practices

インフラにおけるテストのベストプラクティスやテクニックなどを紹介しています。

Terratest を使ってみた所感

  • Terraform スクリプトを書く際に output にテストしたい項目を出す必要があるのが手間
  • そもそも Terraform の output 値を見るテストには意味があまり感じられない
  • Go のテストコードを書くことができればそれ以上の知識は必要ないため、習得のハードルは高くないと感じた
  • 動的テストとなるので、Terraform plan を元に静的解析をしてくれるような機能が欲しい
  • 各種パッケージ使ってテストしたい項目について API を叩いたりリクエスト送ったりして期待値と比較する、または、疎通確認を行うのが主な使い方になりそう
  • どの環境でもやりそうな基本的なテストスイートを作っておけばその部分に関しては自動化できる

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ