jjfromnh
jjfromnh

Reputation: 119

How do you define Terraform locals that include nested blocks

Here's an example access policy resource using Zscaler's ZPA Terraform provider. My question is, how can I define the three conditions as a locals variable that can be used as dynamic block with a for_each? I need to include these same conditions blocks in dozens/hundreds of access policy resources, and it would be nice if I didn't have to copy/paste that entire section of code.

resource "zpa_policy_access_rule" "policy-name" {
  name             = "policy-name"
  description      = "description"
  action           = "ALLOW"
  default_rule     = false
  lss_default_rule = false
  operator         = "AND"    

  conditions {
    negated  = false
    operator = "OR"
    operands {
      object_type = "POSTURE"
      lhs         = data.zpa_posture_profile.lvl_1_check_1.posture_udid
      rhs         = true
    }
    operands {
      object_type = "POSTURE"
      lhs         = data.zpa_posture_profile.lvl_1_check_2.posture_udid
      rhs         = true
    }
    operands {
      object_type = "POSTURE"
      lhs         = data.zpa_posture_profile.lvl_1_check_3.posture_udid
      rhs         = true
    }
  }

  conditions {
    negated  = false
    operator = "OR"
    operands {
      object_type = "POSTURE"
      lhs         = data.zpa_posture_profile.lvl_2_check_1.posture_udid
      rhs         = true
    }
    operands {
      object_type = "POSTURE"
      lhs         = data.zpa_posture_profile.lvl_2_check_2.posture_udid
      rhs         = true
    }
  }

  conditions {
    negated  = false
    operator = "AND"
    operands {
      object_type = "POSTURE"
      lhs         = data.zpa_posture_profile.lvl_3_check_1.posture_udid
      rhs         = true
    }
    operands {
      object_type = "POSTURE"
      lhs         = data.zpa_posture_profile.lvl_3_check_2.posture_udid
      rhs         = true
    }
  }
}

I figured out how to create locals for the "operands" contained within the "conditions" blocks, for example:

locals {

  LEVEL_1_CHECKS = [
    {
      object_type = "POSTURE"
      rhs = true
      lhs = data.zpa_posture_profile.lvl_1_check_1.posture_udid
    },
    {
      object_type = "POSTURE"
      rhs = true
      lhs = data.zpa_posture_profile.lvl_1_check_2.posture_udid
    },
    {
      object_type = "POSTURE"
      rhs = true
      lhs = data.zpa_posture_profile.lvl_1_check_3.posture_udid
    }
  ]

  LEVEL_2_CHECKS = [
    {
      object_type = "POSTURE"
      rhs = true
      lhs = data.zpa_posture_profile.lvl_2_check_1.posture_udid
    },
    {
      object_type = "POSTURE"
      rhs = true
      lhs = data.zpa_posture_profile.lvl_2_check_2.posture_udid
    }
  ]

  LEVEL_3_CHECKS = [
    {
      object_type = "POSTURE"
      rhs = true
      lhs = data.zpa_posture_profile.lvl_3_check_1.posture_udid
    },
    {
      object_type = "POSTURE"
      rhs = true
      lhs = data.zpa_posture_profile.lvl_3_check_2.posture_udid
    }
  ]
}

However, I'm still stuck with a lot of repeating code when creating new policy resources:

resource "zpa_policy_access_rule" "policy-name" {
  name             = "policy-name"
  description      = "description"
  action           = "ALLOW"
  default_rule     = false
  lss_default_rule = false
  operator         = "AND"    

  conditions {
    negated  = false
    operator = "OR"
    dynamic "operands" {
      for_each = local.LEVEL_1_CHECKS
      content {
        object_type = operands.value.object_type
        rhs = operands.value.rhs
        lhs = operands.value.lhs
      }
    }
  }

  conditions {
    negated  = false
    operator = "AND"
    dynamic "operands" {
      for_each = local.LEVEL_2_CHECKS
      content {
        object_type = operands.value.object_type
        rhs = operands.value.rhs
        lhs = operands.value.lhs
      }
    }
  }

  conditions {
    negated  = false
    operator = "OR"
    dynamic "operands" {
      for_each = local.LEVEL_3_CHECKS
      content {
        object_type = operands.value.object_type
        rhs = operands.value.rhs
        lhs = operands.value.lhs
      }
    }
  }
}

How can I define all of the "conditions" blocks using locals? They never change, so I'm assuming this should be a straightforward task, but as it is now I'm going to have to copy/paste 50+ lines into every new resource to define these items.

This is using the Zscaler ZPA Terraform provider: https://registry.terraform.io/providers/zscaler/zpa/latest

Upvotes: 0

Views: 1675

Answers (1)

Marcin
Marcin

Reputation: 238189

You can nest dynamic blocks. This will allow you to have one local variable, instead of three:

locals {

  LEVEL_CHECKS  = {
    LEVEL_1_CHECKS = {
       negated  = false
       operator = "OR"  
       checks = [
            {
            object_type = "POSTURE"
            rhs = true
            lhs = data.zpa_posture_profile.lvl_1_check_1.posture_udid
            },
            {
            object_type = "POSTURE"
            rhs = true
            lhs = data.zpa_posture_profile.lvl_1_check_2.posture_udid
            },
            {
            object_type = "POSTURE"
            rhs = true
            lhs = data.zpa_posture_profile.lvl_1_check_3.posture_udid
            }
        ]
    },
    LEVEL_2_CHECKS = {
        negated  = false
        operator = "AND"  
        checks = [
            {
            object_type = "POSTURE"
            rhs = true
            lhs = data.zpa_posture_profile.lvl_2_check_1.posture_udid
            },
            {
            object_type = "POSTURE"
            rhs = true
            lhs = data.zpa_posture_profile.lvl_2_check_2.posture_udid
            }
        ]
    },
    LEVEL_3_CHECKS = {
        negated  = false
        operator = "OR"
        checks = [
            {
            object_type = "POSTURE"
            rhs = true
            lhs = data.zpa_posture_profile.lvl_3_check_1.posture_udid
            },
            {
            object_type = "POSTURE"
            rhs = true
            lhs = data.zpa_posture_profile.lvl_3_check_2.posture_udid
            }
        ]
    }
    }
}

then

  
  dynamic "conditions" {
    for_each = local.LEVEL_CHECKS
    content {
        negated  = conditions.value.negated
        operator = conditions.value.operator
        dynamic "operands" {
            for_each = conditions.value.checks
            content {
                object_type = operands.value.object_type
                rhs = operands.value.rhs
                lhs = operands.value.lhs
            }
        }    
    }
  }  

Upvotes: 1

Related Questions