
Une chaîne d'intégration n'a pas de clavier et ne lit pas la sortie colorée : elle décide sur des codes de retour. Écrire Terraform pour l'automation, c'est donc rendre chaque commande non interactive, et savoir exactement ce que son code veut dire.
Tout ce qui suit a été exécuté sur Terraform v1.15.4, codes et durées
compris. Trois idées répandues n'y survivent pas, celle du fmt -check qui
rendrait 1, celle du plan qui attendrait une variable manquante, et celle
du plan sauvegardé qui figerait tout.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Les trois issues d'un plan, lues sur son seul code de retour
- Comment empêcher toute commande de se bloquer sur une invite
- Ce qu'un plan sauvegardé fige, et les options qui y deviennent décoratives
- Traiter un verrou d'état concurrent au lieu de le subir
- Pourquoi un plan sauvegardé ne doit jamais rejoindre le dépôt
Prérequis
Section intitulée « Prérequis »- Terraform 1.11 ou plus récent (installer Terraform).
- Connaître le workflow de base (le workflow Terraform).
Les trois issues d'un plan
Section intitulée « Les trois issues d'un plan »C'est la brique de toute porte de revue automatisée. Sans option,
terraform plan rend 0 aussi bien quand il n'y a rien à faire que quand
il y a des changements : impossible de décider. L'option -detailed-exitcode sépare les
trois cas.
| Code | Ce que cela veut dire |
|---|---|
| 0 | aucun changement, l'infrastructure est conforme |
| 1 | erreur : la configuration ne planifie pas |
| 2 | des changements sont en attente |
Mesuré, dans cet ordre : avant l'apply le plan rend 2, après l'apply il rend 0, et sur une configuration cassée il rend 1.
terraform plan -input=false -detailed-exitcodeError: Missing name for resource
on casse.tf line 1, in resource "local_file": 1: resource "local_file" {
All resource blocks must have 2 labels (type, name).Ne jamais se bloquer sur une invite
Section intitulée « Ne jamais se bloquer sur une invite »Une commande qui attend une saisie dans une CI ne s'arrête pas : elle occupe
un agent jusqu'au délai maximum du job. L'option -input=false l'interdit, et
transforme l'attente en échec immédiat.
Mesuré, sur une variable racine sans valeur :
terraform plan -input=falseError: No value for required variable
The root module input variable "obligatoire" is not set, and has no defaultvalue. Use a -var or -var-file command line argument to provide a value forthis variable.Code 1, en 0,0 seconde. La commande n'attend pas : elle échoue tout de suite, ce qui est exactement le comportement voulu.
La chaîne complète, étape par étape
Section intitulée « La chaîne complète, étape par étape »-
Contrôler le format, sur toute l'arborescence :
Fenêtre de terminal terraform fmt -check -recursiveLe code de retour est 3 en cas d'écart, pas 1. Mesuré sur un fichier mal indenté, la commande liste le fichier fautif et sort en 3.
-
Initialiser sans interaction :
Fenêtre de terminal terraform init -input=false -
Valider, en sortie machine :
Fenêtre de terminal terraform validate -json -
Planifier dans un fichier, et décider :
Fenêtre de terminal terraform plan -out=tfplan -input=false -detailed-exitcode -
Appliquer le fichier de plan, jamais une nouvelle planification :
Fenêtre de terminal terraform apply -input=false tfplanAppliquer le fichier garantit que ce qui est déployé est bien ce qui a été revu. Un
applyqui replanifie pourrait faire autre chose, si l'état a bougé entre-temps.
Ce qu'un plan sauvegardé fige, et ce qu'il ne fige pas
Section intitulée « Ce qu'un plan sauvegardé fige, et ce qu'il ne fige pas »C'est le point le plus contre-intuitif de cette page. Un plan sauvegardé fige les valeurs de variables et les actions prévues. Il ne protège pas de tout.
Une tentative de changer une variable est bien refusée :
terraform apply -input=false -var environnement=autre tfplanError: Can't change variable when applying a saved plan
The variable environnement cannot be set using the -var and -var-file optionswhen applying a saved plan file, because a saved plan includes the variablevalues that were set when it was created.Code 1. Jusque-là, tout va bien. En revanche, les modes de planification passés à cet apply sont acceptés et ignorés, en silence. Mesuré, chaque option sur un plan frais de création :
| Option passée à l'apply d'un plan sauvegardé | Résultat mesuré |
|---|---|
-var | erreur, code 1 |
-destroy | accepté, et les ressources sont créées |
-refresh=false | accepté, sans effet |
-target=... | accepté, sans effet : tout est créé |
-parallelism=1 | accepté |
Traiter le verrou, au lieu de le subir
Section intitulée « Traiter le verrou, au lieu de le subir »Deux exécutions concurrentes sur le même état, c'est le quotidien d'une CI qui déclenche sur plusieurs branches. Terraform pose un verrou pendant l'opération, et le comportement par défaut est brutal.
Mesuré, un plan lancé pendant qu'un apply détient le verrou :
Error: Error acquiring the state lock
Error message: resource temporarily unavailableCode 1, en 0,0 seconde : aucune attente. Avec un délai, la même commande patiente puis aboutit :
terraform plan -input=false -lock-timeout=60sAcquiring state lock. This may take a few moments...Code 0, après 15 secondes, soit le temps que l'apply concurrent se
termine. Un -lock-timeout adapté à la durée de vos applies transforme donc un
échec de pipeline en simple attente.
Les pièges mesurés
Section intitulée « Les pièges mesurés »Le plan sauvegardé contient vos secrets en clair
Section intitulée « Le plan sauvegardé contient vos secrets en clair »Le fichier produit par -out= a l'air opaque : un grep binaire n'y trouve
rien, puisqu'il est compressé. Cela ne protège rien du tout. Une relecture en
JSON en sort la valeur, et à quatre endroits distincts :
terraform show -json tfplan | jq '.variables'Mesuré, une variable marquée sensitive apparaît en clair dans :
.variables.mot_de_passe.value.planned_values.root_module.resources[0].values.content.resource_changes[0].change.after.content.configuration.root_module.variables.mot_de_passe.defaultUn plan sauvegardé est donc un artefact sensible. Il ne se committe
jamais, et son nom par défaut, tfplan, n'a pas d'extension : un
.gitignore qui ne connaît que *.tfplan ne l'attrape pas.
tfplan*.tfplanfmt -check ne rend pas 1
Section intitulée « fmt -check ne rend pas 1 »Le code est 3. Un pipeline qui teste -eq 1 laisse donc passer un dépôt mal
formaté, et celui qui oublie -recursive ne regarde qu'un seul
répertoire. Ces deux détails suffisent à rendre un contrôle de format entièrement
décoratif.
Un apply qui replanifie n'applique pas ce qui a été revu
Section intitulée « Un apply qui replanifie n'applique pas ce qui a été revu »Sans fichier de plan, terraform apply replanifie au moment de l'exécution.
Entre la revue et l'application, l'état a pu bouger. C'est précisément ce que
le couple plan -out puis apply <fichier> évite, et c'est la raison pour
laquelle une porte de revue sérieuse applique un fichier.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Correction |
|---|---|---|
| le job reste bloqué sans rien afficher | une commande attend une saisie | ajouter -input=false partout |
| le pipeline est vert avec des changements en attente | code lu sans -detailed-exitcode | traiter 0, 1 et 2 séparément |
| un contrôle de format toujours vert | fmt -check sans -recursive, ou code testé -eq 1 | ajouter l'option, et tester le code 3 |
Error acquiring the state lock | une exécution concurrente détient le verrou | -lock-timeout calé sur la durée de vos applies |
Can't change variable when applying a saved plan | -var passé à l'apply d'un fichier de plan | fixer les variables au moment du plan -out |
un apply -destroy a créé des ressources | le mode appartient au fichier de plan | enregistrer un plan de destruction avec plan -destroy -out= |
Saved plan is stale | l'état a changé depuis l'enregistrement | replanifier, puis appliquer le nouveau fichier |
| un secret apparaît dans un artefact de CI | le plan sauvegardé a été publié | ne jamais le committer ni l'exposer en artefact |
Mettre en pratique
Section intitulée « Mettre en pratique »Le lab une chaîne non interactive fait bâtir les cinq étapes et consigner
les codes relevés dans des fichiers de preuves. Les tests ne lisent aucune sortie
humaine : ils rejouent eux-mêmes fmt -check, validate -json, les trois
valeurs de -detailed-exitcode, l'apply concurrent sous verrou et les options du
plan sauvegardé, puis comparent à vos relevés. L'apply doit durer assez longtemps
pour qu'un verrou soit observable. Il se joue hors ligne.
À retenir
Section intitulée « À retenir »-detailed-exitcodesépare les trois issues : 0 rien, 1 erreur, 2 changements.-input=falsetransforme une attente d'invite en échec immédiat, en 0,0 seconde.fmt -checkrend 3, jamais 1, et ne voit qu'un répertoire sans-recursive.- Une porte de revue applique un fichier de plan, pas une nouvelle planification.
- Un plan sauvegardé fige les variables :
-varà l'apply est refusé. - Mais
-destroy,-refresh=falseet-targety sont acceptés et ignorés : un apply-destroysur un plan de création crée. - Par défaut, un verrou concurrent fait échouer la commande immédiatement ;
-lock-timeoutla fait patienter. - Un plan sauvegardé contient les secrets en clair, lisibles par
show -json, à quatre endroits. - Son nom par défaut n'a pas d'extension : le
.gitignoredoit nommertfplan. TF_IN_AUTOMATIONet-no-colorallègent des sorties qui n'ont pas de terminal pour les lire.
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les questions ci-dessous portent sur ce qui casse une chaîne : le code de retour mal lu, la commande qui attend, et le plan sauvegardé qu'on croit inaltérable.
Les trois codes
| Code | Signification |
|---|---|
| 0 | aucun changement, l'infrastructure est conforme |
| 1 | erreur : la configuration ne planifie pas |
| 2 | des changements sont en attente |
Mesuré sur 1.15.4
Avant l'apply le plan rend 2, après l'apply 0, et sur une configuration cassée 1.Le piège de lecture
Un pipeline qui traite « échec » comme « code différent de 0 » confond une erreur avec des changements en attente. Celui qui teste-eq 1 rate les deux. Les trois valeurs se traitent séparément.L'option
terraform plan -input=false
Error: No value for required variable
The root module input variable "obligatoire" is not set, and has no default
value.
Code 1, en 0,0 seconde : la commande n'attend pas, elle échoue tout de suite.Deux réglages complémentaires
TF_IN_AUTOMATION, à toute valeur non vide, retire des sorties les suggestions de commandes suivantes, qui n'ont pas de sens hors d'un terminal.-no-color supprime les séquences d'échappement qui polluent les journaux.Sans -input=false, une commande en attente n'échoue pas : elle occupe un agent jusqu'au délai maximum du job.Le couple à employer
terraform plan -out=tfplan -input=false -detailed-exitcode
terraform apply -input=false tfplan
Ce que cela garantit
Le fichier fige les actions prévues et les valeurs de variables au moment du plan. Ce qui est appliqué est donc exactement ce qui a été revu.Ce qui arrive sinon
Unterraform apply sans fichier replanifie. Si l'état a bougé entre la revue et l'exécution, le contenu réel de l'opération diffère de ce qui a été validé, sans que rien ne le signale.Si le fichier est devenu obsolète, Terraform le dit : Saved plan is stale.Ce qui est refusé
terraform apply -var environnement=autre tfplan
Error: Can't change variable when applying a saved plan
Code 1.Ce qui est accepté et ignoré
Mesuré, chaque option sur un plan frais de création :| Option | Résultat |
|---|---|
-var |
erreur, code 1 |
-destroy |
accepté, les ressources sont créées |
-refresh=false |
accepté, sans effet |
-target=... |
accepté, sans effet : tout est créé |
La conséquence
Unterraform apply -destroy tfplan lancé sur un plan de création rend 0 après avoir créé les ressources. Le mode appartient au fichier, pas à la ligne de commande. Pour détruire, il faut enregistrer un plan de destruction : terraform plan -destroy -out=tfplan.Le comportement par défaut
Unplan lancé pendant un apply :Error: Error acquiring the state lock
Error message: resource temporarily unavailable
Code 1, en 0,0 seconde : aucune attente.Avec un délai
terraform plan -input=false -lock-timeout=60s
Acquiring state lock. This may take a few moments...
Mesuré : code 0 après 15 secondes, soit le temps que l'apply concurrent se termine.Comment le calibrer
Un-lock-timeout du même ordre que la durée de vos applies transforme un échec de pipeline en simple attente. Trop court, il ne sert à rien ; trop long, il masque un verrou orphelin.Mesuré
Ungrep binaire sur le fichier ne trouve rien : il est compressé. Une relecture en JSON, elle, expose la valeur à quatre endroits :.variables.mot_de_passe.value
.planned_values.root_module.resources[0].values.content
.resource_changes[0].change.after.content
.configuration.root_module.variables.mot_de_passe.default
Et cela vaut pour une variable déclarée sensitive.La conséquence
Un plan sauvegardé est un artefact sensible. Il ne se committe jamais, et ne doit pas être publié comme artefact de CI accessible largement.Le piège du nom
Le nom par défaut,tfplan, n'a pas d'extension : un .gitignore qui ne connaît que *.tfplan ne l'attrape pas.tfplan
*.tfplan
Mesuré
Sur un fichier mal indenté,terraform fmt -check liste le fichier fautif et sort en 3. Après reformatage, le même contrôle rend 0.Les deux erreurs classiques
Tester-eq 1 : le contrôle ne détecte alors jamais rien.Oublier -recursive : la commande ne regarde que le répertoire courant. Sur une arborescence à plusieurs niveaux, elle rassure sans rien vérifier.La forme correcte
terraform fmt -check -recursive
Et côté pipeline, tester un code différent de zéro plutôt qu'une valeur précise.La chaîne
terraform fmt -check -recursive
terraform init -input=false
terraform validate -json
terraform plan -out=tfplan -input=false -detailed-exitcode
terraform apply -input=false tfplan
Ce que chaque étape apporte
fmt -check garde le dépôt lisible, et rend 3 en cas d'écart.validate -json donne un diagnostic exploitable par une machine plutôt qu'un texte.plan -out produit à la fois la décision (code 0, 1 ou 2) et l'artefact à appliquer.apply <fichier> garantit que le déployé est bien le revu.Le réglage global
PosezTF_IN_AUTOMATION et -no-color pour des journaux lisibles, et -lock-timeout pour survivre aux exécutions concurrentes.Pour aller plus loin
Section intitulée « Pour aller plus loin »- Tester un module : la suite de tests qu'une chaîne d'intégration peut jouer.
- Versionner ses modules : figer les versions que la chaîne installera.
- Exécuter Terraform en automation : la référence officielle : le workflow non interactif recommandé.
- La commande plan : la référence officielle :
-detailed-exitcode,-outet-lock-timeout.