Terraform の for 式は、コレクション型(list/map/set/tuple/object)を別の形に変換する表現手段です。HCL の式として評価され、リソースそのものを増やすのではなく、入力・中間データを整形します。
Associate 試験では、構文の差異(list 生成と map 生成)、if でのフィルタ、インデックスの扱い、重複キー時の挙動、for_each との役割の違いが頻出です。ここでは、安定した公式挙動に基づき、実務でもすぐ使える形で整理します。
for 式は「コレクションを走査して、新しいコレクションを作る」ための式です。list を作る場合は角括弧、map を作る場合は波括弧を使います。if 句を末尾に置くと要素をフィルタできます。評価は式として行われ、リソース作成数には影響しません。
代表的構文は以下です。list 生成: [for v in xs : EXPR if COND]、map 生成: { for k, v in m : NEW_K => NEW_V if COND }。list 走査時も [for i, v in xs : ...] のようにインデックスを併記可能です。出力の順序は list では安定、map ではキー順は未定義ですが values() で取り出すと list として順序が決まります。
| 機能 | 主な用途 | 出力/効果 | フィルタ可否 |
|---|---|---|---|
| for 式 | 入力データの整形(list/map の生成・変換) | 新しいコレクションを返す(リソース数は増えない) | 可能(if 条件) |
| スプラット演算子 (x.*.attr) | 同種属性の抽出を簡潔に書く | list を返す | 不可(条件は別途扱う) |
| for_each(メタ引数) | 複数リソース/モジュールの作成 | 複数リソースを実体化 | 間接的(事前に map/list を整形) |
for 式の流れ
基本構文の例(list 変換・map 生成・フィルタ)
variable "names" { type = list(string) }
locals {
upper_list = [for n in var.names : upper(n)]
name_len = { for n in var.names : n => length(n) }
only_long = [for n in var.names : n if length(n) > 3]
}
output "upper_list" { value = local.upper_list }
output "name_len" { value = local.name_len }
output "only_long" { value = local.only_long }リストから別のリストを作る場合は [for v in list : EXPR] を基本に、必要に応じて [for i, v in list : ...] でインデックスも利用します。空文字などを除外したいときは if を併用します。
入れ子のリストを平坦化したいときは for 式で内側も展開し、最後に flatten() を適用します。順序を保持したまま変換できる点が for 式の強みです。
リストの整形(インデックス利用・平坦化)
variable "roles" { type = list(string) default = ["web", "db", "", "cache"] }
locals {
upper_roles = [for i, r in var.roles : "${i}-${upper(r)}" if length(r) > 0]
ports_nested = [[80, 443], [8080], []]
ports_flat = flatten([for ps in local.ports_nested : [for p in ps : p]])
}
output "upper_roles" { value = local.upper_roles }
output "ports_flat" { value = local.ports_flat }map 生成は { for k, v in m : NEW_K => NEW_V } で記述します。キーや値の正規化(trimspace、lower/upper、置換)や、条件に合わないペアの除外が定石です。キー重複は計画時にエラーとなるため、distinct() などで事前にユニーク化するか、キー設計を見直します。
list から map を作るときは { for o in list : o.name => o.id } のように特定フィールドをキーにします。values(map) で値だけの list を得られる点も覚えておくと、後段の list 変換に繋げられます。
map の正規化と list→map 変換
variable "tags" { type = map(string) }
variable "servers" {
type = list(object({ name = string, id = string }))
}
locals {
normalized_tags = {
for k, v in var.tags : lower(trimspace(k)) => trimspace(v)
if v != null && trimspace(v) != ""
}
server_map = { for s in var.servers : s.name => s.id }
server_ids_doubled = [for id in values(local.server_map) : "srv-${id}"]
}
output "normalized_tags" { value = local.normalized_tags }
output "server_map" { value = local.server_map }
output "server_ids_doubled" { value = local.server_ids_doubled }for 式の if は、その要素を出力に含めるかどうかの判定です。条件が false の要素は「生成されない」ため、list では要素が詰められ、map ではキーごと欠落します。null は自動では落ちないため、必要なら明示的に条件に含めます。
プラン時に未知(unknown)の値が含まれる場合、if の評価結果も unknown になり得ます。その場合は適用時に最終的なフィルタが決まり、計画に「(known after apply)」が現れます。Associate レベルでは、この評価タイミングの概念を押さえておけば十分です。
null と unknown を考慮したフィルタ
variable "users" {
type = list(object({ name = string, active = bool }))
}
locals {
active_names = [for u in var.users : u.name if try(u.active, false)]
non_empty = [for s in ["a", "", "b", null] : s if s != null && s != ""]
kept_nulls = [for s in ["a", "", "b", null] : s] # 条件がなければ null は残る
}
output "active_names" { value = local.active_names }
output "non_empty" { value = local.non_empty }
output "kept_nulls" { value = local.kept_nulls }for 式は「入力データの整形」に集中させ、実リソースの増減は resource/module の for_each に任せるのが基本です。dynamic ブロックでは for_each に与える list/map を、事前に for 式で組み立てておくと安全です。
実務では、セキュリティグループのルール、タグ、ラベル、IAM ポリシー文など、構造化データを for 式で正規化→dynamic/for_each で展開、が再現性の高いパターンです。
for 式で整形 → dynamic で展開(例: SG ルール)
variable "ingress_rules" {
type = list(object({ port = number, cidr = string }))
}
locals {
ingress_norm = [for r in var.ingress_rules : {
port = r.port
cidr = trimspace(r.cidr)
} if r.cidr != null && trimspace(r.cidr) != ""]
}
resource "aws_security_group" "example" {
name = "example"
description = "example"
vpc_id = "vpc-xxxxxxxx"
dynamic "ingress" {
for_each = local.ingress_norm
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = [ingress.value.cidr]
}
}
}
構文差異([ ... ] と { ... })、if の位置、インデックスの取得、Duplicate key、values()/keys() の併用、flatten と二重 for の組み合わせは頻出です。特に map 生成でのキー重複エラーは定番のひっかけです。
スプラット演算子は簡潔ですが、条件付き加工やキー生成はできません。条件や複雑な加工が必要なときは for 式を選ぶ、という切り分けを覚えておきましょう。
安全なパターン集(重複回避・キー/値抽出)
locals {
# 重複キー回避(ユニーク化してから map 生成)
uniq = distinct(["a", "b", "a"]) # => ["a", "b"]
m = { for v in local.uniq : v => upper(v) } # キーは一意
# map の値リストを加工
doubled = [for v in values({ a = 1, b = 2 }) : v * 2] # => [2, 4]
# zipmap で list から map を合成
keys_ = ["env", "app"]
vals_ = ["prod", "api"]
labels = zipmap(keys_, vals_) # => { env = "prod", app = "api" }
}
output "m" { value = local.m }
output "doubled" { value = local.doubled }
output "labels" { value = local.labels }Associate
問題 1
次の for 式の評価結果として正しいものはどれですか。 variable "roles" { type = list(string) default = ["web", "db", "", "cache"] } locals { upper_roles = [for i, r in var.roles : "${i}-${upper(r)}" if length(r) > 0] } output "upper_roles" { value = local.upper_roles }
正解: B
list 生成の for 式で [for i, r in ... : ... if length(r) > 0] としているため、空文字は除外されます。出力は list で、各要素は "インデックス-大文字化" の形式となり、結果は ["0-WEB", "1-DB", "3-CACHE"] です。map にはならないため D は誤りです。
for 式と resource の for_each は何が違いますか?
for 式は値(list/map 等)を生成・変換する式で、リソース数は増えません。for_each はメタ引数で、与えた map/list に応じてリソース(またはモジュール)を複製します。前者はデータ整形、後者は展開という役割分担です。
リスト走査でインデックスを使うには?
list に対して [for i, v in var.list : EXPR] のように 2 つ目の識別子を置くと i に 0 始まりのインデックスが入ります。map の場合は [for k, v in var.map : ...] で k がキーです。
map 生成でキーが重複するとどうなりますか?
重複キーは計画時に Duplicate key エラーになります。distinct() で事前にユニーク化する、キー設計を見直す、あるいはキーの一部にインデックスや名前空間を付与するなどで回避します。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
Terraform HCL 構文の基礎:Block / Attribute / Expression を正しく使い分ける
Terraform Associate で頻出の HCL 構文を、ブロック・属性・式の3視点で整理。実務で迷いがちな書き...
Terraform Authoring & Ops Pro: 上位資格の範囲と対策
上位レベルを想定したTerraformの設計・運用ドメインを整理し、実務で通用する対策を提示。モジュール設計、ステート運...
Terraform Providers の基本: プラグイン型アーキテクチャを正しく使いこなす
Associate レベルで押さえるべき Provider の基礎、インストール、バージョニング、認証、エイリアス運用を...
Terraform Resourceブロック徹底ガイド: 最小単位のリソース定義
Associateレベルで押さえるべきResourceブロックの構造、依存関係、メタ引数、ライフサイクル制御を実務目線で...
Terraform Data Source徹底理解:既存リソースの参照で壊さず足す
Terraform Associate向けに、Data Sourceを用いた既存リソース参照の基本、選択基準、評価順序、...