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 creates/adds to a directed, acyclic graph making the AWS EC2 instance depend on the AMI data.
Manual intervention with depends_on should be reserved when all else fails. HashiCorp 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 dire situations.
Do's and Don'ts
Example File System
.
|
|___ alpha
| |_ main_a.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
# This references the contents of main_a.tf
module "alpha" {
source = "./alpha"
}
# This references the contents of main_z.tf
module "zeta" {
source = "./zeta"
}
# 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 = module.alpha.output.alpha_instance_public_ip
}
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 now depends on module "alpha" for data 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
}
...