Aller au contenu
Infrastructure as Code medium

Data sources Terraform : lire, utiliser et maîtriser le moment de lecture

45 min de lecture

logo terraform

Une data source Terraform lit une information qui existe déjà, sans jamais la créer ni la modifier. C'est la brique qui vous évite de coder en dur une valeur produite ailleurs : l'ID d'une image, le contenu d'un fichier, les outputs d'un autre état. Vous la déclarez avec un bloc data, vous référencez son résultat, et Terraform va le chercher pour vous.

Ce guide part de la base, écrire et utiliser un bloc data, puis aborde la seule chose vraiment subtile du sujet : le moment où Terraform lit une data source, tantôt pendant le plan, tantôt à l'apply. C'est ce timing qui explique un (known after apply) inattendu, ou un plan qui bouge sans qu'une ligne de HCL ait changé.

Tous les comportements décrits ont été vérifiés sur Terraform v1.15.4, plan JSON à l'appui, avec les seuls providers local et random. Aucun compte cloud n'est nécessaire.

  • Ce qu'est une data source et ce qui la distingue d'une resource
  • Comment écrire un bloc data et utiliser sa valeur dans une ressource
  • Quand Terraform lit une data source : pendant le plan ou à l'apply
  • Les trois causes exactes du report de lecture, et pourquoi depends_on n'en fait pas partie
  • Les meta-arguments d'un bloc data : count, for_each, provider, lifecycle
  • Comment prouver le moment de lecture avec terraform show -json
  • L'avertissement de sécurité sur terraform_remote_state

Une data resource lit un objet ; une managed resource le crée, le modifie et le détruit. C'est toute la différence, et c'est le vocabulaire officiel de la documentation Terraform : managed resources d'un côté, data resources de l'autre. Une data source ne possède pas l'objet qu'elle interroge, elle en prend seulement une photo au moment de l'opération.

On s'en sert dès qu'une valeur vient d'ailleurs que de votre configuration : l'AMI la plus récente d'un cloud, le CIDR d'un réseau créé par une autre équipe, le contenu d'un fichier de version. Coder ces valeurs en dur les fige et les périme ; une data source les lit fraîches à chaque exécution.

La syntaxe reprend celle d'une ressource, avec le mot-clé data et un préfixe data. à l'adressage. Voici une data source qui lit un fichier socle.txt déjà présent :

data "local_file" "socle" {
filename = "${path.module}/socle.txt"
}
output "socle_brut" {
value = data.local_file.socle.content
}

Le bloc porte un type (local_file) et un nom (socle). Son résultat s'adresse par data.<type>.<nom>.<attribut>, ici data.local_file.socle.content. Une fois la configuration appliquée, la valeur se lit directement :

Fenêtre de terminal
terraform output -raw socle_brut
app_version=2.4.1
zone=eu-west-3

Aucune ressource n'a été créée pour cela : Terraform a simplement lu le fichier.

L'intérêt vient de la référence : la valeur lue alimente une ressource gérée, exactement comme l'attribut d'une autre ressource.

resource "local_file" "manifeste" {
filename = "${path.module}/manifeste.txt"
content = "genere depuis : ${data.local_file.socle.content}"
}

Un terraform apply lit d'abord la data source, puis crée la ressource avec la valeur obtenue :

local_file.manifeste: Creation complete after 0s
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Le fichier produit porte bien la donnée lue :

genere depuis : app_version=2.4.1
zone=eu-west-3

Cette référence crée aussi une dépendance implicite : Terraform sait qu'il doit lire data.local_file.socle avant de construire local_file.manifeste.

La différence n'est pas qu'un mot, elle est infalsifiable dans la sortie machine. Le champ mode vaut managed ou data :

Fenêtre de terminal
terraform show -json | jq -c '.values.root_module.resources[] | {address, mode}'
{"address":"data.local_file.socle","mode":"data"}
{"address":"local_file.manifeste","mode":"managed"}

Recopier le contenu d'un fichier dans un local_file au lieu de le lire produirait mode: managed, et cela se verrait. C'est aussi le vocabulaire que vous retrouverez à l'examen.

Voici le cœur du sujet, et la seule vraie subtilité. Terraform tente de lire les data sources pendant le plan, mais peut reporter certaines lectures à l'apply. La documentation le formule ainsi : « Terraform attempts to query data sources during planning, but may defer reads to the apply phase. »

Une croyance très répandue est fausse : depends_on sur un bloc data ne force pas la lecture à l'apply. Le critère n'est pas l'existence d'une dépendance, mais le fait que la ressource visée soit configurée pour changer dans le plan courant.

Le report n'arrive jamais au hasard. Il a trois causes, et trois seulement :

