🎉

Terraform 1.3でoptional()によるobject attributes(variable)へのデフォルト値設定が楽になる!

2022/06/17に公開

tl;dr;

  • Terraform 1.3 で optional() modifier によるデフォルト値のセットがサポートされる予定
  • 現在 Terraform 1.3 Alpha リリースで試す事が可能、フィードバック募集中
    • 英語書くのしんどい場合は私に DM 投げてもらっても大丈夫です!
variable "with_optional_attribute" {
  type = object({
    a = string                # a required attribute
    b = optional(string)      # an optional attribute
    c = optional(number, 127) # an optional attribute with default value
  })
}

これまでの方法 (おさらい)

これまで Terraform v0.14, v0.15 で、それぞれ optional() modifierdefaults() function が実験的機能 (experimental support) として追加されてきました。

optional() modifier (v0.14~, experimental)

v0.14 で追加された optional() modifier を使うと、object type として定義されている variable のうち、任意の attribute を optional としてマークすることができます。

variable "with_optional_attribute" {
  type = object({
    a = string           # a required attribute
    b = optional(string) # an optional attribute
  })
}

この場合、b attribute は optional のため、指定がない場合は Terraform が自動的に null をセットします。これによって module 側で必要に応じて fallback 処理を行うことができますが、あまり理想的とは言えません。

variable の中では default を明示的に指定することもできます。

variable "with_optional_attribute" {
  type = object({
    a = string           # a required attribute
    b = optional(string) # an optional attribute
  })
  default = {
    a = "required"
    b = "optional"
  }
}

しかし上記のコードで得られる挙動は、a attribute のみに値をセットして呼び出した場合、default で定義されている object そのものが override されるため、結局 type の定義に fallback し、b には optional ではなく null がセットされます。

反対に、b attribute のみに値をセットして呼び出した場合、default で定義されている object そのものが override されるため、今度は required として定義されている a attribute がセットされていない、というエラーになってしまいます。

$ terraform plan -var='with_optional_attribute={"a"="foo"}'

Changes to Outputs:
  + with_optional_attribute = {
      + a = "foo"
      + b = null
    }
$ terraform plan -var='with_optional_attribute={"b"="foo"}'
╷
│ Error: Invalid value for input variable
│
│ The argument -var="with_optional_attribute=..." does not contain a valid value for variable "with_optional_attribute":
│ attribute "a" is required.
╵
╷
│ Error: Incorrect variable type
│
│   on main.tf line 5:
│    5: variable "with_optional_attribute" {
│
│ The resolved value of variable "with_optional_attribute" is not appropriate: attribute "a" is required.

こと object type に関しては、残念ながらああり使い勝手はよくありません。
例えば、ネストされた他の要素を含む object type の variable の場合、特定の1要素だけ値をカスタマイズして、他はデフォルト値のままで呼び出したい、というニーズを上手く満たすことができません。

defaults() function (v0.15~, experimental)

そこで v0.15 では、1つの案として defaults() function が追加されました。
これを使うと、variable 中で optional() としてマークした任意の object attribute が null であった場合に、指定したデフォルト値をセットすることができます。

test.tf
terraform {
  experiments = [module_variable_optional_attrs]
}

variable "with_optional_attribute" {
  type = object({
    attr_1 = string
    attr_2 = optional(bool) # an optional attribute
    attr_3 = map(
      object({
        nested_attr_1 = number
        nested_attr_2 = optional(string) # an optional attribute
      })
    )
  })
}

locals {
  with_optional_attribute = defaults(var.with_optional_attribute, {
    # If "attr_2" isn't set then it will default to true
    attr_2 = true

    # If _any_ of the map elements omit "nested_attr_2"
    # then this default will be used instead.
    attr_3 = {
      nested_attr_2 = "default value"
    }
  })
}

output "with_optional_attribute" {
  value = local.with_optional_attribute
}

上記は例ですが、このように別途 locals を用意し、その中で defaults() function を使い、optional() でマークされている attribute に対して、null であった時のデフォルト値を与えています。

terraform.tfvars
with_optional_attribute = {
    attr_1 = "required value"

    attr_3 = {
        "foo" = {
            nested_attr_1 = 100
            nested_attr_2 = "custom value"
        }
        "bar" = {
            nested_attr_1 = 200
        }
    }
}

そこに対して上記のような input variable を与えると、output は下記のようになります。

output
Changes to Outputs:
  + with_optional_attribute = {
      + attr_1 = "required value"
      + attr_2 = true
      + attr_3 = {
          + "bar" = {
              + nested_attr_1 = 200
              + nested_attr_2 = "default value"
            }
          + "foo" = {
              + nested_attr_1 = 100
              + nested_attr_2 = "custom value"
            }
        }
    }

このように、attr_2nested_attr_2 が正しく定義したデフォルト値をとっている事が分かります。

しかし、ご覧いただいた通り、定義そのものが冗長になってしまいますし、あまり直感的ではありません。力技感があって、積極的に自分のモジュールで使いたいかと言われると、うーん… となるかもしれません。

新機能: optional() でデフォルト値を一緒にセットできるようになる

この新機能を使うと、先程の defaults() による例は、下記のようにシンプルに書くことができます。

test.tf
terraform {
  experiments = [module_variable_optional_attrs]
}

variable "with_optional_attribute" {
  type = object({
    attr_1 = string
    # an optional attribute with default value
    attr_2 = optional(bool, true)
    attr_3 = map(
      object({
        nested_attr_1 = number
        # an optional attribute with default value
        nested_attr_2 = optional(string, "default value")
      })
    )
  })
}

output "with_optional_attribute" {
  value = var.with_optional_attribute
}

これで先程の例と全く同じ output が得られるはずです。よさそう!
variable type が object のコレクションであるような、もう少し複雑な場合でも同様に対応できます。

この例では、attr_2 にネストされた object が含まれますが、そのネストされた object の attributes に対しても optional() 使ってデフォルト値を定義することができます。

test.tf
terraform {
  experiments = [module_variable_optional_attrs]
}

variable "with_optional_attribute" {
  type = list(
    object({
      attr_1 = bool
      attr_2 = optional(object({
        nested_attr_1 = optional(number, 1)
        nested_attr_2 = optional(string, "default value")
      }), {})
    })
  )
}

output "with_optional_attribute" {
  value = var.with_optional_attribute
}
terraform.tfvars
with_optional_attribute = [
    {
        attr_1 = true
    },
    {
        attr_1 = false
        attr_2 = {
            nested_attr_1 = 100
        }
    }
]
output
Changes to Outputs:
  + with_optional_attribute = [
      + {
          + attr_1 = true
          + attr_2 = {
              + nested_attr_1 = 1
              + nested_attr_2 = "default value"
            }
        },
      + {
          + attr_1 = false
          + attr_2 = {
              + nested_attr_1 = 100
              + nested_attr_2 = "default value"
            }
        },
    ]

Discussion