Aller au contenu
Infrastructure as Code medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

Cheatsheet Professional : commandes Terraform avancées

21 min de lecture

logo terraform

Référence centrée sur la vitesse d’exécution en lab. L’examen Professional est 100% pratique : vous écrivez du HCL, vous exécutez des commandes et vous résolvez des problèmes réels. Cette page couvre les commandes avancées organisées par objectif d’examen.

BesoinCommande
Importer une ressource existanteterraform import <addr> <id>
Renommer une ressource (déclaratif)Bloc moved {}
Forcer la recréationterraform apply -replace=<addr>
Lister les ressources du stateterraform state list
Inspecter une ressourceterraform state show <addr>
Déplacer dans le stateterraform state mv <old> <new>
Retirer du state sans détruireterraform state rm <addr>
Exécuter les teststerraform test
Sauvegarder un planterraform plan -out=tfplan
Appliquer un plan sauvegardéterraform apply tfplan
Réconcilier le state (changements hors Terraform)terraform plan -refresh-only
Mettre à jour les providersterraform init -upgrade
Debug avancéTF_LOG=TRACE terraform plan
Voir les providers requisterraform providers
Voir les versions Terraform + providers installésterraform version
Mettre à jour / préremplir le lock fileterraform providers lock
Lire le lock fileOuvrir .terraform.lock.hcl
Afficher un output sensibleterraform output -raw <name>

Fenêtre de terminal
# Import classique (CLI)
terraform import aws_instance.web i-0abc123def456
# Import avec adresse de module
terraform import module.vpc.aws_vpc.main vpc-0abc123

Bloc import (déclaratif — Terraform 1.5+) :

import {
to = aws_instance.web
id = "i-0abc123def456"
}
Fenêtre de terminal
# Générer le code HCL automatiquement
terraform plan -generate-config-out=generated_resources.tf
# Renommer une ressource
moved {
from = aws_instance.web
to = aws_instance.application
}
# Déplacer dans un module
moved {
from = aws_instance.web
to = module.compute.aws_instance.main
}
# Renommer un module
moved {
from = module.old_name
to = module.new_name
}

Vérification :

Fenêtre de terminal
# Le plan doit montrer "moved" sans create/destroy
terraform plan
# → aws_instance.application has moved from aws_instance.web
Fenêtre de terminal
# Lister toutes les ressources
terraform state list
# Filtrer par adresse
terraform state list module.vpc
# Inspecter une ressource en détail
terraform state show aws_instance.web
# Déplacer (opération manuelle ponctuelle)
terraform state mv aws_instance.web aws_instance.application
terraform state mv aws_instance.web module.compute.aws_instance.main
# Retirer du state (la ressource reste dans AWS)
terraform state rm aws_instance.legacy
# Télécharger le state distant en JSON
terraform state pull
# Envoyer un state local vers le backend
terraform state push terraform.tfstate
Fenêtre de terminal
# Forcer la destruction + recréation d'une ressource
terraform apply -replace=aws_instance.web
# Avec un plan sauvegardé
terraform plan -replace=aws_instance.web -out=tfplan
terraform apply tfplan
Fenêtre de terminal
# Plan avec cible spécifique
terraform plan -target=module.vpc
# Plan de destruction
terraform plan -destroy
# Apply sans interaction (CI/CD)
terraform apply -input=false -auto-approve
# Refresh only (réconcilier le state avec l'infrastructure réelle)
terraform apply -refresh-only -auto-approve
# Parallélisme (par défaut : 10)
terraform apply -parallelism=20
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
# Exécution locale après création
provisioner "local-exec" {
command = "echo ${self.private_ip} >> hosts.txt"
}
# Exécution distante
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx"
]
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}
# Exécution à la destruction
provisioner "local-exec" {
when = destroy
command = "echo 'Instance destroyed'"
}
}

resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
lifecycle {
# Vérification AVANT la création
precondition {
condition = can(regex("^ami-", var.ami_id))
error_message = "L'AMI doit commencer par 'ami-'."
}
# Vérification APRÈS la création
postcondition {
condition = self.public_ip != ""
error_message = "L'instance doit avoir une IP publique."
}
}
}
check "website_health" {
data "http" "app" {
url = "https://${aws_lb.main.dns_name}/health"
}
assert {
condition = data.http.app.status_code == 200
error_message = "Le site ne répond pas correctement."
}
}
tests/main.tftest.hcl
variables {
instance_type = "t3.micro"
environment = "test"
}
run "create_instance" {
command = apply
assert {
condition = aws_instance.web.instance_type == "t3.micro"
error_message = "Mauvais type d'instance."
}
assert {
condition = aws_instance.web.tags["Environment"] == "test"
error_message = "Tag Environment incorrect."
}
}
run "verify_plan_only" {
command = plan
assert {
condition = aws_instance.web.ami != ""
error_message = "AMI non définie."
}
}
Fenêtre de terminal
# Exécuter les tests
terraform test
# Tests verbeux
terraform test -verbose
# Filtre sur un fichier de test
terraform test -filter=tests/main.tftest.hcl
variable "ingress_rules" {
type = list(object({
port = number
protocol = string
cidr_blocks = list(string)
}))
default = [
{ port = 80, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"] },
{ port = 443, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"] },
]
}
resource "aws_security_group" "web" {
name = "web-sg"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
# flatten : aplatir des listes imbriquées
locals {
all_subnets = flatten([
for env, config in var.environments : [
for subnet in config.subnets : {
env = env
cidr = subnet
}
]
])
}
# setproduct : produit cartésien
locals {
az_subnet_pairs = setproduct(var.azs, var.subnet_cidrs)
}
# cidrsubnet : calcul de sous-réseaux
locals {
subnets = [for i in range(3) : cidrsubnet("10.0.0.0/16", 8, i)]
# → ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
}
# templatefile : template avec variables
resource "aws_instance" "web" {
user_data = templatefile("${path.module}/user-data.tpl", {
hostname = var.hostname
packages = join(" ", var.packages)
})
}
# try et can : gestion d'erreurs
locals {
name = try(var.config.name, "default")
is_valid = can(regex("^[a-z]+$", var.input))
}
variable "server_config" {
type = object({
name = string
instance_type = optional(string, "t3.micro")
tags = optional(map(string), {})
monitoring = optional(object({
enabled = bool
interval = optional(number, 60)
}), { enabled = false })
})
}

terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# Lire le state d'une autre configuration
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "my-terraform-state"
key = "network/terraform.tfstate"
region = "eu-west-1"
}
}
# Utiliser les outputs
resource "aws_instance" "web" {
subnet_id = data.terraform_remote_state.vpc.outputs.private_subnet_ids[0]
}
# Lire les outputs d'un workspace HCP Terraform
data "tfe_outputs" "network" {
config = {
organization = "my-org"
workspaces = {
name = "network-prod"
}
}
}
resource "aws_instance" "web" {
subnet_id = data.tfe_outputs.network.outputs.subnet_id
}
terraform {
required_version = ">= 1.6.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}

Syntaxe des contraintes :

OpérateurSignificationExemple
=Exactement= "5.0.0"
!=Pas cette version!= "5.1.0"
>, >=, <, <=Comparaison>= 5.0
~>Pessimistic (incrémente le dernier chiffre)~> 5.0 → >= 5.0, < 6.0
Fenêtre de terminal
# Pipeline type : plan séparé de l'apply
# Étape 1 : Plan
terraform init -input=false
terraform plan -input=false -out=tfplan
# Étape 2 : Apply (après approbation)
terraform apply -input=false tfplan

modules/
└── compute/
├── main.tf # Ressources
├── variables.tf # Inputs
├── outputs.tf # Outputs
├── versions.tf # Required providers
├── README.md # Documentation
└── tests/
└── main.tftest.hcl # Tests
# Root module
provider "aws" {
alias = "us"
region = "us-east-1"
}
provider "aws" {
alias = "eu"
region = "eu-west-1"
}
module "us_infra" {
source = "./modules/compute"
providers = {
aws = aws.us
}
}
module "eu_infra" {
source = "./modules/compute"
providers = {
aws = aws.eu
}
}
# Étape 1 : Déplacer le code dans le module
# Étape 2 : Ajouter le bloc moved
moved {
from = aws_instance.web
to = module.compute.aws_instance.main
}
moved {
from = aws_security_group.web
to = module.compute.aws_security_group.main
}
# Étape 3 : Vérifier
# terraform plan ne doit montrer AUCUN destroy

provider "aws" {
region = "eu-west-1" # Provider par défaut
}
provider "aws" {
alias = "us_east"
region = "us-east-1"
}
# Utilisation dans une ressource
resource "aws_s3_bucket" "backup" {
provider = aws.us_east
bucket = "backup-us-east"
}
# Méthode 1 : Environment variables (recommandé en CI/CD)
# export AWS_ACCESS_KEY_ID="..."
# export AWS_SECRET_ACCESS_KEY="..."
# export AWS_REGION="eu-west-1"
# Méthode 2 : Shared credentials file
provider "aws" {
shared_credentials_files = ["~/.aws/credentials"]
profile = "production"
}
# Méthode 3 : Assume role
provider "aws" {
assume_role {
role_arn = "arn:aws:iam::123456789:role/terraform"
session_name = "terraform-session"
}
}
Fenêtre de terminal
# Version incompatible
terraform init -upgrade
terraform providers
# Credentials invalides
export TF_LOG=DEBUG
terraform plan 2>&1 | grep -i "auth\|credential\|access"
# Rate limiting
terraform apply -parallelism=5 # Réduire le parallélisme
# Provider lock corrompu
rm .terraform.lock.hcl
terraform init

Fenêtre de terminal
# Niveaux : TRACE > DEBUG > INFO > WARN > ERROR
export TF_LOG=TRACE
export TF_LOG_PATH=/tmp/terraform-debug.log
# Debug uniquement le provider
export TF_LOG_PROVIDER=TRACE
# Debug uniquement le core
export TF_LOG_CORE=DEBUG
# Désactiver
unset TF_LOG TF_LOG_PATH TF_LOG_PROVIDER TF_LOG_CORE
Fenêtre de terminal
# Le plan échoue ? Vérifiez dans cet ordre :
# 1. Syntaxe
terraform validate
# 2. State
terraform state list
terraform state show <resource>
# 3. Provider
terraform providers
cat .terraform.lock.hcl
# 4. Logs détaillés
TF_LOG=DEBUG terraform plan 2>&1 | tail -50
# 5. Refresh
terraform plan -refresh-only

Section intitulée « Naviguer dans la documentation (stratégie examen) »

La documentation est autorisée pendant le lab. Voici les pages les plus utiles :

BesoinOù chercher
Syntaxe d’une ressource AWSregistry.terraform.io/providers/hashicorp/aws → recherche
Fonctions HCLdeveloper.hashicorp.com/terraform/language/functions
Bloc lifecycledeveloper.hashicorp.com/terraform/language/meta-arguments/lifecycle
terraform testdeveloper.hashicorp.com/terraform/language/tests
Check blocksdeveloper.hashicorp.com/terraform/language/checks
Bloc moveddeveloper.hashicorp.com/terraform/language/modules/develop/refactoring
Backend S3developer.hashicorp.com/terraform/language/backend/s3
Provider configurationdeveloper.hashicorp.com/terraform/language/providers/configuration
  • L’examen lab utilise Terraform 1.6 + AWS — entraînez-vous avec cette version
  • La documentation est votre alliée — apprenez à y naviguer vite
  • moved {} est préféré à state mv — apprenez les deux
  • terraform test (.tftest.hcl) est un sujet clé de l’objectif 2
  • Les preconditions/postconditions dans lifecycle sont un pattern récurrent
  • Le provider aliasing multi-région est un sujet incontournable
  • Les provisioners existent mais sont déconseillés — sachez quand les utiliser
  • L’objectif 6 (HCP Terraform) est en QCM — pas besoin de commandes lab

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn