# 📗 Terragrunt

## Référentiel des **blocs de configuration** Terragrunt

### Vue d'ensemble

| Bloc | Type | Rôle |
|---|---|---|
| `terraform` | Block | Configurer l'exécution Terraform/OpenTofu |
| `remote_state` | Block | Configurer le backend d'état |
| `include` | Block | Hériter d'une configuration parente |
| `locals` | Block | Définir des variables réutilisables |
| `dependency` | Block | Accéder aux outputs d'un autre module |
| `dependencies` | Block | Orchestrer l'ordre d'exécution |
| `generate` | Block | Générer des fichiers HCL/JSON avant l'exécution |
| `feature` | Block | Flags de fonctionnalité |
| `exclude` | Block | Exclure conditionnellement un module |
| `errors` | Block | Gérer les erreurs (retry, ignore) |
| `engine` | Block | Configurer un moteur d'exécution alternatif |
| `unit` / `stack` | Block | Déclarer des unités/stacks (fichier `terragrunt.stack.hcl`) |
| `inputs` | **Attribute** | Passer des variables à Terraform |

---

### `terraform`

Contrôle comment Terragrunt exécute Terraform/OpenTofu.

```
terraform {
  source = "github.com/org/modules.git//vpc?ref=v1.0.0"

  extra_arguments "common" {
    commands  = get_terraform_commands_that_need_vars()
    arguments = ["-var-file=common.auto.tfvars"]
  }

  before_hook "show_version" {
    commands = ["init", "plan", "apply"]
    execute  = ["terraform", "version"]
  }

  after_hook "show_outputs" {
    commands = ["apply"]
    execute  = ["terraform", "output", "-json"]
  }

  error_hook "handle_failure" {
    commands   = ["apply"]
    on_errors  = [".*Error: timeout.*"]
    execute    = ["echo", "Apply failed due to timeout"]
  }
}
```

| Attribut | Type | Description |
|---|---|---|
| `source` | string (req) | URL ou chemin local du module. Supporte `git::`, `s3::`, `gcs::`, `http::`, `tfr://` |
| `copy_terraform_lock_file` | bool | Copier `.terraform.lock.hcl` du cache (défaut: `true`) |
| `extra_arguments` | Block (0+) | Arguments CLI supplémentaires conditionnels |
| `before_hook` | Block (0+) | Hook exécuté **avant** la commande |
| `after_hook` | Block (0+) | Hook exécuté **après** la commande |
| `error_hook` | Block (0+) | Hook exécuté en cas d'erreur (matching `on_errors`) |
| `required_var_files` | []string | Fichiers `.tfvars` requis (ignorés s'ils n'existent pas) |

**Hook sub-block attributes :**

| Attribut | Type | Description |
|---|---|---|
| `name` (label) | string | Identifiant du hook |
| `commands` | []string | Commandes qui déclenchent le hook |
| `execute` | []string | Commande et arguments à exécuter |
| `if` | expression | Condition pour exécuter le hook |
| `run_on_error` | bool | Exécuter même si une erreur précédente |
| `suppress_stdout` | bool | Supprimer la sortie standard |
| `working_dir` | string | Répertoire de travail |
| `on_errors` | []string | (error_hook) Regex matchés contre le message d'erreur |

---

### `remote_state`

Configure le backend d'état et génère automatiquement le fichier `backend.tf`.

```
remote_state {
  backend = "s3"

  generate = {
    path      = "backend.tf"
    if_exists = "overwrite_terragrunt"
  }

  config = {
    bucket         = "my-state-bucket"
    key            = "${path_relative_to_include()}/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "my-lock-table"
  }
}
```

| Attribut | Type | Description |
|---|---|---|
| `backend` | string (req) | Nom du backend (`s3`, `gcs`, `local`, etc.) |
| `config` | map (req) | Paramètres du backend passés à Terraform |
| `generate` | map | Générer un fichier `.tf` au lieu de passer `-backend-config` |
| `disable` | bool | Désactiver le bloc |

**`generate` sub-attributes :**

| Attribut | Valeurs |
|---|---|
| `path` | Chemin du fichier à générer (relatif au working dir) |
| `if_exists` | `overwrite`, `overwrite_terragrunt`, `skip`, `error` |

---

### `include`

Hérite et fusionne une configuration parente.

```
include "root" {
  path = find_in_parent_folders("root.hcl")
  expose = true
  merge_strategy = "deep"
}
```

| Attribut | Type | Description |
|---|---|---|
| `name` (label) | string (req) | Identifiant de l'include |
| `path` | string (req) | Chemin vers le fichier parent |
| `expose` | bool | Rendre accessible via `include.<name>` (défaut: `false`) |
| `merge_strategy` | string | `no_merge`, `shallow` (défaut), `deep`, `deep_map_only` |

**Stratégies de merge :**

| Stratégie | Comportement |
|---|---|
| `no_merge` | Pas de fusion — accès uniquement via `include.<name>` |
| `shallow` | Fusion superficielle (les clés du child remplacent celles du parent) |
| `deep` | Fusion profonde (les maps sont fusionnées récursivement) |
| `deep_map_only` | Fusion profonde uniquement pour les maps, pas les lists |

**Accès aux valeurs exposées :**
```
inputs = {
  region = include.root.locals.region
  bucket = include.root.remote_state.config.bucket
}
```

---

### `locals`

Définit des variables réutilisables dans la configuration.

```
locals {
  env            = "dev"
  region         = "eu-west-1"
  project        = "myapp"
  state_key      = "${local.project}/${local.env}/${path_relative_to_include()}"
  parent_config  = read_terragrunt_config(find_in_parent_folders("env.hcl"))
}
```

Un seul bloc `locals` par fichier. Toutes les variables sont préfixées par `local.`.

---

### `dependency`

Accède aux **outputs** d'un autre module Terragrunt.

```
dependency "vpc" {
  config_path = "../vpc"

  mock_outputs_allowed_terraform_commands = ["validate", "plan"]
  mock_outputs = {
    vpc_id     = "vpc-00000000"
    subnet_ids = ["subnet-aaa", "subnet-bbb"]
  }

  mock_outputs_merge_strategy_with_state = "shallow"
}

inputs = {
  vpc_id     = dependency.vpc.outputs.vpc_id
  subnet_ids = dependency.vpc.outputs.subnet_ids
}
```

| Attribut | Type | Description |
|---|---|---|
| `name` (label) | string (req) | Identifiant (référence via `dependency.<name>.outputs`) |
| `config_path` | string (req) | Chemin relatif vers le `terragrunt.hcl` du module dépendant |
| `enabled` | bool | Activer/désactiver la dépendance (défaut: `true`) |
| `skip_outputs` | bool | Ne pas récupérer les outputs (défaut: `false`) |
| `mock_outputs` | any | Outputs simulés si le module n'est pas encore appliqué |
| `mock_outputs_allowed_terraform_commands` | []string | Commandes où les mock outputs sont autorisés |
| `mock_outputs_merge_strategy_with_state` | string | `no_merge`, `shallow`, `deep`, `deep_map_only` |

---

### `dependencies`

Orchestre l'**ordre d'exécution** (sans accéder aux outputs).

```
dependencies {
  paths = ["../vpc", "../security-group"]
}
```

| Attribut | Type | Description |
|---|---|---|
| `paths` | []string (req) | Chemins relatifs vers les modules à exécuter d'abord |

**Différence clé :** `dependencies` ne fait que définir l'ordre ; `dependency` expose en plus les outputs.

---

### `generate`

Génère des fichiers HCL/JSON avant l'exécution de Terraform.

```
generate "provider" {
  path      = "provider.tf"
  if_exists = "overwrite_terragrunt"
  contents  = <<EOF
provider "aws" {
  region = "${local.region}"
}
EOF
}

generate "tags" {
  path      = "tags.auto.tfvars"
  if_exists = "overwrite"
  contents  = <<EOF
tags = {
  environment = "${local.env}"
  project     = "${local.project}"
}
EOF
}
```

| Attribut | Type | Description |
|---|---|---|
| `name` (label) | string (req) | Identifiant unique |
| `path` | string (req) | Chemin du fichier à générer |
| `if_exists` | string (req) | `overwrite`, `overwrite_terragrunt`, `skip`, `error` |
| `contents` | string (req) | Contenu du fichier |
| `if_disabled` | string | Action si `disable = true` : `skip`, `remove`, `error` |
| `comment_prefix` | string | Préfixe des commentaires de signature (défaut: `#`) |
| `disable_signature` | bool | Omettre l'en-tête "generated by Terragrunt" |
| `disable` | bool | Désactiver le bloc |

---

### `errors`

Gestion des erreurs avec retry et ignore.

```
errors {
  retry "transient" {
    retryable_errors   = [".*Error: transient network issue.*"]
    max_attempts       = 3
    sleep_interval_sec = 5
  }

  ignore "safe_warnings" {
    ignorable_errors = [".*Warning: deprecated.*"]
    message          = "Ignoring deprecation warnings"
  }
}
```

| Sous-bloc | Attributs |
|---|---|
| `retry "<name>"` | `retryable_errors` (regex), `max_attempts`, `sleep_interval_sec` |
| `ignore "<name>"` | `ignorable_errors` (regex, `!` pour négation), `message` |

---

### `feature`

Flags de fonctionnalité Terragrunt.

```
feature "terraform_lock" {
  enabled = true
}
```

---

### `exclude`

Exclut conditionnellement un module de l'exécution.

```
exclude {
  path = "live/us-east-1/dev/legacy-module"
}
```

---

### `engine`

Configure un moteur d'exécution alternatif (ex: OpenTofu au lieu de Terraform).

```
engine "opentofu" {
  source = "github.com/opentofu/opentofu/releases/download/v1.6.0/tofu_1.6.0_linux_amd64.zip"
}
```

---

### `inputs` (attribut, pas un bloc)

```
inputs = {
  name       = "my-vpc"
  cidr       = "10.0.0.0/16"
  azs        = ["us-east-1a", "us-east-1b"]
  vpc_id     = dependency.vpc.outputs.vpc_id
  region     = include.root.locals.region
}
```

Passé directement comme variables à Terraform via `-var` ou `.tfvars`.

---

### `unit` et `stack` (fichier `terragrunt.stack.hcl`)

Nouveauté pour les **Stacks** Terragrunt — définies dans un fichier `terragrunt.stack.hcl` distinct de `terragrunt.hcl`.

```
# terragrunt.stack.hcl
locals {
  region = "eu-west-1"
}

unit "vpc" {
  source = "github.com/org/modules.git//vpc?ref=v1.0.0"
  path   = "vpc"
  values = {
    cidr_block = "10.0.0.0/16"
  }
}

stack "subnets" {
  source = "github.com/org/modules.git//stacks/subnets?ref=v1.0.0"
  path   = "subnets"
  values = {
    availability_zone = "eu-west-1a"
  }
}
```

| Bloc | Cardinalité | Rôle |
|---|---|---|
| `locals` | 0 ou 1 | Variables locales du stack |
| `include` | 0+ | Inclure d'autres configurations de stack |
| `unit "<name>"` | 0+ | Déclarer une unité Terragrunt |
| `stack "<name>"` | 0+ | Déclarer un stack imbriqué |

---

### Ordre de parsing

L'ordre dans lequel Terragrunt évalue les blocs est important :

