Aller au contenu
Infrastructure as Code medium

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 tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn