Skill: Refactorer Module
Overview
Cette skill guide les agents IA dans la transformation de configurations Terraform monolithiques en modules réutilisables et maintenables en suivant les principes de conception de modules de HashiCorp et les meilleures pratiques communautaires.
Capability Statement
L'agent analysera le code Terraform existant et le refactorisera systématiquement en modules bien structurés avec :
- Des contrats d'interface clairs (variables et outputs)
- Une encapsulation et une abstraction appropriées
- Versioning et documentation
- Des frameworks de test
- Un chemin de migration pour l'état existant
Prérequis
- Configuration Terraform existante à refactoriser
- Compréhension des dépendances entre ressources
- Accès au fichier d'état actuel (pour la planification de migration)
- Connaissance des patterns de module registry
Paramètres d'entrée
| Paramètre | Type | Requis | Description |
|---|---|---|---|
source_directory |
string | Oui | Chemin de la configuration Terraform existante |
module_name |
string | Oui | Nom du nouveau module |
abstraction_level |
string | Non | "simple", "intermediate", "advanced" (par défaut : intermediate) |
preserve_state |
boolean | Oui | Maintenir la compatibilité d'état |
target_registry |
string | Non | Module registry cible (local, private, public) |
Étapes d'exécution
1. Phase d'analyse
**Identifier les candidats au refactoring**
- Grouper les ressources par fonction logique
- Identifier les patterns répétés
- Mapper les dépendances entre ressources
- Détecter les couplages de configuration
- Analyser les patterns d'utilisation de variables
**Évaluation de la complexité**
- Compter les relations entre ressources
- Mesurer la profondeur de propagation des variables
- Identifier les références entre ressources
- Évaluer la complexité de migration d'état
2. Conception de module
Conception d'interface
# Définir un contrat d'entrée clair
variable "network_config" {
description = "Paramètres de configuration réseau"
type = object({
cidr_block = string
availability_zones = list(string)
enable_nat = bool
})
validation {
condition = can(cidrhost(var.network_config.cidr_block, 0))
error_message = "Le bloc CIDR doit être un CIDR IPv4 valide."
}
}
# Définir un contrat de sortie
output "vpc_id" {
description = "ID du VPC créé"
value = aws_vpc.main.id
}
output "private_subnet_ids" {
description = "Liste des IDs de subnets privés"
value = { for k, v in aws_subnet.private : k => v.id }
}
Stratégie d'encapsulation
**À inclure dans le module :**
- Ressources étroitement couplées (VPC + subnets)
- Ressources avec cycle de vie partagé
- Configuration avec frontières claires
**À garder séparé :**
- Préoccupations transversales (monitoring, tagging)
- Ressources avec cycles de vie différents
- Configurations spécifiques au provider
3. Transformation de code
Avant : Configuration monolithique
# main.tf (monolithique)
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "production-vpc"
Environment = "prod"
}
}
resource "aws_subnet" "public_1" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
tags = {
Name = "public-subnet-1"
Type = "public"
}
}
resource "aws_subnet" "public_2" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "us-east-1b"
tags = {
Name = "public-subnet-2"
Type = "public"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "production-igw"
}
}
# ... plus de ressources de subnet et routing répétitives
Après : Structure modulaire
# modules/vpc/main.tf
locals {
subnet_count = length(var.availability_zones)
}
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
tags = merge(
var.tags,
{
Name = var.name
}
)
}
resource "aws_subnet" "public" {
for_each = var.create_public_subnets ? toset(var.availability_zones) : []
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.cidr_block, 8, index(var.availability_zones, each.value))
availability_zone = each.value
map_public_ip_on_launch = true
tags = merge(
var.tags,
{
Name = "${var.name}-public-${each.value}"
Type = "public"
}
)
}
resource "aws_internet_gateway" "main" {
count = var.create_public_subnets ? 1 : 0
vpc_id = aws_vpc.main.id
tags = merge(
var.tags,
{
Name = "${var.name}-igw"
}
)
}
# modules/vpc/variables.tf
variable "name" {
description = "Préfixe de nom pour toutes les ressources"
type = string
}
variable "cidr_block" {
description = "Bloc CIDR du VPC"
type = string
validation {
condition = can(cidrhost(var.cidr_block, 0))
error_message = "Doit être un bloc CIDR IPv4 valide."
}
}
variable "availability_zones" {
description = "Liste des zones de disponibilité"
type = list(string)
}
variable "create_public_subnets" {
description = "Créer des subnets publics"
type = bool
default = true
}
variable "enable_dns_hostnames" {
description = "Activer les noms d'hôte DNS dans le VPC"
type = bool
default = true
}
variable "enable_dns_support" {
description = "Activer le support DNS dans le VPC"
type = bool
default = true
}
variable "tags" {
description = "Tags à appliquer à toutes les ressources"
type = map(string)
default = {}
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "ID du VPC"
value = aws_vpc.main.id
}
output "vpc_cidr_block" {
description = "Bloc CIDR du VPC"
value = aws_vpc.main.cidr_block
}
output "public_subnet_ids" {
description = "Correspondance des zones de disponibilité aux IDs de subnets publics"
value = { for k, v in aws_subnet.public : k => v.id }
}
output "internet_gateway_id" {
description = "ID de la passerelle Internet"
value = try(aws_internet_gateway.main[0].id, null)
}
# Configuration racine utilisant le module
module "vpc" {
source = "./modules/vpc"
name = "production"
cidr_block = "10.0.0.0/16"
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
tags = {
Environment = "production"
ManagedBy = "Terraform"
}
}
4. Migration d'état
Générer un plan de migration
# migration.tf
# Utiliser des blocs moved pour refactoring d'état (Terraform 1.1+)
moved {
from = aws_vpc.main
to = module.vpc.aws_vpc.main
}
moved {
from = aws_subnet.public_1
to = module.vpc.aws_subnet.public["us-east-1a"]
}
moved {
from = aws_subnet.public_2
to = module.vpc.aws_subnet.public["us-east-1b"]
}
moved {
from = aws_internet_gateway.main
to = module.vpc.aws_internet_gateway.main[0]
}
Migration d'état manuelle (pré-1.1)
# Générer les commandes de migration d'état
terraform state mv aws_vpc.main module.vpc.aws_vpc.main
terraform state mv aws_subnet.public_1 'module.vpc.aws_subnet.public["us-east-1a"]'
terraform state mv aws_subnet.public_2 'module.vpc.aws_subnet.public["us-east-1b"]'
terraform state mv aws_internet_gateway.main 'module.vpc.aws_internet_gateway.main[0]'
5. Documentation de module
# Module VPC
## Overview
Crée un VPC avec des subnets publics et privés configurables sur plusieurs zones de disponibilité.
## Fonctionnalités
- Déploiement de subnets multi-AZ
- Configuration optionnelle de NAT gateway
- Intégration VPC Flow Logs
- Allocation CIDR personnalisable
## Utilisation
\`\`\`hcl
module "vpc" {
source = "./modules/vpc"
name = "my-vpc"
cidr_block = "10.0.0.0/16"
availability_zones = ["us-east-1a", "us-east-1b"]
create_public_subnets = true
create_private_subnets = true
enable_nat_gateway = true
tags = {
Environment = "production"
}
}
\`\`\`
## Prérequis
| Nom | Version |
|-----|---------|
| terraform | >= 1.5.0 |
| aws | ~> 5.0 |
## Entrées
| Nom | Description | Type | Par défaut | Requis |
|-----|-------------|------|-----------|--------|
| name | Préfixe de nom pour les ressources | `string` | n/a | oui |
| cidr_block | Bloc CIDR du VPC | `string` | n/a | oui |
| availability_zones | Liste des AZ | `list(string)` | n/a | oui |
## Sorties
| Nom | Description |
|-----|-------------|
| vpc_id | Identifiant VPC |
| public_subnet_ids | Correspondance des IDs de subnets publics |
| private_subnet_ids | Correspondance des IDs de subnets privés |
## Exemples
Voir le répertoire [examples/](./examples/) pour des exemples d'utilisation complets.
6. Test
Utiliser la skill terraform-test
Test File: Un fichier .tftest.hcl ou .tftest.json contenant la configuration de test et les blocs run qui valident votre configuration Terraform.
Test Block: Bloc de configuration optionnel qui définit les paramètres au niveau du test (disponible depuis Terraform 1.6.0).
Run Block: Définit un scénario de test unique avec des variables optionnelles, des configurations de provider et des assertions. Chaque fichier de test nécessite au moins un bloc run.
Assert Block: Contient les conditions qui doivent s'évaluer à true pour que le test passe. Les assertions échouées causent l'échec du test.
Mock Provider: Simule le comportement du provider sans créer d'infrastructure réelle (disponible depuis Terraform 1.7.0).
Test Modes: Les tests s'exécutent en mode apply (par défaut, crée une infrastructure réelle) ou en mode plan (valide la logique sans créer de ressources).
Structure de fichier
Les fichiers de test Terraform utilisent l'extension .tftest.hcl ou .tftest.json et sont généralement organisés dans un répertoire tests/. Utilisez des conventions de nommage claires pour distinguer les tests unitaires (mode plan) des tests d'intégration (mode apply) :
my-module/
├── main.tf
├── variables.tf
├── outputs.tf
└── tests/
├── unit_test.tftest.hcl # Test unitaire (mode plan)
└── integration_test.tftest.hcl # Test d'intégration (mode apply - crée des ressources réelles)
Patterns de refactoring
Pattern 1 : Groupement de ressources
Extraire les ressources associées en modules cohésifs :
- Networking (VPC, Subnets, Route Tables)
- Compute (ASG, Launch Templates, Load Balancers)
- Data (RDS, ElastiCache, S3)
Pattern 2 : Stratification de configuration
# Module de base avec defaults
module "vpc_base" {
source = "./modules/vpc-base"
# Entrées minimales requises
}
# Wrapper spécifique à l'environnement
module "vpc_prod" {
source = "./modules/vpc-production"
# Hérite de la base, ajoute la config spécifique à prod
}
Pattern 3 : Composition
# Petits modules ciblés
module "vpc" {
source = "./modules/vpc"
}
module "security_groups" {
source = "./modules/security-groups"
vpc_id = module.vpc.vpc_id
}
module "application" {
source = "./modules/application"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
sg_ids = module.security_groups.app_sg_ids
}
Pièges courants
1. Sur-abstraction
# ❌ Ne pas créer de modules trop génériques
variable "resources" {
type = map(map(any)) # Trop flexible, difficile à valider
}
# ✅ Utiliser des interfaces spécifiques et typées
variable "database_config" {
type = object({
engine = string
instance_class = string
})
}
2. Couplage étroit
# ❌ Ne pas coupler les modules par des références directes
# module A
output "instance_id" { value = aws_instance.app.id }
# module B (dans la même config)
resource "aws_eip" "app" {
instance = module.a.instance_id # Couplage étroit
}
# ✅ Passer les dépendances via le module racine
module "compute" {
source = "./modules/compute"
}
resource "aws_eip" "app" {
instance = module.compute.instance_id
}
3. Erreurs de migration d'état
Toujours tester la migration en non-production d'abord :
# Créer un plan pour vérifier l'absence de changements après migration
terraform plan -out=migration.tfplan
# Examiner attentivement
terraform show migration.tfplan
# Appliquer seulement si le plan montre aucun changement
terraform apply migration.tfplan
Stratégie de contrôle de version
# Utiliser le versioning sémantique pour les modules
module "vpc" {
source = "git::https://github.com/org/terraform-modules.git//vpc?ref=v1.2.0"
version = "~> 1.2"
}
# Épingler à des versions spécifiques en production
# Utiliser des plages de version en développement
Critères de succès
- [ ] Le module a une responsabilité unique et bien définie
- [ ] Toutes les variables ont des descriptions et des types
- [ ] Les règles de validation empêchent les configurations invalides
- [ ] Les outputs fournissent suffisamment d'informations pour les consommateurs
- [ ] La documentation inclut des exemples d'utilisation
- [ ] Les tests vérifient le comportement du module
- [ ] La migration d'état s'est déroulée sans recréation de ressources
- [ ] Aucune différence de plan après refactoring
Skills associées
- Terraform code generation - Guide de style pour le nouveau Module Terraform
- Azure Verified Modules - Spécifications de module recommandées pour Azure
Ressources
Historique des révisions
| Version | Date | Changements |
|---|---|---|
| 1.0.0 | 2025-11-07 | Définition initiale de la skill |