Terraform — Expressions et fonctions
| Fiche express | |
|---|---|
| Type | Système de types HCL, expressions, fonctions intégrées |
| Types de base | primitifs, collections, structurés |
| Conditionnel | condition ? val_vrai : val_faux
|
| Splat | liste[*].attribut
|
| Voir aussi | Terraform — Concepts fondamentaux · Terraform — Modules et variables · Terraform — Boucles et méta-arguments |
Le HCL n'est pas qu'un format de déclaration de blocs : c'est un langage d'expressions à part entière, typé, avec ses opérateurs et plus d'une centaine de fonctions intégrées. Cette page détaille le système de types, les constructions d'expressions (interpolation, conditions, splat) et passe en revue les principales familles de fonctions. Elle complète Terraform — Modules et variables, qui aborde les types côté déclaration de variables.
Système de types
HCL distingue trois familles de types.
Types primitifs
string— chaîne de caractères, ex."rke2-node-01"number— entier ou flottant, ex.3,4.2bool—true/false
Types de collection
Regroupent des éléments d'un même type.
list(type)— séquence ordonnée, indexable, doublons autorisésset(type)— ensemble non ordonné, sans doublonmap(type)— dictionnaire clé (string) → valeur
variable "zones" {
type = list(string)
default = ["a", "b", "c"]
}
variable "tags" {
type = map(string)
default = { environnement = "prod", geree_par = "terraform" }
}
Types structurés
Regroupent des éléments de types potentiellement différents, à structure fixe.
object({ ... })— attributs nommés et typés, façon « struct »tuple([...])— séquence positionnelle de types hétérogènes
variable "vm_profile" {
type = object({
cpu = number
memory = number
disks = list(number)
})
default = {
cpu = 4
memory = 8192
disks = [40, 100]
}
}
Valeur spéciale null
null représente l'absence de valeur. Affecter null à un argument optionnel équivaut généralement à ne pas le renseigner (le provider applique sa valeur par défaut).
Interpolation de chaînes
Toute expression peut être injectée dans une chaîne via ${...} :
name = "rke2-${var.environnement}-${count.index}"
Depuis Terraform 0.12, si la chaîne entière est une seule interpolation (rien avant/après), le résultat garde son type d'origine plutôt que d'être converti en string — utile pour passer un nombre ou une liste sans les « stringifier » involontairement.
Les heredoc (<<-EOT ... EOT) supportent aussi l'interpolation, pratique pour des blocs de configuration multi-lignes (cloud-init, YAML embarqué) :
user_data = <<-EOT
#cloud-config
hostname: rke2-${count.index}
EOT
Opérateurs
- Arithmétiques :
+ - * / % - Comparaison :
== != < <= > >= - Logiques :
&& || ! - Concaténation de listes/chaînes : selon le type, via fonctions dédiées (
concat, interpolation) plutôt qu'un opérateur+universel
Expression conditionnelle
Syntaxe ternaire classique :
instance_type = var.environnement == "prod" ? "m5.xlarge" : "t3.medium"
# Combinée à count pour une ressource optionnelle
resource "aws_eip" "this" {
count = var.assign_public_ip ? 1 : 0
}
Très utilisée pour piloter count (ressource présente ou non) ou choisir une valeur selon l'environnement, sans dupliquer de bloc.
Expressions splat
L'opérateur [*] extrait un attribut de tous les éléments d'une liste/tuple en une seule expression, évitant une boucle explicite :
# Équivalent condensé de [for vm in aws_instance.node : vm.id]
resource "aws_instance" "node" {
count = 3
# ...
}
output "ids" {
value = aws_instance.node[*].id
}
Le splat ne fonctionne que sur des listes/tuples (typiquement issues d'une ressource avec count). Pour une ressource en for_each (map), on utilise plutôt une expression for — voir Terraform — Boucles et méta-arguments.
Expressions for
Transforment une collection en une autre (liste ou map), avec filtrage optionnel :
# Liste -> liste, avec filtre
locals {
prod_zones = [for z in var.zones : z if z != "sandbox"]
}
# Liste -> map
locals {
zone_index = { for i, z in var.zones : z => i }
}
Tour d'horizon des fonctions intégrées
Terraform ne permet pas de définir ses propres fonctions : on compose parmi les fonctions intégrées (aucune fonction utilisateur, contrairement à d'autres langages). Quelques familles utiles au quotidien :
Numériques
max(4, 8, 2) # 8
min(4, 8, 2) # 2
ceil(4.1) # 5
floor(4.9) # 4
Chaînes
lower("RKE2-Node") # "rke2-node"
format("rke2-%02d", 3) # "rke2-03"
join("-", ["prod", "toulouse"]) # "prod-toulouse"
split(",", "a,b,c") # ["a", "b", "c"]
trimspace(" x ") # "x"
Collections
concat(["a"], ["b", "c"]) # ["a", "b", "c"]
merge({a = 1}, {b = 2}) # {a = 1, b = 2}
lookup(var.tags, "env", "n/a") # valeur ou défaut si clé absente
contains(var.zones, "a") # true
distinct(["a", "a", "b"]) # ["a", "b"]
length(var.zones) # nombre d'éléments
Encodage et hachage
jsonencode({ a = 1 }) # sérialisation JSON
yamldecode(file("conf.yaml"))
base64encode("secret")
sha256("valeur")
Pratique pour générer un bloc user_data JSON, ou calculer une empreinte utilisée comme déclencheur de recréation (ex. hash d'un fichier de configuration en argument d'un triggers de provisioner).
Date et heure
timestamp() # horodatage courant (ISO 8601)
formatdate("YYYY-MM-DD", timestamp())
À manier avec précaution : timestamp() change à chaque plan, ce qui peut provoquer un diff permanent si la valeur est directement affectée à un attribut de ressource (le documenter explicitement, ex. via ignore_changes — voir Terraform — Boucles et méta-arguments).
Filesystem
file("${path.module}/scripts/init.sh") # contenu brut d'un fichier
templatefile("${path.module}/cloud-init.tpl", { hostname = "node-01" })
fileexists("${path.module}/optional.conf")
templatefile est la fonction la plus utilisée en pratique : elle interpole des variables dans un fichier gabarit externe, évitant les heredoc géants dans le .tf.