Skip to content

ci(infra): instruire l'automatisation du terraform plan/apply — plan relu, apply derrière approbation #33

Description

@arenier

🚧 Draft — à instruire. Description courte volontairement : le périmètre, les critères
d'acceptation et le découpage en tâches restent à écrire avant de démarrer.

De quoi il s'agit

Aujourd'hui, terraform plan et terraform apply se lancent à la main, depuis un poste, contre
la prod. Cette issue instruit leur passage en CI — non pas pour retirer l'humain de la boucle, mais
pour garantir que le plan appliqué est exactement celui qui a été relu.

L'automatisation n'est pas un acquis : la décision actuelle est délibérée, pas un oubli.
infra/README.md (qui arrive avec #30) la formule ainsi, en renvoyant à #12 :

Pas de sandbox GCP (décision figée de l'issue #12) : plan est le seul filet avant un apply
qui touche directement la prod. Toujours relire un plan avant d'applyer.

Toute proposition ici doit donc dire ce qu'elle fait de ce filet, pas seulement comment elle
câble le workflow.

Ce qui existe

Le job terraform de ci.yml fait fmt, validate, tflint, terraform test, checkov. Il ne
fait ni plan ni apply, et il en est structurellement incapable : terraform init -backend=false (jamais de connexion au bucket pick-a-book-tfstate) et mock_provider pour les
tests, hermétique par construction. Le repo n'a par ailleurs ni secret, ni variable, ni environment
GitHub — aucun chemin d'authentification vers GCP.

Pourquoi un apply automatique et non gardé serait dangereux

Ce n'est pas théorique : c'est ce qui se serait produit au merge de #28, relevé en appliquant
l'infrastructure pour de vrai le 2026-08-20.

  • Le plan comportait 2 destructions — bucket et Artifact Registry recréés pour la bascule
    europe-west3 → europe-west1. Sans relecture, personne n'aurait vérifié qu'ils étaient vides.
  • Il aurait échoué en plein milieu : un destroy et un create visaient le même service account
    GCP (pick-a-book-api@…), sous deux adresses Terraform différentes après renommage de module.
    Terraform n'a aucune dépendance entre ces deux adresses ; selon l'ordre, soit le create échoue en
    409 alreadyExists, soit le destroy supprime le SA d'abord — et GCP retient l'identifiant d'un
    SA supprimé pendant ~30 jours
    , ce qui aurait bloqué la recréation. C'est un terraform state mv
    manuel qui l'a évité, opération qu'aucun workflow ne peut décider seul.
  • L'apply a en outre remonté deux erreurs qu'il fallait savoir distinguer : un 404 IAM sur le
    bucket (course de propagation GCS, transitoire, réglée au second apply) et un secret
    DATABASE_URL sans version (réel, bloquant, résolu hors Terraform). Un pipeline qui traite les
    deux de la même façon se trompe une fois sur deux.

La conclusion à instruire n'est donc pas « faut-il automatiser », mais où placer l'approbation
humaine pour qu'elle porte sur le plan et non sur le diff de code
.

À instruire

  • Forme du garde-fou. plan sur PR posté en commentaire, apply sur push main derrière un
    GitHub environment avec reviewer requis ? Et l'apply consomme-t-il un fichier de plan
    sauvegardé
    issu du plan relu (terraform plan -out), seule façon de garantir l'équivalence ?
  • Authentification. Workload Identity Federation plutôt qu'une clé de SA en secret longue durée.
    À provisionner dans infra/ (pool, provider, bindings) — donc décision structurante, ADR avant ou
    avec le code. Prérequis partagé avec ci(cd): pipeline de build et de déploiement d'image — Artifact Registry puis Cloud Run #32 : instruire les deux ensemble évitera de le faire deux
    fois, en gardant deux identités distinctes (le déploiement d'image n'a pas besoin d'écrire l'état
    Terraform).
  • Droits séparés plan / apply. Le plan a besoin d'un accès lecture à l'état et aux ressources,
    l'apply d'un accès écriture. Deux identités, ou une seule ?
  • Verrouillage d'état. Retirer le -backend=false expose au concurrent apply : le backend gcs
    gère le lock, à confirmer et à tester (que se passe-t-il si deux runs se chevauchent ?).
  • Comportement en cas de dérive. Le workflow doit échouer bruyamment plutôt que tenter d'être
    malin : un plan qui contient un destroy inattendu, ou qui exige un state mv, se traite à la
    main. Quel signal, et quelle procédure manuelle documentée ?
  • Périmètre. Un seul environnement (envs/prod) aujourd'hui. Le workflow doit-il déjà être écrit
    pour en accueillir d'autres, ou rester délibérément mono-env ?

Constat annexe : le job terraform ne bloque pas les merges

Relevé le 2026-08-21, en marge de cette issue mais du même sujet — les garde-fous de la CI infra.

main est bien protégée, par le ruleset « Protect main » (id=20000430, actif : deletion,
non_fast_forward, pull_request, required_status_checks). Mais la liste des contextes requis
contient check, et rien d'autre. Le job terraformfmt, validate, tflint,
terraform test, checkov — n'y figure pas.

Observé concrètement : #34 a été mergée par l'auto-merge pendant que le job terraform tournait
encore.
Il est passé, mais rien ne le garantissait. Or #30, #31 et #34 sont trois PR
d'infrastructure d'affilée dont la seule validation réelle n'a jamais conditionné le merge.

Ajouter terraform aux contextes requis est un réglage GitHub, pas un changement de code — mais il
appartient au même sujet que cette issue : jusqu'où la CI doit-elle bloquer sur l'infrastructure.
À trancher avec le reste plutôt qu'à la volée.

Hors périmètre

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions