refactor-module

Par hashicorp · agent-skills

Transformez des configurations Terraform monolithiques en modules réutilisables et maintenables, en suivant les principes de conception de modules de HashiCorp et les bonnes pratiques de la communauté.

npx skills add https://github.com/hashicorp/agent-skills --skill refactor-module

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

Resources

Revision History

Version Date Changes
1.0.0 2025-11-07 Définition initiale de la skill

Skills similaires