Aller au contenu
Infrastructure as Code medium

Exécuter Terraform en automation

55 min de lecture

logo terraform

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.

  • 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

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.

CodeCe que cela veut dire
0aucun changement, l'infrastructure est conforme
1erreur : la configuration ne planifie pas
2des 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.

Fenêtre de terminal
terraform plan -input=false -detailed-exitcode
Error: 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).

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 :

Fenêtre de terminal
terraform plan -input=false
Error: No value for required variable
The root module input variable "obligatoire" is not set, and has no default
value. Use a -var or -var-file command line argument to provide a value for
this variable.

Code 1, en 0,0 seconde. La commande n'attend pas : elle échoue tout de suite, ce qui est exactement le comportement voulu.

  1. Contrôler le format, sur toute l'arborescence :

    Fenêtre de terminal
    terraform fmt -check -recursive

    Le 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.

  2. Initialiser sans interaction :

    Fenêtre de terminal
    terraform init -input=false
  3. Valider, en sortie machine :

    Fenêtre de terminal
    terraform validate -json
  4. Planifier dans un fichier, et décider :

    Fenêtre de terminal
    terraform plan -out=tfplan -input=false -detailed-exitcode
  5. Appliquer le fichier de plan, jamais une nouvelle planification :

    Fenêtre de terminal
    terraform apply -input=false tfplan

    Appliquer le fichier garantit que ce qui est déployé est bien ce qui a été revu. Un apply qui 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 :

Fenêtre de terminal
terraform apply -input=false -var environnement=autre tfplan
Error: Can't change variable when applying a saved plan
The variable environnement cannot be set using the -var and -var-file options
when applying a saved plan file, because a saved plan includes the variable
values 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é
-varerreur, code 1
-destroyaccepté, et les ressources sont créées
-refresh=falseaccepté, sans effet
-target=...accepté, sans effet : tout est créé
-parallelism=1accepté

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 unavailable

Code 1, en 0,0 seconde : aucune attente. Avec un délai, la même commande patiente puis aboutit :

Fenêtre de terminal
terraform plan -input=false -lock-timeout=60s
Acquiring 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.

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 :

Fenêtre de terminal
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.default

Un 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
*.tfplan

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.

SymptômeCause probableCorrection
le job reste bloqué sans rien afficherune commande attend une saisieajouter -input=false partout
le pipeline est vert avec des changements en attentecode lu sans -detailed-exitcodetraiter 0, 1 et 2 séparément
un contrôle de format toujours vertfmt -check sans -recursive, ou code testé -eq 1ajouter l'option, et tester le code 3
Error acquiring the state lockune 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 planfixer les variables au moment du plan -out
un apply -destroy a créé des ressourcesle mode appartient au fichier de planenregistrer un plan de destruction avec plan -destroy -out=
Saved plan is stalel'état a changé depuis l'enregistrementreplanifier, puis appliquer le nouveau fichier
un secret apparaît dans un artefact de CIle plan sauvegardé a été publiéne jamais le committer ni l'exposer en artefact

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.

  • -detailed-exitcode sépare les trois issues : 0 rien, 1 erreur, 2 changements.
  • -input=false transforme une attente d'invite en échec immédiat, en 0,0 seconde.
  • fmt -check rend 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=false et -target y sont acceptés et ignorés : un apply -destroy sur un plan de création crée.
  • Par défaut, un verrou concurrent fait échouer la commande immédiatement ; -lock-timeout la 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 .gitignore doit nommer tfplan.
  • TF_IN_AUTOMATION et -no-color allègent des sorties qui n'ont pas de terminal pour les lire.

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.

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