1. `include` (de toutes les configurations de l'arbre)
2. `locals` (de toutes les configurations de l'arbre)
3. `dependency` (de toutes les configurations, **sans** récupérer les outputs)
4. `terraform` (de toutes les configurations)
5. `dependencies` (de toutes les configurations)
6. `dependency` outputs (récupération réelle)
7. `inputs`, `generate`, `remote_state`, etc.

Cela signifie qu'on peut référencer `local.*` dans `inputs`, et `dependency.*.outputs` dans `inputs`, mais **pas** l'inverse.

---

### Exemple complet combinant plusieurs blocs

```
# live/us-east-1/dev/app/terragrunt.hcl

include "root" {
  path = find_in_parent_folders("root.hcl")
}

locals {
  env    = "dev"
  region = "us-east-1"
}

dependency "vpc" {
  config_path = "../vpc"
  mock_outputs = {
    vpc_id     = "vpc-mock"
    subnet_ids = ["subnet-mock"]
  }
}

dependencies {
  paths = ["../vpc", "../security-group"]
}

terraform {
  source = "${get_path_to_repo_root()}//modules/app"
}

inputs = {
  name         = "my-app-${local.env}"
  vpc_id       = dependency.vpc.outputs.vpc_id
  subnet_ids   = dependency.vpc.outputs.subnet_ids
  aws_region   = local.region
}

generate "provider" {
  path      = "provider.tf"
  if_exists = "overwrite_terragrunt"
  contents  = <<EOF
provider "aws" {
  region = "${local.region}"
}
EOF
}
```

## Tour d'horizon des fonctions intégrées de Terragrunt

### Chemins et hiérarchie de configuration

| Fonction | Description | Exemple |
|---|---|---|
| `find_in_parent_folders([name], [fallback])` | Monte l'arborescence et retourne le chemin absolu du premier fichier correspondant. Sans argument, cherche `terragrunt.hcl`. | `include "root" { path = find_in_parent_folders() }` |
| `path_relative_to_include([label])` | Chemin relatif **depuis** le fichier parent inclus **vers** le fichier actuel. | `key = "prefix/${path_relative_to_include()}"` |
| `path_relative_from_include([label])` | Chemin relatif **depuis** le fichier actuel **vers** le fichier parent inclus. | |
| `get_terragrunt_dir()` | Chemin absolu du répertoire contenant le `terragrunt.hcl` actuel. | `path = "${get_terragrunt_dir()}/output"` |
| `get_parent_terragrunt_dir([label])` | Chemin du répertoire du fichier parent inclus. | |
| `get_original_terragrunt_dir()` | Chemin du répertoire du `terragrunt.hcl` enfant d'origine (utile dans `read_terragrunt_config`). | |
| `get_working_dir()` | Chemin du répertoire de travail courant. | |

**Exemple typique d'héritage :**

```
# live/us-east-1/dev/mysql/terragrunt.hcl
include "root" {
  path = find_in_parent_folders()
}

locals {
  env_config = read_terragrunt_config(find_in_parent_folders("env.hcl"))
}

remote_state {
  backend = "s3"
  config = {
    bucket = "mycompany-state"
    key    = "us-east-1/${local.env_config.locals.environment}/mysql/terraform.tfstate"
    region = "us-east-1"
  }
}
```

### Lecture de configuration

| Fonction | Description |
|---|---|
| `read_terragrunt_config(path)` | Parse un fichier HCL Terragrunt et retourne son contenu (`.locals`, `.inputs`, `.dependency`, etc.) **sans fusionner**. |
| `read_tfvars_file(path)` | Lit un fichier `.tfvars` ou `.tfvars.json` et retourne une map. |
| `sops_decrypt_file(path)` | Déchiffre un fichier chiffré avec [SOPS](https://github.com/getsops/sops) et retourne le contenu. |

**Différence clé `include` vs `read_terragrunt_config` :**
- `include` **fusionne** les blocs (`remote_state`, `generate`, `terraform`, etc.) du fichier parent dans la configuration courante.
- `read_terragrunt_config` se contente de **lire** les données — rien n'est fusionné. On accède aux valeurs via `.locals`, `.inputs`, `.dependency`, etc.

```
locals {
  root_config   = read_terragrunt_config(find_in_parent_folders("root.hcl"))
  region_config = read_terragrunt_config(find_in_parent_folders("region.hcl"))

  project     = local.root_config.locals.project
  aws_region  = local.region_config.locals.aws_region
}
```

### Variables d'environnement et plateforme

| Fonction | Description |
|---|---|
| `get_env(name)` | Retourne la valeur d'une variable d'environnement. **Erreur** si absente. |
| `get_env(name, default)` | Retourne la valeur ou `default` si la variable n'est pas définie. |
| `get_platform()` | Retourne le système d'exploitation courant (`linux`, `darwin`, `windows`). |

### Git

| Fonction | Description |
|---|---|
| `get_repo_root()` | Chemin absolu de la racine du dépôt Git. |
| `get_path_to_repo_root()` | Chemin relatif de la racine du dépôt vers le répertoire courant. |
| `get_path_from_repo_root()` | Chemin relatif de la racine du dépôt vers le répertoire courant. |

```
terraform {
  source = "${get_path_to_repo_root()}//modules/vpc"
}
```

### AWS

| Fonction | Description |
|---|---|
| `get_aws_account_id()` | ID du compte AWS associé aux credentials courants. |
| `get_aws_account_alias()` | Alias du compte AWS. |
| `get_aws_caller_identity_arn()` | ARN de l'identité appelante (utilisateur ou rôle). |
| `get_aws_caller_identity_user_id()` | ID de l'utilisateur appelant. |

```
remote_state {
  backend = "s3"
  config = {
    bucket = "mycompany-${get_aws_account_id()}-state"
  }
}
```

### Commandes et exécution

| Fonction | Description |
|---|---|
| `run_cmd(args...)` | Exécute une commande shell et retourne sa sortie standard. Flags disponibles : `--terragrunt-quiet`, `--terragrunt-no-cache`, `--terragrunt-global-cache`. |
| `get_terraform_command()` | Retourne la commande Terraform en cours (`plan`, `apply`, `destroy`, …). |
| `get_terraform_cli_args()` | Retourne la liste des arguments CLI passés à Terraform. |

```
locals {
  git_commit = run_cmd("git", "rev-parse", "HEAD")
}
```

### Commandes Terraform (pour `extra_arguments`)

| Fonction | Description |
|---|---|
| `get_terraform_commands_that_need_vars()` | Liste des commandes qui nécessitent les variables. |
| `get_terraform_commands_that_need_input()` | Commandes qui nécessitent l'input interactif. |
| `get_terraform_commands_that_need_locking()` | Commandes qui nécessitent le verrouillage d'état. |
| `get_terraform_commands_that_need_parallelism()` | Commandes qui nécessitent le paramètre de parallélisme. |

```
terraform {
  extra_arguments "lock" {
    commands  = get_terraform_commands_that_need_locking()
    arguments = ["-lock-timeout=20m"]
  }
}
```

### Utilitaires et manipulation de données

| Fonction | Description |
|---|---|
| `deep_merge(map1, map2, ...)` | Fusionne profondément plusieurs maps/objets. |
| `startswith(str, prefix)` | `true` si la chaîne commence par le préfixe. |
| `endswith(str, suffix)` | `true` si la chaîne se termine par le suffixe. |
| `strcontains(str, substr)` | `true` si la chaîne contient le sous-élément. |
| `timecmp(t1, t2)` | Compare deux timestamps (retourne `-1`, `0`, `1`). |
| `constraint_check(version, constraint)` | Vérifie si une version semver satisfait une contrainte. |
| `get_terragrunt_source_cli_flag()` | Retourne la valeur du flag `--terragrunt-source` passé en CLI. |
| `get_default_retryable_errors()` | Retourne la liste des erreurs par défaut considérées comme réessayables. |

### Suivi de fichiers (pour `--filter-affected`)

| Fonction | Description |
|---|---|
| `mark_as_read(path)` | Marque explicitement un fichier comme lu (affecte le calcul de `--filter-affected`). |
| `mark_glob_as_read(glob)` | Marque tous les fichiers correspondant à un motif glob. |

```
# S'assurer que les fichiers .tf sont considérés comme "lus"
mark_as_read("${get_terragrunt_dir()}/*.tf")
mark_glob_as_read("${get_terragrunt_dir()}/**")
```

### Bonnes pratiques

- **`find_in_parent_folders` + `read_terragrunt_config`** est le duo le plus utilisé pour construire une hiérarchie de configuration DRY (racine → région → environnement → module).
- **`path_relative_to_include`** est indispensable pour générer des clés d'état S3/EOS uniques par module sans hardcoder les chemins.
- **`run_cmd`** doit être utilisé avec parcimonie : chaque appel déclenche un processus shell, ce qui ralentit l'évaluation. Privilégier `get_env` ou des fichiers de configuration statiques quand c'est possible.
- Toutes les fonctions sont utilisables dans les blocs `locals`, `inputs`, `remote_state`, `generate`, `terraform`, `include` et `dependency`.
