درس 16 — Le code Terraform, couche par couche

00. Les bases de Terraform (pour lire et écrire ce projet)

11 دقائق قراءة

هذا المحتوى غير مترجم بعد. تُعرض النسخة الأصلية.

Ce cours explique toutes les notions Terraform utilisées dans les fichiers du dossier terraform/. Lisez-le une fois, puis gardez-le ouvert à côté des fiches de chaque fichier (01-… à 12-…).


1. Terraform en une phrase

Vous décrivez dans des fichiers .tf l'infrastructure que vous voulez (« je veux une clé de chiffrement, un filtre Model Armor, une app Gemini Enterprise… »). Terraform compare cette description avec ce qui existe vraiment, puis crée, modifie ou supprime ce qu'il faut pour que la réalité corresponde.

On ne dit pas comment faire (« clique ici, puis là ») : on dit ce qu'on veut. C'est le principe déclaratif.


2. Le cycle de travail

terraform init       # 1. télécharge les "providers" (plugins) déclarés dans versions.tf
terraform fmt        # 2. met en forme les fichiers (indentation, alignement)
terraform validate   # 3. vérifie la syntaxe et les types, SANS contacter Google
terraform plan       # 4. SIMULATION : affiche ce qui serait créé (+), modifié (~), détruit (-)
terraform apply      # 5. applique (demande "yes")
terraform output     # 6. affiche les valeurs de sortie (outputs.tf)
terraform destroy    # supprime tout ce que ce dossier a créé

Comment lire un plan :

SymboleSignificationVigilance
+sera créé—
~sera modifié sur placelire ce qui change
-/+sera détruit puis recréé⚠️ perte possible (données, ID)
-sera détruit⚠️

3. L'état (« state »)

Terraform garde la mémoire de ce qu'il a créé dans un fichier terraform.tfstate. C'est grâce à lui qu'il sait que google_kms_crypto_key.ge correspond à telle clé réelle chez Google.

  • Ne jamais le modifier à la main, ne jamais le committer (il peut contenir des informations sensibles).
  • En équipe, on le stocke dans un bucket GCS (bloc backend "gcs" de versions.tf).

4. Un dossier = un « module »

Terraform lit tous les fichiers .tf d'un dossier et les fusionne. Les noms de fichiers (00_foundation.tf, 10_identity_access.tf…) servent uniquement aux humains à ranger le code. Les numéros n'imposent aucun ordre d'exécution : Terraform calcule l'ordre lui-même (section 9).


5. La syntaxe : des blocs

Tout est écrit en HCL (HashiCorp Configuration Language). La brique de base est le bloc :

TYPE "ÉTIQUETTE_1" "ÉTIQUETTE_2" {
  argument = valeur
  sous_bloc {
    argument = valeur
  }
}

Les 6 types de blocs utilisés dans ce projet :

BlocRôleExemple du projet
terraform { }Réglages de Terraform lui-même (versions, backend)versions.tf
provider "google" { }Configure le plugin qui parle à Google Cloudversions.tf
variable "nom" { }Une entrée (paramètre)variables.tf
locals { }Des valeurs calculées réutilisables00_foundation.tf
resource "type" "nom" { }Quelque chose que Terraform crée et gèrepartout
data "type" "nom" { }Quelque chose que Terraform lit seulementdata "google_project" "this"
output "nom" { }Une sortie affichée après applyoutputs.tf

Les valeurs

nom        = "texte"                 # chaîne (string)
nombre     = 30                      # nombre
actif      = true                    # booléen
liste      = ["a", "b", "c"]         # liste
dico       = { cle = "valeur" }      # map / objet
commentaire = 1  # ceci est un commentaire (aussi //  et /* … */)

6. Les blocs en détail

6.1 resource : ce que Terraform crée

resource "google_kms_key_ring" "ge" {
  name     = "gemini-enterprise"
  location = "europe"
}
  • google_kms_key_ring = le type (défini par le provider Google ; la doc de chaque type est sur registry.terraform.io → hashicorp/google → Resources).
  • ge = le nom local, choisi par vous, pour y faire référence ailleurs.
  • Adresse de la ressource : google_kms_key_ring.ge.

6.2 data : ce que Terraform lit

data "google_project" "this" {}          # lit les infos du projet courant
# utilisation : data.google_project.this.number

Une source de données ne crée rien. Elle va chercher une information qui existe déjà.

6.3 variable : les entrées

variable "session_ttl_days" {
  description = "Durée de conservation des conversations."
  type        = number
  default     = 30                 # sans default, la variable est OBLIGATOIRE
  validation {                     # (optionnel) refuse les valeurs invalides
    condition     = var.session_ttl_days > 0
    error_message = "Doit être positif."
  }
}
# utilisation : var.session_ttl_days

