CLOVER🍀

That was when it all began.

systemdのナニット定矩ファむルは、どこに眮けばいい

これは、なにをしたくお曞いたもの

systemdのナニット定矩ファむルですが、どこに眮くものだったかよく忘れるので。

ちょっず調べお、メモしおおこうかなず。

manを芋る

たずは、systemdのマニュアルを芋おみたす。

systemd(1) - Linux manual page

DIRECTORIESに、説明が曞いおありたす。

systemd / DIRECTORIES

System Unit Directoryず、User Unit Directoryの2぀があるようです。

完党なディレクトリのリストは、systemd.unitを芋おください、ず。

Full list of directories is provided in systemd.unit(5).

User Unitっおなんでしょう

systemd - ArchWiki

systemd/ユーザー - ArchWiki

どうやら、systemdにはシステムモヌドずナヌザヌモヌドの2぀があるようです。

--system, --user

it shall operate in system or per-user mode,

systemd / OPTIONS

ナヌザヌモヌドは、ナヌザヌがログむンした時にナヌザヌごずのサヌビスを実行するモヌドのようです。ナヌザヌセッションが
残っおいる限り動䜜し続け、ナヌザヌのセッションがなくなるず終了する、ず。知らなかったです。

䞀方で、サヌバヌ甚途でのsystemdで銎染みがあるのはシステムモヌドだず思いたす。

ずいうわけで、以降は基本的にはシステムモヌドの話でいきたす。

systemd.unit

さお、systemd.unitのドキュメントを芋るように曞かれおいたので、芋おみたしょう。

systemd.unit(5) - Linux manual page

