AskLearn
Loading...
← Back to Terraform Course
AdvancedConfiguration

Validation and Best Practices

Configuration standards

Tutorial 25: Configuration Validation and Best Practices

Learning Objectives

By the end of this tutorial, you will be able to:

  • Implement comprehensive configuration validation strategies
  • Apply Terraform best practices for maintainable infrastructure code
  • Use automated testing and validation tools
  • Design secure and scalable Terraform configurations
  • Establish effective code organization and documentation patterns

Prerequisites

  • Completed all previous Configuration module tutorials (16-24)
  • Understanding of Terraform lifecycle and state management
  • Knowledge of infrastructure security principles

Introduction

This tutorial consolidates best practices for Terraform configuration management, covering validation strategies, code organization, security practices, and testing methodologies. These practices ensure your infrastructure code is maintainable, secure, and reliable at scale.

Configuration Validation

Input Validation

# variables.tf
variable "environment" {
  description = "Environment name (dev, staging, prod)"
  type        = string
  
  validation {
    condition = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be one of: dev, staging, prod."
  }
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
  
  validation {
    condition = can(regex("^[tm][0-9]+[a-z]*\\.(nano|micro|small|medium|large|xlarge|[0-9]+xlarge)$", var.instance_type))
    error_message = "Instance type must be a valid EC2 instance type (e.g., t3.micro, m5.large)."
  }
}

variable "vpc_cidr" {
  description = "VPC CIDR block"
  type        = string
  default     = "10.0.0.0/16"
  
  validation {
    condition = can(cidrhost(var.vpc_cidr, 0))
    error_message = "VPC CIDR must be a valid IPv4 CIDR block."
  }
  
  validation {
    condition = tonumber(split("/", var.vpc_cidr)[1]) <= 24
    error_message = "VPC CIDR block must be /24 or larger (smaller prefix)."
  }
}

variable "tags" {
  description = "Resource tags"
  type        = map(string)
  default     = {}
  
  validation {
    condition = alltrue([
      for k, v in var.tags : can(regex("^[a-zA-Z][a-zA-Z0-9\\-_]*$", k))
    ])
    error_message = "Tag keys must start with a letter and contain only alphanumeric characters, hyphens, and underscores."
  }
  
  validation {
    condition = alltrue([
      for k, v in var.tags : length(v) > 0 && length(v) <= 256
    ])
    error_message = "Tag values must be between 1 and 256 characters."
  }
}

variable "backup_schedule" {
  description = "Backup schedule configuration"
  type = object({
    enabled     = bool
    frequency   = string
    retention   = number
    time        = string
  })
  default = {
    enabled   = false
    frequency = "daily"
    retention = 7
    time      = "03:00"
  }
  
  validation {
    condition = contains(["daily", "weekly", "monthly"], var.backup_schedule.frequency)
    error_message = "Backup frequency must be daily, weekly, or monthly."
  }
  
  validation {
    condition = var.backup_schedule.retention >= 1 && var.backup_schedule.retention <= 365
    error_message = "Backup retention must be between 1 and 365 days."
  }
  
  validation {
    condition = can(regex("^([01]?[0-9]|2[0-3]):[0-5][0-9]$", var.backup_schedule.time))
    error_message = "Backup time must be in HH:MM format (24-hour)."
  }
}

variable "allowed_ports" {
  description = "List of allowed ports"
  type        = list(number)
  default     = [80, 443]
  
  validation {
    condition = alltrue([
      for port in var.allowed_ports : port >= 1 && port <= 65535
    ])
    error_message = "All ports must be between 1 and 65535."
  }
  
  validation {
    condition = length(distinct(var.allowed_ports)) == length(var.allowed_ports)
    error_message = "Port list must not contain duplicates."
  }
}

variable "database_config" {
  description = "Database configuration"
  type = object({
    engine         = string
    version        = string
    instance_class = string
    storage_gb     = number
    multi_az       = bool
    encrypted      = bool
  })
  
  validation {
    condition = contains(["mysql", "postgres", "mariadb"], var.database_config.engine)
    error_message = "Database engine must be mysql, postgres, or mariadb."
  }
  
  validation {
    condition = var.database_config.storage_gb >= 20 && var.database_config.storage_gb <= 65536
    error_message = "Database storage must be between 20 and 65536 GB."
  }
  
  validation {
    condition = can(regex("^db\\.[tm][0-9]+\\.(nano|micro|small|medium|large|xlarge|[0-9]+xlarge)$", var.database_config.instance_class))
    error_message = "Database instance class must be a valid RDS instance type."
  }
}

Advanced Validation Patterns

# validation.tf
locals {
  # Complex validation logic
  validation_results = {
    # Check environment consistency
    environment_valid = (
      var.environment == "prod" ? 
      var.database_config.multi_az && var.database_config.encrypted :
      true
    )
    
    # Validate network configuration
    network_valid = (
      length(var.subnet_cidrs) <= pow(2, (24 - tonumber(split("/", var.vpc_cidr)[1]))) - 2
    )
    
    # Check security requirements
    security_valid = (
      var.environment == "prod" ?
      contains(var.allowed_ports, 443) && !contains(var.allowed_ports, 22) :
      true
    )
    
    # Validate resource naming
    naming_valid = (
      can(regex("^[a-z][a-z0-9-]*[a-z0-9]$", var.project_name)) &&
      length(var.project_name) >= 3 &&
      length(var.project_name) <= 63
    )
  }
  
  # Aggregate validation
  all_validations_passed = alltrue(values(local.validation_results))
}

# Custom validation checks
resource "null_resource" "validation_check" {
  count = local.all_validations_passed ? 0 : 1
  
  triggers = {
    validation_error = "Configuration validation failed: ${jsonencode(local.validation_results)}"
  }
  
  lifecycle {
    precondition {
      condition     = local.validation_results.environment_valid
      error_message = "Production environments must have multi-AZ and encryption enabled."
    }
    
    precondition {
      condition     = local.validation_results.network_valid
      error_message = "Too many subnets for the specified VPC CIDR block."
    }
    
    precondition {
      condition     = local.validation_results.security_valid
      error_message = "Production environments must use HTTPS and not expose SSH."
    }
    
    precondition {
      condition     = local.validation_results.naming_valid
      error_message = "Project name must be 3-63 characters, start with letter, end with letter/number."
    }
  }
}

# Output validation results for debugging
output "validation_results" {
  description = "Configuration validation results"
  value = local.validation_results
}

Code Organization Best Practices

File Structure Standards

terraform-project/
ā”œā”€ā”€ README.md
ā”œā”€ā”€ .gitignore
ā”œā”€ā”€ .terraform-version
ā”œā”€ā”€ .tflint.hcl
ā”œā”€ā”€ environments/
│   ā”œā”€ā”€ dev/
│   │   ā”œā”€ā”€ main.tf
│   │   ā”œā”€ā”€ variables.tf
│   │   ā”œā”€ā”€ outputs.tf
│   │   ā”œā”€ā”€ terraform.tfvars
│   │   └── backend.tf
│   ā”œā”€ā”€ staging/
│   │   └── ...
│   └── prod/
│       └── ...
ā”œā”€ā”€ modules/
│   ā”œā”€ā”€ vpc/
│   │   ā”œā”€ā”€ main.tf
│   │   ā”œā”€ā”€ variables.tf
│   │   ā”œā”€ā”€ outputs.tf
│   │   ā”œā”€ā”€ versions.tf
│   │   └── README.md
│   ā”œā”€ā”€ compute/
│   │   └── ...
│   └── database/
│       └── ...
ā”œā”€ā”€ shared/
│   ā”œā”€ā”€ data.tf
│   ā”œā”€ā”€ locals.tf
│   └── variables.tf
ā”œā”€ā”€ scripts/
│   ā”œā”€ā”€ deploy.sh
│   ā”œā”€ā”€ validate.sh
│   └── test.sh
└── tests/
    ā”œā”€ā”€ unit/
    ā”œā”€ā”€ integration/
    └── end-to-end/

Module Structure Template

# modules/example/versions.tf
terraform {
  required_version = ">= 1.0"
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
  }
}

# modules/example/variables.tf
variable "project_name" {
  description = "Name of the project"
  type        = string
  
  validation {
    condition     = can(regex("^[a-z][a-z0-9-]*[a-z0-9]$", var.project_name))
    error_message = "Project name must start with a letter, contain only lowercase letters, numbers, and hyphens, and end with a letter or number."
  }
}

variable "environment" {
  description = "Environment name"
  type        = string
  
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "tags" {
  description = "Common tags to apply to all resources"
  type        = map(string)
  default     = {}
}

# modules/example/locals.tf
locals {
  # Common naming convention
  name_prefix = "${var.project_name}-${var.environment}"
  
  # Standard tags applied to all resources
  common_tags = merge(var.tags, {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "terraform"
    Module      = basename(abspath(path.module))
    CreatedAt   = formatdate("YYYY-MM-DD", timestamp())
  })
  
  # Environment-specific configurations
  environment_config = {
    dev = {
      instance_type = "t3.micro"
      min_instances = 1
      max_instances = 2
    }
    staging = {
      instance_type = "t3.small"
      min_instances = 1
      max_instances = 3
    }
    prod = {
      instance_type = "t3.medium"
      min_instances = 2
      max_instances = 10
    }
  }
  
  config = local.environment_config[var.environment]
}

# modules/example/main.tf
# Resource implementation with consistent patterns

# modules/example/outputs.tf
output "example_id" {
  description = "ID of the example resource"
  value       = aws_example.main.id
}

output "example_arn" {
  description = "ARN of the example resource"
  value       = aws_example.main.arn
}

output "example_endpoint" {
  description = "Endpoint of the example resource"
  value       = aws_example.main.endpoint
  sensitive   = contains(["staging", "prod"], var.environment)
}

# modules/example/README.md
```markdown
# Example Module

This module creates and manages example resources.

## Usage

```hcl
module "example" {
  source = "./modules/example"
  
  project_name = "my-project"
  environment  = "dev"
  
  tags = {
    Owner = "[email protected]"
  }
}

Requirements

NameVersion
terraform>= 1.0
aws~> 5.0

Providers

NameVersion
aws~> 5.0

Inputs

NameDescriptionTypeDefaultRequired
project_nameName of the projectstringn/ayes
environmentEnvironment namestringn/ayes
tagsCommon tagsmap(string){}no

Outputs

NameDescription
example_idID of the example resource
example_arnARN of the example resource

Environment Configuration Pattern

# environments/base/variables.tf
variable "project_name" {
  description = "Name of the project"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
}

variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-west-2"
}

variable "common_tags" {
  description = "Common tags for all resources"
  type        = map(string)
  default     = {}
}

# environments/base/main.tf
terraform {
  required_version = ">= 1.0"
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
  
  default_tags {
    tags = merge(var.common_tags, {
      Project     = var.project_name
      Environment = var.environment
      ManagedBy   = "terraform"
    })
  }
}

# Data sources
data "aws_availability_zones" "available" {
  state = "available"
}

data "aws_caller_identity" "current" {}

data "aws_region" "current" {}

# Local values
locals {
  # Consistent naming
  name_prefix = "${var.project_name}-${var.environment}"
  
  # Network configuration
  vpc_cidr = {
    dev     = "10.0.0.0/16"
    staging = "10.1.0.0/16"
    prod    = "10.2.0.0/16"
  }[var.environment]
  
  # Availability zones (limit to 3 for consistency)
  availability_zones = slice(data.aws_availability_zones.available.names, 0, 3)
  
  # Common tags
  common_tags = merge(var.common_tags, {
    Project     = var.project_name
    Environment = var.environment
    Region      = data.aws_region.current.name
    AccountId   = data.aws_caller_identity.current.account_id
  })
}

# Module instantiation
module "vpc" {
  source = "../../modules/vpc"
  
  project_name = var.project_name
  environment  = var.environment
  
  vpc_cidr           = local.vpc_cidr
  availability_zones = local.availability_zones
  
  tags = local.common_tags
}

module "security" {
  source = "../../modules/security"
  
  project_name = var.project_name
  environment  = var.environment
  
  vpc_id   = module.vpc.vpc_id
  vpc_cidr = module.vpc.vpc_cidr
  
  tags = local.common_tags
}

# environments/dev/main.tf
module "infrastructure" {
  source = "../base"
  
  project_name = "myapp"
  environment  = "dev"
  aws_region   = "us-west-2"
  
