04. 型
Terraform は型を持っています。値を厳密に書ける言語ほど、apply 前にミスを気付けます。型の階層を 1 度頭に入れると、variable の宣言や data の出力を読むのが格段に楽になります。
型の全体像
type
├── string / number / bool ← プリミティブ
├── list(T) / set(T) / map(T) ← コレクション(中身は同じ型)
├── tuple([T1, T2, ...]) ← 構造型(順序固定、要素ごとに型違い OK)
├── object({a=T1, b=T2, ...}) ← 構造型(名前付き、属性ごとに型違い OK)
└── any ← 何でも(型推論を投げる)
プリミティブ型
# 3 種類のプリミティブ型を 1 行ずつ宣言した例
# 値の例はコメント側に書いてある("hello" / 30 / true 等を受け取る変数)
variable "name" { type = string } # "hello"
variable "size" { type = number } # 30, 6.5
variable "enabled" { type = bool } # true / false
文字列と数値は自動変換されます("15" → 15)。これが効きすぎて気付かないバグを生むこともあるので、type は明示しておくのが吉。
コレクション型(list / set / map)
list(T)
順序付きの並び。インデックス(0 始まり)でアクセス。
# list(string) = 「string 型の要素を順序付きで複数持つ」型
variable "azs" {
type = list(string)
default = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
}
# 添字でアクセス(0 始まり)
# var.azs[0] == "ap-northeast-1a"
set(T)
順序なし、重複なし。for_each や、aws_iam_role_policy_attachment のような「一意な集合」を扱う時に便利。
# set(string) = 「string 型の要素を順序なし・重複なしで持つ」型
# for_each に渡すコレクションは set または map である必要があるため重要
variable "users" {
type = set(string)
default = ["alice", "bob", "carol"]
}
map(T)
キー(string)と値(T 型)のペア。タグなどでよく使う。
# map(string) = キー(string)×値(string)の組をいくつも持つ型
# 値の型は全部同じ。タグや設定値の組み合わせで頻出
variable "tags" {
type = map(string)
default = {
Project = "hcl-guide"
Env = "dev"
}
}
# キー名でアクセス
# var.tags["Project"] == "hcl-guide"
構造型(object / tuple)
コレクション型は「中身が全部同じ型」が前提でした。属性ごとに違う型を持つときは 構造型 を使います。
object
# object({...}) = 「属性ごとに型が違う」構造体
# 各属性の名前と型を { } の中で宣言する
variable "user" {
type = object({
name = string
age = number
is_admin = bool
languages = list(string)
})
default = {
name = "Alice"
age = 30
is_admin = true
languages = ["ja", "en"]
}
}
# ドット記法で属性にアクセス
# var.user.name == "Alice"
tuple
順序固定の異種コレクション。要素数も型も完全に決まっている。
# tuple([T1, T2, T3]) = 「位置ごとに型が違う」固定長の配列
# 下の例は「1 番目 number, 2 番目 number, 3 番目 string」を必ず持つ
variable "coord" {
type = tuple([number, number, string])
default = [35.6812, 139.7671, "Tokyo"]
}
# 添字でアクセス(属性名は無いので 0, 1, 2 で指定)
# var.coord[0] == 35.6812
実務では tuple よりも object のほうが自己説明的で使われやすいです。
optional() で省略可能属性
object 型の属性を「省略してよい」と宣言できます。デフォルト値も指定可能。
# list(object(...)) は「object 型の要素を複数並べた」型
# optional(TYPE, DEFAULT) で「省略可能、省略時は DEFAULT を使う」と宣言できる
variable "subnets" {
type = list(object({
cidr_block = string
availability_zone = string
is_public = optional(bool, false) # 省略時 false(明示しなくて OK)
tags = optional(map(string)) # 省略時 null(デフォルト未指定)
}))
}
# 呼び出し側(tfvars 等)はこう書ける
# subnets = [
# { cidr_block = "10.0.1.0/24", availability_zone = "ap-northeast-1a", is_public = true },
# { cidr_block = "10.0.2.0/24", availability_zone = "ap-northeast-1c" } ← is_public 省略
# ]
よく効く場面
モジュールの入力変数で、「ほぼデフォルトでよくて、たまに上書きしたい」 パターンに最適。
optional() がないと、呼び出し側にすべての属性を書かせる羽目になる。
any(最終手段)
型推論を放棄するキーワード。便利そうに見えますが、型エラーを apply まで遅延させるだけ なので公式は控えめにと言っています。
# any 型 = 「どんな値でも受け入れる」
# 型チェックを諦めるので、validation や apply 時にしかエラーが出ない
variable "config" {
type = any
}
使うべき場面: 別システムにそのまま JSON で流すような、「中身を Terraform で見ない」値。
型変換
近い型は自動変換されます。明示的に変換したい時は関数で:
# 明示的に型を変換する関数群(自動変換が効かないところで使う)
tostring(123) # "123" ← 数値を文字列に
tonumber("3.14") # 3.14 ← 文字列を数値に
tobool("true") # true ← 文字列を真偽値に
tolist(["a", "b"]) # list(string) ← 順序付きの list として明示
toset(["a", "a"]) # set(string) ← 重複排除(同じ要素が消えて 1 つになる)
tomap({a = 1, b = 2}) # map(number) ← オブジェクトを map として明示