
Un bloc dynamic génère des blocs imbriqués à partir d'une collection, là où
le schéma du provider expose un bloc répétable (ingress, setting,
source, part). C'est l'outil quand le nombre de ces blocs dépend d'une
variable plutôt que d'une écriture à la main.
Ce guide part de la base, écrire un dynamic et lire ce qu'il produit, puis
couvre les formes avancées (iterator, imbrication) et le mur que tout le monde
rencontre un jour : un dynamic ne peut pas générer un bloc de méta-arguments
comme lifecycle. Les exemples exécutables ont été vérifiés sur Terraform
v1.15.4 avec le provider local hashicorp/archive, sans cloud.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Ce qu'est un bloc
dynamicet quand il est justifié - L'écrire :
for_each,content, et la variable d'itération - Filtrer dans le
for_each, jamais dans lecontent - Renommer la variable avec
iterator, et pourquoi c'est parfois obligatoire - Le piège de
.keysur un set - Imbriquer un
dynamicdans un autre - Le mur des méta-arguments :
lifecycleetprovisionerne se génèrent pas
Prérequis
Section intitulée « Prérequis »- Le meta-argument
for_each(for_each Terraform) - Les expressions
for(expressions for Terraform) - Terraform 1.15.x, la série stable courante
Qu'est-ce qu'un bloc dynamic ?
Section intitulée « Qu'est-ce qu'un bloc dynamic ? »Un bloc dynamic remplace plusieurs blocs imbriqués identiques par un seul
bloc qui les génère. Là où vous écririez trois blocs ingress à la main, un
dynamic "ingress" les produit depuis une liste de règles. Il ne s'applique
qu'aux blocs répétables du provider : un attribut ou un bloc de
méta-arguments ne se génèrent pas ainsi.
Un dynamic a trois parties : son label, qui nomme le bloc à produire ; sa
for_each, la collection à parcourir ; son bloc content, qui décrit un
bloc généré. Une variable d'itération, nommée par défaut comme le label, donne
accès à l'élément courant.
Écrire un bloc dynamic
Section intitulée « Écrire un bloc dynamic »Voici un dynamic "source" sur une data source archive_file, dont le bloc
source est répétable :
locals { entrees = { "config.yaml" = "cle: valeur\n" "notes.txt" = "rien a signaler\n" }}
data "archive_file" "bundle" { type = "zip" output_path = "${path.module}/bundle.zip"
dynamic "source" { for_each = local.entrees content { filename = source.key content = source.value } }}
output "nb_fichiers" { value = length(data.archive_file.bundle.source)}Le bloc génère autant de blocs source que la map compte d'entrées. La variable
source porte le nom du label et expose source.key et source.value. Après
apply :
terraform output nb_fichiers2Deux entrées, deux blocs générés.
Filtrer dans le for_each, jamais dans le content
Section intitulée « Filtrer dans le for_each, jamais dans le content »C'est l'erreur la plus fréquente. Pour n'inclure qu'une partie des éléments, on
filtre la collection, dans le for_each, avec une expression for :
dynamic "source" { for_each = { for k, v in local.entrees : k => v if length(v) > 0 } content { filename = source.key content = source.value }}Un if dans le content n'aurait aucun effet : à ce stade, le bloc est déjà
décidé. Le nombre de blocs se règle en amont, sur la collection passée à
for_each.
Renommer la variable avec iterator
Section intitulée « Renommer la variable avec iterator »Par défaut, la variable d'itération porte le nom du label. L'argument
iterator la renomme :
dynamic "source" { for_each = local.entrees iterator = fichier content { filename = fichier.key content = fichier.value }}Ce n'est pas qu'un confort. C'est obligatoire quand un dynamic en contient un
autre du même nom : sans iterator, les deux variables porteraient le même
label et la référence serait ambiguë. La documentation officielle en fait la
solution recommandée dans ce cas.
Le piège de key sur un set
Section intitulée « Le piège de key sur un set »for_each accepte une map ou un set. Sur un set, une subtilité documentée :
key est identique à value, et ne devrait pas être utilisé.
locals { lignes = toset(["alpha", "bravo"])}
dynamic "source" { for_each = local.lignes content { filename = "${source.key}.txt" content = source.value }}["alpha.txt", "bravo.txt"]source.key vaut ici "alpha" puis "bravo", exactement comme source.value.
Réservez source.key aux maps, où la clé porte une information propre ; sur
un set, tenez-vous-en à source.value.
Imbriquer un dynamic dans un autre
Section intitulée « Imbriquer un dynamic dans un autre »Quand un bloc répétable contient lui-même un bloc répétable, on imbrique deux
dynamic. Le bloc content du premier héberge le second. C'est là que
iterator devient indispensable, pour distinguer les deux niveaux :
dynamic "origin_group" { for_each = var.groupes iterator = groupe content { name = groupe.key
dynamic "origin" { for_each = groupe.value.origines iterator = origine content { hostname = origine.value } } }}Le second for_each parcourt une valeur du premier (groupe.value.origines).
Sans les deux iterator, les variables origin_group et origin seraient
confondues.
Le mur : dynamic ne génère pas un méta-argument
Section intitulée « Le mur : dynamic ne génère pas un méta-argument »Un dynamic répète un bloc du provider. Il ne peut pas générer un bloc de
méta-arguments comme lifecycle ou provisioner. La raison est documentée :
« It is not possible to generate meta-argument blocks such as lifecycle and
provisioner blocks, since Terraform must process these before it is safe to
evaluate expressions. » Ces blocs sont traités avant l'évaluation des
expressions, donc avant qu'un dynamic puisse produire quoi que ce soit.
Si vous tentez un dynamic sur un bloc que le contexte n'attend pas, le message
d'erreur nomme le label visé, jamais le mot dynamic :
dynamic "reglage" { for_each = [1] content { option = reglage.value }}Error: Unsupported block typeBlocks of type "reglage" are not expected here.Le même message apparaît pour un dynamic "lifecycle" : Blocks of type "lifecycle" are not expected here. Un bloc lifecycle s'écrit toujours
littéralement.
L'argument labels
Section intitulée « L'argument labels »Certains blocs répétables portent un label (comme setting chez un
provider). L'argument labels d'un dynamic fournit ces labels, dans
l'ordre, et peut utiliser la variable d'itération :
dynamic "setting" { for_each = var.reglages labels = [setting.key] content { value = setting.value }}C'est le seul moyen de générer des blocs qui exigent un label, que le bloc
content seul ne saurait poser.
À utiliser avec parcimonie
Section intitulée « À utiliser avec parcimonie »Un dynamic rend la configuration plus difficile à lire qu'une suite de blocs
littéraux, et la documentation le déconseille pour les cas simples : « Overuse of
dynamic blocks can make configuration hard to read and maintain. » Réservez-le
aux blocs dont le nombre dépend vraiment d'une variable. Un bloc qui ne
varie jamais, comme un en-tête commun, reste littéral, y compris au milieu d'un
ensemble par ailleurs dynamique.
Dépannage
Section intitulée « Dépannage »Ces messages ont été relevés au mot près sur Terraform v1.15.4. Le premier est
le plus trompeur : il nomme le bloc visé, pas le mot dynamic.
| Message | Cause | Solution |
|---|---|---|
Unsupported block type / Blocks of type "X" are not expected here. | Un dynamic sur un bloc absent du schéma, ou sur un méta-argument (lifecycle) | Vérifier que le provider expose bien un bloc X répétable ; écrire les méta-arguments littéralement |
Missing item separator / Expected a comma to mark the beginning of the next item. | Une liste mal formée passée à for_each, souvent une virgule oubliée | Corriger la syntaxe de la collection |
| Un bloc généré de trop, ou de moins | Un if posé dans le content au lieu du for_each | Filtrer la collection dans le for_each |
Deux niveaux de dynamic qui se mélangent | Variables d'itération homonymes | Nommer chaque niveau avec iterator |
À retenir
Section intitulée « À retenir »- Un
dynamicgénère des blocs répétables du provider, jamais un attribut ni un bloc de méta-arguments. - Sa
for_eachfournit la collection, soncontentdécrit un bloc, la variable d'itération porte le nom du label par défaut. - On filtre dans le
for_each, avec une expressionfor; unifdans lecontentn'a aucun effet. iteratorrenomme la variable, et devient obligatoire pour imbriquer deuxdynamic.- Sur un set,
keyégalevalue: n'utilisezkeyque sur une map. lifecycleetprovisionerne se génèrent pas : Terraform les traite avant d'évaluer les expressions. Le message d'erreur nomme le label du bloc.labelsfournit les labels des blocs qui en exigent.- À réserver aux blocs dont le nombre varie ; sinon, du littéral, plus lisible.
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les questions ci-dessous portent sur ce qui coince le plus : les blocs qu'un
dynamic ne peut pas générer, l'endroit où filtrer, et le message d'erreur
qui pointe le label du bloc. Chaque réponse donne la vérification correspondante.
Le rôle
Un blocdynamic répète un bloc imbriqué autant de fois que la collection qu'il parcourt :dynamic "ingress" {
for_each = var.ports
content {
from_port = ingress.value
to_port = ingress.value
}
}
Sa limite
Il ne fonctionne que sur les blocs répétables exposés par le schéma du provider (ingress, setting, rule…). Il ne peut pas générer un attribut simple, ni un bloc de méta-arguments comme lifecycle.Ne pas en abuser
La documentation le déconseille pour les cas simples : undynamic est moins lisible qu'une suite de blocs littéraux. Réservez-le aux blocs dont le nombre dépend vraiment d'une variable.Vérifié sur Terraform v1.15.4.Le message
dynamic "lifecycle" { # refusé
for_each = [1]
content { prevent_destroy = true }
}
Error: Unsupported block type
Blocks of type "lifecycle" are not expected here.
La raison
Les méta-arguments (lifecycle, provider, depends_on) sont traités avant l'évaluation des expressions, au moment de construire le graphe de dépendances. Un dynamic, lui, dépend d'une collection évaluée plus tard. Les deux moments sont incompatibles.La solution
Écrire le bloclifecycle littéralement. Un méta-argument ne se génère jamais, il se pose en dur.Vérifié sur Terraform v1.15.4.Le filtre se place dans le for_each
dynamic "ingress" {
for_each = [for r in var.regles : r if r.actif]
content {
from_port = ingress.value.port
}
}
La clause if de l'expression for décide quels éléments engendrent un bloc.Pourquoi pas dans content
Unif placé à l'intérieur du bloc content n'a aucun effet : à ce stade, Terraform a déjà décidé de créer le bloc. Le nombre de blocs se fixe au niveau du for_each, pas de leur contenu.Vérifié sur Terraform v1.15.4.Le cas obligatoire
Par défaut, la variable d'itération porte le label du bloc : dansdynamic "ingress", on écrit ingress.value. Quand un dynamic en contient un autre du même nom, les deux variables entrent en collision.dynamic "regle" {
for_each = var.regles
iterator = regle
content {
dynamic "port" {
for_each = regle.value.ports
iterator = port
content { numero = port.value }
}
}
}
Le cas de confort
iterator sert aussi à donner un nom plus parlant que le label du bloc, même sans imbrication. C'est facultatif, mais lisible.Vérifié sur Terraform v1.15.4.La démonstration
echo '{ for k, v in toset(["alpha","beta"]) : k => v }' | terraform console
{
"alpha" = "alpha"
"beta" = "beta"
}
Sur un set, key et value sont identiques : un set n'a pas de clés distinctes de ses valeurs.La règle pratique
| Collection | .key |
.value |
|---|---|---|
| set | égale .value, à ne pas utiliser |
la valeur |
| map | la clé | la valeur associée |
dynamic alimenté par un set, écrire ingress.value, jamais ingress.key.Vérifié sur Terraform v1.15.4.Le message pointe le label, pas dynamic
dynamic "ingress" { # ce bloc n'existe pas sur cette ressource
for_each = [1]
content { port = ingress.value }
}
Error: Unsupported block type
Blocks of type "ingress" are not expected here.
Le message nomme ingress, le label de votre dynamic, jamais le mot dynamic. C'est ce qui permet de trouver le bloc fautif dans un fichier qui en compte plusieurs.La cause habituelle
Une faute de frappe sur le nom du bloc, ou un bloc qui n'existe pas sur cette ressource. Vérifiez le nom exact dans la documentation du provider.Vérifié sur Terraform v1.15.4.La recommandation officielle
La documentation Terraform est explicite : undynamic nuit à la lisibilité et ne devrait pas remplacer des blocs statiques par confort.Le bon critère
| Situation | Écriture |
|---|---|
| Le nombre de blocs est fixe et connu | blocs littéraux |
| Le nombre dépend d'une variable | bloc dynamic |
ingress toujours identiques se lisent mieux écrits en clair. Un nombre de règles qui varie selon l'environnement justifie le dynamic.Le coût caché
Undynamic déplace la logique dans le for_each, loin du content. Sur une configuration relue par quelqu'un d'autre, cette indirection a un prix qu'un gain de quelques lignes ne compense pas toujours.Vérifié sur Terraform v1.15.4.Pour aller plus loin
Section intitulée « Pour aller plus loin »- Le bloc lifecycle : Détaille les sept règles du bloc, qui s'écrit toujours littéralement.
- Le style guide Terraform : Fixe les conventions d'écriture qui gardent un
dynamiclisible. - Structure d'un module : Range les blocs générés dans un module, là où
dynamicsert le plus.