Terraform’s Dependency Management: locals
Introduction:
When working with Terraform, one issue that can cause confusion is management of dependencies between resources. A common pitfall, often overlooked, involves the timing and order of resource creation, especially when dealing with local variables and resource attributes. Here is a quick example
The Issue:
In a recent Terraform project, an I got the following error was encountered while creating Azure resources. The code aimed to create a managed disk in a newly defined resource group, but it failed with a ResourceGroupNotFound
error.
│ Error: creating/updating Managed Disk "vm-example-basic-usage000-datadisk000" (Resource Group "rg-baron-tootsie"): performing CreateOrUpdate: unexpected status 404 with error: ResourceGroupNotFound: Resource group 'rg-baron-tootsie' could not be found.
│
│ with module.linux_vm_datadisks.azurerm_managed_disk.datadisks["vm-example-basic-usage000-datadisk000"],
│ on .terraform\modules\linux_vm_datadisks\modules\linux_virtual_machine_datadisks\main.tf line 83, in resource "azurerm_managed_disk" "datadisks":
│ 83: resource "azurerm_managed_disk" "datadisks" {
│
Yet my upon inspection my rg-baron-tootsie resource group was there in Azure
So terraform is saying the resource group is not there, but it was there !!!
Here is a snip of my offending code
locals {
title = random_shuffle.title.result[0]
funny_pet_name = random_shuffle.funny_pet_name.result[0]
combined_name = "${local.title}-${local.funny_pet_name}"
secret_value = "secretvalue_${local.combined_name}"
truncated_name = length(local.combined_name) > 20 ? substr(local.combined_name, 0, 20) : local.combined_name
secret_name = substr(local.truncated_name, -1, 1) == "-" ? substr(local.truncated_name, 0, length(local.truncated_name) - 1) : local.truncated_name
vn_name = "vn-${local.secret_name}"
sn_name = "sn-${local.secret_name}"
ni_name = "ni-${local.secret_name}"
vm_name_prefix = "vm-example-basic-usage"
vm_name = "${local.vm_name_prefix}000"
resource_group_name = "rg-${local.secret_name}"
}
resource "azurerm_resource_group" "example" {
name = local.resource_group_name
location = var.location
}
module "linux_vm_datadisks" {
source = "git@github.com:GlennTrusted/terramodules.git//modules/linux_virtual_machine_datadisks?ref=Version1"
dd_location = var.location
dd_resource_group_name = local.resource_group_name
dd_linux_vm_builds = local.linux_vm_builds
dd_vm_payload = local.vm_payload
dd_tags = { "Purpose" = "Validation of Terraform Module Only" }
}
Repeating the apply again another example unit test worked without issues. What was going wrong?
Root Cause:
The crux of the problem lay in the way Terraform computes local values and manages dependencies. Here, dd_resource_group_name
was assigned from local.resource_group_name
, which Terraform evaluated before the actual creation of the resource group.
The Fix
module "linux_vm_datadisks" {
source = "git@github.com:GlennTrusted/terramodules.git//modules/linux_virtual_machine_datadisks?ref=Version1"
dd_location = var.location
dd_resource_group_name = azurerm_resource_group.example.name
dd_linux_vm_builds = local.linux_vm_builds
dd_vm_payload = local.vm_payload
dd_tags = { "Purpose" = "Validation of Terraform Module Only" }
}
Why does this work? Since the value of local.resource_group_name and the value of azurerm_resource_group.example.name are exactly the same. By directly referencing the resource group’s name from the azurerm_resource_group.example
resource. This created an explicit dependency, ensuring that Terraform would first create the resource group before attempting to create the managed disk.
Key Takeaway:
This scenario underscores the importance of understanding Terraform’s dependency graph. Explicit references to resource attributes create clear dependencies, guiding Terraform on the correct order of operations. In contrast, local values do not establish such dependencies, potentially leading to errors if Terraform attempts to use them prematurely.