Aller au contenu
Infrastructure as Code medium

Expressions for Terraform : filtrer, regrouper, aplatir une collection

30 min de lecture

logo terraform

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.

  • 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 for ne peut pas faire, et quand passer à dynamic

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 = [] }
}
}
Fenêtre de terminal
terraform console

Tuple 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.

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",
]

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
}

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. If
duplicates are expected, use the ellipsis (...) after the value expression to
enable 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' expression
Grouping 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.

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[*])
1

1, 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é.

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])
true

C'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.

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.

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.

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.

MessageCauseSolution
Error: Duplicate object keyDeux 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 tupleUn ... dans un [ for ... ]Le groupement n'existe qu'avec des accolades
the "for_each" argument must be a map, or set of stringsUn tuple ou une liste passés à for_eachProduire 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 numberUn toset([1,2,3])Convertir en chaînes, ou utiliser une map
Une longueur inattendue de 1Un splat [*] appliqué à une mapRemplacer par une expression for explicite
  1. Crochets [ ] produisent un tuple, accolades { } un object. La forme des délimiteurs dicte le type de sortie.
  2. La clause if se place après l'expression et voit la clé comme la valeur.
  3. Une expression for sur une map trie les clés lexicalement.
  4. Deux entrées produisant la même clé exigent l'ellipsis ..., qui groupe les valeurs en listes ; sinon Terraform lève Duplicate object key.
  5. Deux for imbriqués plus flatten croisent deux niveaux ; les entrées à liste vide disparaissent d'elles-mêmes.
  6. 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 expression for.
  7. for_each veut une map ou un set de chaînes, jamais un tuple.
  8. Une expression for ne génère pas de blocs de configuration : pour cela, un bloc dynamic.

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.

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn