DevOps Elastic Hayway
Document

SUBSCRIBE TO GET FULL ACCESS TO THE E-BOOKS FOR FREE 🎁SUBSCRIBE NOW

Professional Dropdown with Icon

SUBSCRIBE NOW TO GET FREE ACCESS TO EBOOKS

TerraformLesson 12 / 2412 min readUpdated September 13, 2026

Conditions Terraform : count et expressions

À la fin de ce tutoriel, vous saurez écrire des expressions ternaires condition ? a : b, activer ou désactiver une ressource avec count = var.enabled ? 1 : 0, préférer un for_each sur map filtrée pour la stabilité d’adresse, utiliser coalesce, try, can et one, éviter les pièges null vs omit et de typage, puis valider un lab léger en Region ca-central-1 avec destroy systématique.

Niveau : Débutant → Intermédiaire · Temps estimé : 35–45 min · Versions testées : Terraform 1.16.2 (série ≥ 1.9), AWS provider ~> 5.0 · Dernière vérification : 2026-09-10

Slug proposé : terraform-conditionnels · Série : Terraform · Remplace / fusionne : #1655 (P6 bonus #2 : conditionnels — quasi vide)

Prérequis

  • Avoir suivi Boucles (terraform-boucles-for-each) : count, for_each, expression for, adresses stables
  • Avoir suivi Variables (terraform-variables) et Locals (terraform-locals) : booléens, maps, valeurs dérivées
  • Terraform ≥ 1.9, AWS CLI v2, profil de lab (aws sts get-caller-identity OK)

Coût estimé : quasi 0 € (bucket S3 vide tagué ou user IAM de lab sans clés). Destroy obligatoire. Aucun secret dans le HCL / tfvars.

Auth sans secrets. Utilisez AWS_PROFILE / SSO. Jamais d’access_key / secret_key dans le provider. Pour ce lab, une politique limitée (S3 ou IAM users de test) suffit.

Ce que nous allons construire

Lab conditionnels Terraform (ca-central-1)
  ├── versions.tf / providers.tf → TF ≥ 1.9, aws ~> 5.0, region ca-central-1
  ├── variables.tf               → bool enable_lab_bucket (+ option map filtrée)
  ├── locals.tf                  → ternaires, coalesce / try / can / one
  ├── main.tf                    → ressource optionnelle via count (ou for_each filtré)
  ├── outputs.tf                 → id conditionnel (try / one)
  └── init → plan (0 ou 1) → apply → destroy

(Alt : « Conditions Terraform : count conditionnel, ternaires et for_each filtré — lab AWS ca-central-1 ».)

Ce guide remplace le post WordPress #1655 (quasi vide) et aligne P2 sur expressions conditionnelles + présence/absence, actualisé 2026 (TF ≥ 1.9, aws ~> 5.0, ca-central-1).

Étape 1 — Expressions ternaires condition ? a : b

Le ternaire HCL ressemble à celui d’autres langages, avec une contrainte forte : les deux branches doivent produire le même type. C’est l’outil idéal pour choisir une chaîne de tag, un statut de versioning ou un suffixe de nom — pas pour décider du nombre d’instances d’une resource (réservé à count / for_each).

Cas réel : environment == "prod" ? "cc-prod" : "cc-lab" alimente un tag FinOps. Si vous écrivez la même expression dans dix resources, une typo crée un drift de tags. Passez par un local nommé : le plan reste lisible et la revue de code plus courte.

Piège : enchaîner trois ternaires imbriqués. Préférez des locals intermédiaires ou une map de lookup (lookup / try) pour garder le module maintenable.

En HCL, le ternaire choisit une valeur selon un booléen. C’est l’outil de base du terraform conditional : tags optionnels, noms dérivés, arguments calculés — sans créer ou supprimer une ressource à lui seul.

variable "environment" {
  type    = string
  default = "lab"
}

variable "enable_versioning" {
  type    = bool
  default = false
}

locals {
  bucket_suffix = var.environment == "prod" ? "prod" : "lab"
  versioning_status = var.enable_versioning ? "Enabled" : "Suspended"

  # Tags conditionnels : null = souvent « omis » côté provider (voir pièges)
  cost_center = var.environment == "prod" ? "cc-prod" : null
}

