daily-dreamer/terraform-standardize

Normaliser du code Terraform brut, brownfield ou importé (AWS, GCP, Azure) en production-ready selon les standards HashiCorp : isole provider.tf, versions.tf et backend.tf, paramétrise les chaînes codées en dur dans variables.tf et locals.tf, remplace les identifiants cloud statiques par des références dynamiques, applique les garde-fous ForceNew et zéro-dérive avec des blocs moved, et exécute le formatage HCL canonique, la validation et les résumés de plan.

Qu'est-ce que terraform-standardize ?

Cette compétence vous permet de transformer en profondeur tout code Terraform existant (brut, brownfield, importé par Terraformer ou terraform import, ou hérité) en une architecture modulaire et prête pour la production, conforme aux meilleures pratiques HashiCorp. Elle vous guide à travers un processus en cinq phases : audit et identification des odeurs de code, création d'une structure de fichiers standard (provider.tf, versions.tf, backend.tf, variables.tf, locals.tf, outputs.tf), paramétrisation des valeurs codées en dur en variables typées et locales, remplacement des identifiants statiques par des références dynamiques en HCL, et enfin formatage, validation et vérification avec terraform plan. La compétence gère les trois grands clouds (AWS, GCP, Azure) et inclut des garde-fous critiques pour les projets brownfield, comme la préservation de l'état avec les blocs moved, la gestion des attributs ForceNew pour éviter une destruction intempestive des ressources, et le nettoyage spécifique aux imports Terraformer. À la fin d'une standardisation, vous obtiendrez une base de code propre, maintenable et facile à faire évoluer, avec un résumé clair des différences intentionnelles et non intentionnelles dans le plan Terraform.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/daily-dreamer/terraform-standardize/tree/HEAD/skills/terraform-standardize

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Compétence de standardisation Terraform

Cette compétence guide l'agent dans le refactoring de code Terraform brut, brownfield, importé (par exemple depuis Terraformer ou terraform import) ou hérité, en une architecture modulaire et prête pour la production, conforme aux standards HashiCorp.

Quand utiliser cette compétence

Activez cette compétence lorsque :

  • L'utilisateur demande de "normaliser", "refactorer", "nettoyer" ou "modulariser" des configurations Terraform.
  • Le code Terraform brut contient des chaînes codées en dur, des nombres magiques ou des identifiants cloud statiques (ami-*, vpc-*, subnet-*, sg-*, arn:*).
  • Des configurations monolithiques dans un seul fichier (main.tf avec tout dedans) doivent être séparées en une disposition multi-fichiers standard (provider.tf, variables.tf, locals.tf, outputs.tf, backend.tf).
  • Les ressources cloud ont besoin d'une taxonomie de balisage standardisée (par ex. Environment, Project, ManagedBy = "terraform").
  • La base de code manque de définitions de variables, de contraintes de type ou de blocs de description.

Flux de travail de standardisation

Suivez cette procédure en 5 phases lors de la standardisation de toute base de code Terraform :

┌────────────────────────────────────────────────────────┐
│ Phase 1 : Audit et identification des odeurs de code  │
│ • Détecter les littéraux codés en dur, IDs, régions   │
│   et CIDRs                                             │
│ • Inspecter la structure des fichiers existants        │
│   par rapport à la disposition standard                │
└──────────────────────────┬─────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────┐
│ Phase 2 : Établir une architecture de fichiers         │
│           standard                                     │
│ • Extraire provider.tf, versions.tf et backend.tf     │
│ • Créer variables.tf, locals.tf et outputs.tf          │
└──────────────────────────┬─────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────┐
│ Phase 3 : Paramétriser les littéraux en variables      │
│           et locales                                  │
│ • Extraire les variables d'environnement/région        │
│   avec types                                           │
│ • Construire local.common_tags pour un balisage        │
│   unifié des ressources                                │
│ • Créer terraform.tfvars.example                        │
└──────────────────────────┬─────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────┐
│ Phase 4 : Liaison par références dynamiques et         │
│           renommage                                    │
│ • Remplacer les IDs de ressources statiques par des    │
│   références dynamiques                                │
│ • Remplacer les AMIs/sous-réseaux codés en dur par des │
│   data sources                                         │
│ • Normaliser les étiquettes de ressources (snake_case, │
│   pas de types)                                        │
└──────────────────────────┬─────────────────────────────┘
                           │
                           ▼
┌────────────────────────────────────────────────────────┐
│ Phase 5 : Formater, valider et vérifier                │
│ • Exécuter `terraform fmt -recursive`                  │
│ • Exécuter `terraform validate`                        │
│ • Vérifier avec `terraform plan` (zéro dérive)         │
└────────────────────────────────────────────────────────┘

Instructions pas à pas

1. Phase 1 : Audit et découverte

Avant de faire des modifications, analysez le répertoire cible pour identifier :

  1. Disponibilité de l'interface CLI : Exécutez terraform version (ou tofu version pour OpenTofu) pour déterminer si les vérifications de formatage, de validation et de plan peuvent être exécutées dans la Phase 5.
  2. Fichiers standard manquants : Vérifiez si versions.tf, provider.tf, variables.tf, locals.tf et outputs.tf existent.
  3. IDs et littéraux codés en dur :
    • Identifiants cloud : ami-[a-f0-9]+, vpc-[a-f0-9]+, subnet-[a-f0-9]+, sg-[a-f0-9]+, igw-[a-f0-9]+, liens complets GCP (https://www.googleapis.com/compute/...), IDs project statiques
    • Blocs CIDR : "10.0.0.0/16", "0.0.0.0/0"
    • Types d'instances : "t3.micro", "m5.large", "e2-standard-4"
    • Régions et numéros de compte statiques : "us-east-1", "us-central1", "123456789012"
    • Identifiants bruts ou secrets non chiffrés
  4. Référence de formatage : Si Terraform est installé, vérifiez si les fichiers sont conformes à terraform fmt -check -recursive.

2. Phase 2 : Créer les fichiers centraux standard

Assurez-vous que la configuration cible est divisée en fichiers distincts, à responsabilité unique :

provider.tf / versions.tf

Centralisez les paramètres Terraform, la version minimale de l'interface CLI et les fournisseurs requis (adaptez pour AWS, GCP ou Azure) :

# versions.tf
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    # Exemple GCP :
    # google = {
    #   source  = "hashicorp/google"
    #   version = "~> 5.5"
    # }
  }
}

# provider.tf (AWS)
provider "aws" {
  region = var.aws_region

  default_tags {
    tags = local.common_tags
  }
}

# provider.tf (alternative GCP)
# provider "google" {
#   project        = var.project_id
#   region         = var.region
#   default_labels = local.common_labels
# }

Stratégie de balisage/étiquetage unique : Utilisez soit les default_tags / default_labels au niveau du fournisseur (en mettant uniquement les clés spécifiques aux ressources sur les ressources individuelles) soit merge(local.common_tags, ...) / merge(local.common_labels, ...) sur chaque ressource — ne dupliquez jamais les deux.

backend.tf (si un état distant est utilisé)

Séparez la configuration de stockage d'état (s3 pour AWS, gcs pour GCP, azurerm pour Azure) :

terraform {
  backend "s3" {
    bucket         = "myorg-terraform-state"
    key            = "services/app/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

# Exemple GCP :
# terraform {
#   backend "gcs" {
#     bucket = "myorg-terraform-state"
#     prefix = "services/app"
#   }
# }

3. Phase 3 : Convertir les chaînes simples en variables et locales

Extrayez tous les paramètres codés en dur dans variables.tf et locals.tf :

Règles pour variables.tf :

  • Déclarez toujours type : (string, number, bool, list(string), map(string)).
  • Incluez toujours description : Expliquez le but et le format attendu.
  • Fournissez un default uniquement quand c'est pertinent : Les options de configuration comme environment ou aws_region peuvent avoir des valeurs par défaut, mais les valeurs sensibles ne doivent pas en avoir.
  • Marquez les secrets comme sensitive = true : Les variables de mot de passe, jeton et secret doivent être marquées comme sensibles.

Exemple de variables.tf :

variable "aws_region" {
  type        = string
  description = "La région AWS où les ressources seront provisionnées."
  default     = "us-east-1"
}

variable "environment" {
  type        = string
  description = "Nom de l'environnement de déploiement (ex. dev, staging, prod)."
  default     = "production"

  validation {
    condition     = contains(["dev", "staging", "production"], var.environment)
    error_message = "L'environnement doit être l'un des suivants : dev, staging, production."
  }
}

variable "instance_type" {
  type        = string
  description = "Taille de l'instance EC2 pour le cluster applicatif."
  default     = "t3.medium"
}

variable "vpc_cidr" {
  type        = string
  description = "Bloc CIDR de base pour le cloud privé virtuel."
  default     = "10.0.0.0/16"
}

Règles pour locals.tf :

Utilisez locals pour les valeurs dérivées en interne, les préfixes d'environnement et les balises communes :

locals {
  name_prefix = "${var.project_name}-${var.environment}"

  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

terraform.tfvars.example

Créez toujours un fichier d'exemple de valeurs de variables pour que les coéquipiers sachent comment fournir les valeurs :

aws_region   = "us-east-1"
environment  = "production"
project_name = "checkout-service"

4. Phase 4 : Remplacer les valeurs statiques par des références dynamiques (garde-fous zéro-dérive)

Convertissez les identifiants cloud statiques et les références de ressources disjointes en expressions HCL natives tout en protégeant l'infrastructure en production contre un remplacement accidentel :

Garde-fou d'immuabilité et ForceNew (Critique pour brownfield / Terraformer)

  • Distinguer les adresses Terraform des attributs cloud name : Renommer une étiquette de ressource Terraform (tfer--prod-main-vpc → main) avec un bloc moved {} est sûr et sans interruption de service. Modifier un attribut cloud name (par ex., google_compute_network.name, google_compute_subnetwork.name, google_compute_firewall.name, google_compute_instance.name, ou aws_s3_bucket.bucket) est ForceNew et détruit et recrée l'infrastructure en production. Paramétrez les noms cloud existants dans variables.tf / terraform.tfvars au lieu de les réécrire en "${local.name_prefix}-...".
  • Épingler les images/AMIs importées sur les imports brownfield : Passer d'un ami-* ou debian-12-bookworm-v20240515 codé en dur à une recherche most_recent = true / famille data remplacera les VM existantes si une image plus récente existe. Épinglez le nom ou l'ID exact de l'image/AMI importé via une variable/un filtre data lorsque zéro-dérive est requis, et n'activez most_recent = true que lorsque l'utilisateur opte pour des mises à niveau d'image.
  • Garder le durcissement de sécurité séparé du refactoring zéro-dérive : Ne modifiez pas silencieusement les attributs comportementaux/de sécurité (par ex., http_tokens "optional" → "required", public_access_prevention "inherited" → "enforced", ou les scopes OAuth des VM) lors d'une migration zéro-dérive. Considérez le durcissement comme une option explicite ou listez clairement chaque différence intentionnelle.
  • Préserver les clés de balises/étiquettes héritées lors de la migration : Si les ressources existantes utilisent env = "prod", préservez les clés existantes (ou ajoutez les clés standardisées en plus) à moins que le remplacement des clés ne soit approuvé.

Liste de nettoyage spécifique à Terraformer

  • Supprimez les attributs d'état calculés en lecture seule (tags_all sur AWS ; effective_labels et terraform_labels sur GCP) et les attributs vides/par défaut ("", [], {}, false, 0).
  • Supprimez les sous-blocs conflictuels vidés de l'état (par ex., network_interface principal EC2 lorsque subnet_id est défini).
  • Remplacez les interpolations héritées ${data.terraform_remote_state.*.outputs.*} et les URL de liens complets par des références directes aux ressources (par ex., google_compute_network.main.id) ou des recherches data.
  • Fusionnez les ressources dupliquées numérotées (vm-01, vm-02) en une seule ressource for_each associée à des blocs moved (to = google_compute_instance.api["01"]).
  • Extrayez les chaînes user_data / startup-script échappées en \n en ligne dans des fichiers scripts/ chargés via file() ou templatefile().
  • Lors de la mutation des scopes ou machine_type des VM GCP, assurez-vous que allow_stopping_for_update = true est définit si des mises à jour sur place sont prévues.

Anti-patron (Brut / Codé en dur) :

# MAUVAIS : IDs codés en dur et chaînes littérales
resource "aws_instance" "tfer--i-0123456789abcdef0" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.medium"
  subnet_id     = "subnet-01234567"
  vpc_security_group_ids = [
    "sg-0abcdef1234567890"
  ]
}

Patron standard (Refactoré) :

# 1. Préservation de l'état : Indiquer à Terraform que cette ressource a été renommée (empêche la destruction et la recréation)
moved {
  from = aws_instance.tfer--i-0123456789abcdef0
  to   = aws_instance.web
}

# 2. Recherche dynamique pour l'image de base (épingler le nom exact pour le brownfield zéro-dérive, ou most_recent pour greenfield)
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = [var.ami_name_pattern]
  }
}

