liveBook · Manning

Resource graph

As explained in Terraform in Depth: Infrastructure as Code with Terraform and OpenTofu

A Terraform resource graph is the internal graph of infrastructure resources and their dependency relationships that drives Terraform's processing order.

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

How Terraform in Depth: Infrastructure as Code with Terraform and OpenTofu covers it

How deep it goes
Defines the directed acyclic graph, identifies its node types, and explains module boundaries and plan-time graph construction without detailing graph algorithms.
Examples
Uses TLS-module plan output and a DNS-record condition controlling an elastic IP address to expose ordering and graph-construction constraints.
What it assumes
Assumes familiarity with Terraform resources and configuration references, while defining the graph’s node types and dependency rules.
What you can do after
Diagnose/debug with it when circular dependencies or values unavailable at plan time prevent Terraform from building the graph.
How it connects
Connects Terraform state, dependency graphs, provider configurations, modules, planning, count, and for_each.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Terraform in Depth: Infrastructure as Code with Terraform and OpenTofu. The section links point to the parts of the book it draws on.