azureterraformentra

Can data azuread_users accept null value?


I'm trying to pass null value for adding users with the below try block, but it fails

│ Error: Missing required argument │
│   with module.entragroups.azuread_privileged_access_group_eligibility_schedule.eligible-pim-assignment["group_01"],
│   on .terraform/modules/entragroups/entra/groups/main.tf line 53, in resource "azuread_privileged_access_group_eligibility_schedule" "eligible-pim-assignment":
│   53:   principal_id    = try(data.azuread_users.eligible-pim-users[each.key].object_ids[0], null)
│
│ The argument "principal_id" is required, but no definition was fou

The source code:


    resource "azuread_privileged_access_group_eligibility_schedule" "eligible-pim-assignment" {
  for_each        = var.entra_groups
  group_id        = azuread_group.group[each.key].object_id
  principal_id    = try(data.azuread_users.eligible-pim-users[each.key].object_ids[0], null)
  assignment_type = each.value.assignment_type
  #  duration             = each.value.duration
  justification        = each.value.justification
  start_date           = each.value.start_date
  expiration_date      = each.value.expiration_date
  permanent_assignment = each.value.permanent_assignment_active
}
data "azuread_users" "eligible-pim-users" {
  for_each             = var.entra_groups
  user_principal_names = each.value.eligible_pim_assignment_users
  ignore_missing       = true
}


variable "entra_groups" {
  description = "entra groups"
  type = map(object({
    display_name                  = string
    security_enabled              = optional(bool)
    assignable_to_role            = optional(bool)
    security_group_owners         = optional(list(string), [])
    active_pim_assignment_spn     = optional(list(string), [])
    assignment_type               = optional(string)
    duration                      = optional(string)
    eligible_pim_assignment_users = optional(list(string), [])
    active_pim_assignment_users   = optional(list(string), [])
    permanent_assignment_schedule = optional(bool)
    permanent_assignment_active   = optional(bool)
    justification                 = optional(string)
    start_date                    = optional(string)
    expiration_date               = optional(string)
  }))
  default = {}
}

  group_01 = {
    display_name                  = "test"
    description                   = ""
    security_enabled              = true
    assignable_to_role            = true
    active_pim_assignment_spn     = []
    assignment_type               = "member"
    duration                      = ""
    eligible_pim_assignment_users = []
    active_pim_assignment_users   = []
    permanent_assignment_schedule = false
    start_date                    = "2025-03-07T01:02:03Z"
    expiration_date               = "2026-03-01T01:02:03Z"
    permanent_assignment_active   = true
    justification                 = "as requested"
  }


Solution

  • Null values are how Terraform represents that one of the arguments of a resource hasn't been set. Setting principal_id = null is equivalent to not setting that argument at all.

    If the provider considers a particular argument to be required, then it must not be set to null. That is true regardless of whether you explicitly assign null to it or if you just omit its definition so that Terraform sets it to null automatically.


    If your goal is for instances of azuread_privileged_access_group_eligibility_schedule.eligible-pim-assignment to be declared only when there is a matching instance of data.azuread_users.eligible-pim-users then you could potentially represent that by adding an extra filtering condition to the for_each argument for that resource:

    resource "azuread_privileged_access_group_eligibility_schedule" "eligible-pim-assignment" {
      for_each = {
        for k, v in var.entra_groups : k => v
        if length(data.azuread_users.eligible-pim-users[k].object_ids) != 0
      }
    
      # ...
      principal_id = data.azuread_users.eligible-pim-users[each.key].object_ids[0]
      # ...
    }
    

    This new for_each definition uses a for expression that includes an if clause that therefore filters out any elements of var.entra_groups which don't have any "eligible users".

    Therefore the principal_id argument no longer needs the try function, because that argument will be evaluated only for each.key values where object_ids has at least one element, and therefore the [0] operation should always succeed.


    Bonus tip: If you have a list that might have either zero or one elements and you want to turn that into a single value that is either set or null, a more concise way to represent that is using the one function.

    For example, instead of try(data.azuread_users.eligible-pim-users[each.key].object_ids[0], null) you can write one(data.azuread_users.eligible-pim-users[each.key].object_ids). If there are no elements in object_ids then this will return null, but if there is exactly one element in object_ids then this will return that one element.

    This extra technique is appropriate only when you expect that there will be a maximum of one element in the given list. one will fail with an error if the list has more than one element.