Understanding Terraform Module Structure
Example Structure
Without modules
- complex configurations
 - huge file
 - no overview
 
why modules
- organize and group configurations
 - encapsulate into distinct logical components
 - reuse
 
Modules Project Structure
- main.tf
 - variables.tf
 - outputs.tf
 - providers.tf
 


Here’s an example structure for a simple AWS VPC module:
modules/
└── vpc/
    ├── main.tf
    ├── variables.tf
    └── outputs.tf
- 
main.tf: This file contains the resource definitions. For instance, defining an AWS VPC:
resource "aws_vpc" "example" { cidr_block = var.cidr_block tags = { Name = var.name } } - 
variables.tf: Here, you declare the input variables:
variable "cidr_block" { description = "The CIDR block for the VPC." type = string } variable "name" { description = "The name of the VPC." type = string } - 
outputs.tf: This file defines what output values the module will return:
output "vpc_id" { value = aws_vpc.example.id } 
Using Modules in Terraform Configuration
To utilize a module within your main Terraform configuration, you would include it like this in your root module:
module "my_vpc" {
  source     = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
  name       = "my-vpc"
}This allows you to create multiple instances of your VPC with different configurations by simply reusing the module.
Best Practices for Module Structure
- Encapsulation: Group related resources together in a single module to simplify management and promote reuse.
 - Versioning: Use version control for your modules to track changes and ensure stability across deployments.
 - Naming Conventions: Use clear and consistent naming conventions for modules and variables to enhance readability.
 - Documentation: Provide documentation within each module to explain its purpose, input variables, and outputs.
 
Advanced Structuring Techniques
As projects scale, it may be beneficial to adopt more complex structures:
- Stacks vs. Modules: A stack can consist of multiple modules, allowing for independent deployment and management of infrastructure components[3]. This separation can help manage large infrastructures more effectively.
 - Environment-Specific Modules: Create separate directories or repositories for different environments (e.g., development, staging, production) to maintain clear boundaries and configurations[4].
 
By following these guidelines, you can create robust and reusable Terraform modules that streamline your infrastructure management processes.



The distinction between variables.tf and terraform.tfvars in Terraform is primarily about declaration versus assignment of variables.
variables.tf
- Purpose: This file is used to declare variables that your Terraform configuration will use. It defines the variable names, types, and optional default values.
 - Content Example:
variable "instance_type" { type = string default = "t2.micro" } variable "region" { type = string } - Functionality: By declaring a variable in 
variables.tf, you inform Terraform that this variable exists and can be referenced usingvar.<variable_name>in your configuration files. You can also set default values, making the variable optional during execution. 
terraform.tfvars
- Purpose: This file is specifically for assigning values to the variables declared in 
variables.tf. It provides the actual values that Terraform will use when executing plans or applying configurations. - Content Example:
instance_type = "t2.large" region = "us-west-2" - Functionality: Terraform automatically loads this file when you run commands like 
terraform applyorterraform plan, allowing you to set specific values for your variables without needing to specify them in the command line each time. If a variable is defined in bothvariables.tfandterraform.tfvars, the value fromterraform.tfvarswill override any default value set invariables.tf. 
Summary of Differences
| Feature | variables.tf | terraform.tfvars | 
|---|---|---|
| Purpose | Declare variables and their types | Assign values to declared variables | 
| File Type | .tf file (HCL syntax) | .tfvars file (HCL or JSON syntax) | 
| Default Values | Can set default values | Cannot set default values | 
| Automatic Loading | Not automatically loaded | Automatically loaded by Terraform | 
| Usage Context | Used to define what variables are needed | Used to provide specific values for those variables | 
In essence, variables.tf defines what inputs your configuration expects, while terraform.tfvars provides the actual inputs for those definitions when running Terraform commands
Other
Terraform Modules: Complete Guide for Associate Exam
What Are Terraform Modules?
A Terraform module is a container for multiple resources that are used together. Every Terraform configuration has at least one module, called the root module, which consists of the resources defined in the .tf files in the main working directory.
Key Concepts
- Root Module: The main configuration directory where you run 
terraformcommands - Child Module: A module called by another module
 - Published Module: A module published to the Terraform Registry or a private registry
 
Module Structure
Basic Module Directory Structure
my-module/
├── main.tf          # Primary resource definitions
├── variables.tf     # Input variable declarations
├── outputs.tf       # Output value definitions
├── versions.tf      # Provider requirements (optional)
├── README.md        # Documentation (recommended)
└── examples/        # Example usage (recommended)
    └── basic/
        ├── main.tf
        └── variables.tf
Essential Files
main.tf
Contains the primary logic and resources:
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type
  
  tags = {
    Name = var.instance_name
  }
}variables.tf
Defines input variables:
variable "ami_id" {
  description = "The AMI ID to use for the instance"
  type        = string
}
 
variable "instance_type" {
  description = "The instance type"
  type        = string
  default     = "t3.micro"
}
 
variable "instance_name" {
  description = "Name tag for the instance"
  type        = string
}outputs.tf
Defines outputs that other modules can reference:
output "instance_id" {
  description = "The ID of the EC2 instance"
  value       = aws_instance.web.id
}
 
output "public_ip" {
  description = "The public IP of the instance"
  value       = aws_instance.web.public_ip
}Using Modules
Module Sources
Local Modules
module "web_server" {
  source = "./modules/web-server"
  
  ami_id        = "ami-12345678"
  instance_type = "t3.small"
  instance_name = "web-01"
}Registry Modules
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 3.0"
  
  name = "my-vpc"
  cidr = "10.0.0.0/16"
}Git Sources
module "example" {
  source = "git::https://github.com/user/repo.git//modules/example?ref=v1.0.0"
}Other Sources
- HTTP URLs
 - S3 buckets
 - GCS buckets
 
Module Block Syntax
module "MODULE_NAME" {
  source = "SOURCE"
  
  # Input variables
  variable_name = value
  
  # Meta-arguments
  count      = 2
  for_each   = var.environments
  depends_on = [resource.example]
  providers  = {
    aws = aws.us_west_2
  }
}Input Variables
Variable Types
# String
variable "region" {
  type = string
}
 
# Number
variable "instance_count" {
  type = number
}
 
# Boolean
variable "enable_monitoring" {
  type = bool
}
 
# List
variable "availability_zones" {
  type = list(string)
}
 
# Map
variable "tags" {
  type = map(string)
}
 
# Object
variable "server_config" {
  type = object({
    name = string
    port = number
  })
}Variable Validation
variable "instance_type" {
  type        = string
  description = "EC2 instance type"
  
  validation {
    condition = contains([
      "t3.micro", "t3.small", "t3.medium"
    ], var.instance_type)
    error_message = "Instance type must be t3.micro, t3.small, or t3.medium."
  }
}Variable Precedence (highest to lowest)
- Command line flags (
-varand-var-file) *.auto.tfvarsfilesterraform.tfvarsfile- Environment variables (
TF_VAR_name) - Variable defaults
 - Interactive prompts
 
Output Values
Output Syntax
output "instance_ip" {
  description = "The private IP address of the instance"
  value       = aws_instance.web.private_ip
  sensitive   = false  # Default is false
}Sensitive Outputs
output "database_password" {
  value     = aws_db_instance.example.password
  sensitive = true
}Accessing Module Outputs
# In root module
resource "aws_security_group_rule" "allow_web" {
  source_security_group_id = module.web_server.security_group_id
}Data Sources in Modules
data "aws_ami" "latest" {
  most_recent = true
  owners      = ["amazon"]
  
  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}
 
resource "aws_instance" "web" {
  ami = data.aws_ami.latest.id
  # ...
}Module Composition
Calling Multiple Modules
module "vpc" {
  source = "./modules/vpc"
  
  cidr_block = "10.0.0.0/16"
}
 
module "web_servers" {
  source = "./modules/web-server"
  
  vpc_id    = module.vpc.vpc_id
  subnet_id = module.vpc.public_subnet_ids[0]
  
  depends_on = [module.vpc]
}Module Dependencies
module "database" {
  source = "./modules/database"
  
  depends_on = [module.vpc]
}Provider Configuration in Modules
Default Provider Inheritance
Child modules inherit provider configurations from the root module.
Explicit Provider Passing
# Root module
provider "aws" {
  alias  = "us_west_2"
  region = "us-west-2"
}
 
module "servers" {
  source = "./modules/servers"
  
  providers = {
    aws = aws.us_west_2
  }
}Module Provider Requirements
# In module's versions.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.0"
    }
  }
}Module Versioning
Semantic Versioning
- MAJOR: Breaking changes
 - MINOR: New features (backward compatible)
 - PATCH: Bug fixes (backward compatible)
 
Version Constraints
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 3.14"  # Allows 3.14.x
}Version Constraint Operators
=: Exact version!=: Exclude version>,>=,<,<=: Comparison operators~>: Pessimistic constraint
Module Registry
Public Registry
- Browse at registry.terraform.io
 - Naming convention: 
