Aller au contenu
Infrastructure as Code medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

Boucles for Terraform : transformer les collections

14 min de lecture

logo terraform

Votre for_each crée 5 VMs, mais vous voulez un output qui liste leurs noms en majuscules, ou filtrer uniquement celles de l’environnement prod. Avec for_each seul, c’est impossible — il faut une couche de transformation.

Les expressions for de HCL sont cette couche. Elles ne créent pas de ressources — c’est le rôle de for_each — mais elles transforment des collections : construire une liste de noms, pivoter une map, filtrer par condition. On les retrouve surtout dans les locals et les outputs, mais elles sont utilisables partout où Terraform attend une valeur dérivée d’une collection. Sans cette compétence, chaque dérivation d’une valeur existante impose soit de la duplication, soit un output intermédiaire inutile. Les expressions for sont également le mécanisme pour alimenter un for_each avec des données transformées depuis une source brute.

  • for → liste : [for k, v in map : expression]
  • for → map : { for k, v in map : clé => valeur }
  • Filtre conditionnel : [for ... if condition]
  • Boucle sur une liste vs sur une map
  • Usages typiques : construire des noms, filtrer, transformer pour for_each

Pensez à la compréhension de liste en Python :

vms = { "web": 512, "db": 1024, "cache": 512 }
# Transfo 1 : liste de noms
noms = [f"lab-{k}" for k, v in vms.items()]
# ["lab-web", "lab-db", "lab-cache"]
# Transfo 2 : filtrer par valeur
backend = {k: v for k, v in vms.items() if v > 512}
# { "db": 1024 }
# Transfo 3 : map mémoire → liste de noms
noms_upper = [n.upper() for n in noms]
# ["LAB-WEB", "LAB-DB", "LAB-CACHE"]

Les expressions for de Terraform font exactement cela : transformer des collections sans créer des ressources.

La logique : « pour chaque élément de la collection, appliquer une transformation et en faire une liste ».

[for k, v in var.vms : "lab-${k}"] # ← Liste de noms construits
# ["lab-cache", "lab-db", "lab-web"]
[for name in var.hostnames : upper(name)] # ← Transformer une liste
# ["WEB", "DB", "CACHE"]

La logique : « construire une nouvelle map en utilisant une expression pour la clé et une pour la valeur »

{ for k, v in var.vms : k => v.memory } # ← Nouvelle map : clé => mémoire
# { "cache" = 512, "db" = 1024, "web" = 512 }

Ajouter une condition pour exclure certains éléments :

{ for k, v in var.vms : k => v if v.role == "backend" } # ← Seulement les VMs backend
# { "cache" = {...}, "db" = {...} }
variable "vms" {
type = map(object({ memory = number, role = string }))
default = {
"web" = { memory = 512, role = "frontend" }
"db" = { memory = 1024, role = "backend" }
"cache" = { memory = 512, role = "backend" }
}
}
locals {
# Liste de noms
vm_names_list = [for k, v in var.vms : "lab10-${k}"]
# Map nom => mémoire
vm_memory_map = { for k, v in var.vms : k => v.memory }
# Filtre : seulement les VMs backend
backend_vms = { for k, v in var.vms : k => v if v.role == "backend" }
# Transformer une liste
names_upper = [for name in local.vm_names_list : upper(name)]
# Construire des noms de disques
disk_names = { for k, v in var.vms : k => "lab10-${k}.qcow2" }
}

Sorties réelles :

backend_vms = {
"cache" = { memory = 512, role = "backend" }
"db" = { memory = 1024, role = "backend" }
}
disk_names = {
"cache" = "lab10-cache.qcow2"
"db" = "lab10-db.qcow2"
"web" = "lab10-web.qcow2"
}
names_upper = [
"LAB10-CACHE",
"LAB10-DB",
"LAB10-WEB",
]
vm_memory_map = {
"cache" = 512
"db" = 1024
"web" = 512
}
vm_names_list = [
"lab10-cache",
"lab10-db",
"lab10-web",
]

Quand la source est une liste (et non une map), la variable d’itération est l’élément (pas de clé/valeur) :

variable "envs" {
type = list(string)
default = ["dev", "staging", "prod"]
}
locals {
env_upper = [for env in var.envs : upper(env)]
# ["DEV", "STAGING", "PROD"]
env_names = [for env in var.envs : "vm-${env}"]
# ["vm-dev", "vm-staging", "vm-prod"]
}

Avec index dans la liste :

locals {
indexed = [for i, env in var.envs : "${i}: ${env}"]
# ["0: dev", "1: staging", "2: prod"]
}

Un usage très courant : transformer une liste en set utilisable par for_each. for_each n’accepte pas directement une liste (ou un tuple) — il exige une map ou un set. La fonction toset() fait cette conversion :

variable "hostnames" {
type = list(string)
default = ["web", "db", "cache"]
}
resource "libvirt_volume" "disk" {
for_each = toset(var.hostnames) # list → set pour for_each
name = "${each.key}.qcow2"
}

Ou avec une boucle for pour filtrer avant :

resource "libvirt_volume" "disk" {
for_each = toset([for h in var.hostnames : h if h != "cache"])
name = "${each.key}.qcow2"
}

Après un for_each, les ressources sont des maps. Une boucle for extrait les attributs souhaités :

output "vm_names" {
value = { for k, v in libvirt_domain.vm : k => v.name }
# { "web" = "lab10-web", "db" = "lab10-db", ... }
}
output "vm_ips" {
# Filtrer les VMs avec une IP assignée
value = { for k, v in libvirt_domain.vm : k => v.network_interface[0].addresses[0]
if length(v.network_interface[0].addresses) > 0 }
}
locals {
roles = { "web" = "frontend", "db" = "backend" }
# Inverser : valeur => clé
by_role = { for name, role in local.roles : role => name }
# { "frontend" = "web", "backend" = "db" }
}

Pour regrouper les valeurs avec la même clé en liste :

locals {
vms_by_role = {
for k, v in var.vms : v.role => k...
}
# { "frontend" = ["web"], "backend" = ["db", "cache"] }
}

L’opérateur ... (ellipsis) indique à Terraform d’accumuler les valeurs en liste quand des clés se répètent.

ExpressionRésultatUsage
[for x in list : f(x)]Liste transforméeDériver une liste
[for i, x in list : ...]Liste avec indexAccéder à la position
{ for k, v in map : k => f(v) }Map transforméeDériver une map
[for k, v in map : expr if cond]Liste filtréeSélectionner des entrées
{ for k, v in map : k => v if cond }Map filtréeFiltrer une map
{ for k, v in map : v => k... }Map groupéeInverser/grouper
SymptômeCause probableSolution
Error: duplicate map keyDeux éléments produisent la même cléUtiliser ... pour grouper, ou revoir l’expression de clé
for ne filtre pasCondition if mal placéeLa condition se place après l’expression, pas avant
Résultat en liste au lieu de mapCrochets [] au lieu d’accolades {}{ for ... } pour une map, [ for ... ] pour une liste
  1. [for x in col : expr] → liste ; { for k, v in col : k => v } → map
  2. if condition filtre les éléments — se place après l’expression
  3. Les boucles for transforment des données partout où Terraform attend une valeur — locals, outputs, argument for_each, etc.
  4. toset([for ...]) prépare une collection pour for_each
  5. v.role => k... groupe les valeurs avec la même clé en liste

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn