Aller au contenu
Infrastructure as Code medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

Conditions Terraform : ternaire et validation de variables

14 min de lecture

logo terraform

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.

  • 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
  • null comme valeur conditionnelle : désactiver un attribut optionnel selon un flag

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_fausse
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 = 512
vcpu = 1
vm_name = "dev-lab07-vm"
disk_name = "lab07-vm-dev.qcow2"
# terraform apply -var="environment=prod"
memory_mib = 2048
vcpu = 4
vm_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.

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

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.

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
# ...
}

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)

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 :

Fenêtre de terminal
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.
  • condition doit renvoyer un booléen
  • Depuis Terraform 1.9, condition peut 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_message doit 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.

Trois fonctions reviennent souvent dans les conditions :

  • contains(liste, valeur) : renvoie true si la valeur est dans la liste. Exemple : contains(["dev", "prod"], var.env)
  • startswith(chaîne, préfixe) : renvoie true si la chaîne commence par le préfixe. Exemple : startswith(var.vm_name, "lab")
  • can(expression) : renvoie true si l’expression s’évalue sans erreur, false sinon. Indispensable pour les regex — si le pattern échoue, can() capte l’erreur au lieu de faire planter le plan.
# 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 nom
variable "vm_name" {
type = string
validation {
condition = startswith(var.vm_name, "lab")
error_message = "vm_name doit commencer par 'lab'."
}
}
OpérateurSignification
==É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
}
variables.tf
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.tf
locals {
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
}
SymptômeCause probableSolution
Error: Invalid value for variable avant le planBloc validation qui échoueCorriger la valeur de la variable
condition référence un objet non disponibleDépendance circulaire ou objet non encore évaluéVérifier les dépendances entre variables et locals
Ternaire imbriqué trop complexeLisibilité dégradéeRemplacer par lookup(map, var.env, défaut)
Attribut null crée quand même la ressourceLa ressource doit utiliser count = 0 ou for_each videnull ne s’applique qu’aux attributs, pas aux ressources
  1. condition ? vrai : faux — l’unique syntaxe conditionnelle de HCL
  2. Les ternaires imbriqués fonctionnent mais au-delà de 2 niveaux, préférez une map + lookup
  3. validation {} rejette les valeurs invalides avant de toucher à l’infrastructure
  4. null dans un attribut optionnel = attribut ignoré par Terraform
  5. can() permet de valider les regex et conversions sans faire planter le plan

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn