Terraform Resource Creation Order - depends_on

Terraform Resource Creation Order - depends_on
In: Terraform NetDevOps Palo Alto

Have you ever tried to make a sandwich only to realize you need to toast the bread before you can add the butter or roast a chicken and realized that you forgot to preheat the oven? Managing dependencies is a crucial part of any task, and the same is true when working with infrastructure as code using Terraform. Fortunately, Terraform's depends_on feature can help you manage dependencies between resources in your configuration.

In this blo post, we will go through an example of how to use depends_onmeta-argument with Palo Alto Firewall's pan_os provider.

Overview

Terraform determines the order of resource creation and updates based on dependencies defined in your configuration file and through the use of the depends_on argument. However, Terraform cannot always automatically determine the correct order of resource creation or updates, especially in complex configurations.

Let's say I'm only creating a single Interface and a single Zone, and at the end adding the interface to the zone. The configuration file may look like the one below.

How Terraform Automatically Determines Dependencies Between Resources?

resource "panos_panorama_ethernet_interface" "ports" {
    template = "test_template"
    name = "ethernet1/1"
    mode = "layer3"
    static_ips = ["10.1.1.1/24"]
    comment = "test interface"
}

resource "panos_zone" "zones" {
  template   = "test_template"
  name       = "TEST_ZONE"
  mode       = "layer3"
  interfaces = [panos_panorama_ethernet_interface.ports.name]
}

in this example configuration, Terraform will automatically know the dependency between the panos_panorama_ethernet_interfaceresource and the panos_zone resource because the interfaces argument in the panos_zone resource references the name of the Ethernet interface created by the panos_panorama_ethernet_interface resource using the expression panos_panorama_ethernet_interface.ports.name.

When Terraform processes the configuration, it builds a dependency graph that includes all the resources and their dependencies. In this case, when Terraform processes the panos_zone resource, it sees that it depends on the panos_panorama_ethernet_interface resource because the interfaces argument references the name of the Ethernet interface resource. As a result, Terraform will ensure that the Ethernet interface resource is created or updated before the zone resource is created or updated.

Explicitly defining dependencies using "depends_on"

The following configuration creates Ethernet interfaces and Zones in Panorama. The Ethernet interfaces are created using a for_each loop that iterates over the key-value pairs in the interfaces variable, which specifies the IP address, zone, and comment for each Ethernet interface.

The panos_panorama_ethernet_interface resource is used to create the Ethernet interfaces in the template, and the panos_zone resource is used to create the corresponding zones, with each zone referencing the Ethernet interfaces using the interfaces argument.

variable interfaces {
    default = {
    "ethernet1/1" = { ip_address = "10.10.1.1/24", zone = "USERS", comment = "user traffic" }
    "ethernet1/2" = { ip_address = "10.10.2.1/24", zone = "SERVERS", comment = "web traffic" }
    "ethernet1/3" = { ip_address = "10.10.3.1/24", zone = "SERVERS", comment = "app traffic" }
    "ethernet1/10" = { ip_address = "1.1.1.1/30", zone = "OUTSIDE", comment = "internet traffic" }
    }
}
resource "panos_panorama_ethernet_interface" "ports" {
    template = "test_template"
    for_each = var.interfaces
    name = each.key
    mode = "layer3"
    static_ips = [each.value.ip_address]
    comment = each.value.comment
}

resource "panos_zone" "zones" {
  template = "test_template"
  for_each   = var.interfaces
  name       = each.value.zone
  mode       = "layer3"
  interfaces = [
    for k, v in var.interfaces :
    k if v.zone == each.value.zone
  ]
}

In this case, Terraform is creating the zones before the Ethernet interfaces because it hasn't been explicitly told that the zones depend on the Ethernet interfaces. Without this dependency, Terraform may create the zones before the Ethernet interfaces, causing issues when the zones try to reference interfaces that don't yet exist.

To solve this issue, you can use Terraform's depends_on argument to explicitly specify that the zones depend on the Ethernet interfaces, ensuring that the interfaces are created before the zones. You just need a single line for this to work, just add line #19.

resource "panos_panorama_ethernet_interface" "ports" {
    template = "test_template"
    for_each = var.interfaces
    name = each.key
    mode = "layer3"
    static_ips = [each.value.ip_address]
    comment = each.value.comment
}

resource "panos_zone" "zones" {
  template = "test_template"
  for_each   = var.interfaces
  name       = each.value.zone
  mode       = "layer3"
  interfaces = [
    for k, v in var.interfaces :
    k if v.zone == each.value.zone
  ]
  depends_on = [panos_panorama_ethernet_interface.ports]
}

By adding the depends_on argument to the panos_zone resource and specifying that it depends on the panos_panorama_ethernet_interface resource, Terraform will ensure that the Ethernet interfaces are created before the zones, preventing any issues caused by the zones referencing non-existent interfaces.

Code Breakdown

If you are interested in how the for_each loop works, here is a breakdown.

  1. for_each = var.interfaces - This tells Terraform to create a panos_zone resource for each key-value pair in the var.interfaces map. In this loop, each.key represents the current interface name (e.g., "ethernet1/1"), and each.value represents the map containing the interface's IP address, zone, and comment.
  2. name = each.value.zone - The name attribute is set to the value of the zone property for the current interface in the iteration. This means each zone will have a unique name, based on the zones defined in the interfaces map.
  3. mode = "layer3" - This sets the mode of the zone to Layer 3.
  4. interfaces = [...] - The interfaces attribute is set to an array that contains the interface names that belong to the current zone in the iteration. The array is created using a list comprehension, which is explained in the next step.
  5. for k, v in var.interfaces : k if v.zone == each.value.zone - This line is a list comprehension that iterates over the var.interfaces map again. k represents the interface name (e.g., "ethernet1/1"), and v represents the map containing the interface's IP address, zone, and comment. The list comprehension filters and collects the interface names (k) if the zone property of the current interface (v.zone) matches the zone name of the current iteration (each.value.zone). This is how the interfaces attribute is populated with the relevant interface names for each zone.

In summary, the for_each in the panos_zone resource iterates over the var.interfaces map, creating a zone for each unique interface configuration, and assigns the appropriate interfaces to each zone based on the zone property.

Closing up

In conclusion, the depends_on argument is useful for managing resource dependencies in your configuration. But adding explicit dependencies between resources using depends_on can increase the complexity of your configuration, making it more difficult to read and understand.

Table of Contents
Written by
Suresh Vina
Tech enthusiast sharing Networking, Cloud & Automation insights. Join me in a welcoming space to learn & grow with simplicity and practicality.
Comments
More from Packetswitch
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Packetswitch.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.