
En prod, votre VM doit avoir 4 Go de RAM et un disque supplémentaire. En dev, 512 Mo suffisent et le disque est inutile. Sans conditions, vous dupliquez toute la configuration ou vous maintenez deux fichiers divergents.
Les expressions conditionnelles de HCL permettent d’adapter
votre infrastructure selon une variable : environnement, flag feature,
type de déploiement. Terraform n’a pas de bloc if/else — il utilise
l’opérateur ternaire condition ? valeur_si_vrai : valeur_si_faux,
combinable dans les locals pour simuler des if/else if/else
complexes. Associé au bloc validation des variables, il permet
également de rejeter une valeur invalide avec un message d’erreur clair
avant le plan — bien plus tôt qu’une erreur API obscure à l’apply.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Opérateur ternaire :
condition ? valeur_si_vrai : valeur_si_faux - Ternaires imbriqués : simuler un
if / else if / else - Bloc
validation: rejeter une valeur de variable invalide avec un message clair nullcomme valeur conditionnelle : désactiver un attribut optionnel selon un flag
Prérequis
Section intitulée « Prérequis »- Variables Terraform maîtrisées (variables Terraform)
- Locals compris (locals Terraform)
L’opérateur ternaire
Section intitulée « L’opérateur ternaire »L’idée en langage naturel : « si l’environnement est prod, alors 2048 Mo de RAM ; sinon 512 Mo ».
HCL n’a pas de bloc if/else. Cette phrase se traduit directement en opérateur ternaire :
condition ? valeur_si_vraie : valeur_si_fausseExemple : mémoire selon l’environnement
Section intitulée « Exemple : mémoire selon l’environnement »locals { memory_mib = var.environment == "prod" ? 2048 : 512 # ↑ condition à évaluer ↑ si vrai ↑ si faux}Appliqué avec environment = "dev" → memory_mib = 512.
Appliqué avec environment = "prod" → memory_mib = 2048.
Concrètement, passer de dev à prod recalcule tout :
# terraform apply -var="environment=dev"memory_mib = 512vcpu = 1vm_name = "dev-lab07-vm"disk_name = "lab07-vm-dev.qcow2"
# terraform apply -var="environment=prod"memory_mib = 2048vcpu = 4vm_name = "prod-lab07-vm"disk_name = "lab07-vm-prod.qcow2"Terraform recalcule les expressions, détecte les diffs, et met à jour les ressources affectées.
Ternaire imbriqué : if / else if / else
Section intitulée « Ternaire imbriqué : if / else if / else »Pour trois cas ou plus, les ternaires s’imbriquent :
locals { vcpu = var.environment == "prod" ? 4 : (var.environment == "staging" ? 2 : 1)}Lisez-le de gauche à droite :
prod→ 4- sinon,
staging→ 2 - sinon → 1
Ternaire dans les attributs de ressource
Section intitulée « Ternaire dans les attributs de ressource »Le ternaire s’utilise directement dans les attributs, pas seulement dans les locals :
resource "libvirt_domain" "vm" { memory = var.environment == "prod" ? 2048 : 512 vcpu = var.environment == "prod" ? 4 : 1}Mais l’approche locals reste préférable : les valeurs calculées sont
nommées, réutilisables, et testables dans les outputs.
Construire des noms conditionnels
Section intitulée « Construire des noms conditionnels »Un usage fréquent : inclure l’environnement dans le nom d’une ressource pour éviter les collisions :
locals { disk_suffix = var.environment == "prod" ? "prod" : "dev" disk_name = "${var.vm_name}-${local.disk_suffix}.qcow2" # dev → "lab07-vm-dev.qcow2" # prod → "lab07-vm-prod.qcow2"}
resource "libvirt_volume" "disk" { name = local.disk_name # ...}null comme valeur conditionnelle
Section intitulée « null comme valeur conditionnelle »Certains attributs sont optionnels : un second disque, un réseau supplémentaire, une IP fixe.
Plutôt que de dupliquer le bloc resource, on conditionne la valeur de l’attribut : si
l’option est désactivée, on passe null.
Pour un argument optionnel, passer null revient à omettre l’argument :
Terraform utilise alors la valeur par défaut du schéma du provider, ou
n’applique simplement pas l’attribut. En revanche, passer null à un
argument obligatoire provoque une erreur.
locals { second_disk_name = var.enable_second_disk ? "${var.vm_name}-data.qcow2" : null # ↑ si flag activé ↑ nom du disque ↑ désactivé}Si enable_second_disk = false, second_disk_name = null. L’attribut
est traité comme omis — Terraform ne crée pas de second disque.
# enable_second_disk = false (défaut)second_disk_name = tostring(null)Le bloc validation
Section intitulée « Le bloc validation »Sans validation, une variable mal renseignée (ex. environment = "preprod" au lieu
de "staging") peut déclencher une erreur cryptique depuis l’API du provider, au
beau milieu d’un apply. Le bloc validation coupe court à ça : Terraform
vérifie la valeur avant de communiquer avec quoi que ce soit.
variable "environment" { type = string default = "dev"
validation { condition = contains(["dev", "staging", "prod"], var.environment) # ↑ contains() : renvoie true si la valeur est dans la liste error_message = "environment doit être dev, staging ou prod." # ↑ affiché tel quel si la condition est fausse }}Avec une valeur invalide, le plan échoue immédiatement :
terraform plan -var="environment=invalid"│ Error: Invalid value for variable││ on variables.tf line 1:│ 1: variable "environment" {││ environment doit être dev, staging ou prod.Règles d’écriture d’un bloc validation
Section intitulée « Règles d’écriture d’un bloc validation »conditiondoit renvoyer un booléen- Depuis Terraform 1.9,
conditionpeut référencer d’autres variables et d’autres objets de la configuration, tant que cela ne crée pas de dépendance circulaire. Avant 1.9, seule la variable en cours (var.X) était autorisée. error_messagedoit produire une chaîne. Une chaîne littérale reste le choix le plus simple et le plus lisible, mais les expressions (interpolation, heredoc) sont autorisées.
Fonctions utiles dans les blocs validation
Section intitulée « Fonctions utiles dans les blocs validation »Trois fonctions reviennent souvent dans les conditions :
contains(liste, valeur): renvoietruesi la valeur est dans la liste. Exemple :contains(["dev", "prod"], var.env)startswith(chaîne, préfixe): renvoietruesi la chaîne commence par le préfixe. Exemple :startswith(var.vm_name, "lab")can(expression): renvoietruesi l’expression s’évalue sans erreur,falsesinon. Indispensable pour les regex — si le pattern échoue,can()capte l’erreur au lieu de faire planter le plan.
Exemples courants de validations
Section intitulée « Exemples courants de validations »# Vérifier un format d'IP (regex)variable "ip_address" { type = string validation { # can() : ne pas planter si la regex ne correspond pas — renvoyer false condition = can(regex("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", var.ip_address)) error_message = "ip_address doit être une IPv4 valide (ex: 192.168.1.10)." # Note : cette regex vérifie la forme mais pas la validité des octets # (999.999.999.999 passerait). Suffisant pour un contrôle de surface. }}
# Vérifier une taille minimale (comparaison numérique directe)variable "memory_mib" { type = number validation { condition = var.memory_mib >= 256 error_message = "memory_mib doit être au minimum 256 MiB." }}
# Vérifier un préfixe de nomvariable "vm_name" { type = string validation { condition = startswith(var.vm_name, "lab") error_message = "vm_name doit commencer par 'lab'." }}Opérateurs de comparaison disponibles
Section intitulée « Opérateurs de comparaison disponibles »| Opérateur | Signification |
|---|---|
== | Égal |
!= | Différent |
< <= > >= | Comparaisons numériques |
&& | ET logique |
|| | OU logique |
! | Négation |
locals { is_prod_or_staging = var.environment == "prod" || var.environment == "staging" needs_monitoring = var.environment == "prod" && var.enable_monitoring}Structure complète de l’exemple
Section intitulée « Structure complète de l’exemple »variable "environment" { type = string default = "dev" validation { condition = contains(["dev", "staging", "prod"], var.environment) error_message = "environment doit être dev, staging ou prod." }}variable "vm_name" { type = string default = "lab07-vm"}variable "enable_second_disk" { type = bool default = false}
# main.tflocals { memory_mib = var.environment == "prod" ? 2048 : 512 vcpu = var.environment == "prod" ? 4 : (var.environment == "staging" ? 2 : 1) disk_suffix = var.environment == "prod" ? "prod" : "dev" disk_name = "${var.vm_name}-${local.disk_suffix}.qcow2" second_disk_name = var.enable_second_disk ? "${var.vm_name}-data.qcow2" : null}Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Error: Invalid value for variable avant le plan | Bloc validation qui échoue | Corriger la valeur de la variable |
condition référence un objet non disponible | Dépendance circulaire ou objet non encore évalué | Vérifier les dépendances entre variables et locals |
| Ternaire imbriqué trop complexe | Lisibilité dégradée | Remplacer par lookup(map, var.env, défaut) |
Attribut null crée quand même la ressource | La ressource doit utiliser count = 0 ou for_each vide | null ne s’applique qu’aux attributs, pas aux ressources |
À retenir
Section intitulée « À retenir »condition ? vrai : faux— l’unique syntaxe conditionnelle de HCL- Les ternaires imbriqués fonctionnent mais au-delà de 2 niveaux, préférez une map +
lookup validation {}rejette les valeurs invalides avant de toucher à l’infrastructurenulldans un attribut optionnel = attribut ignoré par Terraformcan()permet de valider les regex et conversions sans faire planter le plan