terraformconditional-statements

Conditional creation of some resource items based on variables in Terraform?


I’m exploring Terraform for automated creation of NAT rules on a Palo Alto firewall using the panos_nat_rule_group resource.

A NAT rule have multiple options for the “Translated Packet”. We can translate the “Source” and/or the “Destination” IP of the packet. Furthermore, there are several translation options of these IPs.

Is it possible to create a variable which includes all possible options like this:

variable natRules {
    description = "Variable for the NAT rules"
    type = list(object({
        name                    = string
        description             = string
        audit_comment           = string

        original_packet   = object({
            source_zones            = list(string)
            destination_zone        = string
            destination_interface   = string
            service                 = string
            source_addresses        = list(string)
            destination_addresses   = list(string)
        })

        translated_packet = object({
            source = object({
                static_ip = object({
                    translated_address = string
                    bi_directional = bool
                })
                dynamic_ip_and_port = object({
                    translated_address = object({
                        translated_addresses = list(string)
                    })
                    interface_address = object({
                        interface = string
                        ip_address = string
                    })
                })
                dynamic_ip = object({
                    translated_addresses = list(string)
                })
            })
            destination = object({
                static_translation = object({
                    address = string
                    port = number
                })
                dynamic_translation = object({
                    address = string
                    port = number
                    distribution = string
                })
            })
        })
    }))
}

Then in the “terraform.tfvars” to put the values of the different NAT rules but populate only what is needed for the particular rule. For example, if we want to translate only the Source IP to a particular static IP to populate only the necessary objects in the variable and leave the rest empty. If we want to make another translation again to put only the needed values.

natRules = [
    {
        name                    = "Static-IP-Port"
        description             = "Test nat rule"
        audit_comment           = ""

        original_packet = {
            source_zones            = ["Inside"]
            destination_zone        = "Outside"
            destination_interface   = "any"
            service                 = "any"
            source_addresses        = ["any"]
            destination_addresses   = ["any"]
        }

        translated_packet = {
            source = {
                static_ip = {
                    translated_address = "10.1.1.1"
                    bi_directional = false
                }
                dynamic_ip_and_port = {
                    translated_address = {
                        translated_addresses = [""]
                    }
                    interface_address = {
                        interface = ""
                        ip_address = ""
                    }
                }
                dynamic_ip = {
                    translated_addresses = [""]
                }
            }
            destination = {
                static_translation = {
                    address = ""
                    port = null
                }
                dynamic_translation = {
                    address = ""
                    port = null
                    distribution = ""
                }
            }
        }
    },
    {
        name                    = "Dynamic-IP-Port"
        description             = "Test nat rule"
        audit_comment           = ""

        original_packet = {
            source_zones            = ["Inside"]
            destination_zone        = "Outside"
            destination_interface   = "any"
            service                 = "any"
            source_addresses        = ["any"]
            destination_addresses   = ["any"]
        }

        translated_packet = {
            source = {
                static_ip = {
                    translated_address = ""
                    bi_directional = false
                }
                dynamic_ip_and_port = {
                    translated_address = {
                        translated_addresses = [""]
                    }
                    interface_address = {
                        interface = "ethernet1/1"
                        ip_address = "11.11.11.1/24"
                    }
                }
                dynamic_ip = {
                    translated_addresses = [""]
                }
            }
            destination = {
                static_translation = {
                    address = ""
                    port = null
                }
                dynamic_translation = {
                    address = ""
                    port = null
                    distribution = ""
                }
            }
        }
    }
]

Then in main.tf to have a single resource only which will check what variables are set and based on that to apply only that part of the resource. What kind of conditional checking can be implemented?

resource "panos_nat_rule_group" "CreateNATRules" {
    dynamic "rule" {
        for_each = var.natRules

        content {
            name                      = rule.value.name
            description               = rule.value.description
            audit_comment             = rule.value.audit_comment

            original_packet {
                source_zones          = rule.value.original_packet.source_zones
                destination_zone      = rule.value.original_packet.destination_zone
                destination_interface = rule.value.original_packet.destination_interface
                service               = rule.value.original_packet.service
                source_addresses      = rule.value.original_packet.source_addresses
                destination_addresses = rule.value.original_packet.destination_addresses               
            }

            translated_packet {
                source {
                    static_ip {
                        translated_address = rule.value.translated_packet.source.static_ip.translated_address
                        bi_directional = rule.value.translated_packet.source.static_ip.bi_directional
                    }
                    dynamic_ip_and_port {
                        interface_address {
                            interface = rule.value.translated_packet.source.dynamic_ip_and_port.interface_address.interface
                            ip_address = rule.value.translated_packet.source.dynamic_ip_and_port.interface_address.ip_address
                        }
                    }
                    dynamic_ip {
                        translated_addresses  = rule.value.translated_packet.source.dynamic_ip.translated_addresses 
                    }

                }
                destination {
                    }
            }  

        }
        
    }

    lifecycle {
        create_before_destroy = true
    }
}

Solution

  • In the variable type definition add the optional modifier on the objects attributes that are not required in every scenario with the default value set to null.

    Then in tfvars specify only values that are required in the particular use case. Do not set them to empty strings, don't specify them at all.

    On the resource level use nested dynamic blocks with a condition in the for_each statement to translate only required values:

    for_each = <SOME_OBJECT_ATTRIBUTE> != null ? {SOME_MAP_FROM_VAR} : {}

    Empty {} means do nothing if the conditional results to false.