Module 8 — Variables, types avancés et garde-fous
Durée indicative du cours : 1h45
Durée indicative de l’atelier : — (atelier naming / validations à venir)
Durée totale (cours + atelier) : ~2h30–3h00
Objectif du module
Le module 4 Basics a posé le catalogue des types et un aperçu de validation / sensitive.
Ici, on conçoit des interfaces (racine + modules) : compositions de types, optional(), validations robustes, attributs nullable / ephemeral / sensitive, et références built-in (path.*, terraform.workspace).
Rappel express des types
| Type | Usage typique |
|---|---|
string / number / bool |
Scalaires |
list(T) |
Suite ordonnée d’éléments homogènes |
set(T) |
Ensemble unique, sans ordre |
map(T) |
Dictionnaire clé → valeur homogène (ex. tags map(string)) |
object({…}) |
Structure hétérogène nommée |
tuple([…]) |
Positions fixes typées (rare en interface publique) |
any |
Échappatoire — à éviter en interface de module |
Quand choisir quoi
| Besoin | Préférer |
|---|---|
| Plusieurs éléments homogènes ordonnés | list |
| Ensemble unique sans ordre | set |
| Tags / dictionnaire homogène | map |
| Config structurée (name + cidr + …) | object |
| Positions fixes typées | tuple (documenter pourquoi) |
| « On verra plus tard » | Pas any en API publique |
Contraste déjà vu en Basics : list vs object.
optional() dans les objets
variable "subnet" {
type = object({
name = string
cidr = string
delegation = optional(string) # omis → null
nsg_id = optional(string, null) # défaut explicite
})
}
Les champs optionnels assouplissent les tfvars et les appels module sans abandonner le typage.
Compositions fréquentes
variable "subnets" {
type = list(object({
name = string
cidr = string
}))
}
# Variante souvent meilleure pour for_each (clés = noms) :
variable "subnets_map" {
type = map(object({
cidr = string
}))
}
Lien module 10
for_each aime les clés stables.
Une map(object(…)) est souvent plus sûre qu’une list + count.
Naming convention + validations
Construire la convention
Exemple d’équipe lab (à adapter) :
| Ressource | Motif |
|---|---|
| Resource group | rg-<projet>-<env>-<alias> |
| VNet | vnet-<projet>-<env>-<alias> |
| Subnet | snet-<projet>-<env>-<alias>-<rôle> |
Puis on encode avec locals + format / interpolation, et on valide tôt au plan.
locals {
rg_name = "rg-${var.project}-${var.environment}-${var.alias}"
}
variable "alias" {
type = string
description = "Suffixe court minuscules/chiffres"
validation {
condition = length(var.alias) >= 2 && can(regex("^[a-z0-9]+$", var.alias))
error_message = "alias : minuscules/chiffres, au moins 2 caractères."
}
}
Rappel nommage Basics : Nommage et tags.
Opérateurs dans condition
La condition d’un bloc validation est une expression booléenne.
| Opérateurs | Rôle | Exemple |
|---|---|---|
== != |
Égalité / différence | var.env == "lab" |
> >= < <= |
Comparaison | length(var.prefix) >= 3 |
&& \|\| |
Et / Ou | var.env == "lab" && length(var.alias) > 0 |
! |
Négation | !contains(local.forbidden, var.sku) |
variable "node_count" {
type = number
validation {
condition = var.node_count >= 1 && var.node_count <= 5
error_message = "node_count doit être entre 1 et 5."
}
}
Fonctions fréquentes dans condition
| Expression | Rôle |
|---|---|
contains(list, value) |
Valeur dans une liste autorisée |
can(expr) |
true si expr réussit (absorbe l’erreur) |
can(tonumber(…)) |
La chaîne est-elle numérique ? |
can(regex(pattern, str)) |
Motif OK ? |
alltrue(list(bool)) |
Toutes les conditions sont true |
anytrue(list(bool)) |
Au moins une est true |
variable "location" {
type = string
validation {
condition = contains(["switzerlandnorth", "switzerlandwest"], var.location)
error_message = "location non autorisée pour ce lab."
}
}
variable "extra_cidrs" {
type = list(string)
validation {
condition = alltrue([
for c in var.extra_cidrs : can(cidrhost(c, 0))
])
error_message = "chaque entrée de extra_cidrs doit être un CIDR valide."
}
}
can vs erreur brute
Sans can, un tonumber("abc") ou un mauvais regex peut faire échouer autrement.
Avec can, vous obtenez un false propre pour la validation.
Règles :
conditiondoit valoirtruepour accepter la valeur.- Messages actionnables (quoi corriger).
- Plusieurs blocs
validationpossibles sur la même variable.
Lab proposé — regex + naming
- Écrire la convention RG / VNet / subnet.
- Construire les noms en
locals. - Valider avec
can(regex(…)). - Tester dans
terraform console. - Cas négatifs (majuscules, underscore) →
planéchoue. - Cas positifs → déployer un lab léger.
Arguments avancés des variable
| Attribut | Basics | Associate |
|---|---|---|
type / default / description |
Oui | Rappel |
sensitive |
Aperçu | Profondeur (state !) |
validation |
Aperçu | Pratique |
nullable |
Non | Oui |
ephemeral |
Non | Oui (selon version lab) |
nullable
variable "override_name" {
type = string
nullable = false # null interdit ; omis ≠ null selon défaut
default = "lab"
}
Utile sur les interfaces de modules : clarifier si null est une valeur métier acceptée.
sensitive — masque ≠ protection du state
sensitive masque beaucoup d’affichages CLI / UI.
Le secret peut quand même être dans le tfstate (souvent en clair) dès qu’une ressource ou un output le référence.
Démo formateur (à faire)
apply avec un faux secret → terraform output masqué → ouvrir / grepper le state → valeur visible.
Conclusion : ACL + chiffrement du backend, pas de commit du state, alternatives Key Vault / CI / ephemeral.
ephemeral et arguments write-only
Objectif examen TA-004 (4h) : distinguer sensitive (masque l’affichage, mais le state peut encore contenir la valeur) et les mécanismes éphémères (Terraform 1.10+ / famille 1.12 pour l’examen), qui visent à ne pas persister certains secrets dans le state ni dans les fichiers de plan.
Variable ephemeral
Une variable marquée ephemeral = true ne peut être utilisée que dans des contextes éphémères (par exemple un autre argument write-only, ou un bloc ephemeral). Elle n’est pas destinée à être stockée comme une variable classique dans le state.
Bloc ephemeral (ressource temporaire)
Le bloc ephemeral décrit une ressource temporaire pour l’opération en cours. Terraform ne la met pas dans le state comme une ressource managée classique.
Exemple conceptuel (provider random) :
La valeur se lit ensuite via ephemeral.random_password.db.result pour alimenter un argument write-only d’une ressource managée.
Arguments write-only (*_wo)
Certains providers exposent des arguments write-only (souvent suffixés _wo, avec une version _wo_version pour forcer une mise à jour). Terraform envoie la valeur au provider pendant l’opération, puis ne la conserve pas dans le state.
Schéma mental (lab) :
- Générer ou lire un secret en
ephemeral. - Le passer à un argument write-only (secret Azure Key Vault, mot de passe, etc. selon le provider).
- Vérifier que le state ne contient pas ce secret en clair (contrairement à un
sensitiveclassique).
Documentation : Ephemeral values · Write-only arguments.
Message examen TA-004
sensitive = masquer (CLI / UI), pas « effacer du state ».
ephemeral / write-only = ne pas persister le secret dans state / plan.
Vault (ou Key Vault) reste une bonne pratique d’équipe pour stocker et pivoter les secrets hors Git.
Lab formateur
Sur le Terraform du lab (viser 1.12 comme l’examen 004), montrer un secret sensitive encore visible dans le state, puis un chemin ephemeral / write-only si le provider Azure du lab le permet. Sinon, démo courte avec random + un provider qui expose *_wo.
Références built-in — path.* et workspace
Ce ne sont pas des locals que vous écrivez : Terraform les fournit.
| Concept | Exemple | Rôle |
|---|---|---|
locals |
local.rg_name |
Valeur calculée par l’auteur |
var. |
var.prefix |
Entrée externe |
path.module |
chemin du module courant | Fichiers relatifs au module |
path.root |
module racine | Ancrage depuis un sous-module |
path.cwd |
répertoire de travail au plan | Prudence en CI |
terraform.workspace |
nom du workspace actif | Logique légère par workspace |
terraform.appversion |
version du binaire | Diagnostics / conditions rares |
output "paths" {
description = "Comparer path.module, path.root et path.cwd"
value = {
module = path.module
root = path.root
cwd = path.cwd
}
}
Démo sans cloud
À la racine, module et root sont souvent proches.
Dans un sous-module, l’écart path.module vs path.root devient visible.
Preferez path.module / path.root pour file / templatefile, pas path.cwd.
terraform.workspace
Voir aussi module 5 — workspaces CLI.
locals {
name_suffix = terraform.workspace == "default" ? "lab" : terraform.workspace
# ou : lookup({ default = "lab", dev = "dev", prod = "prod" }, terraform.workspace, "lab")
}
Limites
Utile en lab / démo.
Ne pas en faire la seule stratégie multi-env.
Distinguer workspaces CLI et workspaces Terraform Cloud (module 11).
Suite
Poursuivre avec le module 9 — Outputs et preconditions.
Pratique : interface de module typée + validations naming ; démo paths et workspace.