これは、なにをしたくて書いたもの?
Terraformに対するテストコードを書くのに、Terratestというものが使えるというようなので。
こちらを試してみようかな、と。
Terratest
Terratestとは、インフラストラクチャーをテストするためのパターン、ヘルパー関数を提供するGoのライブラリです。
Terratest | Automated tests for your infrastructure code.
ドキュメントとGoDdocは、こちら。
ちゃんと見るまで、ずっとTerraform向けのテストライブラリだと思っていたのですが、こんな感じでけっこういろんな
テストができるようです。
※ドキュメント等に記載されている順番から、少し入れ替えています
- Testing Terraform code
- Testing Packer templates
- Testing Docker images
- Testing Helm Charts
- Working with AWS APIs
- Working with Azure APIs
- Working with GCP APIs
- Working with Kubernetes APIs
- Executing commands on servers over SSH
- Making HTTP requests
- Running shell commands
- And much more
https://github.com/gruntwork-io/terratest/tree/v0.35.1/modules
基本的な使い方は、こちらに載っています。
Quick start / Setting up your project
テストコードは通常のGoのテストコードとして書き、go test
コマンドで実行することになります。
テストコードの実行時に、
- Terratestを使用してIaCツール(Terraform、Packer、Dockerなど)を実行して、実際の環境を構築
- Terratestが提供するライブラリを使用して、HTTPリクエストの実行、API呼び出しなどを行って確認
というステップを踏み、最後にテスト中に構築した環境(リソース)を破棄します。
各テストのサンプルは、Quick startやExamplesにあるのですが
GitHubリポジトリには、さらに多数のサンプルがあります。
https://github.com/gruntwork-io/terratest/tree/v0.35.1/examples
インフラをコードでテストする際の考え方は、こちらを見るとよいようです。
How to test infrastructure code: automated testing for Terraform, Kub…
今回は、Terraformを使ったテストコードを書いてみたいと思います。
環境
今回の環境は、こちら。
$ go version go version go1.16.4 linux/amd64 $ terraform version Terraform v0.15.5 on linux_amd64
Terratest(0.35.1時点)では、Terratestが要求しているのはGo 1.13以上、のみです。
ですが、TerratestでTerraformのテストを書こうとすると、内部的にterraform
コマンドを実行するため、結局Terraformも
必要になります。
また、TerraformではMySQL Providerを使うことにします。
Provider: MySQL - Terraform by HashiCorp
ここで使用するMySQLは8.0.25で、172.17.0.2でアクセスできるものとします。
お題
今回は、TerraformでMySQLに関するリソースを構築し、それに対するテストを書きます。
テストは、Quick startに習って単純にTerraformのoutputを確認するもののと、構築されたリソースの確認を含めて
行うものの2つを行いましょう。
準備
まずは、ディレクトリを作成します。
Quick startを見ていると、Terratestを使ったテストコードはtest
ディレクトリに置き、またGoモジュールのルートディレクトリと
した方が良さそうですね。
Quick start / Setting up your project
terraform
、test
ディレクトリをそれぞれ作成することにしました。
$ mkdir terraform test
Terratest側のテストコードも、そのようになっています(Goモジュールのルートディレクトリではないですが)。
https://github.com/gruntwork-io/terratest/tree/v0.35.1/test
今回作成するディレクトリやファイルは、最終的にこんな感じになりました。
. ├── terraform │ ├── complex │ │ └── main.tf │ └── simple │ └── main.tf └── test ├── go.mod ├── go.sum └── mysql_test.go
では、Goモジュール側のセットアップをしましょう。test
ディレクトリへ移動し、
$ cd test
Goモジュールの初期化。
$ go mod init getting-started go: creating new go.mod: module getting-started
Terratestをgo get
します。
$ go get github.com/gruntwork-io/terratest/modules/terraform@v0.35.1
go get github.com/gruntwork-io/terratest@v0.35.1
だと、ライブラリが足りない状態になりました…。
あとは、テストライブラリとしてtestifyを、MySQLへアクセスするためにMySQLのドライバーをインストールします。
$ go get github.com/stretchr/testify@v1.7.0 $ go get github.com/go-sql-driver/mysql@v1.6.0
go.mod
は、こうなりました。
go.mod
module getting-started go 1.16 require ( github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/gruntwork-io/terratest v0.35.1 // indirect github.com/stretchr/testify v1.7.0 // indirect )
テストコードの雛形
最初に、テストコードの雛形を書いておきましょう。
mysql_test.go
package test import ( "database/sql" "fmt" "testing" _ "github.com/go-sql-driver/mysql" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" ) // ここに、テストを書く!
Terratestを使って、Terraformを実行する
最初に、Terraformの構成ファイルを書きましょう。先ほど作成したterraform
ディレクトリ内に、ディレクトリを追加します。
$ mkdir terraform/simple $ cd terraform/simple
こんな感じに、データベースを作成するだけのmain.tf
ファイルを作成。
main.tf
terraform { required_version = "0.15.5" required_providers { mysql = { source = "terraform-providers/mysql" version = "1.9.0" } } } provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" } resource "mysql_database" "app" { name = "my_database" } output "database_name" { value = mysql_database.app.name }
output
は、データベース名のみです。
一応、単体でも確認しておきましょう。
$ terraform init $ terraform fmt -diff -recursive $ terraform apply $ terraform destroy
次に、テストコードにテストを追加します。
こちらを参考に。
Quick start / Example #1: Terraform “Hello, World”
func TestTerraformMySqlHelloWorld(t *testing.T) { terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ TerraformDir: "../terraform/simple", }) defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) databaseName := terraform.Output(t, terraformOptions, "database_name") assert.Equal(t, "my_database", databaseName) }
こちらで、実行するTerraformの構成ファイルが配置されているディレクトリを指定。
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../terraform/simple",
})
WithDefaultRetryableErrors
という関数は、Options
さらにリトライ設定を追加するものになります。
func WithDefaultRetryableErrors
戻り値は、Options
のままです。
テストの成功/失敗に関わらず、最後にterraform destroy
するようにしています。
defer terraform.Destroy(t, terraformOptions)
あとはterraform init
とterraform apply
を行い
terraform.InitAndApply(t, terraformOptions)
output
を取得してアサーションして完了です。
databaseName := terraform.Output(t, terraformOptions, "database_name") assert.Equal(t, "my_database", databaseName)
output
を扱う関数は他にもあり、全部のoutput
を取得したりもできそうですが、型アサーションが必要そうなので、
それなりに型を合わせたものを使うのが良さそうです。
では、実行してみましょう。
$ go test -v
実行ログは、こんな感じになりました。
$ go test -v === RUN TestTerraformMySqlHelloWorld TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 retry.go:91: terraform [init -upgrade=false] TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: Running command terraform with args [init -upgrade=false] TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: Initializing the backend... TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: Initializing provider plugins... TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: - Reusing previous version of terraform-providers/mysql from the dependency lock file TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: - Using previously-installed terraform-providers/mysql v1.9.0 TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: Terraform has been successfully initialized! TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: You may now begin working with Terraform. Try running "terraform plan" to see TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: any changes that are required for your infrastructure. All Terraform commands TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: should now work. TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: If you ever set or change modules or backend configuration for Terraform, TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: rerun this command to reinitialize your working directory. If you forget, other TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: commands will detect it and remind you to do so if necessary. TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 retry.go:91: terraform [apply -input=false -auto-approve -lock=false] TestTerraformMySqlHelloWorld 2021-06-05T22:02:09+09:00 logger.go:66: Running command terraform with args [apply -input=false -auto-approve -lock=false] TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: Terraform used the selected providers to generate the following execution TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: plan. Resource actions are indicated with the following symbols: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: + create TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: Terraform will perform the following actions: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: # mysql_database.app will be created TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: + resource "mysql_database" "app" { TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: + default_character_set = "utf8" TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: + default_collation = "utf8_general_ci" TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: + id = (known after apply) TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: + name = "my_database" TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: } TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: Plan: 1 to add, 0 to change, 0 to destroy. TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: Changes to Outputs: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: + database_name = "my_database" TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: mysql_database.app: Creating... TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: mysql_database.app: Creation complete after 0s [id=my_database] TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: Apply complete! Resources: 1 added, 0 changed, 0 destroyed. TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: Outputs: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: database_name = "my_database" TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 retry.go:91: terraform [output -no-color -json database_name] TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: Running command terraform with args [output -no-color -json database_name] TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: "my_database" TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 retry.go:91: terraform [destroy -auto-approve -input=false -lock=false] TestTerraformMySqlHelloWorld 2021-06-05T22:02:10+09:00 logger.go:66: Running command terraform with args [destroy -auto-approve -input=false -lock=false] TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: mysql_database.app: Refreshing state... [id=my_database] TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: Terraform used the selected providers to generate the following execution TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: plan. Resource actions are indicated with the following symbols: TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: - destroy TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: Terraform will perform the following actions: TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: # mysql_database.app will be destroyed TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: - resource "mysql_database" "app" { TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: - default_character_set = "utf8" -> null TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: - default_collation = "utf8_general_ci" -> null TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: - id = "my_database" -> null TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: - name = "my_database" -> null TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: } TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: Plan: 0 to add, 0 to change, 1 to destroy. TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: Changes to Outputs: TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: - database_name = "my_database" -> null TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: mysql_database.app: Destroying... [id=my_database] TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: mysql_database.app: Destruction complete after 1s TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: Destroy complete! Resources: 1 destroyed. TestTerraformMySqlHelloWorld 2021-06-05T22:02:11+09:00 logger.go:66: --- PASS: TestTerraformMySqlHelloWorld (2.46s) PASS ok getting-started 2.462s
よくよく見ると、terraform
コマンドの実行内容もログに含まれています。
terraform [init -upgrade=false] terraform [apply -input=false -auto-approve -lock=false] terraform [output -no-color -json database_name] terraform [destroy -auto-approve -input=false -lock=false]
なので、terraform
コマンドがインストールされていない場合は、このテストは実行に失敗します。
TestTerraformMySqlHelloWorld 2021-06-05T22:05:01+09:00 retry.go:99: Returning due to fatal error: FatalError{Underlying: error while running command: exec: "terraform": executable file not found in $PATH; } apply.go:15: Error Trace: apply.go:15 mysql_test.go:20 Error: Received unexpected error: FatalError{Underlying: error while running command: exec: "terraform": executable file not found in $PATH; } Test: TestTerraformMySqlHelloWorld
なんとなく、基本的な使い方はわかった気がします。
あとは、その他のTerratestが提供するIaCツールサポート、ライブラリの使い方を覚えたり、
ドキュメントに「テストのベストプラクティス」が載っているので、こちらを見たりして慣れていくのかな、と
思います。
もう少し複雑な例を
続いて、先ほどのシンプルな例にもう少し要素を加えたものをやってみましょう。
ディレクトリを追加。
$ mkdir terraform/complex $ cd terraform/complex
Terraformの構成ファイルは、こちら。
main.tf
terraform { required_version = "0.15.5" required_providers { mysql = { source = "terraform-providers/mysql" version = "1.9.0" } } } variable "database_name" { type = string } variable "app_user_name" { type = string default = "app_user" } variable "app_user_password" { type = string } provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" } resource "mysql_database" "app" { name = var.database_name } resource "mysql_user" "app_user" { user = var.app_user_name plaintext_password = var.app_user_password host = "%" } resource "mysql_grant" "user_grant" { user = mysql_user.app_user.user host = mysql_user.app_user.host database = mysql_database.app.name privileges = ["ALL"] } output "database_name" { value = mysql_database.app.name } output "app_user_name" { value = mysql_user.app_user.user }
今回は、variable
も取るようにしました。
variable "database_name" { type = string } variable "app_user_name" { type = string default = "app_user" } variable "app_user_password" { type = string }
ユーザーも作成するようにしています。
resource "mysql_user" "app_user" { user = var.app_user_name plaintext_password = var.app_user_password host = "%" } resource "mysql_grant" "user_grant" { user = mysql_user.app_user.user host = mysql_user.app_user.host database = mysql_database.app.name privileges = ["ALL"] }
テストコードは、こちら。
func TestTerraformMySqlWithValidation(t *testing.T) { appUserPassword := "app_password" terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ TerraformDir: "../terraform/complex", Vars: map[string]interface{}{ "database_name": "practice", "app_user_password": appUserPassword, }, }) defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) databaseName := terraform.Output(t, terraformOptions, "database_name") appUserName := terraform.Output(t, terraformOptions, "app_user_name") assert.Equal(t, "practice", databaseName) assert.Equal(t, "app_user", appUserName) // validation db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@(%s:%d)/%s", appUserName, appUserPassword, "172.17.0.2", 3306, databaseName)) assert.Nil(t, err) assert.NotNil(t, db) defer db.Close() err = db.Ping() assert.Nil(t, err) }
Options
に、今回はVar
の指定を追加しました。-var
と同じですね。
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ TerraformDir: "../terraform/complex", Vars: map[string]interface{}{ "database_name": "practice", "app_user_password": appUserPassword, }, })
terraform destroy
の仕込みや、terraform init
、terraform apply
の実行、terraform output
からのアサーションを行いつつ
defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) databaseName := terraform.Output(t, terraformOptions, "database_name") appUserName := terraform.Output(t, terraformOptions, "app_user_name") assert.Equal(t, "practice", databaseName) assert.Equal(t, "app_user", appUserName)
最後に、作成したユーザーで、MySQLに接続できることを確認します。
// validation db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@(%s:%d)/%s", appUserName, appUserPassword, "172.17.0.2", 3306, databaseName)) assert.Nil(t, err) assert.NotNil(t, db) defer db.Close() err = db.Ping() assert.Nil(t, err)
このように、構築されたリソースに対して動作確認(HTTPリクエストを実行する、API呼び出しを行う、SSHでコマンドを
実行する、など)することを、バリデーションと呼んでいるみたいです。
テストで確認。
$ go test -v
実行ログ。
$ go test -v === RUN TestTerraformMySqlWithValidation TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 retry.go:91: terraform [init -upgrade=false] TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: Running command terraform with args [init -upgrade=false] TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: Initializing the backend... TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: Initializing provider plugins... TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: - Reusing previous version of terraform-providers/mysql from the dependency lock file TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: - Using previously-installed terraform-providers/mysql v1.9.0 TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: Terraform has been successfully initialized! TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: You may now begin working with Terraform. Try running "terraform plan" to see TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: any changes that are required for your infrastructure. All Terraform commands TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: should now work. TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: If you ever set or change modules or backend configuration for Terraform, TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: rerun this command to reinitialize your working directory. If you forget, other TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: commands will detect it and remind you to do so if necessary. TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 retry.go:91: terraform [apply -input=false -auto-approve -var database_name=practice -var app_user_password=app_password -lock=false] TestTerraformMySqlWithValidation 2021-06-05T22:37:52+09:00 logger.go:66: Running command terraform with args [apply -input=false -auto-approve -var database_name=practice -var app_user_password=app_password -lock=false] TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: Terraform used the selected providers to generate the following execution TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: plan. Resource actions are indicated with the following symbols: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + create TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: Terraform will perform the following actions: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: # mysql_database.app will be created TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + resource "mysql_database" "app" { TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + default_character_set = "utf8" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + default_collation = "utf8_general_ci" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + id = (known after apply) TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + name = "practice" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: } TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: # mysql_grant.user_grant will be created TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + resource "mysql_grant" "user_grant" { TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + database = "practice" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + grant = false TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + host = "%!"(MISSING) TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + id = (known after apply) TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + privileges = [ TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + "ALL", TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: ] TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + table = "*" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + tls_option = "NONE" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + user = "app_user" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: } TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: # mysql_user.app_user will be created TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + resource "mysql_user" "app_user" { TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + host = "%!"(MISSING) TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + id = (known after apply) TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + plaintext_password = (sensitive value) TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + tls_option = "NONE" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + user = "app_user" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: } TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: Plan: 3 to add, 0 to change, 0 to destroy. TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: Changes to Outputs: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + app_user_name = "app_user" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: + database_name = "practice" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: mysql_database.app: Creating... TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: mysql_user.app_user: Creating... TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: mysql_user.app_user: Creation complete after 1s [id=app_user@%!](MISSING) TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: mysql_database.app: Creation complete after 1s [id=practice] TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: mysql_grant.user_grant: Creating... TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: mysql_grant.user_grant: Creation complete after 0s [id=app_user@%!:(MISSING)`practice`] TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: Apply complete! Resources: 3 added, 0 changed, 0 destroyed. TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: Outputs: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: app_user_name = "app_user" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: database_name = "practice" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 retry.go:91: terraform [output -no-color -json database_name] TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: Running command terraform with args [output -no-color -json database_name] TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: "practice" TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 retry.go:91: terraform [output -no-color -json app_user_name] TestTerraformMySqlWithValidation 2021-06-05T22:37:53+09:00 logger.go:66: Running command terraform with args [output -no-color -json app_user_name] TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: "app_user" TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 retry.go:91: terraform [destroy -auto-approve -input=false -var app_user_password=app_password -var database_name=practice -lock=false] TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: Running command terraform with args [destroy -auto-approve -input=false -var app_user_password=app_password -var database_name=practice -lock=false] TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: mysql_database.app: Refreshing state... [id=practice] TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: mysql_user.app_user: Refreshing state... [id=app_user@%!](MISSING) TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: mysql_grant.user_grant: Refreshing state... [id=app_user@%!:(MISSING)`practice`] TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: Terraform used the selected providers to generate the following execution TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: plan. Resource actions are indicated with the following symbols: TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - destroy TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: Terraform will perform the following actions: TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: # mysql_database.app will be destroyed TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - resource "mysql_database" "app" { TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - default_character_set = "utf8" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - default_collation = "utf8_general_ci" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - id = "practice" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - name = "practice" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: } TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: # mysql_grant.user_grant will be destroyed TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - resource "mysql_grant" "user_grant" { TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - database = "practice" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - grant = false -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - host = "%!"(MISSING) -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - id = "app_user@%!:(MISSING)`practice`" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - privileges = [ TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - "ALL", TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: ] -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - table = "*" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - tls_option = "NONE" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - user = "app_user" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: } TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: # mysql_user.app_user will be destroyed TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - resource "mysql_user" "app_user" { TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - host = "%!"(MISSING) -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - id = "app_user@%!"(MISSING) -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - plaintext_password = (sensitive value) TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - tls_option = "NONE" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - user = "app_user" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: } TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: Plan: 0 to add, 0 to change, 3 to destroy. TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: Changes to Outputs: TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - app_user_name = "app_user" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:54+09:00 logger.go:66: - database_name = "practice" -> null TestTerraformMySqlWithValidation 2021-06-05T22:37:55+09:00 logger.go:66: mysql_grant.user_grant: Destroying... [id=app_user@%!:(MISSING)`practice`] TestTerraformMySqlWithValidation 2021-06-05T22:37:55+09:00 logger.go:66: mysql_grant.user_grant: Destruction complete after 0s TestTerraformMySqlWithValidation 2021-06-05T22:37:55+09:00 logger.go:66: mysql_database.app: Destroying... [id=practice] TestTerraformMySqlWithValidation 2021-06-05T22:37:55+09:00 logger.go:66: mysql_user.app_user: Destroying... [id=app_user@%!](MISSING) TestTerraformMySqlWithValidation 2021-06-05T22:37:55+09:00 logger.go:66: mysql_user.app_user: Destruction complete after 0s TestTerraformMySqlWithValidation 2021-06-05T22:37:55+09:00 logger.go:66: mysql_database.app: Destruction complete after 0s TestTerraformMySqlWithValidation 2021-06-05T22:37:55+09:00 logger.go:66: TestTerraformMySqlWithValidation 2021-06-05T22:37:55+09:00 logger.go:66: Destroy complete! Resources: 3 destroyed. TestTerraformMySqlWithValidation 2021-06-05T22:37:55+09:00 logger.go:66: --- PASS: TestTerraformMySqlWithValidation (3.12s) PASS ok getting-started 3.127s
最初に作成したテストと、terraform
コマンドの実行バリエーションが異なるのは、-var
が追加されたことですね。
terraform [apply -input=false -auto-approve -var database_name=practice -var app_user_password=app_password -lock=false]
これで、variable
を渡したり、実際に構築したリソースを確認したりするテストができました、と。
まとめ
Terratestを使って、Terraformの構成ファイルに対するテストを行ってみました。
調べてみると、Terraform以外のインフラ関係のツールのテストもできそうなので、もうちょっと調べてみてもいいかもですね。
最後に、テストコード全体を載せておきます。
mysql_test.go
package test import ( "database/sql" "fmt" "testing" _ "github.com/go-sql-driver/mysql" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" ) func TestTerraformMySqlHelloWorld(t *testing.T) { terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ TerraformDir: "../terraform/simple", }) defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) databaseName := terraform.Output(t, terraformOptions, "database_name") assert.Equal(t, "my_database", databaseName) } func TestTerraformMySqlWithValidation(t *testing.T) { appUserPassword := "app_password" terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ TerraformDir: "../terraform/complex", Vars: map[string]interface{}{ "database_name": "practice", "app_user_password": appUserPassword, }, }) defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) databaseName := terraform.Output(t, terraformOptions, "database_name") appUserName := terraform.Output(t, terraformOptions, "app_user_name") assert.Equal(t, "practice", databaseName) assert.Equal(t, "app_user", appUserName) // validation db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@(%s:%d)/%s", appUserName, appUserPassword, "172.17.0.2", 3306, databaseName)) assert.Nil(t, err) assert.NotNil(t, db) defer db.Close() err = db.Ping() assert.Nil(t, err) }