Definitions
- Terraform module: any directory containing one or more configuration files (e.g., somefile.tf). Terraform treats all modules equally, there is no hierarchy.
- Top-level configuration: the directory where you execute terraform init and provide input variables.
- Variables: scoped to the module. In order to use values outside of their module, you must explicitly output them.
- depends_on: meta-argument used in resource blocks (and rarely module blocks); depends_on is always a [list].
Introduction
Terraform tracks dependencies by inference. When you define a resource with arguments, Terraform automatically creates a hierarchical connection between them. Under the hood, creating an "aws_instance" with argument "ami_id" means Terraform adds to a directed, acyclic graph making the AWS EC2 instance depend on the AMI data.
Manual intervention with depends_on should be reserved for when all else fails. Terraform allows depends_on for all resource blocks. In versions 0.13 and later, you can use depends_on when defining modules - which adds additional complexity.
When you make one module depend on another, all data in module "zeta" now depend on all data in module "alpha". This makes necessary dependencies hard to track and distinguish from others.
It is best to directly refer to the specific output variable in the first module. Avoid depending on an entire module even if it saves you time initially.
According to the docs, "Explicitly specifying a dependency is only necessary when a resource or module relies on some other resource's behavior but doesn't access any of that resource's data in its arguments" (https://www.terraform.io/language/meta-arguments/depends_on). Again, this is rare and should be used only in atypical situations.
Do's and Don'ts
Example File System
.
├── alpha
│ └── main_a.tf
├── main.tf
└── zeta
└── main_z.tf
Do directly refer to the output variable from the original module. There is no need for depends_on in the configuration below.
Recommended
# main.tf
module "alpha" {
source = "./alpha"
}
module "zeta" {
source = "./zeta"
some_input_var = module.alpha.alpha_instance_public_ip
}
...
# main_a.tf
resource "aws_instance" "dev" {
ami = "ami-abc123"
instance_type = "t2.micro"
associate_public_ip_address = true
tags = {
Name = "alpha-dev"
}
}
output "alpha_instance_public_ip" {
value = aws_instance.dev.public_ip
}
...
# main_z.tf
variable "some_input_var" {
default = "1.1.1.1"
}
resource "some_other_resource" "r" {
some_argument = var.some_input_var
}
...
Don't indirectly reference using data sources and depends_on. When you create a resource, do not simply create it and read it back as a data source; it is best to directly reference it.
Not Recommended
# This module is the same as main_a.tf above module "alpha" { source = "./alpha" } # This module reads data from a data source that depends on module "alpha", so uses depends_on module "zeta" {
source = "./zeta" depends_on = [module.alpha] }
# main_a.tf (same as main_a.tf above)
...
resource "aws_instance" "dev" {
ami = "ami-abc123"
instance_type = "t2.micro"
associate_public_ip_address = true
tags = {
Name = "alpha-dev"
}
}
output "alpha_instance_public_ip" {
value = aws_instance.dev.public_ip
}
...
# main_z.tf
# This module depends on module "alpha" for data
...
data "aws_eip" "alpha-dev-ip" {
tags = { Name = "alpha-dev" }
}
resource "some_other_resource" "other_r" {
some_argument = data.aws_eip.alpha-dev-ip.some_attribute
}
...