  common_tags = {
    Owner       = "development-team"
    CostCenter  = "engineering"
    Application = "myapp"
  }
}

# environments/dev/backend.tf
terraform {
  backend "s3" {
    bucket         = "mycompany-terraform-state"
    key            = "myapp/dev/terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

# environments/dev/terraform.tfvars
# Development-specific overrides

Security Best Practices

Secrets Management

# security/secrets.tf
# DO NOT store secrets in plain text

# Use AWS Secrets Manager
resource "aws_secretsmanager_secret" "db_password" {
  name                    = "${local.name_prefix}-db-password"
  description             = "Database password for ${local.name_prefix}"
  recovery_window_in_days = var.environment == "prod" ? 30 : 0
  
  tags = local.common_tags
}

resource "aws_secretsmanager_secret_version" "db_password" {
  secret_id = aws_secretsmanager_secret.db_password.id
  secret_string = jsonencode({
    username = var.db_username
    password = random_password.db_password.result
  })
}

resource "random_password" "db_password" {
  length  = 32
  special = true
  
  keepers = {
    # Change password when these values change
    username = var.db_username
    version  = "v1"
  }
}

# Reference secrets in resources
data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = aws_secretsmanager_secret.db_password.id
}

locals {
  db_credentials = jsondecode(data.aws_secretsmanager_secret_version.db_password.secret_string)
}

resource "aws_db_instance" "main" {
  identifier = "${local.name_prefix}-database"
  
  engine         = "mysql"
  engine_version = "8.0"
  instance_class = var.db_instance_class
  
  allocated_storage = 20
  storage_encrypted = true
  kms_key_id       = aws_kms_key.database.arn
  
  db_name  = var.db_name
  username = local.db_credentials.username
  password = local.db_credentials.password
  
  # Security group allows only application access
  vpc_security_group_ids = [aws_security_group.database.id]
  db_subnet_group_name   = aws_db_subnet_group.main.name
  
  backup_retention_period = var.environment == "prod" ? 30 : 7
  backup_window          = "03:00-04:00"
  maintenance_window     = "sun:04:00-sun:05:00"
  
  deletion_protection = var.environment == "prod"
  skip_final_snapshot = var.environment != "prod"
  
  tags = local.common_tags
}

# KMS key for encryption
resource "aws_kms_key" "database" {
  description             = "KMS key for ${local.name_prefix} database encryption"
  deletion_window_in_days = var.environment == "prod" ? 30 : 7
  
  tags = local.common_tags
}

resource "aws_kms_alias" "database" {
  name          = "alias/${local.name_prefix}-database"
  target_key_id = aws_kms_key.database.key_id
}

IAM Best Practices

# security/iam.tf
# Principle of least privilege

# Application role with minimal permissions
resource "aws_iam_role" "app" {
  name = "${local.name_prefix}-app-role"
  
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
        Condition = {
          StringEquals = {
            "aws:RequestedRegion" = data.aws_region.current.name
          }
        }
      }
    ]
  })
  
  tags = local.common_tags
}

# Specific policy for application needs
resource "aws_iam_role_policy" "app" {
  name = "${local.name_prefix}-app-policy"
  role = aws_iam_role.app.id
  
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "secretsmanager:GetSecretValue"
        ]
        Resource = [
          aws_secretsmanager_secret.db_password.arn
        ]
        Condition = {
          StringEquals = {
            "secretsmanager:ResourceTag/Project" = var.project_name
          }
        }
      },
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:PutObject"
        ]
        Resource = [
          "${aws_s3_bucket.app.arn}/*"
        ]
      },
      {
        Effect = "Allow"
        Action = [
          "kms:Decrypt",
          "kms:GenerateDataKey"
        ]
        Resource = [
          aws_kms_key.database.arn
        ]
        Condition = {
          StringEquals = {
            "kms:ViaService" = "secretsmanager.${data.aws_region.current.name}.amazonaws.com"
          }
        }
      }
    ]
  })
}

# Instance profile
resource "aws_iam_instance_profile" "app" {
  name = "${local.name_prefix}-app-profile"
  role = aws_iam_role.app.name
  
  tags = local.common_tags
}

