CLOVER🍀

That was when it all began.

TerraformでリソースやモジュールのOutputを一括で設定する

これは、なにをしたくて書いたもの?

Terraformを使っていて、

output "xxx" {
  value = resource_a.this.xxx
}

output "yyy" {
  value = resource_a.this.yyy
}

output "zzz" {
  value = resource_a.this.zzz
}

...

といった感じで、あるリソースやモジュールのOutputを頑張って並べていくのが面倒だなぁと思い…調べてみたら、
Terraform 0.12からこれを一括で設定できるようになっていたみたいです。

ドキュメントに書いてないじゃないですかっ、気づかないよ!

Output Values - Configuration Language - Terraform by HashiCorp

Resource and module object values

Release Notesなどによると、これを「Resource and module object values」と言い、リソースやモジュール全体を式においての
値として扱えるようになりました。

https://github.com/hashicorp/terraform/blob/v0.12.0/CHANGELOG.md

Release v0.12.0 · hashicorp/terraform · GitHub

Terraform 0.11→0.12で追加された新機能 | Developers.IO

使い方は、こんな感じですね。

output "foo_database" {
  value = mysql_database.foo
}

これまで以下のように属性をたくさん並べていたのを、一括でOutputとして設定するというやり方で良い時はだいぶ楽に
書くことができるようになります、と。

output "foo_database_name" {
  value = mysql_database.foo.name
}

output "foo_database_default_character_set" {
  value = mysql_database.foo.default_character_set
}

...

Release Notesをよく見ると、Output Variablesだけではなく、Input Variablesとして使えるということが書いてあります。

including passing them through input variables and output values to other modules, using an attribute-less reference syntax, like aws_instance.foo.

リソースのインスタンスをまるっと渡して参照できるということですね。モジュールを作る時に使うことがあったり
するでしょうか?覚えておきましょう。

ブログの方には、1行だけ書いてありました。

Generalized type system: use lists and maps more freely, and use resources as object values.

Announcing Terraform 0.12

わからん…。

自分は、TerraformのIssueを眺めていて0.12にこの機能が入っていそうだと気づき、0.12の新機能の情報を探して把握した
感じですね。

Improvement: Export all outputs of a sub module in the current module. Use the map key entries for generating separated output variables. · Issue #8554 · hashicorp/terraform · GitHub

Allow entire resource to be output · Issue #9067 · hashicorp/terraform · GitHub

まあ、紹介はこのくらいにして、使っていってみましょう。

環境

今回の環境は、こちら。

$ terraform version
Terraform v0.12.26
+ provider.mysql v1.9.0

ProviderとしてはMySQLを使って試すことにします。MySQL自体は、8.0で172.17.0.2で動作しているものとします。

リソースのOutputを一括で設定してみる

Terraformのリソース定義ファイルを、こんな感じで用意。
main.tf

terraform {
  required_version = "0.12.26"
}

provider "mysql" {
  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"

  version = "1.9.0"
}

resource "mysql_database" "this" {
  name = "my_database"

  default_character_set = "utf8mb4"
  default_collation     = "utf8mb4_ja_0900_as_cs_ks"
}

output "this_database" {
  value = mysql_database.this
}

リソースは、シンプルにひとつだけにしました。

resource "mysql_database" "this" {
  name = "my_database"

  default_character_set = "utf8mb4"
  default_collation     = "utf8mb4_ja_0900_as_cs_ks"
}

で、このリソースのOutputを一括で指定しています。valueに設定するのは、「リソース名.name」です。

output "this_database" {
  value = mysql_database.this
}

applyしてみましょう。

$ terraform apply

リソース作成後、Output Variablesがこんな感じで設定されます。

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

this_database = {
  "default_character_set" = "utf8mb4"
  "default_collation" = "utf8mb4_ja_0900_as_cs_ks"
  "id" = "my_database"
  "name" = "my_database"
}

MySQL Providerのmysql_databaseリソースでのOutput(Attribute)は4つなので、全部網羅されていますね。

MySQL: mysql_database - Terraform by HashiCorp

これは、欲しい時は便利な機能なので覚えておきましょう。

モジュールで試す

今度は、同じことをモジュールでやってみましょう。

こんな感じで、サブモジュールを作成。
modules/database/main.tf

variable "name" {
  type = string
}

resource "mysql_database" "this" {
  name = var.name

  default_character_set = "utf8mb4"
  default_collation     = "utf8mb4_ja_0900_as_cs_ks"
}

output "name" {
  value = mysql_database.this.name
}

output "default_character_set" {
  value = mysql_database.this.default_character_set
}

データベース名だけ変数にしたものですね。

Outputは、意図的に2つに絞っています。

output "name" {
  value = mysql_database.this.name
}

output "default_character_set" {
  value = mysql_database.this.default_character_set
}

