
Un bloc locals nomme des valeurs calculées, pour ne pas répéter la même
expression à dix endroits. C'est simple en apparence, mais trois comportements
surprennent et causent des bugs difficiles à diagnostiquer : plusieurs blocs
locals fusionnent, un ternaire convertit ses types sans prévenir, et un
local dérivé d'une ressource n'est pas connu au plan.
Ce guide part de la base, ce qu'est un local et ce qu'il peut référencer, puis
traite ces trois pièges, plus la propagation de la sensibilité. Tous les
comportements ont été vérifiés sur Terraform v1.15.4, la plupart dans
terraform console, avec local et random.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Ce qu'un local peut référencer, et ce qui le distingue d'une variable
- Pourquoi plusieurs blocs
localsfusionnent - Le piège de conversion de type du ternaire
- Pourquoi un local peut être inconnu au plan
- Comment la sensibilité se propage à travers un local
Prérequis
Section intitulée « Prérequis »- Variables et types (variables Terraform)
- Terraform 1.15.x, la série stable courante
Qu'est-ce qu'un local ?
Section intitulée « Qu'est-ce qu'un local ? »Un local nomme une valeur calculée. Sa syntaxe se réduit à
nom = expression. Il n'accepte ni type, ni description, ni sensitive,
ni validation : c'est exactement ce qui le distingue d'une variable, qui,
elle, peut être typée et documentée. Un local est une valeur interne, que
rien ne surcharge depuis l'extérieur.
locals { ref = replace(lower(var.enseigne), "_", "-")}Un local peut référencer quatre choses, et la documentation les liste précisément : une variable, un attribut de ressource, la sortie d'une fonction, et un autre local. Beaucoup de guides n'illustrent que les variables et les autres locals, mais référencer un attribut de ressource est justement la source du deuxième piège.
Les blocs locals fusionnent
Section intitulée « Les blocs locals fusionnent »On peut écrire autant de blocs locals {} qu'on veut, dans un ou plusieurs
fichiers. Terraform les fusionne : un local d'un bloc peut en référencer un
autre déclaré ailleurs, tant qu'aucun cycle n'apparaît.
locals { ref = replace(lower(var.enseigne), "_", "-")}
locals { etiquette = "${local.ref}-${var.palier}"}terraform console> local.etiquette"ma-boutique-gold"local.etiquette, dans le second bloc, consomme local.ref du premier. La
règle de style officielle : un local partagé entre plusieurs fichiers va dans
locals.tf ; un local propre à un fichier se déclare en haut de ce fichier.
Le piège du ternaire : les types se convertissent en silence
Section intitulée « Le piège du ternaire : les types se convertissent en silence »C'est l'erreur la plus fréquente, et beaucoup de guides l'énoncent à l'envers. On croit souvent qu'un ternaire dont les deux branches n'ont pas le même type échoue. C'est faux : Terraform convertit vers un type commun sans broncher, et n'échoue que si aucune conversion n'est possible.
> type(true ? 20 : 5)number
> type(true ? 20 : "5")string20 : "5" ne lève aucune erreur, il rend une chaîne. Un local censé
porter un nombre se retrouve typé chaîne, et le bug se révèle bien plus loin, là
où ce nombre est utilisé dans un calcul. La documentation officielle recommande
d'ailleurs d'être explicite en cas de doute :
> true ? tostring(20) : "cinq""20"La règle : ne mettez pas de guillemets autour d'un nombre, et si les branches
diffèrent vraiment, convertissez-les avec une fonction (tostring, tonumber).
Un local dérivé d'une ressource est inconnu au plan
Section intitulée « Un local dérivé d'une ressource est inconnu au plan »Un local se calcule pendant le plan, sauf s'il référence un attribut de
ressource qui n'existe pas encore. Sa valeur est alors (known after apply),
exactement comme l'attribut dont il dépend. Beaucoup de guides affirment à tort
qu'un local est toujours résolu au plan.
resource "random_id" "tirage" { byte_length = 4}
locals { empreinte = upper(random_id.tirage.hex)}Sur un plan à froid, un output exposant local.empreinte apparaît en inconnu :
random_id.tirage.hex n'est produit qu'à l'apply. Le plan JSON le montre dans
output_changes[].after_unknown. C'est aussi ce qui crée une dépendance
implicite dans le graphe : le local dépend de la ressource.
terraform plan -out=tfplanterraform show -json tfplan | jq '.output_changes.empreinte.after_unknown'trueLa sensibilité se propage à travers un local
Section intitulée « La sensibilité se propage à travers un local »Dernier piège, et il bloque l'apply. Terraform traite comme sensible toute
expression qui utilise une valeur sensible. Un local qui assemble une chaîne à
partir d'une variable sensitive = true devient donc lui-même sensible :
variable "mot_de_passe" { type = string sensitive = true}
locals { dsn = "postgres://app:${var.mot_de_passe}@localhost/base"}Un output qui expose local.dsn sans sensitive = true fait échouer
Terraform, dès validate :
Error: Output refers to sensitive valuesLa correction est d'annoter la sortie sensitive = true. La sensibilité n'est
pas une propriété qu'on choisit : elle se propage toute seule à travers les
locals, et c'est le piège le plus courant du sous-objectif 2f.
Local, variable ou output ?
Section intitulée « Local, variable ou output ? »Les trois servent à des choses différentes. Le tableau les tranche par ce qu'ils acceptent et par leur portée.
| Peut être typé | Surchargeable de l'extérieur | Visible hors du module | |
|---|---|---|---|
variable | oui (type, validation) | oui (CLI, tfvars) | non |
local | non (nom = expression) | non | non |
output | non | non | oui |
Un point souvent mal compris : un local n'est pas lisible depuis un autre module. Pour transmettre une valeur calculée à un module enfant, on la passe en argument, pas en la lisant directement.
Quand utiliser un local
Section intitulée « Quand utiliser un local »Dès qu'une expression est répétée ou assez complexe pour gêner la
lecture d'une ressource : une expression for, un ternaire imbriqué, un
assemblage de chaîne. La documentation met en garde contre l'abus : « Use local
values sparingly, as overuse can make your code harder to understand. » Un local
utilisé une seule fois et trivial n'apporte rien.
Dépannage
Section intitulée « Dépannage »Ces symptômes viennent tous des particularités ci-dessus. Le tableau relie chacun à sa cause réelle.
| Symptôme | Cause | Solution |
|---|---|---|
| Un calcul échoue sur un local censé être un nombre | Le ternaire a glissé vers une chaîne | Retirer les guillemets, ou convertir avec tonumber |
Error: Output refers to sensitive values | Un local dérive d'une variable sensible | Marquer la sortie sensitive = true |
Un output est (known after apply) de façon inattendue | Le local dérive d'un attribut de ressource | Normal : la valeur n'existe qu'à l'apply |
| Un local n'est pas visible dans un autre module | Les locals sont internes au module | Passer la valeur en argument du module enfant |
À retenir
Section intitulée « À retenir »- Un local est un nom pour une expression : ni
type, nidescription, nisensitive, contrairement à une variable. - Un local peut référencer une variable, un attribut de ressource, une fonction ou un autre local.
- Plusieurs blocs
localsfusionnent ; ils se référencent librement. - Le ternaire convertit ses types en silence :
20 : "5"rend une chaîne, pas une erreur. - Un local dérivé d'une ressource est inconnu au plan (
known after apply). - La sensibilité se propage : une sortie dérivant d'une variable sensible
doit être marquée
sensitive. - Un local n'est pas lisible depuis un autre module : passer par un argument.
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les questions ci-dessous portent sur les comportements qui surprennent le plus : la fusion des blocs, la conversion de type du ternaire, et la propagation de la sensibilité.
Ce qui les sépare
| Typable | Surchargeable | Visible hors du module | |
|---|---|---|---|
variable |
oui | oui (CLI, tfvars) | non |
local |
non | non | non |
Un local, c'est juste une valeur
Sa syntaxe se réduit ànom = expression. Il n'accepte ni type, ni description, ni sensitive, ni validation. C'est une valeur interne, calculée, que rien ne surcharge depuis l'extérieur.Quatre constructions
La documentation les liste : une variable, un attribut de ressource, la sortie d'une fonction, et un autre local.locals {
ref = replace(lower(var.enseigne), "_", "-")
hash = substr(random_id.build.hex, 0, 8)
}
La conséquence à connaître
Référencer un attribut de ressource rend le local inconnu au plan : sa valeur n'existe qu'à l'apply.Terraform fusionne les blocs
locals {
ref = replace(lower(var.enseigne), "_", "-")
}
locals {
etiquette = "${local.ref}-${var.palier}"
}
local.etiquette, dans le second bloc, consomme local.ref du premier. La seule limite est l'absence de dépendance circulaire.La règle de style
Un local partagé entre fichiers va danslocals.tf ; un local propre à un fichier se déclare en haut de ce fichier.Pas une erreur, une conversion silencieuse
> type(true ? 20 : 5)
number
> type(true ? 20 : "5")
string
20 : "5" ne lève aucune erreur, il rend une chaîne. Le bug se révèle bien plus loin, au calcul suivant.La parade
Ne pas mettre de guillemets autour d'un nombre. Si les branches diffèrent vraiment, convertir :true ? tostring(20) : "cinq". La documentation recommande d'être explicite en cas de doute.Le cas général et l'exception
Un local se calcule au plan, sauf s'il dérive d'un attribut de ressource pas encore créé.locals {
empreinte = upper(random_id.tirage.hex)
}
La preuve
terraform show -json tfplan | jq '.output_changes.empreinte.after_unknown'
true
random_id.tirage.hex n'existe qu'à l'apply : le local hérite de cette inconnue et crée une dépendance implicite dans le graphe.La sensibilité se propage
variable "mot_de_passe" {
sensitive = true
}
locals {
dsn = "postgres://app:${var.mot_de_passe}@localhost/base"
}
Un output exposant local.dsn sans sensitive = true échoue :Error: Output refers to sensitive values
La correction
Annoter la sortiesensitive = true. La sensibilité n'est pas un choix : elle se propage toute seule à travers les locals.Les locals sont internes
La documentation est explicite : « You can access local values in the module where you define them, but not in other modules. »La solution officielle
Passer la valeur en argument du module enfant :module "reseau" {
source = "./modules/reseau"
base_name = local.base_name
}
Ce n'est pas la non-surcharge qui est en jeu, mais la portée : un local n'existe que dans son module.Pour aller plus loin
Section intitulée « Pour aller plus loin »- Conditions et ternaires : Le ternaire et ses garde-fous, forme la plus fréquente dans un bloc locals.
- Expressions for Terraform : La transformation de collections qui justifie le plus souvent un local nommé.
- Le style guide Terraform : Les conventions officielles sur le placement et le nommage des valeurs locales.