Les valeurs se donnent dans terraform.tfvars (session_ttl_days = 15) ou en ligne de commande (terraform plan -var session_ttl_days=15).

Types complexes (utilisés pour les connecteurs) :

type = map(object({
  data_source = string                          # champ obligatoire
  modes       = optional(list(string), ["FEDERATED"])   # champ optionnel AVEC valeur par défaut
}))

6.4 locals : les valeurs calculées

locals {
  kms_location = var.ge_location == "eu" ? "europe" : "us"
}
# utilisation : local.kms_location   (attention : "local." au singulier)

On s'en sert pour ne pas répéter un calcul et pour donner un nom parlant à une expression.

6.5 output : les sorties

output "cmek_key" {
  value = google_kms_crypto_key.ge[0].id
}
# lecture : terraform output cmek_key

7. Les références : relier les blocs entre eux

ÉcritureSignifie
var.project_idla variable project_id
local.ge_sala valeur locale ge_sa
google_kms_crypto_key.ge.idl'attribut id de la ressource
google_kms_crypto_key.ge[0].ididem, quand la ressource utilise count (élément n° 0)
google_model_armor_template.shield["ge-prompt-shield"].ididem, quand la ressource utilise for_each (élément de clé "ge-prompt-shield")
data.google_project.this.numberl'attribut number d'une source de données
"projects/${var.project_id}"interpolation : insérer une valeur dans un texte

Les attributs disponibles (id, name, number…) sont listés dans la doc du type, section Attributes Reference.


8. Créer plusieurs exemplaires : count et for_each

8.1 count : 0 ou N exemplaires (souvent pour optionnel)

resource "google_kms_key_ring" "ge" {
  count = var.enable_cmek ? 1 : 0     # 1 si CMEK activé, 0 sinon (= n'existe pas)
  ...
}
# référence : google_kms_key_ring.ge[0]

8.2 for_each : un exemplaire par élément d'une map ou d'un set

resource "google_model_armor_template" "shield" {
  for_each    = toset(["ge-prompt-shield", "ge-response-shield"])
  template_id = each.value          # each.key / each.value = l'élément en cours
}
# référence : google_model_armor_template.shield["ge-prompt-shield"]

Avec une map, each.key est la clé et each.value la valeur :

for_each = var.connectors                  # { gdrive = {...}, sharepoint = {...} }
collection_id = each.key                   # "gdrive", puis "sharepoint"
data_source   = each.value.data_source     # "google_drive", puis "sharepoint"

Pourquoi préférer for_each à count pour des listes : avec count, retirer le 1er élément décale tous les autres (Terraform croit qu'ils ont changé). Avec for_each, chaque élément a une clé stable.


9. L'ordre de création : automatique, ou forcé avec depends_on

Terraform construit un graphe de dépendances :

  • si la ressource B utilise un attribut de A (kms_key = google_kms_crypto_key.ge[0].id), Terraform crée A avant B ;
  • si la dépendance n'apparaît pas dans le code mais existe dans la réalité (ex. « la clé doit être enregistrée avant de créer l'app »), on la déclare :
depends_on = [google_discovery_engine_cmek_config.ge]

10. Les blocs imbriqués optionnels ou répétés : dynamic

