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_on
meta-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_interface
resource 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.
for_each = var.interfaces
- This tells Terraform to create apanos_zone
resource for each key-value pair in thevar.interfaces
map. In this loop,each.key
represents the current interface name (e.g., "ethernet1/1"), andeach.value
represents the map containing the interface's IP address, zone, and comment.name = each.value.zone
- Thename
attribute is set to the value of thezone
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.mode = "layer3"
- This sets the mode of the zone to Layer 3.interfaces = [...]
- Theinterfaces
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.for k, v in var.interfaces : k if v.zone == each.value.zone
- This line is a list comprehension that iterates over thevar.interfaces
map again.k
represents the interface name (e.g., "ethernet1/1"), andv
represents the map containing the interface's IP address, zone, and comment. The list comprehension filters and collects the interface names (k
) if thezone
property of the current interface (v.zone
) matches the zone name of the current iteration (each.value.zone
). This is how theinterfaces
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.