# Security group with restrictive rules
resource "aws_security_group" "app" {
  name_prefix = "${local.name_prefix}-app-"
  vpc_id      = var.vpc_id
  description = "Security group for ${local.name_prefix} application"
  
  # Only allow HTTPS inbound
  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  # Restrictive outbound rules
  egress {
    description = "HTTPS to internet"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  egress {
    description     = "Database access"
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.database.id]
  }
  
  lifecycle {
    create_before_destroy = true
  }
  
  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-app-sg"
  })
}

# Database security group - only app access
resource "aws_security_group" "database" {
  name_prefix = "${local.name_prefix}-db-"
  vpc_id      = var.vpc_id
  description = "Security group for ${local.name_prefix} database"
  
  ingress {
    description     = "MySQL from app"
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.app.id]
  }
  
  # No outbound rules needed for RDS
  
  lifecycle {
    create_before_destroy = true
  }
  
  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-db-sg"
  })
}

Testing and Validation

Automated Testing

# tests/terratest/example_test.go
package test

import (
    "testing"
    
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

func TestTerraformExample(t *testing.T) {
    t.Parallel()
    
    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../environments/dev",
        Vars: map[string]interface{}{
            "project_name": "test-project",
            "environment":  "dev",
        },
        EnvVars: map[string]string{
            "AWS_DEFAULT_REGION": "us-west-2",
        },
    })
    
    defer terraform.Destroy(t, terraformOptions)
    
    terraform.InitAndApply(t, terraformOptions)
    
    // Test outputs
    vpcId := terraform.Output(t, terraformOptions, "vpc_id")
    assert.NotEmpty(t, vpcId)
    
    // Test resource properties
    subnetIds := terraform.OutputList(t, terraformOptions, "public_subnet_ids")
    assert.Equal(t, 3, len(subnetIds))
}

Validation Scripts

#!/bin/bash
# scripts/validate.sh

set -e

echo "šŸ” Running Terraform validation..."

# Check format
echo "Checking Terraform format..."
terraform fmt -check -recursive .

# Validate syntax
echo "Validating Terraform syntax..."
find . -name "*.tf" -exec dirname {} \; | sort -u | while read dir; do
    echo "Validating $dir..."
    (cd "$dir" && terraform init -backend=false && terraform validate)
done

# Run security scanning
echo "Running security scan..."
if command -v tfsec &> /dev/null; then
    tfsec .
else
    echo "āš ļø  tfsec not installed. Install with: brew install tfsec"
fi

# Run linting
echo "Running Terraform lint..."
if command -v tflint &> /dev/null; then
    tflint --recursive
else
    echo "āš ļø  tflint not installed. Install with: brew install tflint"
fi

# Check for common issues
echo "Checking for common issues..."

# Check for hardcoded secrets
if grep -r "password.*=" --include="*.tf" --include="*.tfvars" .; then
    echo "āŒ Found potential hardcoded passwords"
    exit 1
fi

# Check for hardcoded IPs
if grep -rE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" --include="*.tf" . | grep -v "0.0.0.0/0" | grep -v "127.0.0.1"; then
    echo "āš ļø  Found hardcoded IP addresses"
fi

echo "āœ… Validation completed successfully!"

Pre-commit Hooks

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.81.0
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_docs
        args:
          - --hook-config=--path-to-file=README.md
          - --hook-config=--add-to-existing-file=true
          - --hook-config=--create-file-if-not-exist=true
      - id: terraform_tflint
        args:
          - --args=--only=terraform_deprecated_interpolation
          - --args=--only=terraform_deprecated_index
          - --args=--only=terraform_unused_declarations
          - --args=--only=terraform_comment_syntax
          - --args=--only=terraform_documented_outputs
          - --args=--only=terraform_documented_variables
          - --args=--only=terraform_typed_variables
          - --args=--only=terraform_module_pinned_source
          - --args=--only=terraform_naming_convention
          - --args=--only=terraform_required_version
          - --args=--only=terraform_required_providers
          - --args=--only=terraform_standard_module_structure
      - id: terraform_tfsec
        args:
          - --args=--minimum-severity=MEDIUM
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.4.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
      - id: check-merge-conflict

Documentation Standards

Module Documentation Template

# Module Name

Brief description of what this module does and why you would use it.

## Usage