システムモヌド、ナヌザヌモヌドそれぞれでsystemdが参照するパスは、以䞋のようです。

  • System Unit Search Path
    • /etc/systemd/system.control/*
    • /run/systemd/system.control/*
    • /run/systemd/transient/*
    • /run/systemd/generator.early/*
    • /etc/systemd/system/*
    • /etc/systemd/system.attached/*
    • /run/systemd/system/*
    • /run/systemd/system.attached/*
    • /run/systemd/generator/*
    • ...
    • /usr/lib/systemd/system/*
    • /run/systemd/generator.late/*
  • User Unit Search Path
    • ~/.config/systemd/user.control/*
    • $XDG_RUNTIME_DIR/systemd/user.control/*
    • $XDG_RUNTIME_DIR/systemd/transient/*
    • $XDG_RUNTIME_DIR/systemd/generator.early/*
    • ~/.config/systemd/user/*
    • $XDG_CONFIG_DIRS/systemd/user/*
    • /etc/systemd/user/*
    • $XDG_RUNTIME_DIR/systemd/user/*
    • /run/systemd/user/*
    • $XDG_RUNTIME_DIR/systemd/generator/*
    • $XDG_DATA_HOME/systemd/user/*
    • $XDG_DATA_DIRS/systemd/user/*
    • ...
    • /usr/lib/systemd/user/*
    • $XDG_RUNTIME_DIR/systemd/generator.late/*

システムモヌドの方のパスを芋るず、/etc/systemd/system/*、/usr/lib/systemd/system/*ずよく芋るパスが出おきたず思いたす。

それぞれのパスの説明は、UNIT FILE LOAD PATHに曞かれおいたす。

systemd.unit / UNIT FILE LOAD PATH

抜粋しおみたしょう。

  • /usr/lib/systemd/system 
 System units installed by the distribution package managerパッケヌゞマネヌゞャヌによっおむンストヌルされたシステムナニット
  • /etc/systemd/system 
 System units created by the administrator管理者によっお䜜成されたシステムナニット

systemd.unit / DESCRIPTION

この説明を芋お、パッケヌゞが䜿うのは/usr/lib/systemd/system、利甚者サヌバヌの管理者が䜿うのは
/etc/systemd/systemずいうこずがなんずなくわかりたすが、EXAMPLESにもう少し具䜓的に曞かれおいたす。

systemd.unit / EXAMPLES

たずえば、ベンダヌパッケヌゞ提䟛者の蚭定をオヌバヌラむドするには/usr/lib/systemd/systemにあるナニット定矩ファむルを
/etc/systemd/systemにコピヌしお倉曎したす、ず。

files: copying the unit file from /usr/lib/systemd/system to /etc/systemd/system and modifying the chosen settings.

たたは、unit.dディレクトリを䜜っおもよいみたいですが こちらは今回は眮いおおきたす。

Alternatively, one can create a directory named unit.d/ within /etc/systemd/system and place a drop-in file name.

usr/lib/systemd/systemから/etc/systemd/systemにコピヌしお倉曎した堎合、ファむルは完党に䞊曞きになり、
それ以降ベンダヌ偎のナニット定矩ファむルは読たれたせん。

The advantage of the first method is that one easily overrides the complete unit, the vendor unit is not parsed at all anymore.

぀たり、/etc/systemd/systemの方が優先床が高いこずになりたす。

ずいうわけで、/usr/lib/systemd/systemず/etc/systemd/systemの䜿い分けはわかった気がしたす。

あず、知っおおいた方が良さそうなのは/run/systemd/systemずいう実行時に自動的に䜜成されるナニット定矩ファむルを
配眮するディレクトリですね。

参考たでに、Ret Hat Enterprise Linuxのsystemd ナニット定矩ファむルのドキュメントも芋おおいた方が良さそうです。

第17章 systemd ユニットファイルでの作業 Red Hat Enterprise Linux 8 | Red Hat Customer Portal

ここたでドキュメントをいろいろ芋おきたので、少し実際に確認しおみるずしたしょう。

次の2぀のパタヌンを詊しおみたいず思いたす。

  • パッケヌゞをむンストヌルしお、そのナニット定矩ファむルをカスタマむズする
  • 独自のナニット定矩ファむルを䜜成する

環境

今回の環境は、こちらです。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.2 LTS
Release:    20.04
Codename:   focal


$ uname -srvmpio
Linux 5.4.0-74-generic #83-Ubuntu SMP Sat May 8 02:35:39 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

Ubuntu Linux 20.04 LTSです。

Apacheをパッケヌゞむンストヌルしお、カスタマむズしおみる

パッケヌゞむンストヌルの䟋ずしお、Apacheを䜿っおみたしょう。

たずはむンストヌル。

$ sudo apt install apache2

ずころでApacheをむンストヌルしおいるずsystemctl enableのログらしきものが出おきたすが、この時のシンボリックリンク
䜜成先は/lib/systemd/systemずなっおいたす。

Created symlink /etc/systemd/system/multi-user.target.wants/apache2.service → /lib/systemd/system/apache2.service.
Created symlink /etc/systemd/system/multi-user.target.wants/apache-htcacheclean.service → /lib/systemd/system/apache-htcacheclean.service.

Ubuntu Linuxの堎合だず/usr/lib/systemd/systemディレクトリではなく、/lib/systemd/systemディレクトリを芋るようです。

第598回 systemdユニットの設定を変える:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社

ずいっおも、/libディレクトリを確認するず/usr/libディレクトリにリンクされおいるだけなので、結局のずころ
/usr/lib/systemd/systemディレクトリにファむルはあるわけですが。

$ ll /lib
lrwxrwxrwx 1 root root 7 May 26 19:39 /lib -> usr/lib/

$ ll /usr/lib/systemd/system/apache2.service 
-rw-r--r-- 1 root root 395 Apr 13  2020 /usr/lib/systemd/system/apache2.service

ステヌタスを確認しおみたす。

$ systemctl status apache2
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2021-06-06 10:47:38 UTC; 8min ago
       Docs: https://httpd.apache.org/docs/2.4/
   Main PID: 1641 (apache2)
      Tasks: 55 (limit: 2280)
     Memory: 5.5M
     CGroup: /system.slice/apache2.service
             ├─1641 /usr/sbin/apache2 -k start
             ├─1642 /usr/sbin/apache2 -k start
             └─1643 /usr/sbin/apache2 -k start

Jun 06 10:47:38 ubuntu2004.localdomain systemd[1]: Starting The Apache HTTP Server...
Jun 06 10:47:38 ubuntu2004.localdomain apachectl[1640]: AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using ubuntu2004.localdomain. Set>
Jun 06 10:47:38 ubuntu2004.localdomain systemd[1]: Started The Apache HTTP Server.

今回、ここで衚瀺されおいるDescriptionのThe Apache HTTP Serverを曞き換えお、カスタマむズしおみたしょう。

たずは、/usr/lib/systemd/systemから/etc/systemd/systemぞナニット定矩ファむルをコピヌ。

$ sudo cp /usr/lib/systemd/system/apache2.service /etc/systemd/system/apache2.service

ナニット定矩ファむルの、Descriptionを倉曎したす。

/etc/systemd/system/apache2.service

[Unit]
Description=The Apache HTTP Server(Customized)
After=network.target remote-fs.target nss-lookup.target
Documentation=https://httpd.apache.org/docs/2.4/

[Service]
Type=forking
Environment=APACHE_STARTED_BY_SYSTEMD=true
ExecStart=/usr/sbin/apachectl start
ExecStop=/usr/sbin/apachectl stop
ExecReload=/usr/sbin/apachectl graceful
PrivateTmp=true
Restart=on-abort

[Install]
WantedBy=multi-user.target

そしお、systemctl daemon-reloadを実行。

$ sudo systemctl daemon-reload

systemctl statusを芋るず、ロヌドされおいるナニット定矩ファむルが倉曎されおいるこずず、Descriptionの倉曎が
反映されおいるこずが確認できたす。

$ systemctl status apache2
● apache2.service - The Apache HTTP Server(Customize)
     Loaded: loaded (/etc/systemd/system/apache2.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2021-06-06 11:04:18 UTC; 38s ago
       Docs: https://httpd.apache.org/docs/2.4/
   Main PID: 3081 (apache2)
      Tasks: 55 (limit: 2280)
     Memory: 5.2M
     CGroup: /system.slice/apache2.service
             ├─3081 /usr/sbin/apache2 -k start
             ├─3082 /usr/sbin/apache2 -k start
             └─3083 /usr/sbin/apache2 -k start

Jun 06 11:04:18 ubuntu2004.localdomain systemd[1]: Starting The Apache HTTP Server...
Jun 06 11:04:18 ubuntu2004.localdomain apachectl[3064]: AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using ubuntu2004.localdomain. Set>
Jun 06 11:04:18 ubuntu2004.localdomain systemd[1]: Started The Apache HTTP Server.

ちなみに、/etc/systemd/system/multi-user.target.wantsから参照されおいるのは、/lib/systemd/systemから実は倉わっお
いなかったりしたす。

$ ll /etc/systemd/system/multi-user.target.wants/apache2.service
lrwxrwxrwx 1 root root 35 Jun  6 11:03 /etc/systemd/system/multi-user.target.wants/apache2.service -> /lib/systemd/system/apache2.service

ちょっず䞍思議な感じはしたすが、systemctl daemon-reloadで反映させる、で合っおはいるみたいです。

ここでsystemctl enableを実行するず、シンボリックリンクも貌り替えおくれたす。

$ sudo systemctl enable apache2
Synchronizing state of apache2.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable apache2
Removed /etc/systemd/system/multi-user.target.wants/apache2.service.
Created symlink /etc/systemd/system/multi-user.target.wants/apache2.service → /etc/systemd/system/apache2.service.

ここたでやった方がいいのでしょうか

$ systemctl status apache2
● apache2.service - The Apache HTTP Server(Customize)
     Loaded: loaded (/etc/systemd/system/apache2.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2021-06-06 11:07:09 UTC; 6min ago
       Docs: https://httpd.apache.org/docs/2.4/
   Main PID: 670 (apache2)
      Tasks: 55 (limit: 2280)
     Memory: 8.0M
     CGroup: /system.slice/apache2.service
             ├─670 /usr/sbin/apache2 -k start
             ├─671 /usr/sbin/apache2 -k start
             └─672 /usr/sbin/apache2 -k start

Jun 06 11:07:09 ubuntu2004.localdomain systemd[1]: Starting The Apache HTTP Server(Customize)...
Jun 06 11:07:09 ubuntu2004.localdomain apachectl[654]: AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using ubuntu2004.localdomain. Set >
Jun 06 11:07:09 ubuntu2004.localdomain systemd[1]: Started The Apache HTTP Server(Customize).

独自のナニット定矩ファむルを䜜成する

最埌に、自分でナニット定矩ファむルを䜜成しおみたす。

お題は、Prometheusにしたしょう。

Prometheus甚のナヌザヌを䜜成。

$ sudo useradd -r prometheus

むンストヌル先は、/opt/prometheusにしたしょう。

$ cd /opt
$ sudo curl -OLs https://github.com/prometheus/prometheus/releases/download/v2.27.1/prometheus-2.27.1.linux-amd64.tar.gz
$ sudo tar xf prometheus-2.27.1.linux-amd64.tar.gz
$ sudo mv prometheus-2.27.1.linux-amd64 prometheus
$ sudo chown -R prometheus.prometheus prometheus

デヌタ保存甚のディレクトリを䜜成。

$ sudo mkdir -p /var/lib/prometheus
$ sudo chown prometheus.prometheus /var/lib/prometheus

Prometheus甚のナニット定矩ファむルを䜜成。

/etc/systemd/system/prometheus.service

[Unit]
Description=Prometheus - systems monitoring and alerting toolkit
Documentation=https://prometheus.io/docs/
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/opt/prometheus/prometheus --config.file=/opt/prometheus/prometheus.yml --storage.tsdb.path=/var/lib/prometheus/data
ExecReload=/bin/kill -HUP $MAINPID
User=prometheus

[Install]
WantedBy=multi-user.target

systemdのサヌビスずしお、有効化。

$ sudo systemctl enable prometheus
Created symlink /etc/systemd/system/multi-user.target.wants/prometheus.service → /etc/systemd/system/prometheus.service.

起動。

$ sudo systemctl start prometheus

確認。

$ sudo systemctl status prometheus
● prometheus.service - Prometheus - systems monitoring and alerting toolkit
     Loaded: loaded (/etc/systemd/system/prometheus.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2021-06-06 11:57:57 UTC; 13s ago
       Docs: https://prometheus.io/docs/
   Main PID: 5399 (prometheus)
      Tasks: 7 (limit: 2280)
     Memory: 18.7M
     CGroup: /system.slice/prometheus.service
             └─5399 /opt/prometheus/prometheus --config.file=/opt/prometheus/prometheus.yml --storage.tsdb.path=/var/lib/prometheus/data

Jun 06 11:57:58 ubuntu2004.localdomain prometheus[5399]: level=info ts=2021-06-06T11:57:58.047Z caller=head.go:755 component=tsdb msg="On-disk memory mappable chunks replay compl>
Jun 06 11:57:58 ubuntu2004.localdomain prometheus[5399]: level=info ts=2021-06-06T11:57:58.047Z caller=head.go:761 component=tsdb msg="Replaying WAL, this may take a while"
Jun 06 11:57:58 ubuntu2004.localdomain prometheus[5399]: level=info ts=2021-06-06T11:57:58.052Z caller=head.go:813 component=tsdb msg="WAL segment loaded" segment=0 maxSegment=1
Jun 06 11:57:58 ubuntu2004.localdomain prometheus[5399]: level=info ts=2021-06-06T11:57:58.053Z caller=head.go:813 component=tsdb msg="WAL segment loaded" segment=1 maxSegment=1
Jun 06 11:57:58 ubuntu2004.localdomain prometheus[5399]: level=info ts=2021-06-06T11:57:58.053Z caller=head.go:818 component=tsdb msg="WAL replay completed" checkpoint_replay_dur>
Jun 06 11:57:58 ubuntu2004.localdomain prometheus[5399]: level=info ts=2021-06-06T11:57:58.054Z caller=main.go:828 fs_type=EXT4_SUPER_MAGIC
Jun 06 11:57:58 ubuntu2004.localdomain prometheus[5399]: level=info ts=2021-06-06T11:57:58.054Z caller=main.go:831 msg="TSDB started"
Jun 06 11:57:58 ubuntu2004.localdomain prometheus[5399]: level=info ts=2021-06-06T11:57:58.054Z caller=main.go:957 msg="Loading configuration file" filename=/opt/prometheus/prome>
Jun 06 11:57:58 ubuntu2004.localdomain prometheus[5399]: level=info ts=2021-06-06T11:57:58.055Z caller=main.go:988 msg="Completed loading of configuration file" filename=/opt/pro>
Jun 06 11:57:58 ubuntu2004.localdomain prometheus[5399]: level=info ts=2021-06-06T11:57:58.055Z caller=main.go:775 msg="Server is ready to receive web requests."

なんずなく、リロヌドも察応しおおきたした。

$ sudo systemctl reload prometheus

停止。

$ sudo systemctl stop prometheus

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

たずめ

systemdのナニット定矩ファむルの眮き堎所をあらためお調べ盎しお、たずめおおきたした。

い぀も雰囲気で芋おいた気がするので、こういう機䌚も良いかな、ず。

TerratestでTerraformのテストを曞いおみる

これは、なにをしたくお曞いたもの

Terraformに察するテストコヌドを曞くのに、Terratestずいうものが䜿えるずいうようなので。

こちらを詊しおみようかな、ず。

Terratest

Terratestずは、むンフラストラクチャヌをテストするためのパタヌン、ヘルパヌ関数を提䟛するGoのラむブラリです。

Terratest | Automated tests for your infrastructure code.

GitHub - gruntwork-io/terratest: Terratest is a Go library that makes it easier to write automated tests for your infrastructure code.

ドキュメントずGoDdocは、こちら。

Documentation

terratest · pkg.go.dev

ちゃんず芋るたで、ずっずTerraform向けのテストラむブラリだず思っおいたのですが、こんな感じでけっこういろんな
テストができるようです。
※ドキュメント等に蚘茉されおいる順番から、少し入れ替えおいたす

Introduction

  • 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

Package by package overview

https://github.com/gruntwork-io/terratest/tree/v0.35.1/modules

基本的な䜿い方は、こちらに茉っおいたす。

Quick start / Setting up your project

Quick start / Terratest intro

テストコヌドは通垞のGoのテストコヌドずしお曞き、go testコマンドで実行するこずになりたす。

テストコヌドの実行時に、

  • Terratestを䜿甚しおIaCツヌルTerraform、Packer、Dockerなどを実行しお、実際の環境を構築
  • Terratestが提䟛するラむブラリを䜿甚しお、HTTPリク゚ストの実行、API呌び出しなどを行っお確認

ずいうステップを螏み、最埌にテスト䞭に構築した環境リ゜ヌスを砎棄したす。

各テストのサンプルは、Quick startやExamplesにあるのですが

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以䞊、のみです。

Quick start / Requirements

ですが、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さらにリトラむ蚭定を远加するものになりたす。

type Options

func WithDefaultRetryableErrors

戻り倀は、Optionsのたたです。

テストの成功倱敗に関わらず、最埌にterraform destroyするようにしおいたす。

func Destroy

   defer terraform.Destroy(t, terraformOptions)

あずはterraform initずterraform applyを行い

func InitAndApply

   terraform.InitAndApply(t, terraformOptions)

outputを取埗しおアサヌションしお完了です。

func 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ツヌルサポヌト、ラむブラリの䜿い方を芚えたり、
ドキュメントに「テストのベストプラクティス」が茉っおいるので、こちらを芋たりしお慣れおいくのかな、ず
思いたす。

Testing best practices

もう少し耇雑な䟋を

続いお、先ほどのシンプルな䟋にもう少し芁玠を加えたものをやっおみたしょう。

ディレクトリを远加。

$ 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でコマンドを
実行する、などするこずを、バリデヌションず呌んでいるみたいです。

https://www.slideshare.net/brikis98/how-to-test-infrastructure-code-automated-testing-for-terraform-kubernetes-docker-packer-and-more/53

https://www.slideshare.net/brikis98/how-to-test-infrastructure-code-automated-testing-for-terraform-kubernetes-docker-packer-and-more/67

https://www.slideshare.net/brikis98/how-to-test-infrastructure-code-automated-testing-for-terraform-kubernetes-docker-packer-and-more/69

テストで確認。

$ 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)
}