Aller au contenu

Module 9 — Outputs : exposition, secrets et preconditions

Durée indicative du cours : 0h45
Durée indicative de l’atelier : — (atelier / démo à venir)
Durée totale (cours + atelier) : ~1h00–1h15

Objectif du module

En Basics, les outputs exposent des valeurs pour la lecture humaine et les scripts locaux.
En Associate, on traite les outputs comme un contrat : CI/CD, composition entre modules, stacks, et garde-fous (precondition) — y compris quand il faut refuser d’exposer une valeur incohérente.
On complète avec le bloc racine check pour des règles transverses (plusieurs ressources, cohérence globale).

Rappel — anatomie d’un output

output "rg_name" {
  description = "Nom du resource group"
  value       = azurerm_resource_group.main.name
}

output "rg_id" {
  description = "ID Azure du resource group"
  value       = azurerm_resource_group.main.id
}
terraform output
terraform output rg_id
terraform output -raw rg_id
terraform output -json
Attribut Rôle
value Expression exposée
description Documentation humaine / terraform-docs
sensitive Masque beaucoup d’affichages CLI
precondition Associate — garde-fou avant publication

Outputs sensibles

output "connection_string" {
  description = "Chaîne de connexion (masquée en CLI)"
  value       = azurerm_storage_account.lab.primary_connection_string
  sensitive   = true
}

Même logique que pour les variables (module 8) :

  • sensitive masque ;
  • le state peut toujours contenir la valeur ;
  • -json et certains outils peuvent exposer autrement — discipline d’équipe requise.

Contrat module

Si un module publie un secret en output, tous les consommateurs héritent du risque state.
Preferez souvent Key Vault + référence, ou un flux CI, plutôt qu’une chaîne en clair dans le state racine.

precondition sur un output

Bloc imbriqué dans l’output :

output "app_url" {
  description = "URL HTTPS de l’application"
  value       = "https://${azurerm_linux_web_app.app.default_hostname}"

  precondition {
    condition     = length(azurerm_linux_web_app.app.default_hostname) > 0
    error_message = "Hostname applicatif vide — ne pas exposer d’URL."
  }
}

Comportement :

  • Évalué au plan / apply.
  • Si condition est false, Terraform échoue avec error_message.
  • L’output n’est pas « publié tranquillement » avec une valeur absurde.

Cas d’usage :

  • refuser une URL vide, un ID absent, un environnement interdit ;
  • garde-fou après calcul / création, côté contrat du module ;
  • combiner avec sensitive = true : masquage et refus sont des rôles différents.

Documentation : Custom Conditions · Output Values.

Ne pas confondre les garde-fous

Mécanisme Où Quand ça joue Idée
variable → validation Entrée Fourniture des valeurs « mauvaise entrée »
output → precondition Sortie Plan / apply sur l’output « mauvaise valeur à exposer »
resource → lifecycle → precondition / postcondition Ressource Avant / après la ressource Garde-fous locaux
Bloc check Racine Plan / apply Assertions multi-ressources (voir ci-dessous)
resource "azurerm_resource_group" "main" {
  name     = local.rg_name
  location = var.location

  lifecycle {
    precondition {
      condition     = var.environment != "prod" || var.force_prod
      error_message = "Création prod refusée sans force_prod."
    }

    postcondition {
      condition     = self.location == var.location
      error_message = "Le Resource Group n’est pas dans la région attendue."
    }
  }
}
  • precondition : évaluée avant de créer / mettre à jour la ressource.
  • postcondition : évaluée après, sur l’état connu de la ressource (self.…).

Objectif examen TA-004 (4g) : savoir choisir le bon garde-fou (entrée / sortie / ressource / check transverse), pas seulement citer les mots-clés.

Pédagogie

validation = filtrage avant.
precondition sur output = contrôle du contrat sortant.
check = règle transversale sur l’infra déjà modélisée.
Les trois peuvent coexister dans le même projet.

Bloc check (assertions transverses)

En Basics, le panorama des blocs HCL cite check sans le dérouler. Ici, on l’utilise quand une règle concerne plusieurs ressources ou l’ensemble du déploiement, pas une seule variable ni un seul output.

Prérequis : Terraform 1.5+.

Structure

Un bloc check est un bloc racine (comme variable ou resource). Il contient un ou plusieurs sous-blocs assert :

check "storage_no_public_blob" {
  assert {
    condition = (
      azurerm_storage_account.lab.allow_nested_items_to_be_public == false
    )
    error_message = "Le Storage Account autorise l’accès public : risque de fuite de données."
  }

  assert {
    condition     = azurerm_storage_account.lab.min_tls_version == "TLS1_2"
    error_message = "Le Storage Account doit imposer TLS 1.2 minimum."
  }
}

Chaque assert porte :

  • condition : expression booléenne ;
  • error_message : message affiché si la condition est fausse.

Comportement au plan

Lors d’un terraform plan (ou apply), Terraform évalue les assert après avoir calculé le plan des ressources.

  • Si une condition est fausse, Terraform signale un échec de check avec le error_message.
  • Les checks servent à attraper des incohérences globales (sécurité, alignement région, tags obligatoires…) sans dupliquer la logique dans chaque resource.

Checks vs échec bloquant

Selon la version et le contexte, un check raté peut apparaître comme diagnostic dans le plan sans toujours bloquer la suite comme une validation de variable.
Retenez surtout le rôle : assertion transversale au moment du plan.
Doc : Checks.

Exemple — cohérence entre ressources

check "same_region" {
  assert {
    condition = (
      azurerm_resource_group.lab.location ==
      azurerm_storage_account.lab.location
    )
    error_message = "Le Resource Group et le Storage Account doivent être dans la même région."
  }
}

Utile quand plusieurs ressources sont paramétrées par des variables : le check détecte une incohérence avant de la découvrir dans le portail.

Quand choisir quoi ?

Besoin Mécanisme
Filtrer une valeur d’entrée variable → validation
Refuser de publier un output incohérent output → precondition
Garde-fou local à une ressource lifecycle → precondition / postcondition
Règle sur plusieurs ressources ou l’infra globale Bloc check

Outputs et CI/CD

Quand : un pipeline (Azure DevOps, GitHub Actions…) déploie l’infra puis a besoin d’une valeur produite par Terraform (URL, ID, nom) pour l’étape suivante.
Pourquoi : l’output est le contrat lisible entre Terraform et le reste de la chaîne — sans parser le state à la main.

Exemple : créer l’app, puis lire l’URL pour des tests smoke.

output "app_url" {
  description = "URL HTTPS de l’application"
  value       = "https://${azurerm_linux_web_app.app.default_hostname}"
}

Côté script / job CI :

APP_URL=$(terraform output -raw app_url)
# lancer des tests smoke sur $APP_URL

Points à retenir :

  • Préférer -raw pour injecter une chaîne dans une variable d’environnement.
  • Préférer -json si l’outil attend une structure (plusieurs clés).
  • Un output sensitive reste masqué en CLI, mais le secret peut vivre dans le state — discipline d’équipe (voir plus haut).

Outputs entre modules et stacks

Quand : vous composez plusieurs modules (réseau, app, données) dans un même projet, ou plusieurs stacks (projets Terraform séparés).
Pourquoi : le module enfant expose ce dont le parent a besoin (vnet_id, subnet_ids) sans dupliquer la logique Azure.

Dans un module enfant (modules/network) :

output "vnet_id" {
  description = "ID du Virtual Network"
  value       = azurerm_virtual_network.main.id
}

output "vnet_name" {
  description = "Nom du Virtual Network"
  value       = azurerm_virtual_network.main.name
}

Dans le module parent (racine) :

module "network" {
  source = "./modules/network"
}

resource "azurerm_subnet" "app" {
  # ...
  virtual_network_name = module.network.vnet_name
}

Le parent lit module.network.<output>. C’est la base de la composition de modules.

Variante avec map d’IDs :

# Dans modules/network/outputs.tf
output "subnet_ids" {
  value = { for k, s in azurerm_subnet.this : k => s.id }
}

# Racine
module "app" {
  source    = "./modules/app"
  subnet_id = module.network.subnet_ids["app"]
}

Cross-stack (plusieurs projets / states) : terraform_remote_state ou publication via Terraform Cloud — le contrat (noms d’outputs stables) compte autant que la technique. Ce sujet sera détaillé avec les backends distants.

Lien Basics

En Terraform Basics, les outputs restent sur un seul projet (lecture humaine, script local). Les cas CI/CD et inter-modules vivent ici.

Parcours démo / atelier (cible)

  1. Output simple sans precondition (rappel Basics).
  2. Ajouter une precondition qui échoue → lire le message.
  3. Corriger → plan OK ; terraform output / -json.
  4. Option : même idée en lifecycle.precondition sur une ressource pour contraster.
  5. Option : ajouter un bloc check (ex. même région RG / storage) et lire le diagnostic au plan.

À enrichir

Exemple fil rouge plateforme (output réseau consommé par app) ; pièges -raw vs -json avec sensitive.

Suite

Poursuivre avec le module 10 — Configuration avancée.
Théorie suivante : count / for_each, fonctions HCL, dynamic, provisioners et self.