Aller au contenu
Infrastructure as Code medium

terraform state list : lister et filtrer les instances du state

30 min de lecture

logo terraform

terraform state list affiche les instances que Terraform gère, une par ligne, sous la forme de leur adresse. C'est la commande de départ de tout travail sur le state : cette adresse est le seul nom qu'une instance possède, et c'est elle que réclament state show, state mv, state rm, import et -replace. Ce guide montre comment lire ces adresses, comment filtrer nativement, y compris dans les modules, et comment compter les ressources gérées sans se tromper d'une unité.

Tous les comportements de ce guide ont été exécutés sur Terraform v1.15.4, avec les providers local et random : aucun hyperviseur ni compte cloud n'est nécessaire pour les reproduire.

  • Ressource contre instance, la distinction qui explique toute la sortie
  • Les quatre formes d'adresses : simple, count, for_each, dans un module
  • Filtrer nativement par ressource, par instance et par module
  • -id : retrouver une adresse quand on ne connaît que l'identifiant réel
  • Compter les ressources gérées sur la sortie JSON, modules inclus

C'est la distinction à poser avant tout le reste, et c'est le vocabulaire de la documentation officielle. Une ressource est ce que vous déclarez : un bloc resource. Une instance est ce que Terraform crée et enregistre dans le state. Un bloc en count = 3 est donc une ressource et trois instances, donc trois lignes dans la sortie.

C'est pour cela qu'une commande dont le nom parle de « lister les ressources » affiche en réalité des instances, et qu'un filtre sur une ressource peut rendre plusieurs lignes.

Le projet de démonstration mélange les quatre cas de figure : une ressource simple, une ressource en count, une ressource en for_each, un module, et une data source de chaque côté.

# main.tf : les quatre formes d'adresses en un seul projet
resource "random_pet" "noeud" {
count = 3
length = 2
}
resource "random_pet" "zone" {
for_each = toset(["eu-west", "us-east"])
length = 2
}
resource "local_file" "rapport" {
filename = "${path.root}/rapport.txt"
content = "noeuds=${length(random_pet.noeud)}\n"
}
data "local_file" "lecture" {
filename = local_file.rapport.filename
depends_on = [local_file.rapport]
}
module "reseau" {
source = "./modules/reseau"
}

Le module reseau déclare de son côté deux random_pet en for_each, un local_file et une data source local_file : c'est lui qui produit les adresses préfixées, et surtout la data source dans un module, qui réserve une surprise au moment de compter.

Après terraform apply :

Fenêtre de terminal
terraform state list
data.local_file.lecture
local_file.rapport
random_pet.noeud[0]
random_pet.noeud[1]
random_pet.noeud[2]
random_pet.zone["eu-west"]
random_pet.zone["us-east"]
module.reseau.data.local_file.relecture
module.reseau.local_file.carte
module.reseau.random_pet.sous_reseau["a"]
module.reseau.random_pet.sous_reseau["b"]

Onze lignes alors que le projet ne déclare que sept blocs resource ou data : la sortie compte des instances.

Chaque ligne est une adresse, et il n'y a que quatre formes à connaître. Les retenir, c'est pouvoir écrire une adresse de mémoire au lieu de la copier.

DéclarationAdresse d'une instanceCe qui identifie l'instance
ressource simplelocal_file.rapportrien, elle est unique
countrandom_pet.noeud[0]l'index, entre crochets
for_eachrandom_pet.zone["eu-west"]la clé, entre crochets et guillemets
dans un modulemodule.reseau.random_pet.sous_reseau["a"]le préfixe module.<nom>.
data sourcedata.local_file.lecturele préfixe data.

L'ordre de la sortie suit la profondeur des modules

Section intitulée « L'ordre de la sortie suit la profondeur des modules »

La liste n'est pas triée alphabétiquement de bout en bout. La documentation est explicite : la sortie est ordonnée « according to module depth order followed alphabetically ». Les instances de la configuration racine sortent donc en premier, puis celles des modules, du moins profond au plus profond.

Dans la sortie ci-dessus, module.reseau.data.local_file.relecture apparaît après random_pet.zone["us-east"], ce qu'un tri purement alphabétique interdirait. En pratique : le bas d'une longue liste est toujours la partie modularisée, ce qui est commode pour repérer d'un coup d'œil ce qui vient des modules.

L'argument d'adresse n'est pas une vérification d'existence, c'est un filtre, et la page officielle parle de « patterns » en « resource addressing format ». Trois usages en découlent.

  1. Toutes les instances d'une ressource, en donnant l'adresse sans index :

    Fenêtre de terminal
    terraform state list random_pet.noeud
    random_pet.noeud[0]
    random_pet.noeud[1]
    random_pet.noeud[2]

    Une adresse sans index désigne la famille, pas une instance unique.

  2. Une instance précise, en donnant l'index ou la clé :

    Fenêtre de terminal
    terraform state list 'random_pet.zone["eu-west"]'
    random_pet.zone["eu-west"]
  3. Tout le contenu d'un module, avec une adresse de module :

    Fenêtre de terminal
    terraform state list module.reseau
    module.reseau.data.local_file.relecture
    module.reseau.local_file.carte
    module.reseau.random_pet.sous_reseau["a"]
    module.reseau.random_pet.sous_reseau["b"]

    module.<nom>.module.<enfant> fonctionne de même, et descend dans les sous-modules.

Plusieurs adresses se cumulent sur la même ligne de commande. La sortie suit alors l'ordre des arguments, et non l'ordre trié : utile quand on veut comparer deux familles côte à côte.

