Skip to main content

Command Palette

Search for a command to run...

5.1 Terraform Dynamic Blocks and For-Each Loops: Smarter Infrastructure Definitions

Updated
4 min read
5.1 Terraform Dynamic Blocks and For-Each Loops: Smarter Infrastructure Definitions

Terraform Dynamic Blocks and For-Each Loops: Smarter Infrastructure Definitions

Hey everyone! Ever felt like you were copy-pasting blocks of code in your Terraform configurations, just changing minor details for slightly different resources? That's a sign you could be making your infrastructure definitions smarter and more maintainable. Today, we're diving into two powerful Terraform features that will help you do just that: Dynamic Blocks and For-Each Loops.

Think of your Terraform code as a recipe. If you want to bake five identical cakes, you wouldn't copy-paste the entire recipe five times, right? You'd just make one recipe and bake it five times! Dynamic Blocks and For-Each Loops let you do something similar for your infrastructure.

What are Dynamic Blocks?

Imagine you're ordering a pizza. Some people want pepperoni, some want mushrooms, some want both, and some want neither. A dynamic block in Terraform lets you define optional blocks of configuration based on conditions. Think of it as saying, "If the customer wants pepperoni, then add the pepperoni ingredients."

In essence, a dynamic block lets you conditionally generate repetitive configurations within a resource. It's great for situations where certain arguments are only needed based on specific conditions.

What are For-Each Loops?

For-Each loops are more straightforward. They allow you to create multiple resources or configurations based on a list or a map. Continuing the cake analogy, if you want to bake five identical cakes, you would use the for-each loop with the number 5.

Real-World Example: Kubernetes Network Policies

Let's say we're deploying a Kubernetes application and want to create Network Policies to control traffic flow. We want to create policies for each namespace, and each namespace might have different allowed ingress rules.

resource "kubernetes_network_policy" "example" {
  for_each = var.namespaces

  metadata {
    name      = "allow-from-${each.key}"
    namespace = each.key
  }

  spec {
    pod_selector {
      match_labels = {
        app = "my-app"
      }
    }

    # Using Dynamic Blocks for ingress rules
    dynamic "ingress" {
      for_each = each.value.allowed_namespaces

      content {
        from {
          namespace_selector {
            match_labels = {
              name = ingress.value
            }
          }
        }
      }
    }

    policy_types = ["Ingress"]
  }
}

variable "namespaces" {
  type = map(object({
    allowed_namespaces = list(string)
  }))
  default = {
    "namespace-a" = {
      allowed_namespaces = ["namespace-b", "namespace-c"]
    }
    "namespace-b" = {
      allowed_namespaces = ["namespace-a"]
    }
    "namespace-c" = {
      allowed_namespaces = []  # No ingress allowed
    }
  }
}

Explanation:

  • for_each = var.namespaces: This creates a separate kubernetes_network_policy resource for each key-value pair in the namespaces variable. The each.key refers to the namespace name (e.g., "namespace-a"), and each.value refers to the object containing allowed_namespaces.

  • dynamic "ingress": This creates ingress blocks only for the namespaces listed in each.value.allowed_namespaces. If a namespace has an empty allowed_namespaces list, no ingress blocks are created.

  • ingress.value: Inside the dynamic "ingress" block, ingress.value refers to each individual element within the allowed_namespaces list.

Without Dynamic Blocks and For-Each Loops:

Imagine writing this code without these features! You'd have to duplicate the entire kubernetes_network_policy block multiple times, hardcoding the namespace names and allowed namespaces. That would be a maintenance nightmare!

Challenge: Dealing with Complex Logic within Dynamic Blocks

Sometimes, you need more complex logic inside your dynamic blocks. For example, you might want to conditionally add different types of from blocks based on whether you're allowing traffic from a namespace or an IP address.

Solution: Using Ternary Operators and Local Variables

You can use ternary operators and local variables to simplify complex logic:

dynamic "ingress" {
  for_each = each.value.allowed_sources

  content {
    dynamic "from" {
      for_each = ingress.value.type == "namespace" ? [1] : []
      content {
        namespace_selector {
          match_labels = {
            name = ingress.value.name
          }
        }
      }
    }

    dynamic "from" {
      for_each = ingress.value.type == "ip_block" ? [1] : []
      content {
        ip_block {
          cidr = ingress.value.cidr
        }
      }
    }
  }
}

variable "namespaces" {
  type = map(object({
    allowed_sources = list(object({
      type = string
      name = optional(string)
      cidr = optional(string)
    }))
  }))
  default = {
    "namespace-a" = {
      allowed_sources = [
        { type = "namespace", name = "namespace-b" },
        { type = "ip_block", cidr = "10.0.0.0/24" }
      ]
    }
  }
}

In this example, allowed_sources is now a list of objects that have a type attribute and either a name or a cidr depending on the type. We then use two dynamic blocks with ternary operators to conditionally create the correct from blocks.

Benefits of Using Dynamic Blocks and For-Each Loops:

  • Reduced Code Duplication: Write less code and avoid repetitive configurations.

  • Increased Readability: Make your Terraform configurations easier to understand.

  • Improved Maintainability: Simplify changes and updates to your infrastructure.

  • Dynamic Infrastructure: Easily adapt to changing requirements.

Conclusion:

Dynamic Blocks and For-Each Loops are essential tools in your Terraform toolkit. They empower you to create more flexible, maintainable, and efficient infrastructure definitions. Start experimenting with them today, and you'll quickly see the benefits! Happy Terraforming!

More from this blog

TechZen

136 posts