APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Terraform Cloud で Azure モジュールのテストを自動生成してみた

こんにちは、ACS 事業部の埜下です。

みなさん Terraform モジュールのテスト書いてますかー?

テストコードを書くのって大変ですよね。 私は Terraform に限らずテストが苦手で後回しにしがちです

そんなあなた(そして私)に朗報です! 今年 10 月に Terraform Cloud の「テストコードの自動生成機能」がパブリックベータとして公開されました🎉 (ただし Plus プラン限定です

developer.hashicorp.com

ということで、今回は AzureRM プロバイダを使った Terraform モジュールではどのようなテストコードが自動生成されるか試してみました。

1. 事前準備

テストコードを自動生成するためには Terraform Cloud のプライベートレジストリにモジュールを登録する必要があります。

この章ではモジュールの登録手順を紹介していきますので、既にモジュールを登録されている方は次の章へお進みください。

1.1 Git リポジトリの作成

Terraform Cloud のプライベートレジストリに自作のモジュールを登録するためには GitHub や GitLab などの Terraform Cloud が対応している VCS (Version Control System) にモジュール用のリポジトリが必要です。

そしてこのリポジトリ名は terraform-<PROVIDER>-<NAME> という命名規則に準拠していなければなりません。

<PROVIDER> は Terraform Cloud 上で管理される名前なので、モジュール内で実際に利用しているプロバイダ名と異なっていても大丈夫です(e.g. AzureRM プロバイダを使っているモジュール名を terraform-azure-test とする)。

1.2 モジュールの作成

作成するモジュールは標準のモジュール構造に準拠していることが条件です。 とは言っても、LICENSE ファイルなどが無くても認識はしてくれます。

今回のようにお試しでモジュールを作成する場合、最低限 main.tfvariables.tfoutputs.tf を作成して resource ブロックなどを定義してください。 モジュールを作成したら上述のリポジトリに上げておきましょう。

1.3 プライベートレジストリへの登録

モジュールとレジストリの準備ができたら、Terraform Cloud のプライベートレジストリに登録していきます。

Terraform Cloud の [Registry] メニューから [Publish] ボタンを選択します。

モジュールの登録

モジュールを格納した VCS を選択していきます。 ここには Terraform Cloud で設定している VCS が表示されるので環境によって表示されている内容が異なります。 今回はリポジトリを GitHub に作ったので GitHub App を選択しました。

参照する VCS の選択

次の画面でリポジトリを選択します。 GitHub の場合は個人アカウントの他、Organization アカウントを選択できます。

対象のアカウント内のリポジトリのうち、命名規則 terraform-<PROVIDER>-<NAME> に準拠しているリポジトリが一覧に表示されます。

また、GitHub の場合は GitHub Apps に登録している Terraform Cloud アプリが当該リポジトリの参照権限を持っていないと一覧に表示されません。 アプリにアカウント全体のリポジトリ参照権限を与えていない場合、個別に権限を割り当ててください。 私はこの設定を忘れてしまい、一覧にリポジトリが表示されなくて原因特定に時間を溶かしてしまいました。

リポジトリの選択

最後にリポジトリの公開方法を選択します。 公開方法には「タグベース」と「ブランチベース」があります。

タグベースではセマンティックバージョニング(v1.0.0 や 1.0.0 の記載方法)に則ったタグがプライベートレジストリで公開されます。

ブランチベースではリポジトリのブランチを指定しますが、Terraform Cloud 上でモジュールのバージョンを定義する作業が発生します。 そのため、個人的にはリポジトリのタグでバージョンを管理して Terraform Cloud ではタグベースで公開することをおすすめします。

モジュール公開方法の選択

無事に登録できたら以下のように Terraform Cloud のレジストリ一覧に自作モジュールが表示されます。

自作モジュールがプライベートレジストリに登録された

2. Terraform Cloud でテストコードを自動生成する

それでは、Terraform Cloud のプライベートレジストリに登録した自作モジュールのテストコードを Terraform Cloud 上で自動生成してみましょう。

レジストリの一覧から当該モジュールを選択するとモジュールの概要が表示されます。

概要ページの右側を見てみると [Generate tests] というボタンがありますね。

モジュール概要からテストコードを自動生成できる

こちらを選択するとテストコードの確認画面が表示されます。

現在(2023/12/14 時点)、テストコードの自動生成はベータ機能となっています。 そのため「生成されたテストコードは実行前にレビューしてね」と注意書きがあります。

自動生成されたテストコードはレビュー必須との注意が表示される

注意内容を十分に理解して Confirm ボタンを押して待つこと 1,2 分、自作モジュールのテストコードが生成されました! このテストコードはブラウザ上でコピーすることも tar.gz ファイルとしてダウンロードすることも可能です。

数分でテストコードが自動生成される

このように、プライベートレジストリに登録した自作モジュールのテストコードを Terraform Cloud 上で簡単に生成することができました。

3. 自動生成されたテストコードの紹介

それでは、どのようなテストコードが自動生成されたのか見ていきましょう。

上述したとおり、自動生成はベータ機能ということもありレビュー必須(=手直しが必要)ということですが、果たしてどれくらい使えるのでしょうか。

3.1 使用したモジュール

今回はサンプルとして、みんな大好き AKS をデプロイする Terraform モジュールを用意しました。

3.1.1 main.tf

main.tf 内ではリソースグループ azurerm_resource_group と AKS クラスタ azurerm_kubernetes_cluster を作成しています。 また、ローカル変数で接尾辞を定義して各リソースの名前などで使い、いくつかのパラメータは変数を参照するようにしています。

provider "azurerm" {
  features {}
}

locals {
  suffix = "${var.author}-test"
}

resource "azurerm_resource_group" "rg" {
  name     = "rg-${local.suffix}"
  location = var.location
}

resource "azurerm_kubernetes_cluster" "aks" {
  name                    = "aks-${local.suffix}"
  location                = var.location
  resource_group_name     = azurerm_resource_group.rg.name
  dns_prefix              = "aks-${local.suffix}"
  local_account_disabled  = false
  private_cluster_enabled = var.private_cluster_enabled

  default_node_pool {
    name                        = "agentpool"
    node_count                  = var.node_count
    vm_size                     = var.vm_size
    temporary_name_for_rotation = "tmp"
    max_pods                    = 30
  }

  network_profile {
    network_plugin = "azure"
    service_cidr   = "172.17.0.0/16"
    dns_service_ip = "172.17.0.10"
  }

  identity {
    type = "SystemAssigned"
  }

  automatic_channel_upgrade = "patch"

  tags = {
    author = var.author
  }
}

3.1.2 outputs.tf

outputs.tf には作成した AKS の認証情報を kubeconfig に登録する Azure CLI コマンドを出力する output ブロックを定義しています。

output "aks_credential" {
  value       = "az aks get-credentials --resource-group ${azurerm_resource_group.rg.name} --name ${azurerm_kubernetes_cluster.aks.name}"
  description = "AKS 接続のための認証情報"
}

3.1.3 variables.tf

variables.tf では型、デフォルト値、validation ブロックによる入力チェックを意識して複数の変数を定義しています。

variable "author" {
  type        = string
  description = "AKS クラスタ作成者の名前"
}

variable "location" {
  type        = string
  description = "リージョン"
  default     = "japaneast"
}

variable "private_cluster_enabled" {
  type        = bool
  description = "AKS プライベートクラスタの有効化"
  default     = false
}

variable "node_count" {
  type        = number
  description = "デフォルトノードプールのノード数"
  default     = 1
}

variable "vm_size" {
  type        = string
  description = "VM サイズ"
  default     = "Standard_B2ms"

  validation {
    condition     = contains(["Standard_B2ms", "Standard_B4ms"], var.vm_size)
    error_message = "Only part of the B series is allowed: Standard_B2ms | Standard_B4ms."
  }
}

3.2 自動生成されたテストコード

上記のモジュールをプライベートレジストリに登録して自動生成すると次のテストコードが生成されました。

今回は main.tftest.hclvariables.tftest.hcl と 2 つのファイルになっています。 何度か試したのですが 1 つのファイルにテストコードがにまとめられて生成されるパターンもありました。 モジュール内で tf ファイルが増えていったときにテストコードがどのように生成されるのか気になりますね。

3.2.1 main.tftest.hcl

main.tftest.hcl ファイルには azurerm_resource_groupazurerm_kubernetes_cluster に対応するテストコードが生成されていました。

各リソースで設定しているパラメータがすべてテストされるのかと思いきや、AKS の local_account_disabled や private_cluster_enabled などはテストされていません。

# WARNING: Generated module tests should be considered experimental and be reviewed by the module author.

variables {
  author = "test_author"
  location = "japaneast"
  private_cluster_enabled = false
  node_count = 1
  vm_size = "Standard_B2ms"
}

run "resource_group_validation" {
  assert {
    condition     = azurerm_resource_group.rg.name == "rg-test_author-test"
    error_message = "incorrect resource group name"
  }

  assert {
    condition     = azurerm_resource_group.rg.location == "japaneast"
    error_message = "incorrect location"
  }
}

run "kubernetes_cluster_validation" {
  assert {
    condition     = azurerm_kubernetes_cluster.aks.name == "aks-test_author-test"
    error_message = "incorrect AKS name"
  }

  assert {
    condition     = azurerm_kubernetes_cluster.aks.location == "japaneast"
    error_message = "incorrect location"
  }

  assert {
    condition     = azurerm_kubernetes_cluster.aks.resource_group_name == "rg-test_author-test"
    error_message = "incorrect resource group name"
  }

  assert {
    condition     = azurerm_kubernetes_cluster.aks.dns_prefix == "aks-test_author-test"
    error_message = "incorrect DNS prefix"
  }

  assert {
    condition     = azurerm_kubernetes_cluster.aks.default_node_pool.node_count == 1
    error_message = "incorrect node count"
  }

  assert {
    condition     = azurerm_kubernetes_cluster.aks.default_node_pool.vm_size == "Standard_B2ms"
    error_message = "incorrect VM size"
  }

  assert {
    condition     = azurerm_kubernetes_cluster.aks.tags.author == "test_author"
    error_message = "incorrect author tag"
  }
}

run "output_validation" {
  assert {
    condition     = output.aks_credential == "az aks get-credentials --resource-group rg-test_author-test --name aks-test_author-test"
    error_message = "incorrect AKS credential output"
  }
}

3.2.2 variables.tftest.hcl

variables.tftest.hcl ファイルには variables.tf で定義した変数のテストが生成されていました。

validation ブロックを使った変数のテストにはデフォルト値が使われてしまっていました。 デフォルト値を指定してなければどのようなテストになっていたのでしょう?

# WARNING: Generated module tests should be considered experimental and be reviewed by the module author.

run "variables_validation" {
  assert {
    condition     = var.author == "test_author"
    error_message = "incorrect author variable"
  }

  assert {
    condition     = var.location == "japaneast"
    error_message = "incorrect location variable"
  }

  assert {
    condition     = var.private_cluster_enabled == false
    error_message = "incorrect private_cluster_enabled variable"
  }

  assert {
    condition     = var.node_count == 1
    error_message = "incorrect node_count variable"
  }

  assert {
    condition     = var.vm_size == "Standard_B2ms"
    error_message = "incorrect vm_size variable"
  }
}

3.2.3 (補足)テストフレームワークについて

Terraform テストフレームワークで使用されるテストコードについて簡単に紹介すると、variables ブロックでテスト用の変数を定義し、run ブロックで terraform plan または terraform apply した結果をチェックします。

run ブロック内の command フィールドに plan を設定することで terraform plan コマンドによるテストが実行されます。 apply を設定、もしくは command フィールドを指定しない場合はデフォルトの terraform apply コマンドが実行されます。

自動生成されたテストコードはいずれも command フィールドの記述がありませんでした。 テストで terraform apply してはいけないような場合はご注意ください。

developer.hashicorp.com

3.3 自動生成されたテストコードを使ってテスト

こちらのテストコードを無邪気に使ってモジュールをテストしてみます。

3.3.1 テスト 1 回目

$ terraform test
main.tftest.hcl... in progress
  run "resource_group_validation"... fail
╷
│ Error: the "dns_prefix" must begin and end with a letter or number, contain only letters, numbers, and hyphens and be between 1 and 54 characters in length, got "aks-test_author-test"
│ 
│   with azurerm_kubernetes_cluster.aks,
│   on main.tf line 27, in resource "azurerm_kubernetes_cluster" "aks":
│   27:   dns_prefix              = "aks-${local.suffix}"
│ 
╵
  run "kubernetes_cluster_validation"... skip
  run "output_validation"... skip
main.tftest.hcl... tearing down
main.tftest.hcl... fail
variables.tftest.hcl... in progress
  run "variables_validation"... fail
╷
│ Error: No value for required variable
│ 
│   on variables.tf line 1:
│    1: variable "author" {
│ 
│ The module under test for run block "variables_validation" has a required variable "author" with no set value. Use a -var or -var-file command line argument or
│ add this variable into a "variables" block within the test file or run block.
╵
variables.tftest.hcl... tearing down
variables.tftest.hcl... fail

Failure! 0 passed, 2 failed, 2 skipped.

エラーになってしまいました。 エラーの解消は本題ではありませんが、今回はどのような内容か追ってみます。

1 つ目のエラーは AKS の dns_prefix にアンダースコアが使われてるため発生しています。 テストで使用される変数の値を見てみると author = "test_author" という記述があります。 自動生成されたコードは AKS のパラメータでアンダースコアが使えないことは考慮されていないようです。

変数の値をハイフンを使った author = "test-author" に修正します。 あわせて condition 内の値もハイフンに修正しておきます。

2 つ目のエラーは variables.tftest.hcl に変数が設定されていないというものです。 テストフレームワークではテストコードのファイル毎に variables ブロックで変数を定義する必要がありますが、自動生成されたテストコードで変数定義が漏れていたようです。

今回は main.tftest.hclvariables ブロックを variables.tftest.hcl にコピペします。

3.3.2 テスト 2 回目

手直ししたテストコードを使って、再度テストしてみます。

$ terraform test
main.tftest.hcl... in progress
  run "resource_group_validation"... pass
  run "kubernetes_cluster_validation"... fail
╷
│ Error: Unknown variable
│ 
│   on main.tftest.hcl line 46, in run "kubernetes_cluster_validation":
│   46:     condition     = azurerm_kubernetes_cluster.aks.default_node_pool.node_count == 1
│ 
│ There is no variable named "azurerm_kubernetes_cluster".
╵
╷
│ Error: Invalid operation
│ 
│   on main.tftest.hcl line 46, in run "kubernetes_cluster_validation":
│   46:     condition     = azurerm_kubernetes_cluster.aks.default_node_pool.node_count == 1
│ 
│ Block type "default_node_pool" is represented by a list of objects, so it must be indexed using a numeric key, like .default_node_pool[0].
╵
╷
│ Error: Unknown variable
│ 
│   on main.tftest.hcl line 51, in run "kubernetes_cluster_validation":
│   51:     condition     = azurerm_kubernetes_cluster.aks.default_node_pool.vm_size == "Standard_B2ms"
│ 
│ There is no variable named "azurerm_kubernetes_cluster".
╵
╷
│ Error: Invalid operation
│ 
│   on main.tftest.hcl line 51, in run "kubernetes_cluster_validation":
│   51:     condition     = azurerm_kubernetes_cluster.aks.default_node_pool.vm_size == "Standard_B2ms"
│ 
│ Block type "default_node_pool" is represented by a list of objects, so it must be indexed using a numeric key, like .default_node_pool[0].
╵
  run "output_validation"... skip
main.tftest.hcl... tearing down
main.tftest.hcl... fail
variables.tftest.hcl... in progress
  run "variables_validation"... pass
variables.tftest.hcl... tearing down
variables.tftest.hcl... pass

Failure! 2 passed, 1 failed, 1 skipped.

またエラー……だけど進みました!

エラー内容を見てみると AKS の default_node_pool ブロックをうまく参照できていないようです。 default_node_pool ブロックは List 型なので参照時には default_node_pool[0] のようにインデックスの指定が必要なのですが、自動生成されたコードにはインデックスの指定がありませんでした。

3.3.3 テスト 3 回目

インデックスを修正して 3 度目の正直を信じて実行すると……。

$ terraform test
main.tftest.hcl... in progress
  run "resource_group_validation"... pass
  run "kubernetes_cluster_validation"... pass
  run "output_validation"... pass
main.tftest.hcl... tearing down
main.tftest.hcl... pass
variables.tftest.hcl... in progress
  run "variables_validation"... pass
variables.tftest.hcl... tearing down
variables.tftest.hcl... pass

Success! 4 passed, 0 failed.

無事にテストが成功しました!

ということで、今回生成されたテストコードはいくつか手直しすることでテストが通るようになりました。 また、テストに含まれていないパラメータもあるので個別に追加してあげる必要があります。

3.4 他の生成パターンの紹介

さきほどモジュールの内容とほぼ同じ別モジュールでテストコードを生成したところ、次のようなテストが生成されました。

# WARNING: Generated module tests should be considered experimental and be reviewed by the module author.

variables {
  author = "test_author"
}

(snip)

run "variables_validation" {
  assert {
    condition     = var.author == "test_author"
    error_message = "incorrect variable author"
  }
}

Please note that these tests are generated and should be reviewed by the module author.

末尾の Please note that these tests are generated and should be reviewed by the module author. というコメントもテストコードの一部として出力されてしまっていました。

これはさすがにエラーになってしまいました。

 $ terraform test
╷
│ Error: Unsupported block type
│ 
│   on main.tftest.hcl line 68:
│   68: Please note that these tests are generated and should be reviewed by the module author.
│ 
│ Blocks of type "Please" are not expected here.
╵
╷
│ Error: Invalid block definition
│ 
│   on main.tftest.hcl line 68:
│   68: Please note that these tests are generated and should be reviewed by the module author.
│ 
│ Either a quoted string block label or an opening brace ("{") is expected here.
╵

4. おわりに

今回は Terraform Cloud のテストコード自動生成を試してみました。

生成されたテストコードは手直しが必要ではありますが、テストコードを書くハードルが一気に下がったような感覚があります。

まだパブリックベータではありますがテストコード自動生成は Terraform Cloud の Plus プランのみ利用できる機能となっています。

パブリックベータ中は Free や Standard プランでも使えるようにして欲しいなぁという気持ちもありつつ、Plus プランを使える環境がある方はぜひモジュールのテストコードを自動生成して、どんどんテストしていってください!