
Les variables sont les entrées d'un module, les outputs ses
sorties : ensemble ils forment son interface, la seule chose que voit
celui qui l'appelle. Ce guide s'adresse à qui écrit un module destiné à être
réutilisé : au-delà de type et default, vous y verrez optional()
pour les attributs d'objet, nullable face à un null explicite,
validation pour refuser une valeur, et les arguments d'output qui changent
le comportement, sensitive et precondition.
Tous les comportements de ce guide ont été exécutés sur Terraform v1.15.4
avec les providers local et random : sorties JSON, messages d'erreur
et codes de retour compris.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Déclarer une variable : type, description, défaut
- Rendre facultatif un attribut d'objet, ce que
defaultne sait pas faire - Distinguer une valeur absente d'un
nullexplicite - Refuser une valeur invalide avec un message exploitable
- Exposer une sortie sensible, ou la garder sous condition
Prérequis
Section intitulée « Prérequis »- Terraform 1.11 ou plus récent (installer Terraform).
optional()existe depuis la 1.3. - Savoir créer un module et l'appeler depuis une configuration racine.
- Les variables Terraform en général, dont ce guide reprend les bases pour les appliquer aux modules.
Une variable, c'est quoi au juste ?
Section intitulée « Une variable, c'est quoi au juste ? »Un paramètre d'entrée du module, déclaré par un bloc variable. Ce bloc
accepte plus d'arguments qu'on ne le croit, et chacun a un effet
observable :
| Argument | Rôle | Défaut |
|---|---|---|
type | contrainte de type | libre |
description | ce que l'appelant lit | vide |
default | rend la variable facultative | absent, donc obligatoire |
validation | refuse certaines valeurs | aucune |
sensitive | masque la valeur dans les sorties | false |
nullable | accepte, ou non, un null explicite | true |
ephemeral | valeur non persistée (1.10+) | false |
deprecated | avertit l'appelant (1.15+) | absent |
La règle de base ne bouge pas : sans default, la variable est obligatoire,
et l'appelant qui l'oublie voit No value for required variable.
variable "canal" { description = "Canal de diffusion a produire." type = string}optional() : un défaut, mais pour un attribut d'objet
Section intitulée « optional() : un défaut, mais pour un attribut d'objet »Voici la limite que l'on rencontre dès le premier type composite :
default porte sur la variable entière, jamais sur les champs d'un
objet. Déclarez ceci, et tout appel devra fournir les trois attributs :
variable "canal" { type = object({ nom = string frequence = string actif = bool })}Le mécanisme prévu pour cela est optional(type, defaut), disponible depuis
la 1.3 :
variable "canal" { description = "Canal de diffusion : son nom, sa frequence, son etat." type = object({ nom = string frequence = optional(string, "hebdomadaire") actif = optional(bool, true) })}Un appel qui ne fournit que le nom obtient alors un objet complet :
module "bulletin" { source = "./modules/bulletin"
canal = { nom = "interne" }}{ "actif": true, "frequence": "hebdomadaire", "nom": "interne"}Le second argument est la valeur substituée quand l'attribut est absent.
Sans lui, optional(string) rend null : l'attribut devient facultatif,
mais sans valeur de repli.
nullable : un null explicite n'est pas une absence
Section intitulée « nullable : un null explicite n'est pas une absence »C'est le piège le plus discret du bloc variable, et il casse des modules en
production. Un appelant écrit libelle = null en pensant « ne rien passer ».
La variable a pourtant un défaut :
variable "libelle" { type = string default = "bulletin"}Mesuré sur 1.15.4, la valeur reçue est null :
libelle = nullL'explication tient à l'argument nullable, qui vaut true par défaut :
un null explicite est une valeur comme une autre, et il écrase le
défaut. La correction tient en une ligne :
variable "libelle" { type = string default = "bulletin" nullable = false}Le même appel rend alors "bulletin". Un module réutilisable pose donc
nullable = false sur toute variable dont le défaut doit toujours
s'appliquer.
validation : refuser une valeur, et dire où corriger
Section intitulée « validation : refuser une valeur, et dire où corriger »Un bloc validation rejette une valeur avec un message que vous écrivez :
variable "taille_jeton" { description = "Longueur du jeton genere, entre 12 et 64 caracteres." type = number default = 16
validation { condition = var.taille_jeton >= 12 && var.taille_jeton <= 64 error_message = "taille_jeton doit etre comprise entre 12 et 64." }}Le refus nomme deux endroits, et la dernière ligne compte quand plusieurs modules déclarent la même variable :
Error: Invalid value for variable
on main.tf line 6, in module "bulletin": 6: taille_jeton = 8 ├──────────────── │ var.taille_jeton is 8
taille_jeton doit etre comprise entre 12 et 64.
This was checked by the validation rule atmodules/bulletin/variables.tf:19,3-13.Un output porte plus que value
Section intitulée « Un output porte plus que value »Le bloc output accepte lui aussi davantage que les deux arguments
habituels :
| Argument | Rôle |
|---|---|
value | la valeur exposée, obligatoire |
description | ce que l'appelant lit |
type | le contrat de sortie |
sensitive | masque la valeur, et conditionne sa republication |
precondition | permet à la sortie de refuser de se publier |
depends_on | dépendance explicite |
ephemeral | valeur non persistée (1.10+, interdit à la racine) |
deprecated | avertit le consommateur de la sortie (1.15+) |
Un output reste la seule chose qu'un module rend visible : ses ressources sont opaques depuis l'appelant.
sensitive : la contamination remonte
Section intitulée « sensitive : la contamination remonte »Marquer une sortie sensible ne fait pas que masquer un affichage : cela contraint l'appelant.
output "jeton" { description = "Jeton d'acces du canal." sensitive = true value = random_password.jeton.result}Republiez cette valeur à la racine sans la marquer, et le plan échoue :
Error: Output refers to sensitive values
To reduce the risk of accidentally exporting sensitive data that was intendedto be only internal, Terraform requires that any root module outputcontaining sensitive data be explicitly marked as sensitive, to confirm yourintent.Le point qui surprend : le module enfant n'a même pas besoin d'avoir marqué sa propre sortie. La contamination vient de la valeur, dès qu'elle dérive d'une donnée sensible. Chaîner des modules sans le savoir mène droit à ce refus, et c'est une bonne nouvelle : Terraform vous empêche d'exporter un secret par inadvertance.
precondition : une sortie qui refuse de se publier
Section intitulée « precondition : une sortie qui refuse de se publier »Une sortie peut porter une garantie, vérifiée au plan :
output "emplacement" { description = "Chemin du bulletin, publie seulement si le canal est actif." value = local_file.this.filename
precondition { condition = var.canal.actif error_message = "L'emplacement n'est publie que pour un canal actif." }}Appelez le module avec actif = false, et le plan s'arrête :
Error: Module output value precondition failedC'est la différence entre une sortie qui expose une valeur et une sortie qui promet quelque chose : la seconde refuse de mentir quand l'état du module ne permet pas de tenir la promesse.
Dépannage
Section intitulée « Dépannage »Les messages ci-dessous se lisent dans l'ordre où une interface incomplète les produit.
| Symptôme | Cause probable | Solution |
|---|---|---|
No value for required variable | Variable sans default non fournie | La renseigner, ou lui donner un défaut |
| Le plan échoue sur un objet incomplet | Attributs d'objet tous obligatoires | optional(type, defaut) sur les champs facultatifs |
Une variable vaut null malgré son défaut | Un null explicite chez l'appelant | nullable = false |
Un attribut optionnel vaut null | optional() sans second argument | Ajouter la valeur de repli |
validate passe mais le plan refuse | La condition référence une autre variable | Normal : la validation s'évalue au plan |
Output refers to sensitive values | Sortie racine non marquée | sensitive = true sur la sortie racine |
Module output value precondition failed | Une garantie de sortie n'est pas tenue | Corriger l'appel, ou revoir la condition |
Mettre en pratique
Section intitulée « Mettre en pratique »Le lab variables et outputs d'un module remet un module appelé deux fois,
dont toute l'interface est à écrire. L'appel complet fournit tout, l'appel
minimal un seul attribut : c'est lui qui met le contrat à l'épreuve. Les
tests fabriquent les variantes fautives dans des copies temporaires, pour
prouver qu'un null explicite, une valeur hors bornes, un secret non marqué
et un dépôt non chiffré sont bien refusés. Il se joue hors ligne.
À retenir
Section intitulée « À retenir »- L'interface d'un module est la seule chose que voit l'appelant : variables en entrée, outputs en sortie.
- Sans
default, une variable est obligatoire. Avec, elle est facultative. optional(type, defaut)rend facultatif un attribut d'objet, ce quedefaultne sait pas faire.nullablevauttruepar défaut : unnullexplicite écrase le défaut.nullable = falsele rétablit.- La
validations'évalue au plan, pas toujours auvalidate: ne bâtissez pas une CI sur cette seule commande. - Le message d'erreur nomme deux endroits : la valeur fautive et la règle qui l'a refusée.
- Un output accepte
type,sensitive,precondition,depends_on,ephemeraletdeprecated, pas seulementvalue. - La sensibilité remonte : une sortie racine qui republie une valeur sensible doit le déclarer, sinon le plan échoue.
- Une
preconditionpermet à une sortie de refuser de se publier.
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les questions ci-dessous portent sur ce qui casse en pratique : l'attribut
d'objet qu'on ne sait pas rendre facultatif, le null qui écrase un
défaut, et le plan refusé pour une histoire de secret.
optional() porte sur le champ, default sur la variable
variable "canal" {
type = object({
nom = string
frequence = optional(string, "hebdomadaire")
actif = optional(bool, true)
})
}
Un appel qui ne fournit que nom obtient alors un objet complet :{
"actif": true,
"frequence": "hebdomadaire",
"nom": "interne"
}
Le second argument est la valeur substituée quand l'attribut est absent. Sans lui, optional(string) rend null : l'attribut devient facultatif, mais sans valeur de repli.Un null explicite n'est pas une absence
Mesuré sur Terraform 1.15.4. Avec cette déclaration :variable "libelle" {
type = string
default = "bulletin"
}
un appelant qui écrit libelle = null obtient null, pas "bulletin". La correction tient en une ligne :variable "libelle" {
type = string
default = "bulletin"
nullable = false
}
Un module réutilisable pose nullable = false sur toute variable dont le défaut doit toujours s'appliquer.Le validate ne suffit pas toujours
La référence du blocvariable situe l'évaluation des blocs validation pendant la création du plan.Vérifié sur 1.15.4 :- validation sur un argument littéral d'un bloc
module: attrapée parterraform validate; - condition référençant une autre variable :
validaterépondSuccess!et l'erreur tombe au plan ; - variable racine sans valeur fournie :
validatene la contrôle pas.
validate.Un output ne se limite pas à sa valeur
| Argument | Rôle |
|---|---|
value |
la valeur exposée, obligatoire |
description |
ce que l'appelant lit |
type |
le contrat de sortie |
sensitive |
masque la valeur et conditionne sa republication |
precondition |
permet à la sortie de refuser de se publier |
depends_on |
dépendance explicite |
ephemeral |
valeur non persistée (1.10+, interdit à la racine) |
deprecated |
avertit le consommateur de la sortie (1.15+) |
La sensibilité remonte avec la valeur
Error: Output refers to sensitive values
To reduce the risk of accidentally exporting sensitive data that was intended
to be only internal, Terraform requires that any root module output
containing sensitive data be explicitly marked as sensitive, to confirm your
intent.
La correction est d'ajouter sensitive = true sur la sortie racine :output "jeton_partage" {
sensitive = true
value = module.bulletin.jeton
}
C'est une protection, pas une gêne : Terraform vous empêche d'exporter un secret par inadvertance en chaînant des modules.Une sortie peut porter une garantie
output "emplacement" {
value = local_file.this.filename
precondition {
condition = var.canal.actif
error_message = "L'emplacement n'est publie que pour un canal actif."
}
}
Appelé avec actif = false, le plan s'arrête :Error: Module output value precondition failed
C'est la différence entre une sortie qui expose une valeur et une sortie qui promet quelque chose : la seconde refuse de mentir quand l'état du module ne permet pas de tenir la promesse.L'absence de default fait l'obligation
variable "canal" {
description = "Canal de diffusion a produire."
type = string
}
Sans default, l'appelant doit fournir la valeur :Error: No value for required variable
Réservez cette forme à ce qui relève réellement du contrat : le nom d'une ressource, l'identifiant d'un réseau. Tout ce qui a une valeur raisonnable par défaut gagne à en avoir une, assortie de nullable = false pour qu'un null explicite ne la contourne pas.Pour aller plus loin
Section intitulée « Pour aller plus loin »- Publier un module sur le registre : ce qu'une interface documentée permet une fois publiée.
- Bonnes pratiques pour les modules : ce qui distingue un module tenable d'un module jetable.
- Tester un module Terraform : les tests natifs qui vérifient qu'une interface tient ses promesses.
- Custom conditions : la référence officielle :
precondition,postconditionet blocscheck, au-delà des sorties. - Le bloc variable : la référence officielle : tous les arguments,
nullableetdeprecatedcompris. - Le bloc output : la référence officielle :
sensitive,preconditionet le contrat de sortie. - Contraintes de type :
optional()et les types composites.