Règles : branches du même type ; condition en bool (pas "true") ; préférez des locals nommés au chaînage. Le ternaire choisit une valeur, pas le nombre d’instances — pour cela, count / for_each.

Étape 2 — count = var.enabled ? 1 : 0 (ressource optionnelle)

Le toggle 0/1 est le pattern le plus enseigné — et le plus dangereux si on l’applique à une base de données prod sans communication : passer enabled=false détruit l’instance cloud. Pour un bucket de lab, c’est pédagogique. Pour un RDS, c’est un incident. Documentez le flag et préférez parfois un module séparé plutôt qu’un booléen unique « god mode ».

Pour créer 0 ou 1 instance d’une ressource, le pattern classique est :

variable "enable_lab_bucket" {
  type        = bool
  description = "Si true, crée le bucket de lab ; sinon aucune instance."
  default     = false
}

data "aws_caller_identity" "current" {}

resource "aws_s3_bucket" "optional" {
  count  = var.enable_lab_bucket ? 1 : 0
  bucket = "lab-tf-cond-${data.aws_caller_identity.current.account_id}"

  tags = {
    Name      = "lab-conditional"
    ManagedBy = "terraform"
    Lab       = "conditionnels"
  }
}

output "optional_bucket_id" {
  description = "Id du bucket si créé, sinon null."
  value       = try(aws_s3_bucket.optional[0].id, null)
}

Avec enable_lab_bucket = false, le plan montre 0 à créer. Avec true, 1 create. L’adresse devient aws_s3_bucket.optional[0] — d’où l’index [0] dans les références.

Attention : basculer true → false détruit l’objet cloud. C’est voulu pour un flag « feature on/off », pas pour N objets nommés (voir étape 3 et le tuto Boucles).

Étape 3 — for_each avec map filtrée (préférable pour la stabilité)

Dès que plusieurs objets portent un nom métier (logs, tmp, audit), filtrez la map puis itérez avec for_each. Désactiver tmp ne renumérote pas logs : l’adresse state ["logs"] reste stable. C’est exactement le problème que count indexé pose quand on retire l’élément du milieu. Reliez ce réflexe au tuto Boucles.

Quand plusieurs objets peuvent être présents ou absents par clé, filtrez une map plutôt que d’empiler des count :

variable "lab_features" {
  type = map(object({
    enabled = bool
    purpose = string
  }))
  default = {
    logs = { enabled = true,  purpose = "logs" }
    tmp  = { enabled = false, purpose = "scratch" }
  }
}

locals {
  # Map filtrée : seules les clés enabled = true
  active_features = {
    for k, v in var.lab_features : k => v if v.enabled
  }
}

resource "aws_s3_bucket" "features" {
  for_each = local.active_features

  bucket = "lab-tf-feat-${each.key}-${data.aws_caller_identity.current.account_id}"

  tags = {
    Name    = each.key
    Purpose = each.value.purpose
    Lab     = "for-each-filtre"
  }
}

Adresse …["logs"] stable : désactiver tmp ne réordonne rien. Règle P2 : for_each pour objets nommés ; count pour le toggle 0/1. Une clé enabled = false retire cette instance seulement.

Étape 4 — Fonctions utiles : coalesce, try, can, one

try et one rendent les outputs robustes face à une resource absente (count = 0). coalesce évite les chaînes vides quand un override optionnel est null. can sert surtout dans des validations ou des locals défensifs. Ces fonctions ne remplacent pas un booléen clair var.enable_… pour décider de créer ou non — elles sécurisent la lecture après coup.

Ces fonctions complètent le terraform conditional hors meta-arguments :

