
Une expression for en HCL transforme une collection en une autre : filtrer
une liste, pivoter une map, regrouper par clé, croiser deux niveaux. Elle ne crée
aucune ressource, c'est le rôle de for_each ; elle dérive une valeur à
partir d'une collection existante, et on la trouve surtout dans les locals et
les outputs.
Ce guide part de la base, la syntaxe et la distinction tuple / object, puis
couvre les formes avancées et les deux pièges qui coûtent le plus cher : le
splat sur une map, qui ne lève aucune erreur, et le regroupement par
ellipsis, mal connu. Tous les exemples ont été vérifiés sur Terraform
v1.15.4 dans terraform console, avec le seul provider local.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Pourquoi
[ ]produit un tuple et{ }un object, sans confusion possible - Filtrer avec une clause
if, sur la clé comme sur la valeur - Regrouper les valeurs d'une clé partagée avec l'ellipsis
... - Croiser deux niveaux avec des boucles imbriquées et
flatten - Le piège du splat
[*]appliqué à une map - Ce qu'une expression
forne peut pas faire, et quand passer àdynamic
Prérequis
Section intitulée « Prérequis »- Types et collections (déclarer des ressources Terraform)
- Le meta-argument
for_each(for_each Terraform) - Terraform 1.15.x, la série stable courante
Tester une expression sans écrire de ressource
Section intitulée « Tester une expression sans écrire de ressource »terraform console évalue une expression en trois secondes, sans apply ni
ressource. C'est l'outil pour éprouver un for avant de le poser dans un
local. Toutes les sorties de ce guide en proviennent, avec cette donnée de
travail :
locals { equipes = { alpha = { pole = "produit", effectif = 4, competences = ["go", "k8s"] } bravo = { pole = "produit", effectif = 6, competences = ["go"] } delta = { pole = "data", effectif = 3, competences = [] } }}terraform consoleTuple ou object : la forme des délimiteurs décide
Section intitulée « Tuple ou object : la forme des délimiteurs décide »C'est la distinction fondatrice du sujet. Des crochets [ ] produisent un
tuple, des accolades { } produisent un object. Terraform ne confond jamais
les deux, et la fonction type() le prouve :
> type([for k, v in local.equipes : k])tuple([string, string, string])
> type({for k, v in local.equipes : k => v.effectif})object({alpha: number, bravo: number, delta: number})La forme object porte une flèche clé => valeur que le tuple n'a pas. Cette
distinction n'est pas cosmétique : for_each exige une map ou un set, jamais un
tuple, et c'est la source d'erreur numéro un quand on alimente un for_each
depuis un for.
Filtrer avec une clause if
Section intitulée « Filtrer avec une clause if »La forme la plus courante extrait une sous-liste. On itère la map et on ne garde
que ce qu'une clause if, placée après l'expression, laisse passer :
> [for k, v in local.equipes : k if v.pole == "produit"][ "alpha", "bravo",]Deux points à retenir. L'ordre de sortie n'est pas aléatoire : une expression
for sur une map itère les clés triées lexicalement, inutile de réordonner.
Et la condition voit les deux variables d'itération, la clé comme la valeur,
on peut donc filtrer sur la clé :
> [for k, v in local.equipes : k if k != "delta"][ "alpha", "bravo",]Construire une map
Section intitulée « Construire une map »Avec des accolades et une flèche, la même itération produit un object. La clé et la valeur sont libres :
> {for k, v in local.equipes : k => v.effectif}{ "alpha" = 4 "bravo" = 6 "delta" = 3}Regrouper avec l'ellipsis
Section intitulée « Regrouper avec l'ellipsis »Que se passe-t-il quand deux entrées produisent la même clé ? Par défaut,
Terraform refuse. alpha et bravo appartiennent toutes deux au pôle
produit :
> {for k, v in local.equipes : v.pole => k}
Error: Duplicate object key
Two different items produced the key "produit" in this 'for' expression. Ifduplicates are expected, use the ellipsis (...) after the value expression toenable grouping by key.Le message d'erreur donne lui-même la solution : l'ellipsis ... après
l'expression de valeur active le mode groupement. Chaque clé porte alors la
liste des valeurs correspondantes :
> {for k, v in local.equipes : v.pole => k...}{ "data" = [ "delta", ] "produit" = [ "alpha", "bravo", ]}L'ellipsis n'existe qu'avec des accolades. L'appliquer à un tuple échoue :
> [for k, v in local.equipes : k...]
Error: Invalid 'for' expressionGrouping ellipsis (...) cannot be used when building a tuple.C'est logique : grouper n'a de sens que par clé, donc dans un object.
Croiser deux niveaux : boucles imbriquées et flatten
Section intitulée « Croiser deux niveaux : boucles imbriquées et flatten »Dès qu'il faut combiner deux dimensions, chaque équipe et chacune de ses
compétences, on imbrique deux for. Le résultat est une liste de listes, qu'on
aplatit avec flatten :
> flatten([for k, v in local.equipes : [for comp in v.competences : "${k}:${comp}"]])[ "alpha:go", "alpha:k8s", "bravo:go",]Notez delta : sa liste de compétences est vide, la boucle interne ne produit
rien, et flatten l'absorbe. Aucune entrée delta: dans le résultat, sans une
seule clause if pour l'exclure. C'est le motif standard pour dérouler des
paires (serveurs × tags, régions × sous-réseaux) vers un for_each.
Le splat, et son piège sur une map
Section intitulée « Le splat, et son piège sur une map »Terraform propose une forme courte, le splat [*], qui remplace un for
simple sur une liste : var.liste[*].id équivaut à [for o in var.liste : o.id]. Pratique, mais réservé aux listes.
Sur une map, le splat devient un piège, parce qu'il ne lève aucune erreur. Il enveloppe la map entière dans un tuple d'un seul élément :
> length(local.equipes[*])11, et non 3. La configuration continue de valider, puis casse plus loin, là
où on attendait trois entrées. Une ressource pilotée par for_each étant
elle-même une map, le splat lui est tout aussi inadapté.
Valider une collection entière
Section intitulée « Valider une collection entière »Combinée à alltrue, une expression for vérifie une propriété sur chaque
élément d'une collection, ce qu'une comparaison simple ne sait pas faire :
> alltrue([for k, v in local.equipes : v.effectif > 0])trueC'est le motif pour valider une variable de type complexe : alltrue rend true
seulement si la condition tient pour toutes les entrées. Son pendant anytrue
teste l'existence d'au moins un élément conforme.
Alimenter un for_each depuis un for
Section intitulée « Alimenter un for_each depuis un for »C'est le cas d'usage qui relie ce guide à for_each. Comme for_each exige une
map ou un set de chaînes, on lui passe une expression for filtrée :
resource "local_file" "fiche" { for_each = { for k, v in local.equipes : k => v if v.effectif >= 4 }
filename = "${path.module}/${each.key}.txt" content = each.value.pole}Deux erreurs classiques ici. Passer un tuple ([for ...]) échoue :
for_each répond « the for_each argument must be a map, or set of strings ».
Passer un toset de nombres échoue aussi : « for_each supports maps and sets of
strings, but you have provided a set containing type number ». La restriction
aux chaînes est réelle.
Ce qu'une expression for ne sait pas faire
Section intitulée « Ce qu'une expression for ne sait pas faire »Une expression for ne génère que des valeurs de collection. Elle ne peut
pas produire des blocs de configuration imbriqués. Pour répéter un bloc
ingress, setting ou network_interface à l'intérieur d'une ressource, c'est
un bloc dynamic qu'il faut, pas un for. La documentation officielle est
explicite : « For expressions can only generate collection values, and cannot
generate nested configuration blocks. Use dynamic blocks instead. » Confondre
les deux est une impasse fréquente.
Dépannage
Section intitulée « Dépannage »Ces erreurs se lisent toutes au moment du plan ou dans terraform console. Le
tableau donne le message exact relevé sur v1.15.4, sa cause, puis la correction.
| Message | Cause | Solution |
|---|---|---|
Error: Duplicate object key | Deux entrées produisent la même clé dans un { for ... } | Ajouter l'ellipsis ... après la valeur pour grouper |
Grouping ellipsis (...) cannot be used when building a tuple | Un ... dans un [ for ... ] | Le groupement n'existe qu'avec des accolades |
the "for_each" argument must be a map, or set of strings | Un tuple ou une liste passés à for_each | Produire une map { for ... } ou un toset() de chaînes |
"for_each" supports maps and sets of strings, but you have provided a set containing type number | Un toset([1,2,3]) | Convertir en chaînes, ou utiliser une map |
| Une longueur inattendue de 1 | Un splat [*] appliqué à une map | Remplacer par une expression for explicite |
À retenir
Section intitulée « À retenir »- Crochets
[ ]produisent un tuple, accolades{ }un object. La forme des délimiteurs dicte le type de sortie. - La clause
ifse place après l'expression et voit la clé comme la valeur. - Une expression
forsur une map trie les clés lexicalement. - Deux entrées produisant la même clé exigent l'ellipsis
..., qui groupe les valeurs en listes ; sinon Terraform lèveDuplicate object key. - Deux
forimbriqués plusflattencroisent deux niveaux ; les entrées à liste vide disparaissent d'elles-mêmes. - Le splat
[*]sur une map ne lève pas d'erreur et renvoie un tuple d'un seul élément : sur une map, toujours une expressionfor. for_eachveut une map ou un set de chaînes, jamais un tuple.- Une expression
forne génère pas de blocs de configuration : pour cela, un blocdynamic.
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les questions ci-dessous portent sur les points où la syntaxe surprend : le type
produit selon le délimiteur, le splat qui ne parcourt pas une map, et ce que
for_each accepte en sortie. Chaque réponse donne la commande de vérification à
rejouer dans terraform console.
Deux délimiteurs, deux types
| Écriture | Type produit |
|---|---|
[for x in ... : x] |
tuple (suite ordonnée) |
{for k, v in ... : k => v} |
object (clés vers valeurs) |
La vérification
echo 'type([for x in [1,2] : x])' | terraform console
tuple([number, number])
echo 'type({for k,v in {a=1} : k => v})' | terraform console
object({ a: number })
La forme object impose une flèche clé => valeur, la forme tuple non.Vérifié sur Terraform v1.15.4.Le mode de regroupement
L'ellipsis... après l'expression de valeur transforme chaque clé en liste :echo '{for k,v in {x="r1",y="r1",z="r2"} : v => k...}' | terraform console
{
"r1" = ["x", "y"]
"r2" = ["z"]
}
Sans l'ellipsis
Deux entrées produisant la même clé provoquent une erreur :Error: Duplicate object key
C'est le cas dès qu'une expression for projette plusieurs éléments sur la même clé.Vérifié sur Terraform v1.15.4.Le comportement silencieux
echo 'length({a=1,b=2}[*])' | terraform console
1
Le splat [*] est prévu pour les listes et les tuples. Sur une map, il n'échoue pas : il enveloppe la map entière dans un tuple d'un seul élément. D'où la longueur 1, qui surprend.La parade
Utiliser une expressionfor explicite, qui parcourt réellement la map :[for k, v in var.map : v.attribut]
C'est le cas où le splat, pratique sur une liste, devient un piège sur une map.Vérifié sur Terraform v1.15.4.Le motif
Deuxfor imbriqués produisent une liste de listes, que flatten remet à plat :flatten([
for k, v in var.machines : [
for tag in v.tags : "${k}:${tag}"
]
])
Le résultat
Chaque machine est croisée avec chacun de ses tags. Une machine sans tag produit une liste interne vide, qui disparaît d'elle-même aprèsflatten : aucun filtre à écrire pour ce cas.Ce motif est la base de la génération de règles de pare-feu ou d'associations à partir de deux dimensions.Vérifié sur Terraform v1.15.4.La distinction à faire
Une expressionfor produit une valeur : un tuple ou un object. Elle ne génère pas de structure de configuration.Pour répéter un bloc imbriqué, par exemple plusieurs ingress dans une ressource, le mécanisme est le bloc dynamic :dynamic "ingress" {
for_each = var.ports
content {
from_port = ingress.value
to_port = ingress.value
}
}
La complémentarité
Une expressionfor prépare souvent la collection que for_each du bloc dynamic parcourt ensuite. Les deux se combinent, ils ne se remplacent pas.Vérifié sur Terraform v1.15.4.La clause if voit tout
echo '[for k,v in {alpha=1,delta=2} : k if k != "delta"]' | terraform console
["alpha"]
La clause if a accès aux deux variables d'itération. On peut donc filtrer sur la clé, sur la valeur, ou sur les deux :[for k, v in var.map : k if v.actif && k != "defaut"]
L'ordre d'écriture
La clauseif se place à la fin de l'expression, après la projection. Elle décide quels éléments sont conservés, pas ce qu'ils deviennent.Vérifié sur Terraform v1.15.4.La condition
for_each exige une map ou un set de chaînes. Le résultat d'une expression for convient donc uniquement dans la forme accolades :for_each = { for s in var.serveurs : s.nom => s }
Ce qui est refusé
Un tuple, produit par la forme crochets[for ...], est rejeté :Error: Invalid for_each argument
Un set de nombres l'est aussi. La règle est la même que pour toute valeur passée à for_each : la clé doit être une chaîne, connue au plan.Vérifié sur Terraform v1.15.4.Pour aller plus loin
Section intitulée « Pour aller plus loin »- Le bloc lifecycle Terraform : Le contrôle de la recréation des ressources produites par une expression for.
- Le style guide Terraform : Les conventions qui gardent lisible une compréhension for imbriquée.
- Quiz Écrire du code Terraform : Un contrôle des acquis sur variables, expressions et transformations de collections.