Suppose a configuration creates a virtual machine and then uses its address in a DNS record. Terraform needs to know how those resources relate before it can plan their changes. Its resource graph is the internal structure that records resources and their dependencies. References between resource attributes and parameters establish those relationships, which Terraform uses to launch and modify resources in the proper order. section 5.2
The graph is directed and acyclic: dependencies have an order and cannot form a loop. A circular dependency produces an error. Terraform state is essentially a snapshot of the resources in the graph, while planning builds a graph from the configuration, compares it with infrastructure, and derives the actions needed to bring them into sync. section 5.2 section 5.3
The graph contains three kinds of nodes:
- Resource nodes represent individual resources, including data sources. Instances created with count or for_each each appear in the graph. section 5.2.1
- Provider configuration nodes represent provider configurations. Every resource directly depends on one; separate configurations, such as AWS configurations for different regions, have separate nodes. section 5.2.1
- Resource meta nodes group resources when count is greater than one, chiefly to make graph representations easier to read. section 5.2.1
Modules organize reusable configuration, but they are not nodes in the main graph. Terraform schedules the underlying resources according to their relationships rather than processing each module as one indivisible unit. A resource in one module can therefore be created before some resources in another module that it otherwise depends on. section 5.2.3
Inspecting a plan
Run terraform plan to see proposed actions, or terraform plan -out=example.tfplan to save the full plan for later application. The terminal output is a readable representation, not the full plan file. In the illustrated TLS-module output, reading upward from the bottom moves from resources with no dependencies toward those deeper in the dependency tree. terraform graph and terraform show offer other human-readable views. section 5.3
Graph construction also explains a common planning failure: Terraform must know count and for_each values at plan time to create the right resource nodes. A value available only after apply, such as a resource-generated ID or IP address, cannot determine how many nodes to create. If a conditional count uses an attribute of a DNS record, try basing the condition on the known input variable instead. section 5.7.5
Listing 5.14 Avoiding the computed value in count
variable "domain" {
description = "The base domain to use for DNS records."
type = string
}
locals {
use_eip = endswith("example.com", var.domain) #1
ip_address = local.use_eip ? aws_eip.example[0].public_ip :
aws_instance.example.public_ip
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"]
}
resource "aws_instance" "example" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
}
resource "aws_eip" "example" {
count = local.use_eip ? 1 : 0 #2
domain = "vpc"
}
resource "aws_eip_association" "eip_assoc" {
count = local.use_eip ? 1 : 0 #3
instance_id = aws_instance.example.id
allocation_id = aws_eip.example[0].id
}
data "aws_route53_zone" "example" {
name = var.domain
private_zone = true
}
resource "aws_route53_record" "example" {
zone_id = data.aws_route53_zone.example.id
name = "instance.${data.aws_route53_zone.example.id}"
type = "A"
records = [local.ip_address]
}
#1 Rather than depend on the resource, we’re using the variable directly.
#2 Now this will work, as the value can be resolved during the planning phase.
#3 This will also work.
The corrected configuration bases local.use_eip on var.domain, then uses that known condition for aws_eip count instead of deriving it from a DNS-record attribute. section 5.7.5
