Learn how to efficiently share Terraform providers and variables across multiple modules for streamlined infrastructure management.
In Terraform, provider configurations are shared globally, but managing variables across modules requires specific approaches. Terraform doesn't allow direct access to variables across modules. To share values, you must pass them as input variables to each module that needs them. For instance, to share an AWS region variable, define it in the root module and pass it to other modules as an input variable. While Terraform lacks global variables, workarounds exist. One method involves using a shared file with symlinks, placing shared variables in a common directory file, and creating symlinks to this file within each module. However, this can become unwieldy with numerous modules or complex structures. Another approach is using tools like Terragrunt, which extends Terraform and enables global variable definition and management. Terragrunt allows you to define shared variables in a configuration file, making them accessible to all modules. Despite these workarounds, explicitly passing variables as inputs to modules enhances code organization and clarifies dependencies within your Terraform projects.
Terraform's design inherently shares provider configurations globally within a Terraform configuration. This means a provider defined at the root of your project is accessible to all modules without explicitly passing it. While this simplifies provider access, managing variables across modules requires different approaches.
Directly accessing variables across modules isn't supported in Terraform. If you have shared values, you need to pass them as input variables to each module that requires them.
Let's say you have a variable for the AWS region in your root module. You'd define it and then pass it to other modules like this:
# root module
variable "region" {
default = "us-west-2"
}
module "module1" {
source = "./module1"
region = var.region
}
module "module2" {
source = "./module2"
region = var.region
}
In this example, both module1
and module2
receive the region
variable from the root module.
While Terraform doesn't support global variables, there are workarounds. One approach is using a shared file with symlinks. Place your shared variables in a file within a common directory and create symlinks to this file within each module. This way, modules access the same variable values from a central location.
However, this method can become cumbersome with many modules or complex project structures.
Another approach is using tools like Terragrunt. Terragrunt extends Terraform's functionality and allows you to define and manage variables globally. You can define your shared variables in a Terragrunt configuration file, and Terragrunt will make them available to all your modules.
Remember that while these workarounds offer solutions, clearly passing variables as inputs to modules promotes better code organization and understanding dependencies within your Terraform projects.
This code snippet demonstrates three methods of sharing variables in Terraform: passing variables as inputs, using symlinks, and using Terragrunt. The first method, passing variables as inputs, is recommended due to its clarity and maintainability. It involves defining variables in the root module and explicitly passing them to child modules. The second method, using symlinks, creates a shared variable file and links it within each module. This approach is less maintainable due to the dependency on file system links. The third method, using Terragrunt, leverages a separate tool for managing Terraform configurations and allows variable inheritance through configuration files. While symlinks and Terragrunt offer alternatives, directly passing variables as inputs is encouraged for better code organization and dependency management.
This example demonstrates different approaches to managing shared variables in Terraform.
1. Passing Variables as Inputs (Recommended)
# root module (main.tf)
variable "region" {
default = "us-west-2"
}
module "vpc" {
source = "./modules/vpc"
region = var.region
}
module "ec2" {
source = "./modules/ec2"
region = var.region
vpc_id = module.vpc.vpc_id
}
# modules/vpc/main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
variable "region" {}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "main-vpc"
}
}
output "vpc_id" {
value = aws_vpc.main.id
}
# modules/ec2/main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
variable "region" {}
variable "vpc_id" {}
resource "aws_instance" "example" {
ami = "ami-0c55b159c2d3c20c8"
instance_type = "t2.micro"
subnet_id = element(aws_subnet.main.*.id, count.index)
vpc_security_group_ids = [aws_security_group.allow_ssh.id]
count = 2
tags = {
Name = "Example EC2 Instance"
}
}
resource "aws_security_group" "allow_ssh" {
name = "allow_ssh"
description = "Allow SSH inbound traffic"
vpc_id = var.vpc_id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_subnet" "main" {
count = 2
vpc_id = var.vpc_id
cidr_block = cidrsubnet("10.0.0.0/16", 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "Main subnet ${count.index + 1}"
}
}
data "aws_availability_zones" "available" {
state = "available"
}
2. Using Symlinks (Less Maintainable)
shared_vars.tf
in a common directory:# shared_vars.tf
variable "region" {
default = "us-west-2"
}
shared_vars.tf
within each module:# In modules/vpc and modules/ec2 directories
ln -s ../../shared_vars.tf shared_vars.tf
# modules/vpc/main.tf (and similarly in modules/ec2/main.tf)
# ... other code ...
variable "region" {}
# ... use var.region ...
3. Using Terragrunt (Advanced)
terragrunt.hcl
file in your root directory:# terragrunt.hcl
terraform {
source = "git::https://github.com/gruntwork-io/terragrunt-infrastructure-live-code.git//modules/vpc?ref=master"
}
inputs = {
region = "us-west-2"
}
terragrunt.hcl
files in your module directories, inheriting from the root configuration:# modules/vpc/terragrunt.hcl
include {
path = find_in_parent_folders()
}
terraform {
source = "../modules/vpc"
}
region
variable available to your modules.Note: While symlinks and Terragrunt offer workarounds, explicitly passing variables as inputs promotes better code organization and clarity in dependencies. Choose the approach that best suits your project's complexity and maintainability needs.
Prioritize explicitness: While workarounds like symlinks and Terragrunt exist, always lean towards explicitly passing variables as inputs to modules. This enforces a clear dependency chain and makes your code easier to understand and maintain.
Symlink caveats: While symlinks offer a seemingly simple solution, they can lead to issues, especially in larger projects or with team collaboration. Be mindful of:
Terragrunt advantages: Terragrunt shines in managing complex Terraform deployments. Its ability to:
Terraform best practices: Always adhere to Terraform best practices for better code organization:
Consider your context: The best approach for managing shared variables depends on your project's size, complexity, and team structure. Evaluate the trade-offs and choose the method that best suits your needs.
This article explains how Terraform handles sharing resources across modules:
Providers:
Variables:
# root module
variable "region" { ... }
module "module1" {
region = var.region
...
}
Key Takeaway: Explicitly passing variables as inputs promotes better code organization and understanding of dependencies within Terraform projects.
In conclusion, while Terraform simplifies provider sharing globally, managing variables across modules necessitates a deliberate approach. Direct variable access across modules is not supported, requiring the passing of shared values as input variables to each module. While workarounds like shared files with symlinks and tools like Terragrunt exist, explicitly passing variables as inputs is the recommended practice. This approach enhances code organization, clarifies dependencies, and improves the maintainability of your Terraform projects. By adhering to these principles, you can build well-structured and robust infrastructure as code.