Le besoin courant est l'inverse de la liste : vous avez un identifiant réel, un chemin, un UUID, un ARN, et vous cherchez quelle instance le porte. L'option -id filtre sur la valeur de l'attribut id :

Fenêtre de terminal
terraform state list -id=proud-marmoset
random_pet.noeud[0]

C'est le geste qui évite de lire cinquante lignes pour en trouver une. -id se combine avec une adresse, et l'on obtient l'intersection des deux filtres : terraform state list -id=proud-marmoset random_pet.zone ne rend rien si cet identifiant appartient à une autre famille.

Une adresse qui ne correspond à rien produit une erreur et un code de retour 1. Il y a quatre diagnostics distincts, et savoir lequel on a sous les yeux fait gagner du temps :

Ce que vous passezDiagnosticCe qu'il faut corriger
random_pet.absentUnknown resourcela ressource n'est pas au state
random_pet.zone["nope"]Unknown resource instancela ressource existe, pas cette clé
module.absentUnknown modulele nom du module
random_petInvalid addressil manque le nom : une adresse n'est jamais réduite au type

Un -id sans correspondance se comporte au contraire tout autrement : aucune erreur, sortie vide, code de retour 0. Vérifié sur 1.15.4.

Cette asymétrie décide de la façon dont on scripte la commande. Un filtre par adresse se teste sur son code de retour ; un filtre par -id se teste sur le fait que sa sortie soit vide ou non, puisque son code de retour vaut 0 dans les deux cas.

Fenêtre de terminal
# Un -id sans correspondance ne fait PAS echouer un script
terraform state list -id=inexistant > /dev/null 2>&1; echo $? # 0

Voici le piège qui fausse des inventaires entiers. La recette qui circule est celle-ci :

Fenêtre de terminal
# ❌ Faux des qu'un module contient une data source
terraform state list | grep -v ^data | wc -l

Elle repose sur l'idée qu'une data source commence toujours par data. C'est vrai à la racine, et faux dans un module : l'adresse y est module.reseau.data.local_file.relecture, qui commence par module.. Le grep la laisse donc passer et la compte comme une ressource gérée.

Sur le projet de ce guide, la recette annonce 10 ressources gérées là où il y en a 9. L'écart est de un par data source de module, et il grandit avec la modularisation, c'est-à-dire précisément sur les projets où le comptage compte.

Le comptage juste se fait sur la sortie structurée, qui distingue mode: "managed" de mode: "data" et expose la hiérarchie root_module / child_modules :

Fenêtre de terminal
terraform show -json | jq '[.values.root_module
| .. | .resources? // empty | .[]
| select(.mode == "managed")] | length'
9

terraform state identities, la voie des identités

Section intitulée « terraform state identities, la voie des identités »

La 1.15 expose une sous-commande voisine que l'on rencontre en cherchant à relier une adresse de state à l'objet réel :

Fenêtre de terminal
terraform state identities -json

Elle exige le drapeau -json et le dit franchement si on l'oublie : The terraform state identitiescommand requires the-json flag. Elle rend un objet vide ({}) quand les providers du projet n'exposent pas d'identité de ressource, ce qui est le cas de random et local. À connaître pour ne pas la confondre avec -id, qui filtre, lui, sur un attribut du state.

Le réflexe utile est de toujours partir d'une liste sans filtre, puis de resserrer : la plupart des erreurs sont des adresses mal écrites, pas des ressources absentes.

SymptômeCause probableSolution
No state file was found!Aucun apply, ou state ailleursLancer terraform apply, vérifier le backend
Unknown resourceFaute de frappe sur le nomRelister sans filtre, puis copier l'adresse
Unknown resource instanceLa ressource existe, la clé ou l'index nonLister la famille : terraform state list random_pet.zone
Invalid addressAdresse réduite au typeAjouter le nom, ou filtrer un module : module.reseau
no matches found (avant Terraform)zsh a interprété les crochetsQuoter : 'random_pet.noeud[0]'
Un comptage qui ne colle pasLa recette au grep compte une data source de moduleCompter sur show -json avec mode == "managed"
Liste vide après destroyComportement normalRien à corriger

Le lab terraform state list publie l'identifiant de trois instances tirées au sort, dans un projet qui mélange count, for_each et un module local, puis demande leurs adresses exactes. Il fait aussi mesurer l'écart entre la recette au grep et le vrai comptage. Il se joue hors ligne, sans compte cloud.

  • La commande liste des instances, pas des ressources : un bloc en count = 3 donne trois lignes.
  • Quatre formes d'adresses : simple, [index] pour count, ["clé"] pour for_each, préfixe module.<nom>. dans un module.
  • L'argument d'adresse est un filtre : sans index il rend toutes les instances d'une ressource, et module.<nom> est un filtre valide qui descend dans les sous-modules.
  • La sortie est triée par profondeur de module, puis alphabétiquement : la racine d'abord, les modules ensuite.
  • Une adresse qui ne correspond à rien échoue (code 1, quatre diagnostics) ; un -id sans correspondance rend 0 et une sortie vide.
  • Sous zsh, quoter toute adresse à crochets.
  • Compter les ressources gérées avec grep -v ^data est faux dès qu'un module a une data source : compter sur show -json et mode == "managed".

Les questions ci-dessous portent sur ce qui bloque le plus souvent : l'adresse qui rend plusieurs lignes, le module qu'on croyait impossible à filtrer, et le comptage qui se trompe d'une unité. Chaque réponse donne la commande de vérification.

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