Aller au contenu
Infrastructure as Code medium

Variables et outputs d'un module Terraform : paramétrer et exposer

30 min de lecture

logo terraform

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.

  • Déclarer une variable : type, description, défaut
  • Rendre facultatif un attribut d'objet, ce que default ne sait pas faire
  • Distinguer une valeur absente d'un null explicite
  • Refuser une valeur invalide avec un message exploitable
  • Exposer une sortie sensible, ou la garder sous condition

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 :

ArgumentRôleDéfaut
typecontrainte de typelibre
descriptionce que l'appelant litvide
defaultrend la variable facultativeabsent, donc obligatoire
validationrefuse certaines valeursaucune
sensitivemasque la valeur dans les sortiesfalse
nullableaccepte, ou non, un null explicitetrue
ephemeralvaleur non persistée (1.10+)false
deprecatedavertit 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 = null

L'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 at
modules/bulletin/variables.tf:19,3-13.

Le bloc output accepte lui aussi davantage que les deux arguments habituels :

ArgumentRôle
valuela valeur exposée, obligatoire
descriptionce que l'appelant lit
typele contrat de sortie
sensitivemasque la valeur, et conditionne sa republication
preconditionpermet à la sortie de refuser de se publier
depends_ondépendance explicite
ephemeralvaleur non persistée (1.10+, interdit à la racine)
deprecatedavertit 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.

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 intended
to be only internal, Terraform requires that any root module output
containing sensitive data be explicitly marked as sensitive, to confirm your
intent.

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

Les messages ci-dessous se lisent dans l'ordre où une interface incomplète les produit.

SymptômeCause probableSolution
No value for required variableVariable sans default non fournieLa renseigner, ou lui donner un défaut
Le plan échoue sur un objet incompletAttributs d'objet tous obligatoiresoptional(type, defaut) sur les champs facultatifs
Une variable vaut null malgré son défautUn null explicite chez l'appelantnullable = false
Un attribut optionnel vaut nulloptional() sans second argumentAjouter la valeur de repli
validate passe mais le plan refuseLa condition référence une autre variableNormal : la validation s'évalue au plan
Output refers to sensitive valuesSortie racine non marquéesensitive = true sur la sortie racine
Module output value precondition failedUne garantie de sortie n'est pas tenueCorriger l'appel, ou revoir la condition

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.

  • 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 que default ne sait pas faire.
  • nullable vaut true par défaut : un null explicite écrase le défaut. nullable = false le rétablit.
  • La validation s'évalue au plan, pas toujours au validate : 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, ephemeral et deprecated, pas seulement value.
  • La sensibilité remonte : une sortie racine qui republie une valeur sensible doit le déclarer, sinon le plan échoue.
  • Une precondition permet à une sortie de refuser de se publier.

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.

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn