[68] Squelette-terraform #150

Merged
justine merged 10 commits from florian/68-squelette-terraform into develop 2026-09-04 13:38:15 +00:00
Member

Ce que ça change

Le squelette Terraform est en place et branché sur le conteneur d'état créé par
le #67. L'état distant n'est plus une intention : terraform plan affiche
« No changes » contre le backend azurerm.

Closes #68

Preuve

$ terraform init
Initializing the backend...

Successfully configured the backend "azurerm"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- Using previously-installed hashicorp/azurerm v4.81.0

Terraform has been successfully initialized!

$ terraform validate
Success! The configuration is valid.

$ terraform plan
data.azurerm_resource_group.projet: Reading...
data.azurerm_resource_group.projet: Read complete after 1s [id=/subscriptions/ca5c57dd-…/resourceGroups/rg-FHeuze2023_cours-projet-eadl]

No changes. Your infrastructure matches the configuration.

Relecture

  • Un pair a relu et laissé un commentaire, même court
  • Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas

Ce qui suit le code

  • docs/runbooks/ mis à jour, un geste d'exploitation a changé
  • docs/adr/ complété — l'ADR 0007 tranchait déjà, cette MR l'applique

Où regarder en priorité

main.tf ne crée rien, et n'est pourtant pas vide. Un fichier sans contenu
afficherait « No changes » sans jamais joindre Azure : la preuve ci-dessus ne
vaudrait rien, et un défaut d'authentification n'apparaîtrait qu'au premier
apply du #43. La lecture du groupe de ressources fait travailler le
fournisseur à chaque plan. Elle sert aussi à reprendre l'étiquette user
qu'exige la politique de l'abonnement, plutôt qu'à la coder en dur.

resource_provider_registrations = "none" dans providers.tf. Par défaut
azurerm 4 enregistre ses espaces de noms au démarrage, ce qui demande une
autorisation que le rôle Devops-cours-projet-eadl n'accorde pas. Sans cette
ligne, chaque commande échoue sur un droit dont on n'a pas besoin : l'abonnement
école a déjà tout enregistré.

L'homonymie de resource_group_name. Celui de backend.tf héberge le
compte de stockage de l'état, partagé par tout le groupe ; celui de
variables.tf est l'endroit où chaque membre créera ses propres ressources. Ils
coïncident sur le poste où le compte d'état a été créé, pas ailleurs. C'est
commenté sur place, mais c'est le point le plus facile à mal lire.

.gitignore gagne tfplan. Un plan enregistré porte les valeurs de l'état, secrets compris, et rien ne l'écartait du dépôt (ENF-12). La négation !*.tfvars.example passe aussi après *.tfvars — non pour la rendre utile, *.tfvars ne matchant pas terraform.tfvars.example : elle est sans effet dans les deux ordres — mais pour aligner le bloc sur la convention *.env du fichier. Correction d'une motivation inexacte dans la première version de cette description, relevée en relecture.

Non couvert ici, et assumé : tflint et le contrôle de format en chaîne
d'intégration, c'est le #72 ; ce qui sera réellement créé et le plafond de
dépense, c'est le #43.

## Ce que ça change Le squelette Terraform est en place et branché sur le conteneur d'état créé par le #67. L'état distant n'est plus une intention : `terraform plan` affiche « No changes » contre le backend `azurerm`. Closes #68 ## Preuve ``` $ terraform init Initializing the backend... Successfully configured the backend "azurerm"! Terraform will automatically use this backend unless the backend configuration changes. Initializing provider plugins... - Reusing previous version of hashicorp/azurerm from the dependency lock file - Using previously-installed hashicorp/azurerm v4.81.0 Terraform has been successfully initialized! $ terraform validate Success! The configuration is valid. $ terraform plan data.azurerm_resource_group.projet: Reading... data.azurerm_resource_group.projet: Read complete after 1s [id=/subscriptions/ca5c57dd-…/resourceGroups/rg-FHeuze2023_cours-projet-eadl] No changes. Your infrastructure matches the configuration. ``` ## Relecture - [ ] Un pair a relu et laissé un commentaire, même court - [ ] Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas ## Ce qui suit le code - [x] `docs/runbooks/` mis à jour, un geste d'exploitation a changé - [x] `docs/adr/` complété — l'ADR 0007 tranchait déjà, cette MR l'applique ## Où regarder en priorité **`main.tf` ne crée rien, et n'est pourtant pas vide.** Un fichier sans contenu afficherait « No changes » sans jamais joindre Azure : la preuve ci-dessus ne vaudrait rien, et un défaut d'authentification n'apparaîtrait qu'au premier `apply` du #43. La lecture du groupe de ressources fait travailler le fournisseur à chaque `plan`. Elle sert aussi à reprendre l'étiquette `user` qu'exige la politique de l'abonnement, plutôt qu'à la coder en dur. **`resource_provider_registrations = "none"` dans `providers.tf`.** Par défaut azurerm 4 enregistre ses espaces de noms au démarrage, ce qui demande une autorisation que le rôle `Devops-cours-projet-eadl` n'accorde pas. Sans cette ligne, chaque commande échoue sur un droit dont on n'a pas besoin : l'abonnement école a déjà tout enregistré. **L'homonymie de `resource_group_name`.** Celui de `backend.tf` héberge le compte de stockage de l'état, partagé par tout le groupe ; celui de `variables.tf` est l'endroit où chaque membre créera ses propres ressources. Ils coïncident sur le poste où le compte d'état a été créé, pas ailleurs. C'est commenté sur place, mais c'est le point le plus facile à mal lire. .gitignore gagne tfplan. Un plan enregistré porte les valeurs de l'état, secrets compris, et rien ne l'écartait du dépôt (ENF-12). La négation !*.tfvars.example passe aussi après *.tfvars — non pour la rendre utile, *.tfvars ne matchant pas terraform.tfvars.example : elle est sans effet dans les deux ordres — mais pour aligner le bloc sur la convention *.env du fichier. Correction d'une motivation inexacte dans la première version de cette description, relevée en relecture. Non couvert ici, et assumé : `tflint` et le contrôle de format en chaîne d'intégration, c'est le #72 ; ce qui sera réellement créé et le plafond de dépense, c'est le #43.
Six fichiers de configuration, branchés sur le conteneur d'état du #67. L'état
distant est désormais réel : `terraform plan` affiche « No changes ».

Trois choix méritent d'être relus :

- `main.tf` ne crée aucune ressource — c'est le #43 — mais il n'est pas vide
  pour autant. Un fichier sans rien afficherait « No changes » sans jamais
  joindre Azure : la preuve demandée par le ticket ne vaudrait rien, et un
  défaut d'authentification n'apparaîtrait qu'au premier apply du #43. La
  lecture du groupe de ressources fait travailler le fournisseur à chaque plan.
  Elle sert aussi à reprendre l'étiquette `user` qu'exige la politique de
  l'abonnement, plutôt que de la coder en dur.
- `resource_provider_registrations = "none"` dans `providers.tf`. Par défaut
  azurerm 4 enregistre ses espaces de noms au démarrage, ce qui demande une
  autorisation que le rôle Devops-cours-projet-eadl n'accorde pas. Sans cette
  ligne, chaque commande échoue sur un droit dont on n'a pas besoin :
  l'abonnement école a déjà tout enregistré.
- `use_azuread_auth` dans `backend.tf`, plutôt que la clé de compte. La lire
  supposerait `listKeys`, hors du rôle, et poserait un secret de plus à faire
  tourner (ADR 0007).

Le premier apply n'a créé aucune ressource : il n'écrit que les sorties dans
l'état, jusque-là vide. C'est ce qui rend le critère « plan No changes »
vérifiable. Il a suivi la manœuvre qu'il documente — plan -out, relecture du
plan enregistré, apply de ce plan.

`.gitignore` gagne `tfplan` : un plan enregistré porte les valeurs de l'état,
secrets compris, et rien ne l'en écartait (ENF-12). La négation sur
`*.tfvars.example` passe après `*.tfvars`, faute de quoi elle ne servait à rien.
La revue de Gabriel fait afficher au script les cinq lignes à reporter dans
`backend.tf`, `resource_group_name` compris. Il manquait ici : avec
`use_azuread_auth`, `subscription_id` et `tenant_id` suffisent, et l'`init`
passait sans lui. Mais un membre qui suit la sortie du script à la lettre
obtenait un fichier différent du nôtre, et devait deviner lequel faisait
autorité. Vérifié : init, validate et plan « No changes » avec la ligne.

Elle crée une homonymie, signalée en commentaire : ce groupe-là héberge le
compte de stockage de l'état, partagé par tout le groupe, tandis que celui de
`variables.tf` est l'endroit où chaque membre créera ses propres ressources.
Ils coïncident sur le poste où le compte d'état a été créé, pas ailleurs.

Refs #68
[68] PULL_REQUEST_TEMPLATE.md Suprression des modifications
Some checks failed
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 2m4s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Failing after 5m17s
5dd4be7f4c
gabriel removed their assignment 2026-09-04 11:14:31 +00:00
Member

Relecture du squelette (fmt / validate / init -backend=false rejoués). Cohérent avec l'ADR 0007 et le #67, commentaires très clairs, resource_provider_registrations = "none" et gel de key sont les bons choix. Un point à corriger avant fusion, le reste peut suivre.

1. À corriger avant fusion — .terraform.lock.hcl mono-plateforme

Le lock ne contient les hashes h1: que pour une seule plateforme. En rejouant terraform init sur une autre (constaté ici), Terraform réécrit le fichier :

Terraform has made some changes to the provider dependency selections
recorded in the .terraform.lock.hcl file.
+ "h1:XhToZua4gtih1Kv8RdStcfND83G4Tmb6GZFT4jEUhDU=",

Conséquences : churn du lock et conflits de fusion dès qu'un coéquipier sur un autre OS lance init (les commentaires mentionnent les postes Windows du groupe), et surtout terraform init -lockfile=readonly échouera en chaîne d'intégration — ce que prépare le #72.

Correctif, à committer :

cd infra/terraform
terraform providers lock \
  -platform=linux_amd64 -platform=darwin_arm64 \
  -platform=darwin_amd64 -platform=windows_amd64

2. À traiter ou assumer

subscription_id et tenant_id traités différemment alors que les deux sont constants. subscription_id est une variable requise sans défaut : chaque membre doit maintenir un terraform.tfvars avec une valeur identique pour tous (l'exemple le dit lui-même). tenant_id est en dur. Donner un default à subscription_id réduirait terraform.tfvars à la seule ligne qui varie vraiment, resource_group_name.

main.tfdata.azurerm_resource_group.projet.tags["user"] indexe une clé sans filet. Si le groupe visé n'a pas l'étiquette user, plan casse sur un « Invalid index » opaque. La politique d'abonnement et bootstrap.sh la garantissent en pratique, mais une precondition sur le data (ou lookup(..., "user", null)) rendrait l'échec lisible.

3. Détails

  • variables.tf : la validation ^[0-9a-f-]{36}$ accepte 36 tirets. bootstrap.sh utilise déjà le bon motif ^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$ — aligner.
  • outputs.tf : region renvoie var.location, pas data.azurerm_resource_group.projet.location. Pour un output censé se vérifier d'un coup d'œil en relecture, exposer la région réelle du groupe (ou les deux) est plus utile.
  • Dérive #67/#68 : le heredoc final de bootstrap.sh liste les lignes à reporter dans backend.tf mais omet tenant_id et subscription_id, que backend.tf porte pourtant. Un membre qui suit la sortie du script produit un backend.tf différent du committé.
  • .gitignore : *.tfvars ne matche pas terraform.tfvars.example (vérifié), donc !*.tfvars.example est un no-op dans les deux ordres — le changement est sain (il calque la convention *.env), seule la motivation écrite dans le corps de PR est inexacte. L'ajout de tfplan / *.tfplan est justifié et correct.

À ne pas changer

Le tenant verrouillé, use_azuread_auth, le gel de key avec sa doc de récupération, le data plutôt qu'un main.tf vide, l'épinglage ~> 4.0 motivé, le jq du runbook qui exclut no-op et read.

Relecture du squelette (fmt / validate / init -backend=false rejoués). Cohérent avec l'ADR 0007 et le #67, commentaires très clairs, `resource_provider_registrations = "none"` et gel de `key` sont les bons choix. Un point à corriger avant fusion, le reste peut suivre. ## 1. À corriger avant fusion — `.terraform.lock.hcl` mono-plateforme Le lock ne contient les hashes `h1:` que pour une seule plateforme. En rejouant `terraform init` sur une autre (constaté ici), Terraform réécrit le fichier : ``` Terraform has made some changes to the provider dependency selections recorded in the .terraform.lock.hcl file. + "h1:XhToZua4gtih1Kv8RdStcfND83G4Tmb6GZFT4jEUhDU=", ``` Conséquences : churn du lock et conflits de fusion dès qu'un coéquipier sur un autre OS lance `init` (les commentaires mentionnent les postes Windows du groupe), et surtout `terraform init -lockfile=readonly` échouera en chaîne d'intégration — ce que prépare le #72. Correctif, à committer : ```bash cd infra/terraform terraform providers lock \ -platform=linux_amd64 -platform=darwin_arm64 \ -platform=darwin_amd64 -platform=windows_amd64 ``` ## 2. À traiter ou assumer **`subscription_id` et `tenant_id` traités différemment alors que les deux sont constants.** `subscription_id` est une variable requise sans défaut : chaque membre doit maintenir un `terraform.tfvars` avec une valeur identique pour tous (l'exemple le dit lui-même). `tenant_id` est en dur. Donner un `default` à `subscription_id` réduirait `terraform.tfvars` à la seule ligne qui varie vraiment, `resource_group_name`. **`main.tf` — `data.azurerm_resource_group.projet.tags["user"]` indexe une clé sans filet.** Si le groupe visé n'a pas l'étiquette `user`, `plan` casse sur un « Invalid index » opaque. La politique d'abonnement et `bootstrap.sh` la garantissent en pratique, mais une `precondition` sur le `data` (ou `lookup(..., "user", null)`) rendrait l'échec lisible. ## 3. Détails - **`variables.tf`** : la validation `^[0-9a-f-]{36}$` accepte 36 tirets. `bootstrap.sh` utilise déjà le bon motif `^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$` — aligner. - **`outputs.tf`** : `region` renvoie `var.location`, pas `data.azurerm_resource_group.projet.location`. Pour un output censé se vérifier d'un coup d'œil en relecture, exposer la région réelle du groupe (ou les deux) est plus utile. - **Dérive #67/#68** : le heredoc final de `bootstrap.sh` liste les lignes à reporter dans `backend.tf` mais omet `tenant_id` et `subscription_id`, que `backend.tf` porte pourtant. Un membre qui suit la sortie du script produit un `backend.tf` différent du committé. - **`.gitignore`** : `*.tfvars` ne matche pas `terraform.tfvars.example` (vérifié), donc `!*.tfvars.example` est un no-op dans les deux ordres — le changement est sain (il calque la convention `*.env`), seule la motivation écrite dans le corps de PR est inexacte. L'ajout de `tfplan` / `*.tfplan` est justifié et correct. ## À ne pas changer Le tenant verrouillé, `use_azuread_auth`, le gel de `key` avec sa doc de récupération, le `data` plutôt qu'un `main.tf` vide, l'épinglage `~> 4.0` motivé, le `jq` du runbook qui exclut `no-op` et `read`.
florian changed title from florian/68-squelette-terraform to [68]squelette-terraform 2026-09-04 11:37:49 +00:00
florian changed title from [68]squelette-terraform to [68] Squelette-terraform 2026-09-04 11:38:00 +00:00
florian referenced this pull request from a commit 2026-09-04 12:39:55 +00:00
Le verrou ne portait les empreintes que d'une plateforme. Un coéquipier sur un
autre OS le réécrivait à son `init` — churn et conflits de fusion — et le
`-lockfile=readonly` que prépare le #72 aurait échoué en chaîne. Regénéré pour
linux_amd64, darwin_arm64, darwin_amd64 et windows_amd64 ; quatre empreintes au
lieu d'une, et `init -lockfile=readonly` passe désormais.

`subscription_id` prend un défaut. Il est constant pour tout le groupe, et
l'exiger obligeait chacun à maintenir dans son `terraform.tfvars` une valeur
identique pour tous, à côté du seul champ qui varie vraiment. L'exemple se
réduit donc à `resource_group_name`.

L'étiquette `user` était indexée sans filet : un groupe qui n'en porte pas
faisait échouer le `plan` sur un « Invalid index » muet. Le contrôle est une
`postcondition`, pas une `precondition` — celle-ci est évaluée avant la
lecture et ne peut pas porter sur ce qu'elle rapporte, Terraform la refuse
comme auto-référence. Éprouvé en inversant la condition : le message nomme le
groupe et donne la commande de remise en état.

La validation de GUID acceptait trente-six tirets. Motif aligné sur celui de
`bootstrap.sh`, et vérifié dans les deux sens.

`region` renvoyait la variable, c'est-à-dire l'intention, sous un nom qui
laissait croire à un constat. Deux sorties distinctes : `region_du_groupe`, lue
sur Azure, et `region_des_ressources`, demandée pour le #43. Les voir diverger
est un signal utile en relecture.

Enfin la dérive #67/#68 : le heredoc de `bootstrap.sh` omettait `tenant_id` et
`subscription_id`, que `backend.tf` porte. Complété, et la comparaison des deux
blocs est maintenant exacte au caractère près.

Vérifié : fmt, init -lockfile=readonly, validate, plan « No changes » après
apply du renommage des sorties (0 ressource touchée), bootstrap rejoué contre
Azure, contrôle des liens Markdown, scan de fuite de la chaîne.

Refs #68
Le commit 5dd4be7 l'avait retirée par mégarde. C'est un critère d'acceptation
du #68 : sans elle, rien ne réclame la trace du plan avant fusion et celle de
l'apply après, et l'ADR 0007 n'a plus que la bonne volonté pour tenir.

Remise à l'identique de ce qui avait été retiré — vérifié en comparant les
lignes supprimées par 5dd4be7 aux lignes réintroduites — et au même endroit,
entre « Preuve », dont elle est le cas particulier, et « Relecture ». Le diff
ne porte que des ajouts : 25 lignes, aucune suppression.

Refs #68
Merge develop dans florian/68-squelette-terraform
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 18s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 4s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 2m50s
7b591b2466
Merge branch 'develop' into florian/68-squelette-terraform
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 20s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 4s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 2m52s
c10c11d20b
Member

Relecture des corrections (commits bb73e4a + 7d1d29e)

Tous les points de ma relecture précédente sont traités.

Point État
1. Lock mono-plateforme (bloquant) 4 empreintes h1: (linux_amd64, darwin_arm64, darwin_amd64, windows_amd64)
2a. subscription_id sans défaut default dans variables.tf, terraform.tfvars.example réduit à resource_group_name
2b. tags["user"] indexé sans filet postcondition { condition = can(self.tags["user"]) }, message qui nomme le groupe + commande az group update. Choix postcondition vs precondition correct (self-référence). Se déclenche avant local.etiquettes_communes → message lisible dans tous les cas.
3a. Regex GUID ^[0-9a-f-]{36}$ alignée sur bootstrap.sh
3b. output "region" = intention scindé en region_du_groupe (lu sur Azure) et region_des_ressources (var.location). Aucun doc/script ne référençait l'ancien nom.
3c. Dérive heredoc #67/#68 bootstrap.sh ajoute tenant_id/subscription_id, backend.tf réindenté pour coïncider

En plus : 7d1d29e rétablit la section terraform du PULL_REQUEST_TEMPLATE.md retirée par mégarde en 5dd4be7 — diff propre, bon emplacement, c'est un critère d'acceptation du #68.

Reste un nit, non bloquant

Le corps de cette MR dit toujours « La négation sur *.tfvars.example passe désormais après *.tfvars, faute de quoi elle ne servait à rien ». Or *.tfvars ne matche pas terraform.tfvars.example : c'est un no-op dans les deux ordres. Le changement de .gitignore reste sain (il calque la convention *.env, + ajout tfplan/*.tfplan justifié), seule la motivation écrite est inexacte. À corriger dans la description, pas dans le code.

Petit détail

Le heredoc de bootstrap.sh garde une ligne blanche après subscription_id avant la prose — cosmétique.

Fusionnable une fois la description corrigée (ou le point assumé).

## Relecture des corrections (commits `bb73e4a` + `7d1d29e`) Tous les points de ma relecture précédente sont traités. | Point | État | |---|---| | 1. Lock mono-plateforme (bloquant) | ✅ 4 empreintes `h1:` (linux_amd64, darwin_arm64, darwin_amd64, windows_amd64) | | 2a. `subscription_id` sans défaut | ✅ `default` dans `variables.tf`, `terraform.tfvars.example` réduit à `resource_group_name` | | 2b. `tags["user"]` indexé sans filet | ✅ `postcondition { condition = can(self.tags["user"]) }`, message qui nomme le groupe + commande `az group update`. Choix `postcondition` vs `precondition` correct (self-référence). Se déclenche avant `local.etiquettes_communes` → message lisible dans tous les cas. | | 3a. Regex GUID `^[0-9a-f-]{36}$` | ✅ alignée sur `bootstrap.sh` | | 3b. `output "region"` = intention | ✅ scindé en `region_du_groupe` (lu sur Azure) et `region_des_ressources` (`var.location`). Aucun doc/script ne référençait l'ancien nom. | | 3c. Dérive heredoc #67/#68 | ✅ `bootstrap.sh` ajoute `tenant_id`/`subscription_id`, `backend.tf` réindenté pour coïncider | En plus : `7d1d29e` rétablit la section terraform du `PULL_REQUEST_TEMPLATE.md` retirée par mégarde en `5dd4be7` — diff propre, bon emplacement, c'est un critère d'acceptation du #68. ### Reste un nit, non bloquant Le corps de cette MR dit toujours « La négation sur `*.tfvars.example` passe désormais après `*.tfvars`, faute de quoi elle ne servait à rien ». Or `*.tfvars` ne matche pas `terraform.tfvars.example` : c'est un no-op dans les deux ordres. Le changement de `.gitignore` reste sain (il calque la convention `*.env`, + ajout `tfplan`/`*.tfplan` justifié), seule la motivation écrite est inexacte. À corriger dans la description, pas dans le code. ### Petit détail Le heredoc de `bootstrap.sh` garde une ligne blanche après `subscription_id` avant la prose — cosmétique. **Fusionnable une fois la description corrigée (ou le point assumé).**
Merge branch 'develop' into florian/68-squelette-terraform
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 20s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 5s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 3m14s
e36cac2881
Member

good pour moi je merge après passage pipeline

good pour moi je merge après passage pipeline
justine approved these changes 2026-09-04 13:35:14 +00:00
justine merged commit 2dad40428b into develop 2026-09-04 13:38:15 +00:00
justine deleted branch florian/68-squelette-terraform 2026-09-04 13:38:15 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
g2/enervision!150
No description provided.