terraform-PROVIDER-NAME - Automatically versioned from Git tags
 
Private Registry
- Terraform Cloud/Enterprise feature
 - Custom modules for organization use
 
Module Requirements for Registry
- GitHub repository
 - Named 
terraform-PROVIDER-NAME - Repository description
 - Standard module structure
 - Git tags for versions
 
Local Values in Modules
locals {
  common_tags = {
    Environment = var.environment
    Project     = var.project_name
    Owner       = var.owner
  }
  
  instance_name = "${var.project_name}-${var.environment}-web"
}
 
resource "aws_instance" "web" {
  tags = merge(local.common_tags, {
    Name = local.instance_name
  })
}Module Development Best Practices
1. Use Consistent File Structure
- Always include 
main.tf,variables.tf,outputs.tf - Use 
versions.tffor provider requirements 
2. Comprehensive Variable Definitions
variable "instance_type" {
  description = "The type of instance to start"
  type        = string
  default     = "t3.micro"
  
  validation {
    condition     = can(regex("^t3\\.", var.instance_type))
    error_message = "Instance type must be in the t3 family."
  }
}3. Meaningful Outputs
output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}4. Use Data Sources When Appropriate
data "aws_availability_zones" "available" {
  state = "available"
}5. Avoid Hard-coded Values
Use variables or data sources instead of hard-coded values.
Module Meta-Arguments
count
module "servers" {
  source = "./modules/server"
  count  = 3
  
  server_name = "web-${count.index + 1}"
}for_each
module "buckets" {
  source   = "./modules/s3-bucket"
  for_each = toset(var.bucket_names)
  
  bucket_name = each.value
}depends_on
module "database" {
  source = "./modules/database"
  
  depends_on = [module.vpc]
}providers
module "vpc" {
  source = "./modules/vpc"
  
  providers = {
    aws = aws.us_west_2
  }
}Testing Modules
Example Usage
Create examples in the examples/ directory:
examples/
├── basic/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
└── complete/
    ├── main.tf
    ├── variables.tf
    └── outputs.tf
Validation Commands
# Validate syntax
terraform validate
 
# Check formatting
terraform fmt -check
 
# Plan example
terraform plan -var-file="testing.tfvars"Common Patterns
Conditional Resources
resource "aws_instance" "web" {
  count = var.create_instance ? 1 : 0
  
  ami           = var.ami_id
  instance_type = var.instance_type
}Dynamic Blocks
resource "aws_security_group" "web" {
  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}Module Refactoring
Moving Resources
Use moved blocks when refactoring:
moved {
  from = aws_instance.web
  to   = module.web_server.aws_instance.main
}State Management
- Use 
terraform state mvfor complex moves - Plan carefully when restructuring modules
 
Troubleshooting Common Issues
1. Reference to Undeclared Output Value
Problem: module.example.output_name doesn’t exist Solution: Define the output in the module’s outputs.tf
2. Variable Not Declared
Problem: Using a variable not defined in variables.tf Solution: Add variable declaration
3. Provider Configuration Issues
Problem: Provider not available in module Solution: Pass provider explicitly or ensure inheritance
4. Version Conflicts
Problem: Module requires different provider version Solution: Update version constraints
Exam Tips
Key Points to Remember
- Module structure: Know the standard files and their purposes
 - Variable precedence: Understand the order of variable resolution
 - Output referencing: Know how to reference module outputs
 - Provider inheritance: Understand how providers are passed to modules
 - Version constraints: Know the different constraint operators
 - Module sources: Understand different ways to source modules
 - Meta-arguments: Know when and how to use 
count,for_each, etc. 
Common Exam Scenarios
- Identifying missing output declarations
 - Understanding variable precedence
 - Recognizing proper module structure
 - Choosing correct version constraints
 - Troubleshooting module reference errors
 
Commands to Know
# Initialize modules
terraform init
 
# Get/update modules
terraform get
terraform get -update
 
# Validate configuration
terraform validate
 
# Format code
terraform fmt
 
# Show module tree
terraform providersSummary
Terraform modules are essential for:
- Code reusability: Write once, use multiple times
 - Organization: Logical grouping of resources
 - Abstraction: Hide complexity behind simple interfaces
 - Collaboration: Share standardized infrastructure patterns
 - Maintenance: Centralized updates and bug fixes
 
Master these concepts and you’ll be well-prepared for the module-related questions on the Terraform Associate exam!