★ 初級

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 として明示