Skill : Refactor 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 design de modules de HashiCorp et les bonnes 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
Prerequisites
- Configuration Terraform existante à refactoriser
- Compréhension des dépendances entre ressources
- Accès pour inspecter l'état actuel via
terraform state list/terraform show -json(pour la planification de migration) - Connaissance des patterns du module registry
Input Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
source_directory |
string | Yes | Chemin vers la configuration Terraform existante |
module_name |
string | Yes | Nom du nouveau module |
abstraction_level |
string | No | "simple", "intermediate", "advanced" (défaut : intermediate) |
preserve_state |
boolean | Yes | Conserver la compatibilité d'état |
target_registry |
string | No | Target module registry (local, private, public) |
Execution Steps
1. Analysis Phase
**Identifier les Candidats à la Refactorisation**
- Regrouper les ressources par fonction logique
- Identifier les motifs répétés
- Cartographier les dépendances entre ressources
- Détecter le couplage de configuration
- Analyser les patterns d'utilisation de variables
**Évaluation de Complexité**
- Compter les relations entre ressources
- Mesurer la profondeur de propagation des variables
- Identifier les références cross-resource
- Évaluer la complexité de migration d'état
2. Module Design
Interface Design
# 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 des subnets privés"
value = { for k, v in aws_subnet.private : k => v.id }
}
Encapsulation Strategy
**Ce qu'il faut Inclure dans le Module :**
- Ressources étroitement couplées (VPC + subnets)
- Ressources avec un cycle de vie partagé
- Configuration avec des limites claires
**Ce qu'il faut Garder Séparé :**
- Préoccupations transversales (monitoring, tagging)
- Ressources avec des cycles de vie différents
- Configurations spécifiques au provider
3. Code Transformation
Before: Monolithic Configuration
# 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 subnets et de routing répétitives
After: Modular Structure
# 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 pour le 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 = "Mapping des zones de disponibilité aux IDs des 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. State Migration
Inspecting Current State
Avant d'écrire des blocs moved ou des commandes state mv, inspectez l'état actuel pour cartographier
les adresses de ressources existantes. Préférez les commandes documentées, stables et plus efficaces en tokens
plutôt que de lire le fichier d'état brut :
# Énumérer les adresses de ressources actuelles (les entrées pour `moved` / `state mv`)
terraform state list
# Inspecter les valeurs d'attribut résolues quand vous en avez besoin
terraform show -json | jq '.values.root_module'
terraform show -json nécessite que les providers soient installés (terraform init), car il
rend les valeurs par rapport aux schemas du provider. Revenez à l'état brut
(terraform state pull / terraform.tfstate) seulement quand les providers ne sont pas disponibles et
que init ne peut pas s'exécuter, que vous n'avez besoin que d'infos grossières (adresses, outputs, serial/lineage), ou
que vous devez éviter d'exécuter Terraform. Évitez d'analyser le format d'état brut version-4 comme une interface stable. Note : l'état contient des valeurs sensibles en clair dans tous les formats — ne jamais afficher le contenu de l'état dans les logs ou la sortie.
Generate Migration Plan
# migration.tf
# Utiliser les blocs moved pour la refactorisation 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]
}
Manual State Migration (Pre-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. Module Documentation
# Module VPC
## Overview
Crée un VPC avec des subnets publics et privés configurables dans plusieurs zones de disponibilité.
## Features
- Déploiement de subnets multi-AZ
- Configuration optionnelle de passerelle NAT
- Intégration VPC Flow Logs
- Allocation CIDR personnalisable
## Usage
\`\`\`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"
}
}
\`\`\`
## Requirements
| Name | Version |
|------|---------|
| terraform | >= 1.5.0 |
| aws | ~> 5.0 |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|----------|
| name | Préfixe de nom pour les ressources | `string` | n/a | yes |
| cidr_block | Bloc CIDR du VPC | `string` | n/a | yes |
| availability_zones | Liste des AZs | `list(string)` | n/a | yes |
## Outputs
| Name | Description |
|------|-------------|
| vpc_id | Identifiant du VPC |
| public_subnet_ids | Mapping des IDs des subnets publics |
| private_subnet_ids | Mapping des IDs des subnets privés |
## Examples
Voir le répertoire [examples/](./examples/) pour les exemples d'utilisation complets.
6. Testing
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 à l'échelle du test (disponible depuis Terraform 1.6.0).
Run Block : Définit un seul scénario de test 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 être évaluées à true pour que le test réussisse. 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).
File Structure
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)
Refactoring Patterns
Pattern 1: Resource Grouping
Extraire les ressources liées en modules cohésifs :
- Networking (VPC, Subnets, Route Tables)
- Compute (ASG, Launch Templates, Load Balancers)
- Data (RDS, ElastiCache, S3)
Pattern 2: Configuration Layering
# Module de base avec valeurs par défaut
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
}
Common Pitfalls
1. Over-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. Tight Coupling
# ❌ Ne pas coupler les modules via 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. State Migration Errors
Toujours tester la migration en non-production d'abord :
# Créer un plan pour vérifier qu'il n'y a pas de changements après la migration
terraform plan -out=migration.tfplan
# Vérifier attentivement
terraform show migration.tfplan
# Appliquer seulement si le plan ne montre aucun changement
terraform apply migration.tfplan
Version Control Strategy
# Utiliser le semantic versioning pour les modules
module "vpc" {
source = "git::https://github.com/org/terraform-modules.git//vpc?ref=v1.2.0"
version = "~> 1.2"
}
# Épingler les versions spécifiques en production
# Utiliser les plages de version en développement
Success Criteria
- [ ] 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 aux consommateurs
- [ ] La documentation inclut des exemples d'utilisation
- [ ] Les tests vérifient le comportement du module
- [ ] La migration d'état est complétée sans recréation de ressources
- [ ] Aucune différence de plan après refactorisation
Related Skills
- Terraform code generation - Guide de style pour le nouveau Module Terraform
- Azure Verified Modules - Spécifications de modules recommandées pour Azure
Resources
Revision History
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2025-11-07 | Définition initiale de la skill |