Aller au contenu

Module 5 — Le langage de configuration

Durée indicative du cours : 2 h 30
Durée indicative de l’atelier : atelier du jour 3, partagé avec le module 6
Durée totale (cours + atelier) : le jour 3 réunit les modules 5 et 6 et l’atelier du jour

Ce module couvre les objectifs 4a à 4g. Le fil reste la fiche d’environnement. Le langage sert à ne plus recopier le nom, le suffixe et les contrôles à la main.

Ressource et data source

Un bloc resource décrit un objet que Terraform crée, modifie et détruit. Un bloc data lit un objet qui existe déjà, sans le prendre en charge.

resource "local_file" "fiche_environnement" {
  filename = "${path.module}/fiche-environnement.txt"
  content  = "nom=${var.nom}\n"
}

data "local_file" "fiche_lue" {
  filename = local_file.fiche_environnement.filename
}

resource écrit. data relit. Si vous n’avez pas besoin de gérer l’objet, vous le lisez. Si vous devez pouvoir le changer ou le détruire depuis cette configuration, c’est une ressource.

Références

Une ressource s’adresse par TYPE.NOM. Un attribut s’adresse par TYPE.NOM.attribut. Dans l’exemple, local_file.fiche_environnement.filename crée une dépendance : Terraform écrit la fiche avant de la relire.

path.module est le dossier du module courant. path.root est le dossier où vous lancez Terraform. path.cwd est le dossier du shell. Au jour 1, les trois coïncident. Dès qu’un module est appelé, path.module désigne le dossier du module, pas celui de l’appelant.

Variables, sorties et types

Une variable est une entrée. Un output est une sortie. Un local est une valeur calculée dans la configuration, pour ne pas répéter une expression.

variable "nom" {
  type        = string
  description = "Nom de l'environnement"
  default     = "lab-equipe"
}

variable "etiquettes" {
  type = map(string)
  default = {
    equipe = "plateforme"
  }
}

locals {
  ligne_nom = "nom=${var.nom}"
}

output "chemin_fiche" {
  value       = local_file.fiche_environnement.filename
  description = "Emplacement de la fiche"
}

Les types simples sont string, number et bool. Les types composés de l’examen sont ceux-ci.

Type Rôle
list(TYPE) Suite ordonnée, doublons possibles, index numérique
set(TYPE) Ensemble, sans doublon, sans ordre utile pour un index
map(TYPE) Paires clé et valeur, clés de type string
object({ ... }) Attributs nommés, types différents possibles
tuple([ ... ]) Suite courte, type fixé pour chaque position
any Terraform déduit le type. À éviter dans une interface partagée

optional(TYPE, DEFAUT) rend un attribut d’objet facultatif. Vous vous en servez dans le type d’une variable de module, quand l’appelant ne fournit pas chaque champ.

Les valeurs arrivent par défaut dans le bloc, par -var, par un fichier terraform.tfvars, ou par une variable d’environnement TF_VAR_nom. Un fichier .tfvars de secrets ne se commite pas. Le module 7 précise ce point.

terraform output lit les sorties du state. terraform output -raw NOM donne la valeur seule. terraform output -json donne le tout en JSON, pour un script.

Expressions et fonctions

Une expression calcule une valeur au lieu de la recopier.

  • "nom=${var.nom}" interpole.
  • condition ? valeur_si_vrai : valeur_si_faux choisit.
  • [for nom in var.noms : upper(nom)] transforme une liste.
  • {for cle, valeur in var.etiquettes : cle => upper(valeur)} transforme une map.
  • local_file.fiche[*].filename est un splat : la liste des attributs.

Les fonctions à savoir utiliser sur ce fil :

  • join(separateur, liste) assemble un texte
  • length(valeur) compte
  • contains(liste, valeur) teste une présence
  • lookup(map, cle, defaut) lit une map avec une valeur de repli
  • merge(map_a, map_b) fusionne, la map de droite l’emporte sur les clés communes
  • file(chemin) lit un fichier au moment du plan
  • templatefile(chemin, variables) fait la même chose avec des interpolations
locals {
  contenu = join("\n", [
    "nom=${var.nom}",
    "suffixe=${random_id.suffixe.hex}",
  ])
}

file et templatefile lisent au moment où Terraform construit le plan. Le contenu entre dans la configuration. Ce n’est pas un secret à protéger : le module 7 explique pourquoi.

Plusieurs objets de même forme

count et for_each créent plusieurs ressources à partir d’un même bloc. C’est le cas des expressions dynamiques de l’examen.

count produit un nombre d’exemplaires, identifiés par count.index.

resource "local_file" "copie" {
  count    = 2
  filename = "${path.module}/copie-${count.index}.txt"
  content  = "copie ${count.index}\n"
}

