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.
Providers Within Modules - Configuration Language | Terraform ... | Provider configurations, unlike most other concepts in Terraform, are global to an entire Terraform configuration and can be shared across module boundaries.
Share Resources Between Modules - Terraform - HashiCorp Discuss | Hello, Right now, I’m breaking up our monolithic repo into small modules. Currently, there is a “resources” folder hosting all resource shared by all modules. example code like: values = [ “{file("{path.module}/resources/providers/aws.yaml”)}" ] If I break up modules into different repos, it’s not a good idea that I duplicate this “resources” folder in every new one. Any words from wisdom? Thanks,
How to use terraform with providers and variables kept in a different ... | Hello Everyone, I am trying to remove providers.tf and variables.tf file from my terraform directory. The reason for that is I don’t want to expose these files to the user. I just want main.tf file to be placed in the directory and then I will run terraform init , terraform plan and terraform apply in an automated fashion through Gitlab CI pipeline. My current directory structure looks like this: ├── elasticsearch │ ├── elastic.tf │ ├── providers.tf │ └── variables.tf └── modules └...