Any way to do a lookup to set a variable from an object

Is there any way to set a variable to a single item from an object? I am trying to just pass a single item as a variable to a module from an object and I cannot make it work. I am trying to avoid having to do a for_each and each.value.name to do so as the resource doesn’t seem to like a for_each

My groups.auto.tfvars file looks like this

root_mgmt_grps = {
  Identity = {
    display_name        = "Identity"
    name                = "Identity"
    parent_id           = "/providers/Microsoft.Management/ManagementGroups/Platform"
    sub_association     = "true"
    sub_ids             = [] 
  }
}

I want to call a module just using a variable set to the name. I can pass the whole object to the module and do a for_each = var.root_mgmt_grps but it causes issues with some of the resources and I’d like to just singularly call the module with something like

module "testmodule" {
  source = XXXXXX
  group_name = NAME FROM root_mgmt_grps OBJECT
}

I was trying different methods to set a local variable in my root module to set the variable but I’ve had no luck with merge, lookup, etc.

Hi @danielkelly22,

If your intent here is that Identity is a fixed key that represents the only object you want to pass to the module then you can write an expression which accesses that attribute directly, like this:

module "testmodule" {
  source = "..."

  group_name = var.root_mgmt_grps.Identity.name
}

If you want to make a dynamic decision based on the number of elements in the variable value and the keys specified by the caller then I think some sort of for_each usage will be needed to achieve that, but I’m not sure what to suggest with the information given. If the above doesn’t address the problem then it would be helpful to see a bit more context about the underlying goal; for example, it can often be helpful to manually expand the set of resources you’d like to declare without using any variables, modules, or dynamic references, and then we can think about how to replace that with a more systematic declaration based on an input data structure once the goal is clear.

Your suggestion will work for this scenario, but maybe you can help me expand this a bit further. Let’s say I have that same type of object and I am using the sub_association attribute to try and pull the name of the corresponding Group i want to pass to the azurerm_management_group_subscription_association resource.

root_mgmt_grps = {
  Identity = {
    display_name        = "Identity"
    name                = "Identity"
    parent_id           = "/providers/Microsoft.Management/ManagementGroups/Platform"
    sub_association     = "true"
    sub_ids             = [] 
  }
  Testing= {
    display_name        = "Testing"
    name                = "Testing"
    parent_id           = "/providers/Microsoft.Management/ManagementGroups/Platform"
    sub_association     = ""
    sub_ids             = [] 
   }
}

Is there a way to do a local or something in the root module to pull only the name from the Identity block? Something with like a if var.root_mgmt_grps.*.name == “true” and only save the name as a variable to pass?

The situation here, is the subscription module is pretty universal and simple and accepts a single management group var passed in to create the sub and associate the management group. However, the management group sub will take the whole tfvars object and create all the management groups as needed. I am trying to see if I can use the same tfvars object to pass to both modules without have to update the subscription module to add for_each and some variable checking so it only uses the management group we flag with sub_association.

I came up with a pretty ugly and dirty way to get the management group name alone but I feel like there’s a cleaner way to do it. The end result is that it will set the management_group_name to just the group name if sub_association is set otherwise it defaults to the root management group “Platform-Modern”

locals { 
  mgmt_group_object =  { 
    for v in var.root_mgmt_grps : "Name" => v.name
    if v.sub_association != ""
  }
  management_group_object_name = lookup(local.mgmt_group_object, "Name", null)
  management_group_name = local.management_group_object_name != null ? local.management_group_object_name: "Platform-Modern"
}

Hi @danielkelly22,

I’m afraid I’m still not entirely sure how to understand your underlying need here from the parts you’ve shared, but I can at least share some snippets that might be useful building blocks for a more concise answer:

  • I can see you’ve already learned about the if clause of a for expression as a way to filter a collection. I’m seeing an implied requirement here to select all of the management groups that have a non-empty sub_association attribute, in which case I’d probably simplify that a little as follows:

    toset([
      for v in var.root_mgmt_grps : v.name
      if v.sub_association != ""
    ])
    

    This produces a set of names, rather than an object with a Name attribute. I may be misunderstanding what your intent was with that, but hopefully the next point will get the end result you wanted in a different way anyway.

  • If you’re expecting that there will either be zero or one results from that previous filter then you can use Terraform’s one function to concisely state that and have Terraform verify that it’s true. If one succeeds (that is, if there is at most one result) then it’ll produce either the single element of the set or null to represent the absense of a value:

    one(toset([
      for v in var.root_mgmt_grps : v.name
      if v.sub_association != ""
    ]))
    
  • To provide a fallback value to use if the result turns out to be null (that is, if there were no matches) then you can use the coalesce function:

    coalesce(one(toset([
      for v in var.root_mgmt_grps : v.name
      if v.sub_association != ""
    ])), "Platform-Modern")
    

I’ve presented this by gradually adding more items to a single expression, but if you find it easier to read then you can of course split it into multiple steps by declaring some local values like in the example you shared.