L’adresse dans le state est local_file.copie[0], puis [1]. Si vous retirez l’élément du milieu d’une liste qui sert d’index, Terraform décale les suivants et peut remplacer des objets qui n’avaient pas changé.

for_each identifie chaque objet par une clé stable. Vous le préférez dès que l’objet a un nom.

locals {
  lignes = {
    nom     = var.nom
    suffixe = random_id.suffixe.hex
  }
}

resource "local_file" "ligne" {
  for_each = local.lignes
  filename = "${path.module}/${each.key}.txt"
  content  = "${each.value}\n"
}

L’adresse est local_file.ligne["nom"]. Retirer la clé nom ne renumérote pas suffixe.

each.key et each.value existent seulement dans un bloc for_each. count.index existe seulement dans un bloc count.

Un bloc imbriqué se répète avec dynamic. Le nom du bloc dynamique est le nom du bloc imbriqué attendu par le provider. for_each parcourt la collection. content écrit un exemplaire.

dynamic "ligne" {
  for_each = var.lignes
  content {
    nom = ligne.value
  }
}

Dans content, le nom de l’itérateur est le nom du bloc, ici ligne, et non each. Vous utilisez dynamic quand le provider demande plusieurs blocs du même type à l’intérieur d’une ressource. Pour plusieurs ressources entières, vous restez sur count ou for_each.

Dépendances et cycle de vie

Terraform déduit le graphe dès qu’une ressource cite l’attribut d’une autre. depends_on ajoute un lien que cette citation ne peut pas exprimer, par exemple un ordre avec un objet dont vous n’utilisez aucun attribut.

resource "local_file" "recap" {
  filename   = "${path.module}/recap.txt"
  content    = "fiche-prete\n"
  depends_on = [local_file.fiche_environnement]
}

Vous ne mettez pas depends_on partout. Une référence d’attribut suffit dans le cas courant, et elle documente pourquoi le lien existe.

Le bloc lifecycle change la manière dont Terraform remplace ou protège la ressource.

  • create_before_destroy = true crée le remplaçant avant de détruire l’ancien. Utile quand un nom doit rester disponible pendant le remplacement.
  • prevent_destroy = true fait échouer un plan qui détruirait la ressource.
  • ignore_changes = [content] ignore les changements de cet argument vus dans la réalité ou dans la configuration, selon le cas que vous avez choisi de figer.
  • replace_triggered_by = [random_id.suffixe] force le remplacement quand la valeur citée change.

create_before_destroy est le cas que l’examen cite avec depends_on. Les trois autres sont dans la même page de documentation. Vous devez pouvoir dire à quoi chacun sert.

Conditions custom

Une condition custom refuse une valeur avant qu’elle ne devienne un objet réel, ou après.

validation porte sur une variable. Elle est évaluée quand la variable est lue.

variable "nom" {
  type = string

  validation {
    condition     = can(regex("^[a-z0-9-]+$", var.nom))
    error_message = "Le nom ne contient que des minuscules, des chiffres et des tirets."
  }
}

precondition porte sur une ressource, un data source ou un output. Elle doit être vraie avant l’action. postcondition doit être vraie après. Le bloc check porte une règle sur l’ensemble de la configuration. Un check en échec est un avertissement. Il ne bloque pas l’apply, sauf si vous en faites une règle d’équipe dans HCP Terraform.

resource "local_file" "fiche_environnement" {
  filename = "${path.module}/fiche-environnement.txt"
  content  = local.contenu

  lifecycle {
    precondition {
      condition     = length(var.nom) > 2
      error_message = "Le nom doit dépasser deux caractères."
    }
  }
}

validation protège une entrée. precondition protège une action. postcondition contrôle un résultat. check surveille une règle plus large, sans le même blocage.

Ce que l’examen veut pour ce module

Objectif Vous devez pouvoir l’expliquer
4a resource gère un objet. data le lit
4b TYPE.NOM.attribut relie deux objets et crée la dépendance
4c Variable en entrée, output en sortie, local pour une valeur calculée
4d list, set, map, object, tuple, et optional dans un objet
4e Interpolation, condition, for, count, for_each, dynamic, et les fonctions join, lookup, merge, length, contains, file
4f Le graphe vient des références. depends_on ajoute un lien. create_before_destroy crée avant de détruire
4g validation, precondition, postcondition et check ne bloquent pas au même moment

Points à retenir

  • Vous citez un attribut pour exprimer une dépendance.
  • Une interface de module, au module suivant, sera faite de variables et d’outputs.
  • fmt et validate ne remplacent pas une validation métier.
  • ignore_changes fige un argument. Vous l’utilisez seulement quand vous savez quel écart vous choisissez d’ignorer.

Suite

Poursuivre avec le module 6 — Les modules.