terraform のテストをHCLで書く方法
IT技術
はじめに
今までは、terraformでテストを実行するときは、Terratestなどの外部ツールを利用する必要がありました。
2023年10月4日にリリースされた、terraform 1.6.0の機能のterraform test コマンドを利用することで、インフラストラクチャの更新や作成のテストを、Terraform単体で完結できるようになりました。
本記事はterraform test コマンドの紹介記事になります。
リリースノート
Releases · hashicorp/terraform 160-october-4-2023
terraform testのメリット
Terraform 1.6.0以前では、テストコードの作成にTerratestなどの外部ツールを使用する必要があり、HCL以外のプログラミング言語、例えばGo言語の学習が必須でした。
テストコードとインフラストラクチャの更新・作成の記述がHCLに統一されることで、学習コストが削減され、実際のプロジェクトへの導入のハードルが下がります。
テスト実行方法
実行方法としてはシンプルで、拡張子が tftest.hclもしくは、tftest.jsonのファイルにテストコードを記述し、terraform test コマンドを実行するのみです。
terraform testの動作
デフォルトのテストの動作です。
- terraformがテストファイルを検知
- テスト用のインフラストラクチャを作成
- 作成されたインフラストラクチャに対してアサーションと検証を実行
- テスト用に作成されたインフラストラクチャを削除
テスト実行時にインフラを実際に作成したくない場合は、commandオプションにplanを指定することで、実際にインフラを作成せず。planに対してのチェックを実施することができます。
テストコードの記述方法
tftestファイルの基本的な記述方法です。
- 一つのrunブロックに対して、複数のassertを記述することができます。
- assertブロックでは、conditionに条件を記載し、error_messageにテスト失敗時のメッセージを設定します。
- conditionでは、比較演算子を使用して検証を行います。また、for式の利用やlengthなどの関数の記述も可能です。
1run "テスト名" {
2
3 command = plan // 実行する動作(デフォルト:apply)
4
5 assert {
6 condition = リソースの設定値 == "あるべき値"
7 error_message = テスト失敗時のメッセージ
8 }
9
10 // assert は、1つのrunブロックに対して複数記述できます。
11 assert {
12 condition = リソースの設定値 != "あるべきでない値"
13 error_message = "エラーメッセージ"
14 }
15}
実際にテストコードを書いてみる
今回はGCPへのリソースの作成に対して、テストコードを記述していきます。
以降はterraform v1.6.5で実施しています
1% terraform -v
2Terraform v1.6.5
3on darwin_arm64
Cloud Storageのオブジェクト名のチェック
tfファイルでCloud Storageのバケットにindex.htmlを追加します。
以下のtfファイルを作成し、同じディレクトリ内に空のindex.htmlファイルを作成します。
cloud_storage.tf
1provider "google" {
2 project = "project"
3}
4
5resource "google_storage_bucket_object" "test-object" {
6 name = "index.html"
7 bucket = "my-bucket"
8 source = "index.html"
9 content_type = "text/html"
10}
テストコードを、tftest.hclファイルに記述します。
cloud_storage.tftest.hcl
1run "valid_gcs_object_name" {
2 command = plan
3
4 assert {
5 condition = google_storage_bucket_object.test-object.name == "index.html"
6 error_message = "Object name is not index.html"
7 }
8}
コンソールでterraform test を実行
成功
1% terraform test
2cloud_storage.tftest.hcl... in progress
3 run "valid_gcs_object_name"... pass
4cloud_storage.tftest.hcl... tearing down
5cloud_storage.tftest.hcl... pass
6
7Success! 1 passed, 0 failed.
次は失敗させてみます。
cloud_storage.tfのオブジェクト名をtest.htmlに変更
1resource "google_storage_bucket_object" "test-object" {
2 name = "test.html"
3 bucket = "my-bucket"
4 source = "index.html"
5 content_type = "text/html"
6}
コンソールでterraform test を実行
テストが失敗します。失敗したときは、テストコードassertブロックに設定しているerror_messageが表示されます。
Object name is not index.html
1% terraform test
2cloud_storage.tftest.hcl... in progress
3 run "valid_gcs_object_name"... fail
4╷
5│ Error: Test assertion failed
6│
7│ on cloud_storage.tftest.hcl line 5, in run "valid_gcs_object_name":
8│ 5: condition = google_storage_bucket_object.test-object.name == "index.html"
9│ ├────────────────
10│ │ google_storage_bucket_object.test-object.name is "test.html"
11│
12│ Object name is not index.html
13╵
14cloud_storage.tftest.hcl... tearing down
15cloud_storage.tftest.hcl... fail
16
17Failure! 0 passed, 1 failed.
セキュリティポリシーの妥当性をチェック
続いて、アクセスを許可するIPアドレスをを間違えて "*" が設定してしまった際に、テスト時点で検知できるようにしていきます。
src_ip_rangesに"*"を間違えて追加してください。
cloud_armor.tf
1resource "google_compute_security_policy" "policy" {
2 name = "test-policy"
3
4 rule {
5 action = "allow"
6 priority = 2147483647
7
8 match {
9 versioned_expr = "SRC_IPS_V1"
10
11 config {
12 src_ip_ranges = ["*", "192.0.2.0/24", "198.51.100.0/24"] // *を追加してしまった。
13 }
14 }
15
16 description = "Allow access to IPs"
17 }
18}
src_ip_rangesに "*"が含まれていたら、テストを失敗させます。
cloud_armor.tftest.hcl
1run "アクセス制限に*が含まれていないこと" {
2 command = plan
3
4 assert {
5 condition = [for allow_ip in tolist(google_compute_security_policy.policy.rule.*.match[0].*.config[0].*.src_ip_ranges)[0] : allow_ip if allow_ip == "*"][0] != "*"
6 error_message = "アクセス制限に*が含まれています"
7 }
8}
terraform test 実行
1cloud_armor.tftest.hcl... in progress
2 run "アクセス制限に*が含まれていないこと"... fail
3╷
4│ Error: Test assertion failed
5│
6│ on cloud_armor.tftest.hcl line 5, in run "アクセス制限に*が含まれていないこと":
7│ 5: condition = [for allow_ip in google_compute_security_policy.policy.rule.*.match[0].*.config[0].*.src_ip_ranges[0] : allow_ip if allow_ip == "*"][0] != "*"
8│ ├────────────────
9│ │ google_compute_security_policy.policy.rule is set of object with 1 element
10│
11│ アクセス制限に*が含まれています
12╵
13main.tftest.hcl... tearing down
14main.tftest.hcl... fail
15
16Failure! 0 passed, 1 failed.
ちゃんと失敗していますね。src_ip_rangesから、"*"を削除するとテストが通ることも確認してみてください。
conditionの説明
1[for allow_ip in google_compute_security_policy.policy.rule.*.match[0].*.config[0].*.src_ip_ranges[0] : allow_ip if allow_ip == "*"][0] != "*"
[for <ITEM> in <LIST> : <OUTPUT> <条件式>] の形で、<LIST>の中から条件式に該当する値を<OUTPUT>として取得することができます。
<OUTPUT>は配列として出力されるので、最後にインデックス[0]を指定して、src_ip_ranges内の"*"を文字列として取得しています。
for文とテストコードを組み合わせることで、src_ip_rangesにIPが複数設定されていても全て検証するようにしています。
terraformのネストが分かりづらいですね。。もっとシンプルに書きたいところです。
最後に
いかがだったでしょうか?
terraform のテストコードがtfファイルと同じように書けるのはとても便利ですね。
インフラストラクチャの更新の際に、ここだけは変えないでほしい箇所をテストコードに記述しておくとよさそうです。
次はCI/CDツールとの連携も書きたいですね
現在(2023年12月20日時点)beta版の1.7.0では、tfファイル内のoutputをテストコード内で取得したり、プロバイダーやモジュールをモック化する機能が追加されていました。
正式リリースが楽しみですね。更新があれば、また記事にしていきたいと思います。
Terraformリリースノート
Releases · hashicorp/terraform
terraform test ドキュメント
Tests - Configuration Language | Terraform | HashiCorp Developer
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
矢野健太郎です。 バスケットボールとお酒と漫画が好きです。 修行の日々です。