このモジュールを利用する側。
main.tf

terraform {
  required_version = "0.12.26"
}

provider "mysql" {
  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"

  version = "1.9.0"
}

module "database" {
  source = "./modules/database"

  name = "my_database"
}

output "this_database" {
  value = module.database
}

モジュールのOutputに関しては、リソースと同じように「module.モジュール名」で一括で設定。

output "this_database" {
  value = module.database
}

apply。

$ terraform apply

すると、モジュールがOutputに指定している値が全部得られます。

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

this_database = {
  "default_character_set" = "utf8mb4"
  "name" = "my_database"
}

もちろん、モジュール側でリソースの属性を全部Outputに設定していれば

/*
output "name" {
  value = mysql_database.this.name
}

output "default_character_set" {
  value = mysql_database.this.default_character_set
}
*/

output "database" {
  value = mysql_database.this
}

モジュール利用側では、それが全部反映されます。

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

this_database = {
  "database" = {
    "default_character_set" = "utf8mb4"
    "default_collation" = "utf8mb4_ja_0900_as_cs_ks"
    "id" = "my_database"
    "name" = "my_database"
  }
}

まあ、モジュールが内部で利用しているリソースの属性を全部公開するかどうかは微妙なところもあるので、モジュール間の
データの受け渡しの時とかに活用できるといいのかな?と思いますが。

Remote Stateと組み合わせる

Remote Stateと組み合わせてみましょう。

Remote Stateのバックエンドには、Consuleを利用することにします。1.8.0のConsulを、172.17.0.3のサーバーに立てている
ものとします。

データベースの作成と、ユーザーと権限の作成をそれぞれ別々のTerraform管理単位として行ってみましょう。

まずは、データベースの作成側。
main.tf

terraform {
  required_version = "0.12.26"

  backend "consul" {
    address = "172.17.0.3:8500"
    scheme  = "http"
    path    = "terraform/state/mysql/database"
  }
}

provider "mysql" {
  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"

  version = "1.9.0"
}

resource "mysql_database" "this" {
  name = "my_database"

  default_character_set = "utf8mb4"
  default_collation     = "utf8mb4_ja_0900_as_cs_ks"
}

output "this_database" {
  value = mysql_database.this
}

mysql_databaseの属性は、全部Outputとして登録しておきます。

output "this_database" {
  value = mysql_database.this
}

Stateは、Consulに保存します。

terraform {
  required_version = "0.12.26"

  backend "consul" {
    address = "172.17.0.3:8500"
    scheme  = "http"
    path    = "terraform/state/mysql/database"
  }
}

apply。

$ terraform apply

続いて、ユーザーと権限を作る側。
main.tf

terraform {
  required_version = "0.12.26"

  backend "consul" {
    address = "172.17.0.3:8500"
    scheme  = "http"
    path    = "terraform/state/mysql/user"
  }
}

provider "mysql" {
  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"

  version = "1.9.0"
}

data "terraform_remote_state" "database" {
  backend = "consul"

  config = {
    address = "172.17.0.3:8500"
    scheme  = "http"
    path    = "terraform/state/mysql/database"
  }
}

resource "mysql_user" "admin_user" {
  user               = "adminuser"
  plaintext_password = "password"
  host               = "%"
}

resource "mysql_grant" "admin_user" {
  user       = "adminuser"
  host       = "%"
  database   = data.terraform_remote_state.database.outputs.this_database.name
  privileges = ["ALL"]

  depends_on = [mysql_user.admin_user]
}

データベースを作成した時のStateを、Data Sourceとして参照します。

data "terraform_remote_state" "database" {
  backend = "consul"

  config = {
    address = "172.17.0.3:8500"
    scheme  = "http"
    path    = "terraform/state/mysql/database"
  }
}

こちらを、データベース名の取得に利用。

resource "mysql_grant" "admin_user" {
  user       = "adminuser"
  host       = "%"
  database   = data.terraform_remote_state.database.outputs.this_database.name
  privileges = ["ALL"]

  depends_on = [mysql_user.admin_user]
}

apply。

$ terraform apply

これで、Remote StateをData Sourceとして扱った時も確認できました、と。

Sensitive?

ところで、この方法を取った時、sensitiveの設定はどうするんでしょうね?

sensitive — Suppressing Values in CLI Output

やっぱり、こんな感じにまるっと設定することになるんでしょうかね?

output "this_database" {
  value = mysql_database.this
  sensitive = true
}

apply時には、こういう出力結果になります。

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

this_database = <sensitive>

まあ、Stateに値が保存されていること自体は変わらない(コンソールに出力されないだけ)ので、そう困りはしない…かな?

モジュールのOutputをそのまま全部出力する場合は、モジュール側のOutputsにsensitive = trueがあっても、呼び出し側が
sensitiveを設定していないと意味ないので、そこは注意ですねぇ。