Terraform relie quatre éléments :
Dans l'image de la leçon 01 : la configuration est le plan, le state est le relevé, les providers sont les corps de métier, et le CLI est l'architecte.
Terraform utilise principalement le HashiCorp Configuration Language, ou HCL. Le code est organisé en blocs.
resource "aws_s3_bucket" "logs" {
bucket = var.bucket_name
tags = {
Environment = var.environment
ManagedBy = "Terraform"
}
}Dans cet exemple :
resource annonce un objet que Terraform doit gérer ;aws_s3_bucket est le type fourni par le provider AWS ;logs est le nom local utilisé dans ce module ;bucket et tags sont des arguments ;var.bucket_name et var.environment viennent de variables.Le type et le nom local forment ensemble l'adresse de la ressource : aws_s3_bucket.logs. C'est sous ce nom que tu la retrouveras dans un plan, dans terraform state list et dans les références d'autres blocs.
Un provider est un plugin qui connaît l'API d'une plateforme et fournit des types de ressources et de sources de données. Terraform installe les providers requis pendant terraform init. Les contraintes de version et le fichier de verrouillage rendent les exécutions plus prévisibles. Référence officielle — Exigences des providers
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "ca-central-1"
}La version de Terraform CLI et la version d'un provider sont deux choses différentes. Tu les contrôles séparément : terraform version affiche la première (Terraform v1.12.2 sur la machine du cours), .terraform.lock.hcl fige la seconde.
Une ressource représente généralement un objet que Terraform crée ou gère.
resource "aws_security_group" "web" {
name = "web"
}Une data source lit un objet ou une information qui existe déjà sans en devenir automatiquement propriétaire.
data "aws_vpc" "default" {
default = true
}Tu peux ensuite référencer data.aws_vpc.default.id dans une ressource.
Les variables sont les entrées configurables :
variable "environment" {
type = string
description = "Nom de l'environnement"
validation {
condition = contains(["dev", "test", "prod"], var.environment)
error_message = "L'environnement doit être dev, test ou prod."
}
}Les locals calculent des valeurs internes afin d'éviter les répétitions :
locals {
prefix = "application-${var.environment}"
}Les outputs exposent des résultats utiles :
output "bucket_id" {
description = "Identifiant du bucket"
value = aws_s3_bucket.logs.id
}Un projet sans bloc output n'a rien à afficher : terraform output répond alors Warning: No outputs found. C'est le cas du projet 01 ; les outputs arrivent au projet 02.
Terraform détecte une dépendance lorsqu'une ressource en référence une autre.
resource "aws_s3_object" "journal" {
bucket = aws_s3_bucket.logs.id
key = "journal.txt"
source = "journal.txt"
}L'objet dépend du bucket. Terraform doit donc créer le bucket avant d'envoyer le fichier. Les ressources sans dépendance entre elles peuvent être traitées en parallèle. L'ordre des blocs dans le fichier n'a aucune importance : seule la référence compte.
HashiCorp résume le workflow Terraform en trois étapes : Write, Plan, Apply. Référence officielle — Core workflow
Avant la première de ces étapes, il y a un geste unique par dossier : terraform init. Les sorties ci-dessous sont celles du projet 01 de ce module, capturées avec Terraform 1.12.2 sur la machine du cours ; tu les retrouveras mot pour mot quand tu le feras toi-même.
terraform initInitializing the backend...
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.1...
- Installed hashicorp/local v2.9.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. …
Terraform has been successfully initialized!Trois choses se passent : Terraform télécharge le provider (Installing hashicorp/local v2.9.1), le range dans le dossier caché .terraform/, et écrit .terraform.lock.hcl pour retenir la version choisie. La phrase à attendre est Terraform has been successfully initialized!. Sans init, toute autre commande s'arrête : Error: Inconsistent dependency lock file pour plan, Error: Missing required provider pour validate, et les deux te disent quoi faire (run: terraform init).
Tu écris ou modifies les fichiers .tf, puis tu lances généralement :
terraform fmt
terraform validateterraform fmt réaligne les espaces et l'indentation ; il affiche le nom des fichiers qu'il a modifiés, et rien du tout si tout était déjà propre. terraform validate vérifie la syntaxe et la cohérence des blocs sans toucher à rien, et répond :
Success! The configuration is valid.Il attrape les fautes de frappe dans les noms d'arguments (An argument named "contenu" is not expected here. Did you mean "content"?) et les accolades oubliées (Error: Unclosed configuration block). Il ne vérifie pas que le résultat serait une bonne architecture.
Terraform compare l'état souhaité à ce qu'il connaît de l'infrastructure :
terraform planTerraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# local_file.message will be created
+ resource "local_file" "message" {
+ content = "Bonjour, ce fichier a été créé avec Terraform."
+ content_md5 = (known after apply)
+ filename = "./message.txt"
+ id = (known after apply)
…
}
Plan: 1 to add, 0 to change, 0 to destroy.Le plan répond à trois questions, et la dernière ligne les résume en trois nombres :
to add)to change)to destroy)Chaque ligne d'attribut porte le symbole de l'action. (known after apply) signale une valeur que Terraform ne peut pas connaître avant d'avoir créé l'objet : ici l'identifiant du fichier et les empreintes de son contenu.
Après révision :
terraform applyTerraform réaffiche le plan puis demande ton accord :
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
local_file.message: Creating...
local_file.message: Creation complete after 0s [id=ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.Seul yes en entier est accepté ; y, Y ou une ligne vide donnent Apply cancelled. et rien n'est touché. Terraform appelle ensuite les providers dans l'ordre dicté par les dépendances, puis met à jour le state. La dernière ligne reprend les trois nombres du plan : Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Si tu relances terraform plan sans avoir rien changé, le devis est vide :
No changes. Your infrastructure matches the configuration.C'est l'idempotence de la leçon 01, vue dans le terminal.
terraform destroydestroy est un apply dont le plan ne contient que des -. La question posée est plus insistante (Do you really want to destroy all resources? … There is no undo.), la réponse attendue est toujours yes, et la dernière ligne est :
Destroy complete! Resources: 1 destroyed.Après un destroy, terraform state list n'affiche plus rien. Dans ce cours, chaque projet se termine ainsi.
| Symbole | Action | Phrase dans le plan | Ce que ça change pour toi |
|---|---|---|---|
+ | créer | will be created | Un objet nouveau ; rien d'existant n'est touché. |
~ | modifier sur place | will be updated in-place | L'objet reste, un attribut change. |
-/+ | remplacer | must be replaced | L'objet est détruit puis recréé ; son contenu et son identifiant disparaissent. |
- | détruire | will be destroyed | L'objet disparaît. |
Terraform détermine comment atteindre l'état demandé, mais le comportement exact dépend de chaque provider. Certains arguments peuvent être modifiés sur place ; d'autres obligent à remplacer la ressource. Le provider local du projet 01 en donne un exemple net : changer la ligne content d'un local_file ne produit pas un ~, mais un remplacement -/+. Voici le plan réel, capturé après la modification du texte :
# local_file.message must be replaced
-/+ resource "local_file" "message" {
~ content = "Bonjour, ce fichier a été créé avec Terraform." -> "Deuxième version du fichier créée avec Terraform." # forces replacement
~ content_md5 = "6daf774f0eb6d3da439c871afec7cf90" -> (known after apply)
~ id = "ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45" -> (known after apply)
# (3 unchanged attributes hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.Lis les trois indices : must be replaced dans le titre, # forces replacement au bout de la ligne content, et 1 to add, 0 to change, 1 to destroy dans le résumé. Pour un fichier texte, la nuance est sans conséquence. Pour une base de données, -/+ veut dire perte des données : c'est pourquoi le plan doit être lu, même lorsque le changement de code semble petit.
La différence essentielle entre
~et-/+. Le~devantcontentdit que la valeur change. Le-/+devantresourcedit comment Terraform va s'y prendre : détruire, puis recréer. Quand une ligne~porte# forces replacement, c'est elle qui a provoqué le-/+du bloc entier.
resource "type" "nom" a une adresse, type.nom, que tu retrouves dans le plan et dans le state.init une fois par dossier (Terraform has been successfully initialized!), puis fmt, validate (Success! The configuration is valid.), plan (Plan: 1 to add, 0 to change, 0 to destroy.), apply avec yes (Apply complete! Resources: 1 added, 0 changed, 0 destroyed.), et destroy en fin de séance (Destroy complete! Resources: 1 destroyed.).+ crée, ~ modifie sur place, -/+ détruit puis recrée, - détruit. Le résumé Plan: N to add, N to change, N to destroy. les compte.~ ou un -/+ ; le local_file du projet 01 est remplacé dès que content change.terraform init doit-il être exécuté, et quels fichiers crée-t-il ?content, quels trois indices disent qu'il s'agit d'un remplacement et non d'une modification sur place ?y au lieu de yes ?