Fonction Rôle Exemple typique
coalesce(a, b, …) Première valeur non null Défaut de chaîne / ID
try(expr, fallback) Évalue expr ; si erreur → fallback Accéder à [0] optionnel
can(expr) true si expr s’évalue sans erreur Valider une forme avant usage
one(list) Exactement 0 ou 1 élément → null ou l’élément ; sinon erreur Sortie d’un count 0/1
locals {
  display_name = coalesce(var.custom_name, "lab-default")

  # Équivalent plus sûr que length(...) > 0 ? …[0] : null
  bucket_id_safe = try(aws_s3_bucket.optional[0].id, null)
  bucket_id_one  = one(aws_s3_bucket.optional[*].id)

  # can : utile pour valider une structure avant de la consommer
  has_purpose = can(var.lab_features["logs"].purpose)
}

variable "custom_name" {
  type    = string
  default = null
}

one(resource[*].attr) est particulièrement lisible avec count 0/1 : vous évitez le ternaire length(…) == 1 ? …[0] : null. try reste le filet de sécurité le plus courant dans les outputs.

Étape 5 — Pièges : null vs omit, types, références

Les erreurs « Missing resource instance key » et « Inconsistent conditional result types » dominent les forums. La première se corrige avec [0] / clé for_each ou try/one. La seconde impose d’homogénéiser les types. Quant aux valeurs unknown au plan : ne basez jamais count/for_each sur un ID cloud encore inconnu ; décidez avec une variable d’entrée.

  1. null vs omettre l’argument — Passer null à un argument optionnel signifie souvent « utiliser le défaut provider / ne pas envoyer ». Ce n’est pas équivalent à un attribut obligatoire manquant. Vérifiez la doc du resource : certains attributs rejettent null explicitement.
  1. Types des branches ternaires — var.x ? "a" : 1 échoue. Homogénéisez (tostring, tonumber) ou reformulez en locals.
  1. Référencer une ressource à count = 0 — aws_s3_bucket.optional.id (sans index) est invalide. Utilisez try(…[0].id, null), one(…[*].id), ou un ternaire sur var.enable_lab_bucket.
  1. Valeurs inconnues au plan — Une condition dépendant d’un attribut cloud encore inconnu peut bloquer count / for_each (« value is unknown »). Préférez des variables / locals connus au plan pour décider de la présence.
  1. count et for_each ensemble — Interdits sur le même bloc. Choisissez l’un ou l’autre.
  1. Chaîne "false" — Tapez bool dans variable ; évitez les strings "true"/"false".

Étape 6 — Lab ca-central-1 (toggle bool → ressource légère)

Enchaînez plan/apply avec true puis false pour voir le destroy de [0] dans le plan. C’est le moment où le concept devient concret. Variante pédagogique : recréez le même besoin avec map filtrée et comparez terraform state list. Destroy final obligatoire — un bucket lab oublié pollue le compte.

Objectif : activer / désactiver un bucket S3 via enable_lab_bucket, Region ca-central-1.

