Aller au contenu

Module 10 — Configuration avancée : itération, fonctions et provisioners

Durée indicative du cours : 1h45
Durée indicative de l’atelier : — (atelier for_each / fonctions à venir)
Durée totale (cours + atelier) : ~2h30–3h00

Objectif du module

Basics s’arrête à l’accès ponctuel list[0] / map["clé"] (types).
Associate ouvre l’itération (count, for_each, for), les fonctions HCL au programme TA-004, les dépendances / lifecycle (objectif 4f), les blocs dynamic, et les provisioners (dernier recours) avec l’objet self.

count vs for_each

count — N copies quasi identiques

resource "azurerm_resource_group" "lab" {
  count = 3

  name     = "rg-demo-${count.index}"
  location = var.location
}
  • Adresse state : azurerm_resource_group.lab[0], [1], …
  • Retirer l’élément du milieu décale les index → risque de remplacements.

for_each — identité stable

variable "subnets" {
  type = map(object({
    cidr = string
  }))
  default = {
    app = { cidr = "10.10.1.0/24" }
    db  = { cidr = "10.10.2.0/24" }
  }
}

resource "azurerm_subnet" "this" {
  for_each = var.subnets

  name                 = each.key
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = [each.value.cidr]
}
  • Adresse state : azurerm_subnet.this["app"].
  • Retirer db ne renumérote pas app.
Critère count for_each
Identité Index numérique Clé (set / map)
Retrait au milieu Dangereux Sûr pour les autres clés
Accès res[0] res["app"] / each.key
Préférence Lab très simple Dès qu’il y a une identité (noms, env)
terraform state list
# azurerm_subnet.this["app"]
# azurerm_subnet.this["db"]

list → for_each

for_each = toset(var.names) ou, mieux, une map construite en locals avec une expression for.

Expressions for

locals {
  subnet_cidrs = { for s in var.subnet_list : s.name => s.cidr }
  app_only     = { for k, v in var.subnets : k => v if startswith(k, "app") }
}

Utile pour dériver tags, maps d’outputs, ou nourrir un for_each.

Lab proposé — N subnets

VNet + azurerm_subnet en for_each ; comparer une variante count ; lire state list ; option expression for pour outputs.

Blocs dynamic

Certains arguments Azure sont des blocs répétés (règles NSG, etc.).
dynamic génère ces blocs à partir d’une collection :

resource "azurerm_network_security_group" "app" {
  name                = "nsg-${var.alias}-app"
  location            = var.location
  resource_group_name = azurerm_resource_group.main.name

  dynamic "security_rule" {
    for_each = var.nsg_rules
    content {
      name                       = security_rule.value.name
      priority                   = security_rule.value.priority
      direction                  = security_rule.value.direction
      access                     = security_rule.value.access
      protocol                   = security_rule.value.protocol
      source_port_range          = "*"
      destination_port_range     = security_rule.value.destination_port_range
      source_address_prefix      = security_rule.value.source_address_prefix
      destination_address_prefix = "*"
    }
  }
}

Quand l’utiliser

Dès que copier-coller dix blocs identiques devient fragile.
Gardez la collection source typée (module 8).

Fonctions HCL et ternaires

Catalogue officiel : Functions.
Pédagogie : tester dans terraform console, puis figer dans locals.

Cœur à maîtriser (TA-004 + pratique Azure)

Fonction / opérateur Rôle Exemple type
join / split Coller / découper join("-", ["rg", var.project, var.env])
upper / lower / replace Casse / substitution lower(replace(var.prefix, "-", ""))
substr / trimspace Couper / nettoyer Tronquer un nom storage
format Gabarit format("snet-%s-%02d", var.env, 1)
length Longueur chaîne ou collection Contrôle 3–24 car. storage
contains Appartenance Env autorisés
lookup Map + défaut lookup(var.sku, var.env, "Standard_LRS")
merge Fusionner des maps Tags communs + env
concat / flatten / distinct Listes CIDR, dédup
keys / values Collections Clés d’un for_each
max / min / ceil / floor Nombres Plafonds
timestamp / timeadd / formatdate Dates Lab / TTL — pas dans un nom prod
Ternaire cond ? a : b Choix var.env == "prod" ? "GRS" : "LRS"
can / try / coalesce Robustesse Validations, repli
alltrue / anytrue Agrégats booléens Listes de checks
regex / regexall Motifs Naming
tolist / tomap / tostring / tonumber Conversions set → list
jsonencode Sérialiser Policy, app settings
cidrsubnet / cidrhost Réseau Dériver des subnets
file / templatefile Fichiers Avec path.module
locals {
  st_name = substr(
    lower(replace("${var.project}${var.env}${var.alias}", "-", "")),
    0,
    24
  )

  tags = merge(var.tags, {
    environment = var.environment
    managed_by  = "terraform"
  })

  replication = var.environment == "prod" ? "GRS" : "LRS"
}