# 3. Définition de ressource standardisée avec références dynamiques
resource "aws_instance" "web" {
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = var.instance_type
  subnet_id              = aws_subnet.private[0].id
  vpc_security_group_ids = [aws_security_group.web.id]

  # Omettre common_tags ici si provider.tf définit déjà default_tags
  tags = {
    Name = var.instance_name
  }
}

5. Phase 5 : Formatage, audit de dérive et validation

  1. Liste de vérification d'audit de dérive avant plan (toujours effectuer, surtout si l'interface CLI terraform n'est pas installée localement) :

    • Comparez le name cloud, image/ami, location/zone, tags/labels et les paramètres de sécurité/réseau de chaque ressource par rapport à la source importée.
    • Vérifiez qu'aucun champ ForceNew n'a été accidentellement modifié et que chaque ressource tfer--* a un bloc moved {} correspondant.
    • Séparez et signalez toute différence intentionnelle (par ex., durcissement de sécurité) du refactoring zéro-dérive.
  2. Exécuter les vérifications de l'interface CLI Terraform (lorsqu'elle est installée) :

# 1. Formater le code selon l'indentation HCL canonique standard
terraform fmt -recursive

# 2. Initialiser les plugins de fournisseur (utiliser -backend=false lors de la validation hors ligne / sans état) et valider la syntaxe
terraform init -backend=false
terraform validate

# 3. Si vous travaillez dans un environnement actif avec état, assurez-vous que le plan ne produit AUCUNE différence non intentionnelle
terraform plan
  1. Résumer les modifications de terraform plan (lorsqu'une différence est détectée) : Si terraform plan signale autre chose que No changes, arrêtez-vous avant de recommander apply et présentez un résumé structuré :
    • Totaux du plan : Indiquez le nombre exact de ressources (X à ajouter, Y à modifier, Z à détruire) et toute transition d'état de moved.
    • Modifications à haut risque en premier (-/+ remplacer, - détruire) : Mettez en évidence chaque ressource marquée pour remplacement ou suppression, nommez l'attribut ForceNew qui en est la cause (par ex., name, image, boot_disk), et expliquez comment le corriger (comme ajouter un bloc moved {} manquant ou rétablir la valeur d'origine dans tfvars).
    • Mises à jour sur place (~) et créations (+) : Listez chaque ressource affectée et les attributs exacts qui changent (par ex., labels, tags, scopes, public_access_prevention).
    • Dérive intentionnelle vs non intentionnelle : Marquez clairement quelles différences sont intentionnelles (durcissement de sécurité explicite ou standardisation des balises) par rapport à la dérive brownfield non intentionnelle afin que l'utilisateur puisse les approuver ou les annuler.

Directives et références détaillées

Skills associés