★★ 中級

06. 式と関数

HCL は単なる「設定書き」ではなく、ちゃんとした が書けます。三項演算子・for 式・splat・組み込み関数。これらを使えると、コードがぐっと「動く」ようになります。

演算子

カテゴリ演算子
算術+ - * / %
比較== != < <= > >=
論理&& || !
count       = var.is_prod ? 3 : 1
total_size  = var.disk_count * var.disk_size
allow_admin = var.environment == "dev" || var.allow_admin_in_prod

条件式(三項演算子)

condition ? true_value : false_value

# 例
instance_type = var.environment == "prd" ? "t3.large" : "t3.micro"

# null を使った「条件付き引数」のテクニック
backup_retention = var.is_prod ? 30 : null    # null は「未指定」と同じ扱い

for 式

コレクションを変換/フィルタする式。Python の内包表記に似ています。

list を変換(list 内包)

[for s in var.azs : upper(s)]
# input:  ["ap-northeast-1a", "ap-northeast-1c"]
# output: ["AP-NORTHEAST-1A", "AP-NORTHEAST-1C"]

map を変換(object 内包)

{ for k, v in var.tags : k => upper(v) }
# input:  { Env = "dev", Project = "x" }
# output: { Env = "DEV", Project = "X" }

フィルタ(if 句)

[for s in aws_subnet.all : s.id if s.tags.Tier == "public"]
# 「Tier タグが public のサブネットの ID だけ」を抜き出す

map → list、list → map への変換

# list of object → key 付き map に
{
  for s in var.subnets :
  s.name => s
}

# map → list(キー込み)
[for k, v in var.tags : "${k}=${v}"]

splat 式

list / set の各要素から同じ属性を取り出すショートカット。for 式の特殊形と思って OK。

aws_subnet.public[*].id
# ↑ は次と同じ
[for s in aws_subnet.public : s.id]

多用されるのは countfor_each で複数作ったリソースの ID をまとめて取りたい時です。

文字列テンプレートディレクティブ

ヒアドキュメント等の文字列内で、分岐や反復 を書ける構文。%{ ... } がディレクティブ。

user_data = <<-EOT
  #!/bin/bash
  echo "Hello, ${var.name}"

  %{ if var.install_nginx ~}
  yum install -y nginx
  systemctl enable --now nginx
  %{ endif ~}

  %{ for ip in var.allow_ips ~}
  iptables -A INPUT -s ${ip} -j ACCEPT
  %{ endfor ~}
EOT

~} は前後の改行・空白を削除する印。テンプレ出力をきれいに保つのに使います。

組み込み関数(カテゴリ別)

関数は 9 カテゴリ に整理されています。よく使うものを抜粋:

文字列

format("%s-%03d", "node", 7)   # "node-007"
upper("abc")                    # "ABC"
lower("ABC")                    # "abc"
join("-", ["a", "b"])           # "a-b"
split(",", "a,b,c")             # ["a", "b", "c"]
replace("a.b.c", ".", "_")      # "a_b_c"
trimspace("  x  ")              # "x"
substr("hello", 1, 3)           # "ell"

数値

max(1, 2, 3)   # 3
min(1, 2, 3)   # 1
abs(-5)        # 5
ceil(4.2)      # 5
floor(4.8)     # 4

コレクション

length(["a", "b"])             # 2
contains(["a", "b"], "a")      # true
keys({a = 1, b = 2})           # ["a", "b"]
values({a = 1, b = 2})         # [1, 2]
merge({a = 1}, {b = 2})        # {a = 1, b = 2}
concat([1,2], [3,4])           # [1,2,3,4]
distinct([1, 1, 2])            # [1, 2]
flatten([[1,2], [3]])          # [1, 2, 3]
zipmap(["a","b"], [1,2])       # {a = 1, b = 2}

エンコード/パース

jsonencode({ a = 1 })           # "{\"a\":1}"
jsondecode("{\"a\":1}")         # { a = 1 }
yamlencode({ a = 1 })           # "a: 1\n"
base64encode("hello")           # "aGVsbG8="

ファイル

file("./script.sh")                                # 中身を文字列で
fileexists("./local-only.txt")                     # true / false
fileset("./html", "**/*.html")                     # マッチするファイル名 set
filemd5("./script.sh")                             # ファイルのハッシュ
templatefile("./user_data.tpl", { name = "web" })  # 変数差し込み

ハッシュ・暗号

md5("abc")
sha256("abc")
base64sha256("abc")
uuid()    # ランダム UUID(注意: 毎回変わるので state ドリフトの原因になる)

日時

timestamp()                              # "2026-05-10T12:00:00Z"
formatdate("YYYY-MM-DD", timestamp())    # "2026-05-10"
timeadd("2026-05-10T00:00:00Z", "24h")

IP / CIDR

cidrsubnet("10.0.0.0/16", 8, 1)   # "10.0.1.0/24"
cidrhost("10.0.0.0/16", 5)        # "10.0.0.5"
cidrnetmask("10.0.0.0/24")        # "255.255.255.0"

型変換・try/can

tostring(123)
tonumber("3.14")
tolist(set)
toset(list)

try(var.subnets[0].cidr, "10.0.0.0/24")  # 失敗時は第 2 引数
can(cidrnetmask(var.cidr))                # 評価できれば true(validation で便利)

terraform console で試す

関数や式の挙動は、terraform console で対話的に試せます。コードに書く前に必ず確認できる、地味だけど超便利なコマンド。

$ terraform console
> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"
> merge({a=1}, {b=2})
{
  "a" = 1
  "b" = 2
}
> var.environment
"dev"
> exit