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]
多用されるのは count や for_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