Pièges

  • timestamp() / uuid() dans un nom de ressource → recreate à chaque apply.
  • length : chaîne et list/map.
  • lookup vs map[key] : seul lookup accepte un défaut.
  • Ternaire : les deux branches doivent être du même type.

Mini-exercices

  1. Nom storage : lower + replace + substr + validation.
  2. Naming join / split.
  3. Tags merge + lookup SKU.
  4. Ternaire LRS/GRS.
  5. formatdate pour un suffixe lab seulement.
  6. Option : cidrsubnet pour le subnet app.

Dépendances et cycle de vie des ressources

Objectif examen TA-004 (4f) : savoir quand Terraform crée des dépendances tout seul, quand forcer avec depends_on, et comment le bloc lifecycle change le comportement create / destroy.

Dépendances implicites (références)

Dès qu’une ressource référence une autre dans HCL, Terraform construit le graphe :

resource "azurerm_resource_group" "lab" {
  name     = "rg-lab"
  location = var.location
}

resource "azurerm_virtual_network" "lab" {
  name                = "vnet-lab"
  resource_group_name = azurerm_resource_group.lab.name
  location            = azurerm_resource_group.lab.location
  address_space       = ["10.10.0.0/16"]
}

Ici, le VNet dépend du Resource Group : Terraform crée le RG d’abord, et le détruit après le VNet.

Dans la plupart des cas, vous n’avez pas besoin de depends_on. Preferez les références d’attributs.

depends_on — dépendance explicite

Utilisez depends_on quand Terraform ne peut pas voir le lien dans le code (effet de bord, ordre d’API, ressource sans attribut référencé).

resource "azurerm_role_assignment" "reader" {
  # … principal_id, scope, role_definition_name …

  # Exemple pédagogique : forcer l’ordre si aucune référence HCL ne lie les deux
  depends_on = [
    azurerm_resource_group.lab,
  ]
}

Points à retenir :

  • depends_on accepte une liste de ressources (ou modules).
  • Trop de depends_on rend le graphe opaque et ralentit les plans inutiles.
  • Preferez toujours une référence (resource_group_name = azurerm_resource_group.lab.name) quand c’est possible.

Documentation : Resource dependencies.

Bloc lifecycle — règles utiles à l’examen

resource "azurerm_linux_web_app" "app" {
  # … attributs …

  lifecycle {
    create_before_destroy = true
    prevent_destroy       = false
    ignore_changes        = [
      tags["last_manual_touch"],
    ]
  }
}
Argument Effet Quand y penser
create_before_destroy Crée la nouvelle instance avant de détruire l’ancienne (remplacement) Éviter un trou de service quand un attribut force un replace
prevent_destroy Refuse un plan qui détruirait cette ressource Garde-fou lab / prod critique (attention : bloque aussi les remplacements)
ignore_changes Ignore certains attributs dans le plan Tags ou champs gérés hors Terraform (à manier avec prudence)

Les garde-fous precondition / postcondition dans lifecycle sont traités au module 9 (objectif 4g).

Message examen TA-004

depends_on = ordre quand la référence manque.
create_before_destroy = stratégie de remplacement (create puis destroy).
Ce n’est pas la même chose qu’un prevent_destroy.

Provisioners et objet self

Les provisioners exécutent des actions locales ou distantes au create / destroy :

Type Où ça tourne
local-exec Sur la machine qui lance Terraform
remote-exec Sur la ressource (SSH / WinRM)
file Copie de fichiers via connection
resource "azurerm_linux_virtual_machine" "lab" {
  # … attributs VM …

  connection {
    type        = "ssh"
    host        = self.public_ip_address
    user        = var.admin_username
    private_key = var.ssh_private_key
  }

  provisioner "remote-exec" {
    inline = [
      "echo hello from ${self.name}",
    ]
  }
}

Objet self

  • Disponible uniquement dans provisioner / connection.
  • Référence la ressource parente (self.public_ip_address, etc.).
  • Évite un cycle de dépendances qu’une référence azurerm_… normale créerait.

Nom local self ≠ objet self

En Basics, la convention resource "…" "self" est un label HCL.
L’objet self des provisioners est un mécanisme différent.
Ne pas les confondre à l’oral ni à l’examen.

Bonnes pratiques HashiCorp

  • Provisioners = dernier recours.
  • Preferez cloud-init, images Packer, config management, extensions Azure.
  • L’idempotence est fragile.
  • terraform_data (ou null_resource historique) pour des hooks hors ressource métier.

Documentation : Provisioners.

À enrichir

Démo local-exec minimal ; exercice dynamic NSG complet ; fiche « 20 fonctions — 20 exemples console » chronométrée type examen.

Suite

Fin du jour 3.
Poursuivre au jour 4 avec Terraform Cloud, puis la révision examen.