```hcl
module "example" {
  source = "path/to/module"
  
  # Required variables
  project_name = "my-project"
  environment  = "dev"
  
  # Optional variables
  instance_type = "t3.medium"
  
  tags = {
    Owner = "[email protected]"
  }
}

Architecture

Brief explanation of the architecture and components.

Security Considerations

  • List security features and considerations
  • Mention encryption, access controls, etc.
  • Note any security requirements or assumptions

Requirements

NameVersion
terraform>= 1.0
aws~> 5.0

Providers

NameVersion
aws~> 5.0
random~> 3.0

Modules

NameSourceVersion
example./modules/examplen/a

Resources

NameType
aws_instance.exampleresource
aws_security_group.exampleresource

Inputs

NameDescriptionTypeDefaultRequired
project_nameName of the projectstringn/ayes
environmentEnvironment namestringn/ayes
instance_typeEC2 instance typestring"t3.micro"no

Outputs

NameDescription
instance_idID of the EC2 instance
security_group_idID of the security group

Examples

Basic Example

module "basic_example" {
  source = "./modules/example"
  
  project_name = "my-project"
  environment  = "dev"
}

Advanced Example

module "advanced_example" {
  source = "./modules/example"
  
  project_name  = "my-project"
  environment   = "prod"
  instance_type = "t3.large"
  
  tags = {
    Owner       = "platform-team"
    CostCenter  = "engineering"
    Environment = "production"
  }
}

Contributing

Guidelines for contributing to this module.

License

License information.


### Repository Documentation

```markdown
# Project Infrastructure

This repository contains Terraform configurations for managing our infrastructure.

## Structure

ā”œā”€ā”€ environments/ # Environment-specific configurations │ ā”œā”€ā”€ dev/ # Development environment │ ā”œā”€ā”€ staging/ # Staging environment │ └── prod/ # Production environment ā”œā”€ā”€ modules/ # Reusable Terraform modules ā”œā”€ā”€ shared/ # Shared configurations and data ā”œā”€ā”€ scripts/ # Automation scripts └── tests/ # Infrastructure tests


## Getting Started

### Prerequisites

- Terraform >= 1.0
- AWS CLI configured
- Pre-commit hooks (optional but recommended)

### Initial Setup

1. Clone the repository
2. Install dependencies: `./scripts/setup.sh`
3. Configure AWS credentials
4. Initialize Terraform: `terraform init`

### Deployment

```bash
# Deploy to development
cd environments/dev
terraform plan
terraform apply

# Deploy to production
cd environments/prod
terraform plan
terraform apply

Best Practices

  • Always run terraform plan before apply
  • Use feature branches for changes
  • Run validation: ./scripts/validate.sh
  • Keep modules focused and reusable
  • Document all variables and outputs
  • Use semantic versioning for modules

Security

  • Never commit secrets to version control
  • Use AWS Secrets Manager for sensitive data
  • Apply principle of least privilege for IAM
  • Enable encryption at rest and in transit
  • Regular security scanning with tfsec

Support

For questions or issues, contact the platform team.


## Key Takeaways

1. **Validation**: Implement comprehensive input validation with clear error messages
2. **Organization**: Follow consistent file and module organization patterns
3. **Security**: Apply security best practices including secrets management and IAM
4. **Testing**: Use automated testing and validation tools
5. **Documentation**: Maintain clear, comprehensive documentation
6. **Standards**: Establish and enforce coding standards and conventions

## Next Steps

You've now completed the Configuration Management module! You should:

1. Practice implementing these patterns in your own projects
2. Set up automated validation pipelines
3. Establish team coding standards
4. Continue with the State Management module (Tutorials 26-30)
5. Review the [Terraform Best Practices Guide](https://terraform.io/docs/cloud/guides/recommended-practices/index.html)

## Additional Resources

- [Terraform Style Guide](https://www.terraform.io/docs/language/syntax/style.html)
- [TFSec Security Scanner](https://aquasecurity.github.io/tfsec/)
- [TFLint Linter](https://github.com/terraform-linters/tflint)
- [Terratest Testing Framework](https://terratest.gruntwork.io/)
- [Pre-commit Terraform Hooks](https://github.com/antonbabenko/pre-commit-terraform)