Aller au contenu
Infrastructure as Code medium

Blocs dynamiques Terraform : générer des blocs imbriqués

30 min de lecture

logo terraform

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 qu'est un bloc dynamic et quand il est justifié
  • L'écrire : for_each, content, et la variable d'itération
  • Filtrer dans le for_each, jamais dans le content
  • Renommer la variable avec iterator, et pourquoi c'est parfois obligatoire
  • Le piège de .key sur un set
  • Imbriquer un dynamic dans un autre
  • Le mur des méta-arguments : lifecycle et provisioner ne se génèrent pas

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.

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 :

Fenêtre de terminal
terraform output nb_fichiers
2

Deux entrées, deux blocs générés.

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.

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.

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.

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 type
Blocks 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.

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.

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.

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.

MessageCauseSolution
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éeCorriger la syntaxe de la collection
Un bloc généré de trop, ou de moinsUn if posé dans le content au lieu du for_eachFiltrer la collection dans le for_each
Deux niveaux de dynamic qui se mélangentVariables d'itération homonymesNommer chaque niveau avec iterator
  1. Un dynamic génère des blocs répétables du provider, jamais un attribut ni un bloc de méta-arguments.
  2. Sa for_each fournit la collection, son content décrit un bloc, la variable d'itération porte le nom du label par défaut.
  3. On filtre dans le for_each, avec une expression for ; un if dans le content n'a aucun effet.
  4. iterator renomme la variable, et devient obligatoire pour imbriquer deux dynamic.
  5. Sur un set, key égale value : n'utilisez key que sur une map.
  6. lifecycle et provisioner ne se génèrent pas : Terraform les traite avant d'évaluer les expressions. Le message d'erreur nomme le label du bloc.
  7. labels fournit les labels des blocs qui en exigent.
  8. À réserver aux blocs dont le nombre varie ; sinon, du littéral, plus lisible.

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.

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