Dynamic blocks with variable map structure

Hi,
With the GCP provider, I would like to assign roles on projects. Theses roles can optionnaly have a condition.

I can’t figure out how to manage this

Here are 2 variables

  • create 2 SA
  • Grant permissions to SA, one of them have a condition
 variable "sas_nickname_list" {
description = "List of SAs to create at project level"*
  type        = list(string)
  default     = []
}

variable "sas_map" {
  description = "contains the project SAs specifics rights using their nickname"
  type = list(object({
  sa_nickname = string
  role        = string
  condition   = object({
                title       = string
                description = string
                expression  = string    
              })
}))
default = []
}

Values

sas_nickname_list = ["sa1","sa2"] 

sas_map = [
           {
              "sa_nickname" = "sa1"`
             "role"        = "roles/browser"`
           },`
           {
             "sa_nickname" = "sa2"
             "role"        = "roles/viewer"
              "condition" = {
                      title       = "expires_after_2021_12_31"
                      description = "Expiring at midnight of 2021-12-31"
                      expression  = "request.time < timestamp(\"2022-01-01T00:00:00Z\")"
                    }
            },
          ]

Then the code

# Create the Service Accounts
resource "google_service_account" "sas" {
  count        = length(var.sas_nickname_list)
  project      = google_project.main.project_id
  account_id   = "sa-${var.project_name}-${var.sas_nickname_list[count.index]}"
  display_name = "sa-${var.project_name}-${var.sas_nickname_list[count.index]}"
}

# Grant role using sas_map in .tfvars
resource "google_project_iam_member" "sas" {
  count      = length(var.sas_map)
  project    = google_project.main.project_id
  role       = var.sas_map[count.index]["role"]
  dynamic "condition" {
   for_each = lookup(var.sas_map[count.index],"condition",{})
   content {
             title       = condition.value.sas_map[count.index]["condition"]["title"]
             description = condition.value.sas_map[count.index]["condition"]["description"]
             expression  = condition.value.sas_map[count.index]["condition"]["expression"]
    }
  } 
  member     = "serviceAccount:sa-${var.project_name}-${var.sas_map[count.index]["sa_nickname"]}@${google_project.main.project_id}.iam.gserviceaccount.com"*
  depends_on = [google_service_account.sas]
}
output "sa1_permissions" {
  value = var.sas_map[0]*
}
output "sa2_permissions" {*
  value = var.sas_map[1]*
}
output "sa2_condition" {*
  value = var.sas_map[1]["condition"]*

}

Here is the error

sas_map = [
  [{
     "sa_nickname" = "sa1"
     "role" = "roles/browser"
  },
 {
  "sa_nickname" = "sa2"
  "role" = "roles/viewer"
  "condition" = {
          title = "expires_after_2021_12_31"
          description = "Expiring at midnight of 2021-12-31"
          expression = "request.time < timestamp(\"2022-01-01T00:00:00Z\")"
         }
 },
]

The given value is not valid for variable "sas_map": element 0: attribute
condition" is required.

Thank you

I read Type Constraints - Configuration Language - Terraform by HashiCorp
And I see at the bottom of the page that optional Object Type attributes are in fact experimental

Could someone suggest a workaround ? The only thing I imaging for the moment is to create two resources : one to grant roles without conditions and one to grant role with conditions.

Do the ETA of optional object Type Attributes is known ?

Hi @mldmld68,

To include an attribute while not assigning a value to it you can assign the value null instead. Providers cannot distinguish between a null and an omitted argument, so assigning a null directly to a resource argument is always the same as omitting it entirely.

The experimental optional attributes feature you found is designed to serve as a shorthand for this: it declares that for a particular attribute isn’t present then Terraform should automatically assign null to it. However, feedback from that experiment suggests that the design isn’t quite right yet, and so the Terraform team is planning to try a slightly different design in a future release before making it stable, and so I would not suggest depending on that experimental feature in its current form.

Hi apparentlymart,

Thank you for suggestion.
I don’t want to use an experimental feature for our production.

But today, we already have hundreds of tfvars like

sas_map = [
  [{
     "sa_nickname" = "xxxx"
     "role" = "roles/xxxx"
  },
....
]

So you mean I have to update all .tfvars like this ?

sas_map = [
  [{
     "sa_nickname" = "xxxx"
     "role" = "roles/xxxx"
     "condition" = null
  },
....
]

or like this ?

sas_map = [
  [{
     "sa_nickname" = "xxxx"
     "role" = "roles/xxxx"
     "condition" = 
     {
          title = null
          description = null
          expression = null
         }
  },
....
]

In that case, I think it would be better to declare a separate variable in the .tfvars

sas_map = [
  [{
     "sa_nickname" = "xxxx"
     "role" = "roles/xxxx"
....
]
sas_map_with condtions = [
  {
     "sa_nickname" = "xxxx"
     "role" = "roles/xxxx"
     "condition" = 
     {
          title = "xxx"
          description = "xxx"
          expression = "xxx"
         }
  },
....
]

This way, once the experimental will become GA, I could merge the contents of theses two variables

Thanks