Plugin-framework provider covering projects, environments, applications, Compose stacks, managed databases, domains, mounts, ports, redirects, basic auth, registries, SSH keys, certificates and backup destinations, over Dokploy's tRPC-over-REST API. The shim package exposes the provider to other Go modules, which is how pulumi-dokploy bridges it.
539 lines
13 KiB
Markdown
539 lines
13 KiB
Markdown
---
|
|
name: refactor-module
|
|
description: Transform monolithic Terraform configurations into reusable, maintainable modules following HashiCorp's module design principles and community best practices.
|
|
metadata:
|
|
copyright: Copyright IBM Corp. 2026
|
|
version: "0.0.1"
|
|
---
|
|
|
|
# Skill: Refactor Module
|
|
|
|
## Overview
|
|
This skill guides AI agents in transforming monolithic Terraform configurations into reusable, maintainable modules following HashiCorp's module design principles and community best practices.
|
|
|
|
## Capability Statement
|
|
The agent will analyze existing Terraform code and systematically refactor it into well-structured modules with:
|
|
- Clear interface contracts (variables and outputs)
|
|
- Proper encapsulation and abstraction
|
|
- Versioning and documentation
|
|
- Testing frameworks
|
|
- Migration path for existing state
|
|
|
|
## Prerequisites
|
|
- Existing Terraform configuration to refactor
|
|
- Understanding of resource dependencies
|
|
- Access to current state file (for migration planning)
|
|
- Knowledge of module registry patterns
|
|
|
|
## Input Parameters
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|-----------|------|----------|-------------|
|
|
| `source_directory` | string | Yes | Path to existing Terraform configuration |
|
|
| `module_name` | string | Yes | Name for the new module |
|
|
| `abstraction_level` | string | No | "simple", "intermediate", "advanced" (default: intermediate) |
|
|
| `preserve_state` | boolean | Yes | Whether to maintain state compatibility |
|
|
| `target_registry` | string | No | Target module registry (local, private, public) |
|
|
|
|
## Execution Steps
|
|
|
|
### 1. Analysis Phase
|
|
```markdown
|
|
**Identify Refactoring Candidates**
|
|
- Group resources by logical function
|
|
- Identify repeated patterns
|
|
- Map resource dependencies
|
|
- Detect configuration coupling
|
|
- Analyze variable usage patterns
|
|
|
|
**Complexity Assessment**
|
|
- Count resource relationships
|
|
- Measure variable propagation depth
|
|
- Identify cross-resource references
|
|
- Evaluate state migration complexity
|
|
```
|
|
|
|
### 2. Module Design
|
|
|
|
#### Interface Design
|
|
```hcl
|
|
# Define clear input contract
|
|
variable "network_config" {
|
|
description = "Network configuration parameters"
|
|
type = object({
|
|
cidr_block = string
|
|
availability_zones = list(string)
|
|
enable_nat = bool
|
|
})
|
|
|
|
validation {
|
|
condition = can(cidrhost(var.network_config.cidr_block, 0))
|
|
error_message = "CIDR block must be valid IPv4 CIDR."
|
|
}
|
|
}
|
|
|
|
# Define output contract
|
|
output "vpc_id" {
|
|
description = "ID of the created VPC"
|
|
value = aws_vpc.main.id
|
|
}
|
|
|
|
output "private_subnet_ids" {
|
|
description = "List of private subnet IDs"
|
|
value = { for k, v in aws_subnet.private : k => v.id }
|
|
}
|
|
```
|
|
|
|
#### Encapsulation Strategy
|
|
```markdown
|
|
**What to Include in Module:**
|
|
- Tightly coupled resources (VPC + subnets)
|
|
- Resources with shared lifecycle
|
|
- Configuration with clear boundaries
|
|
|
|
**What to Keep Separate:**
|
|
- Cross-cutting concerns (monitoring, tagging)
|
|
- Resources with different lifecycles
|
|
- Provider-specific configurations
|
|
```
|
|
|
|
### 3. Code Transformation
|
|
|
|
#### Before: Monolithic Configuration
|
|
```hcl
|
|
# main.tf (monolithic)
|
|
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"
|
|
}
|
|
}
|
|
|
|
# ... more repetitive subnet and routing resources
|
|
```
|
|
|
|
#### After: Modular Structure
|
|
```hcl
|
|
# 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 = "Name prefix for all resources"
|
|
type = string
|
|
}
|
|
|
|
variable "cidr_block" {
|
|
description = "CIDR block for the VPC"
|
|
type = string
|
|
|
|
validation {
|
|
condition = can(cidrhost(var.cidr_block, 0))
|
|
error_message = "Must be a valid IPv4 CIDR block."
|
|
}
|
|
}
|
|
|
|
variable "availability_zones" {
|
|
description = "List of availability zones"
|
|
type = list(string)
|
|
}
|
|
|
|
variable "create_public_subnets" {
|
|
description = "Whether to create public subnets"
|
|
type = bool
|
|
default = true
|
|
}
|
|
|
|
variable "enable_dns_hostnames" {
|
|
description = "Enable DNS hostnames in the VPC"
|
|
type = bool
|
|
default = true
|
|
}
|
|
|
|
variable "enable_dns_support" {
|
|
description = "Enable DNS support in the VPC"
|
|
type = bool
|
|
default = true
|
|
}
|
|
|
|
variable "tags" {
|
|
description = "Tags to apply to all resources"
|
|
type = map(string)
|
|
default = {}
|
|
}
|
|
|
|
# modules/vpc/outputs.tf
|
|
output "vpc_id" {
|
|
description = "ID of the VPC"
|
|
value = aws_vpc.main.id
|
|
}
|
|
|
|
output "vpc_cidr_block" {
|
|
description = "CIDR block of the VPC"
|
|
value = aws_vpc.main.cidr_block
|
|
}
|
|
|
|
output "public_subnet_ids" {
|
|
description = "Map of availability zones to public subnet IDs"
|
|
value = { for k, v in aws_subnet.public : k => v.id }
|
|
}
|
|
|
|
output "internet_gateway_id" {
|
|
description = "ID of the internet gateway"
|
|
value = try(aws_internet_gateway.main[0].id, null)
|
|
}
|
|
|
|
# Root configuration using 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
|
|
|
|
#### Generate Migration Plan
|
|
```hcl
|
|
# migration.tf
|
|
# Use moved blocks for state refactoring (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)
|
|
```bash
|
|
# Generate state migration commands
|
|
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
|
|
|
|
```markdown
|
|
# VPC Module
|
|
|
|
## Overview
|
|
Creates a VPC with configurable public and private subnets across multiple availability zones.
|
|
|
|
## Features
|
|
- Multi-AZ subnet deployment
|
|
- Optional NAT gateway configuration
|
|
- VPC Flow Logs integration
|
|
- Customizable CIDR allocation
|
|
|
|
## 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 | Name prefix for resources | `string` | n/a | yes |
|
|
| cidr_block | VPC CIDR block | `string` | n/a | yes |
|
|
| availability_zones | List of AZs | `list(string)` | n/a | yes |
|
|
|
|
## Outputs
|
|
|
|
| Name | Description |
|
|
|------|-------------|
|
|
| vpc_id | VPC identifier |
|
|
| public_subnet_ids | Map of public subnet IDs |
|
|
| private_subnet_ids | Map of private subnet IDs |
|
|
|
|
## Examples
|
|
|
|
See [examples/](./examples/) directory for complete usage examples.
|
|
```
|
|
|
|
### 6. Testing
|
|
|
|
Use skill terraform-test
|
|
|
|
**Test File**: A `.tftest.hcl` or `.tftest.json` file containing test configuration and run blocks that validate your Terraform configuration.
|
|
|
|
**Test Block**: Optional configuration block that defines test-wide settings (available since Terraform 1.6.0).
|
|
|
|
**Run Block**: Defines a single test scenario with optional variables, provider configurations, and assertions. Each test file requires at least one run block.
|
|
|
|
**Assert Block**: Contains conditions that must evaluate to true for the test to pass. Failed assertions cause the test to fail.
|
|
|
|
**Mock Provider**: Simulates provider behavior without creating real infrastructure (available since Terraform 1.7.0).
|
|
|
|
**Test Modes**: Tests run in apply mode (default, creates real infrastructure) or plan mode (validates logic without creating resources).
|
|
|
|
#### File Structure
|
|
|
|
Terraform test files use the `.tftest.hcl` or `.tftest.json` extension and are typically organized in a `tests/` directory. Use clear naming conventions to distinguish between unit tests (plan mode) and integration tests (apply mode):
|
|
|
|
```
|
|
my-module/
|
|
├── main.tf
|
|
├── variables.tf
|
|
├── outputs.tf
|
|
└── tests/
|
|
├── unit_test.tftest.hcl # Unit test (plan mode)
|
|
└── integration_test.tftest.hcl # Integration test (apply mode - creates real resources)
|
|
```
|
|
|
|
## Refactoring Patterns
|
|
|
|
### Pattern 1: Resource Grouping
|
|
Extract related resources into cohesive modules:
|
|
- Networking (VPC, Subnets, Route Tables)
|
|
- Compute (ASG, Launch Templates, Load Balancers)
|
|
- Data (RDS, ElastiCache, S3)
|
|
|
|
### Pattern 2: Configuration Layering
|
|
```hcl
|
|
# Base module with defaults
|
|
module "vpc_base" {
|
|
source = "./modules/vpc-base"
|
|
# Minimal required inputs
|
|
}
|
|
|
|
# Environment-specific wrapper
|
|
module "vpc_prod" {
|
|
source = "./modules/vpc-production"
|
|
# Inherits from base, adds prod-specific config
|
|
}
|
|
```
|
|
|
|
### Pattern 3: Composition
|
|
```hcl
|
|
# Small, focused modules
|
|
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
|
|
```hcl
|
|
# ❌ Don't create overly generic modules
|
|
variable "resources" {
|
|
type = map(map(any)) # Too flexible, hard to validate
|
|
}
|
|
|
|
# ✅ Do use specific, typed interfaces
|
|
variable "database_config" {
|
|
type = object({
|
|
engine = string
|
|
instance_class = string
|
|
})
|
|
}
|
|
```
|
|
|
|
### 2. Tight Coupling
|
|
```hcl
|
|
# ❌ Don't couple modules through direct references
|
|
# module A
|
|
output "instance_id" { value = aws_instance.app.id }
|
|
|
|
# module B (in same config)
|
|
resource "aws_eip" "app" {
|
|
instance = module.a.instance_id # Tight coupling
|
|
}
|
|
|
|
# ✅ Do pass dependencies through root module
|
|
module "compute" {
|
|
source = "./modules/compute"
|
|
}
|
|
|
|
resource "aws_eip" "app" {
|
|
instance = module.compute.instance_id
|
|
}
|
|
```
|
|
|
|
### 3. State Migration Errors
|
|
Always test migration in non-production first:
|
|
```bash
|
|
# Create plan to verify no changes after migration
|
|
terraform plan -out=migration.tfplan
|
|
|
|
# Review carefully
|
|
terraform show migration.tfplan
|
|
|
|
# Apply only if plan shows no changes
|
|
terraform apply migration.tfplan
|
|
```
|
|
|
|
## Version Control Strategy
|
|
|
|
```hcl
|
|
# Use semantic versioning for modules
|
|
module "vpc" {
|
|
source = "git::https://github.com/org/terraform-modules.git//vpc?ref=v1.2.0"
|
|
version = "~> 1.2"
|
|
}
|
|
|
|
# Pin to specific versions in production
|
|
# Use version ranges in development
|
|
```
|
|
|
|
## Success Criteria
|
|
|
|
- [ ] Module has single, well-defined responsibility
|
|
- [ ] All variables have descriptions and types
|
|
- [ ] Validation rules prevent invalid configurations
|
|
- [ ] Outputs provide sufficient information for consumers
|
|
- [ ] Documentation includes usage examples
|
|
- [ ] Tests verify module behavior
|
|
- [ ] State migration completed without resource recreation
|
|
- [ ] No plan differences after refactoring
|
|
|
|
## Related Skills
|
|
- [Terraform code generation](https://raw.githubusercontent.com/hashicorp/agent-skills/refs/heads/main/terraform/code-generation/skills/terraform-style-guide/SKILL.md) - Style guide for the new Terraform Module
|
|
- [Azure Verified Modules](https://raw.githubusercontent.com/hashicorp/agent-skills/refs/heads/main/terraform/code-generation/skills/azure-verified-modules/SKILL.md) - Recommended module specifications for Azure
|
|
|
|
## Resources
|
|
- [Terraform Module Development](https://developer.hashicorp.com/terraform/language/modules/develop)
|
|
- [Module Best Practices](https://developer.hashicorp.com/terraform/cloud-docs/registry/design)
|
|
|
|
## Revision History
|
|
|
|
| Version | Date | Changes |
|
|
|---------|------|---------|
|
|
| 1.0.0 | 2025-11-07 | Initial skill definition |
|