CauseFormulation officielle
Dépendance sur une ressource gérée en cours de changement« depends directly or indirectly on a managed resource that is configured to change in the current plan »
Condition personnalisée dépendant d'une telle ressourceles precondition / postcondition du bloc, si elles référencent une ressource qui change
Argument dont la valeur n'est connue qu'à l'apply« arguments whose values must be computed during the apply phase »

Le mot décisif est « configured to change in the current plan ». Ce n'est pas « dépend d'une ressource », c'est « dépend d'une ressource qui bouge maintenant ».

Trois data sources qui ne diffèrent que par ce qui alimente leur argument :

resource "random_pet" "empreinte" {
length = 2
keepers = {
revision = var.revision
}
}
resource "local_file" "rapport" {
filename = "${path.module}/sortie/rapport-${random_pet.empreinte.id}.txt"
content = "revision=${var.revision}\n"
}
# 1. Argument constant : aucune dépendance.
data "local_file" "catalogue" {
filename = "${path.module}/catalogue.txt"
}
# 2. L'argument RÉFÉRENCE l'attribut d'une ressource gérée.
data "local_file" "rapport_relu" {
filename = local_file.rapport.filename
}
# 3. Argument constant, mais depends_on explicite.
data "local_file" "catalogue_ordonne" {
filename = "${path.module}/catalogue.txt"
depends_on = [random_pet.empreinte]
}

Après un premier apply, plus rien ne change. Un nouveau plan lit les trois au plan, y compris celle qui porte depends_on :

Fenêtre de terminal
terraform plan -out=tf.plan
terraform show -json tf.plan | jq '.resource_changes[] | select(.mode == "data")'

Sortie vide. Les trois figurent dans prior_state avec leurs valeurs réelles.

Faites maintenant bouger la ressource gérée :

Fenêtre de terminal
terraform plan -var 'revision=2' -out=tf.plan
terraform show -json tf.plan | jq '.resource_changes[] | select(.mode == "data") | {address, actions: .change.actions}'
{ "address": "data.local_file.rapport_relu", "actions": ["read"] }
{ "address": "data.local_file.catalogue_ordonne", "actions": ["read"] }

catalogue reste absente : son argument ne dépend de rien. Les deux autres sont reportées, parce que la ressource dont elles dépendent est cette fois en mouvement.

La documentation recommande d'ailleurs la sobriété sur ce meta-argument : « You should only use depends_on as a last resort because it can cause Terraform to create more conservative plans that replace more resources than necessary. »

Par le plan converti en JSON, jamais par la sortie de terminal. L'opposition est nette, et c'est la seule preuve non ambiguë :

Moment de lectureOù la data source apparaîtOù elle n'apparaît pas
Planprior_state.values.root_module.resources, avec ses valeurs réellesresource_changes
Applyresource_changes, avec "mode": "data" et "actions": ["read"]prior_state

L'effet se propage aux outputs. Un output qui dérive d'une lecture reportée porte after_unknown: true :

Fenêtre de terminal
terraform show -json tf.plan | jq '.output_changes'
{
"catalogue": { "after_unknown": false },
"rapport": { "after_unknown": true }
}

C'est l'origine exacte du (known after apply) que vous lisez dans le plan en clair.

Le bloc data n'est pas un bloc appauvri : il accepte count, for_each, provider, depends_on et lifecycle, comme une ressource gérée. Les quatre formes ci-dessous ont été appliquées avec succès sur Terraform v1.15.4.

Le meta-argument for_each lit une famille de fichiers en une seule déclaration :

data "local_file" "tous" {
for_each = toset(["a.txt", "b.txt"])
filename = "${path.module}/${each.key}"
}
output "tailles" {
value = { for k, d in data.local_file.tous : k => length(d.content) }
}

L'adressage devient data.local_file.tous["a.txt"], visible tel quel dans terraform state list.

Quand l'index suffit, count fait le même travail par position :

data "local_file" "indexe" {
count = 2
filename = "${path.module}/${count.index == 0 ? "a" : "b"}.txt"
}

Adressage data.local_file.indexe[0], comme pour une ressource gérée.

Le bloc lifecycle porte precondition et postcondition : c'est le moyen de faire échouer une configuration au plan quand une donnée externe n'est pas conforme, plutôt que de laisser l'erreur se propager plus loin.

data "local_file" "controle" {
filename = "${path.module}/catalogue.txt"
lifecycle {
postcondition {
condition = length(self.content) > 0
error_message = "Le catalogue est vide."
}
}
}

Le sujet des conditions est creusé dans valider les entrées d'une configuration Terraform et le bloc lifecycle Terraform.

L'argument provider sur un bloc data cible un alias, indispensable dès que vous lisez dans une seconde région ou un second compte :

provider "local" {
alias = "secondaire"
}
data "local_file" "via_alias" {
provider = local.secondaire
filename = "${path.module}/catalogue.txt"
}

Qu'est-ce qui provoque une dérive par la donnée externe ?

Section intitulée « Qu'est-ce qui provoque une dérive par la donnée externe ? »

Le simple fait que la donnée lue change. C'est la conséquence pratique la plus visible d'une data source, et la plus déroutante quand on ne l'a pas anticipée. Modifiez le fichier lu, sans toucher un seul .tf :

Fenêtre de terminal
echo "ajout=externe" >> catalogue.txt
terraform plan -detailed-exitcode
echo $? # 2

Aucun fichier .tf n'a bougé. Le code de retour 2 signifie pourtant que des changements sont planifiés. Une data source est relue à chaque plan, et c'est cette lecture fraîche, non une valeur conservée, qui alimente le diff des ressources dépendantes.

C'est aussi pourquoi il est trompeur de dire que « Terraform stocke le résultat pour détecter les changements » : la valeur est bien écrite dans l'état pendant la phase de rafraîchissement, mais c'est la relecture qui fait foi.

Que deviennent les data sources lors d'un destroy ?

Section intitulée « Que deviennent les data sources lors d'un destroy ? »

Aucune action de destruction ne les concerne, mais elles disparaissent quand même de l'état. La nuance compte, et elle est souvent mal comprise.

Fenêtre de terminal
terraform plan -destroy -out=tf.plan
terraform show -json tf.plan | jq '[.resource_changes[] | select(.mode == "data")] | length'
# 0

Le plan de destruction ne contient aucune entrée mode: data, et le compteur final les ignore : Terraform ne détruit pas ce qu'il n'a jamais créé. Mais après l'apply de ce plan, terraform state list est vide. Les data resources sont retirées de l'état en même temps que le reste. Il est donc faux de dire qu'une data source « survit » à un destroy : elle n'est pas détruite, elle disparaît sans action de destruction.

terraform_remote_state : l'avertissement à connaître

Section intitulée « terraform_remote_state : l'avertissement à connaître »

terraform_remote_state lit les outputs d'un autre état Terraform. C'est pratique pour partager une valeur entre configurations, et c'est la data source la plus exposée en matière de sécurité.

data "terraform_remote_state" "reseau" {
backend = "s3"
config = {
bucket = "mon-bucket-tfstate"
key = "reseau/terraform.tfstate"
region = "eu-west-3"
}
}

Deux points que la documentation officielle signale explicitement. L'accès aux outputs vaut accès à tout l'état : « Any user or server which has enough access to read the root module output values will also always have access to the full state snapshot data by direct network requests. » Donner accès à un output revient donc à donner accès au snapshot complet, mots de passe et clés compris s'ils y figurent.

Seuls les outputs racine sont exposés, jamais ceux des modules enfants. Un output enfoui dans un module n'est pas lisible par ce biais, mais il reste dans le snapshot. Pour partager des valeurs sans exposer un état entier, préférez un magasin dédié (Vault, SSM Parameter Store, Consul) ou les data sources du fournisseur.

Ces symptômes reviennent souvent, et se lisent tous au moment du plan. Le tableau associe chacun à sa cause réelle, puis à la correction.

SymptômeCauseSolution
(known after apply) sur une valeur attendueLa data source dépend d'une ressource qui change dans ce planNormal. Un argument constant reste connu au plan : vérifiez si la dépendance est nécessaire
no such file or directory au premier applyUne data source à chemin constant est lue pendant le plan, donc avant toute créationFaites dépendre son argument de la ressource qui produit le fichier, ou pré-créez le fichier
Un plan annonce des changements sans modification de codeLa donnée externe lue a changéC'est la dérive par la donnée. plan -detailed-exitcode rend 2
depends_on ajouté, mais la lecture reste au planCe n'est pas un critère de reportComportement attendu. Seul un changement en attente reporte la lecture
Une adresse apparaît en mode: managed alors qu'on attendait une lectureUn resource a été déclaré là où un data était requisRemplacez la ressource par un bloc data
  1. Une data source lit un objet existant sans le posséder ; une managed resource le crée. Le champ mode du state tranche : data ou managed.
  2. On écrit un bloc data "<type>" "<nom>" et on référence son résultat par data.<type>.<nom>.<attribut>, ce qui crée une dépendance implicite.
  3. Terraform lit une data source pendant le plan, sauf report à l'apply.
  4. Le report a trois causes ; depends_on seul n'en est pas une. Le critère est que la ressource visée change dans le plan courant.
  5. La preuve du moment de lecture est dans le plan JSON : prior_state contre resource_changes avec actions: ["read"], et after_unknown sur les outputs.
  6. Le bloc data accepte count, for_each, provider et lifecycle.
  7. terraform_remote_state expose tout le snapshot à qui lit un output : préférez un magasin de secrets dédié.

Les questions ci-dessous portent sur le point qui déroute le plus : le moment où une data source est lue, au plan ou à l'apply, et ce qui en découle sur les valeurs (known after apply). 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