# versions.tf
terraform {
  required_version = ">= 1.9.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# providers.tf
provider "aws" {
  region = "ca-central-1"
  # Auth : AWS_PROFILE / SSO — jamais de clés ici
}

# variables.tf
variable "enable_lab_bucket" {
  type        = bool
  description = "Crée (true) ou omet (false) le bucket de lab."
  default     = true
}

# main.tf — data + resource count (étape 2)
# outputs.tf — try / one (étape 4)

Commandes :

mkdir -p ~/terraform-conditionnels-lab && cd ~/terraform-conditionnels-lab
# collez versions.tf, providers.tf, variables.tf, main.tf, outputs.tf
export AWS_PROFILE=lab
export AWS_REGION=ca-central-1
terraform init
terraform fmt
terraform validate

# Plan avec ressource ON
terraform plan -var='enable_lab_bucket=true'
terraform apply -auto-approve -var='enable_lab_bucket=true'
terraform output

# Plan avec ressource OFF (destroy de l’instance count[0])
terraform plan -var='enable_lab_bucket=false'
terraform apply -auto-approve -var='enable_lab_bucket=false'

terraform destroy -auto-approve

Observez : true → create ; false → destroy de [0]. Variante : comparez avec le for_each filtré (terraform state list).

Étape 7 — Checklist

Avant Dynamic blocks, validez que vous savez choisir : ternaire (valeur), count 0/1 (toggle simple), for_each filtré (objets nommés), et outputs sûrs. Cette grille évite 80 % des anti-patterns conditionnels en revue de module.

  1. Ternaire = valeur conditionnelle ; count / for_each = présence d’instances.
  2. Toggle simple 0/1 → count = var.enabled ? 1 : 0.
  3. Plusieurs objets nommés on/off → map filtrée + for_each (stabilité).
  4. Outputs sûrs : try, one, jamais .id nu sur un count 0.
  5. coalesce pour défauts ; can pour tests d’évaluabilité.
  6. Homogénéité des types ; null ≠ oubli d’attribut obligatoire.
  7. Lab ca-central-1 + destroy ; aucun secret dans le code.

Nettoyage

cd ~/terraform-conditionnels-lab
terraform destroy -auto-approve
rm -rf ~/terraform-conditionnels-lab

Confirmez l’absence du bucket (ou users) de lab dans le compte.

Erreurs fréquentes

Symptôme Cause Correction
Missing resource instance key Référence sans [0] / clé sur ressource count/for_each try(…[0].id, null) ou one(…[*].id)
Inconsistent conditional result types Branches ternaires de types différents Aligner les types ou convertir
Destroy inattendu en passant enabled=false Comportement normal du toggle count Documenter le flag ; snapshots / backups si prod
Invalid count argument / unknown Condition dépend d’une valeur cloud inconnue au plan Décider via variable / local connu
count et for_each set Meta-arguments incompatibles ensemble Choisir l’un ; préférer map filtrée si N clés
Feature « absente » mais adresse qui glisse Plusieurs toggles via count indexé Passer à for_each + clés stables
Booléen passé en string "false" tfvars / CI mal typés type = bool + vraie valeur HCL
one() échoue avec 2+ éléments Splat inattendu Filtrer avant one ou revoir le count
Module enfant ignore le flag Booléen non passé en input Propager var.enable_… explicitement

Quiz (3 questions)

1. Que fait count = var.enable_lab_bucket ? 1 : 0 ?

  • A. Change seulement un tag
  • B. Crée 0 ou 1 instance selon le booléen
  • C. Remplace obligatoirement for_each

2. Pourquoi filtrer une map puis for_each plutôt que plusieurs count 0/1 ?

  • A. C’est plus rapide à appliquer
  • B. Les clés stables évitent le réordonnancement et clarifient l’état
  • C. count est déprécié depuis Terraform 1.9

3. Quelle fonction renvoie l’unique élément d’une liste de 0 ou 1 élément, sinon erreur ?

  • A. coalesce
  • B. can
  • C. one

4. Que se passe-t-il si vous passez enable_lab_bucket=false après un apply true ?

  • A. Rien : le bucket reste
  • B. Terraform planifie le destroy de l’instance [0]
  • C. Seuls les tags changent

5. Pourquoi éviter un ternaire dont les branches sont string et number ?

  • A. C’est plus lent
  • B. Types inconsistants — erreur de validation HCL
  • C. Interdit seulement en workspace prod

Réponses : 1‑B · 2‑B · 3‑C · 4‑B · 5‑B

FAQ

count est-il déprécié ? Non. Il reste pertinent pour 0/1 ou des listages indexés simples. Pour des collections nommées évolutives, for_each est préférable.

Puis-je conditionner un bloc entier dynamic ici ? Les bases sont ici ; la syntaxe dynamic / for_each imbriqué est traitée dans Dynamic blocks. N’anticipez pas en recopiant des blocs opaques.

Comment tester sans créer de bucket ? terraform plan -var='enable_lab_bucket=false' puis true : observez 0 vs 1 create sans apply si vous préférez le mode lecture seule.

Pour aller plus loin

Maillage série Terraform (P2)

← Précédent Boucles (terraform-boucles-for-each)
→ Suivant Dynamic blocks (terraform-dynamic-blocks)
Aussi Variables (terraform-variables) · Locals (terraform-locals)
Carte Overview · … · Loops · Conditionals · Dynamic blocks · Modules · Workspaces · …

Retour parcours Terraform — hub de la série et leçons sœurs.

Share your love

Leave a Reply

Your email address will not be published. Required fields are marked *