Certains sous-blocs doivent être répétés (un par élément d'une liste) ou n'exister que sous condition :

rai_settings {
  dynamic "rai_filters" {                       # génère un bloc rai_filters…
    for_each = ["HATE_SPEECH", "HARASSMENT"]    # …par élément
    content {
      filter_type = rai_filters.value           # l'élément en cours s'appelle comme le bloc
    }
  }
}

Équivaut à écrire à la main :

rai_settings {
  rai_filters { filter_type = "HATE_SPEECH" }
  rai_filters { filter_type = "HARASSMENT" }
}

Bloc conditionnel : for_each = var.sdp_mode == "advanced" ? [1] : [] signifie « une fois si la condition est vraie, zéro fois sinon ».


11. lifecycle : changer le comportement de Terraform

lifecycle {
  prevent_destroy = true     # Terraform REFUSE toute action qui détruirait cette ressource
}

Utilisé sur les clés KMS : les détruire rendrait les données illisibles pour toujours.


12. Les expressions utilisées dans le projet

12.1 Condition (ternaire)

condition ? valeur_si_vrai : valeur_si_faux
var.enable_cmek ? google_kms_crypto_key.ge[0].id : null     # null = "ne pas renseigner l'argument"

12.2 Boucles for (transformer une liste ou une map)

[for g in var.user_groups : "user|${g}"]                    # liste → liste
{ for g in var.user_groups : "user|${g}" => { role = "...", member = g } }   # liste → map
[for e in c.entities : basename(e.data_store) if e.data_store != ""]        # avec filtre "if"

12.3 Texte sur plusieurs lignes (heredoc)

command = <<-EOT
  ligne 1
  ligne 2
EOT

12.4 Les fonctions utilisées

FonctionCe qu'elle faitExemple
toset(liste)liste → ensemble (sans doublons, utilisable par for_each)toset(["a","b"])
concat(l1, l2)colle des listesconcat(["a"], ["b"]) → ["a","b"]
merge(m1, m2)fusionne des mapsmerge({a=1}, {b=2}) → {a=1, b=2}
flatten(liste_de_listes)aplatit[["a"],["b"]] → ["a","b"]
keys(map) / values(map)clés / valeurs d'une map
contains(liste, x)x est-il dans la liste ?contains(["A","B"], "A") → true
jsonencode(valeur)transforme en texte JSONjsonencode({a=1}) → "{\"a\":1}"
join(sep, liste)colle avec un séparateurjoin(",", ["a","b"]) → "a,b"
replace(texte, a, b)remplacereplace("a-b", "-", "_") → "a_b"
basename(chemin)dernière partie d'un cheminbasename("x/y/z") → "z"
setproduct(l1, l2)toutes les combinaisonssetproduct(["k1","k2"],["u1"]) → [["k1","u1"],["k2","u1"]]
try(expr, défaut)expr, ou défaut si erreurtry(e.data_store != null, false)

Pour tester une expression sans rien déployer : terraform console, puis tapez l'expression.


13. Les « providers » et la doc

Un provider est un plugin qui sait parler à une API. Ici : hashicorp/google (Google Cloud).

Pour savoir quels arguments une ressource accepte, ouvrez sa page de doc : https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/<type sans "google_"> Exemple : google_kms_crypto_key → .../docs/resources/kms_crypto_key. Chaque page a :

  • Example Usage : des exemples à copier ;
  • Argument Reference : ce que vous pouvez écrire (Required / Optional) ;
  • Attributes Reference : ce que vous pouvez lire après création (id, name…) ;
  • Import : comment reprendre une ressource créée à la main.

14. Quand Terraform ne sait pas faire : terraform_data + local-exec

Certains réglages Google n'existent pas encore dans le provider (ex. durée de conservation des conversations). On exécute alors une commande depuis Terraform :

resource "terraform_data" "engine_settings" {
  triggers_replace = [jsonencode(local.engine_settings)]   # relancer quand ces valeurs changent
  provisioner "local-exec" {
    command = "curl -X PATCH ..."                          # exécuté sur VOTRE machine
  }
}
  • terraform_data est une ressource « vide » qui sert de support.
  • triggers_replace : quand cette valeur change, la ressource est recréée, donc la commande est relancée.
  • local-exec exécute la commande sur la machine qui lance terraform apply (il faut donc gcloud et curl).
  • À utiliser en dernier recours : Terraform ne sait pas « lire » l'effet de la commande. Si quelqu'un modifie le réglage à la main, Terraform ne le verra pas.

15. Méthode pour écrire votre propre fichier

  1. Identifier la ressource dans la doc du provider (rechercher le service : « kms », « model armor »…).
  2. Copier l'Example Usage, puis ne garder que les arguments utiles.
  3. Remplacer les valeurs en dur par des variables (var.…) ou des références (google_….id).
  4. Ajouter les droits (google_*_iam_member) dont le service a besoin.
  5. Déclarer les dépendances invisibles (depends_on).
  6. terraform fmt → terraform validate → terraform plan → lire → apply.

16. Fiches de ce dossier

FicheFichier expliqué
01-versions.mdversions.tf : versions et configuration du provider
02-variables.mdvariables.tf : toutes les entrées
03-foundation.md00_foundation.tf : APIs, service agents, valeurs communes
04-identity-access.md10_identity_access.tf : identité et rôles
05-data-protection.md20_data_protection.tf : CMEK et secrets
06-ai-guardrails.md30_ai_guardrails.tf : Model Armor et DLP
07-connectors.md40_connectors.tf : connecteurs génériques
08-app.md50_app.tf : app, assistant, réglages API
09-advanced.md60_advanced.tf : Agent Gateway, VPC-SC
10-detection.md70_detection.tf : audit et alertes
11-outputs.mdoutputs.tf : les sorties
12-tfvars.mdterraform.tfvars.example : la configuration
13-logging.md80_logging.tf : journaux d'usage et de sécurité, buckets, alerte
التالي