Terraform - for_each + concat

Hi, how are you?

I would like some help from the community please.

I will try to detail as much as possible.

I will create users through resource "keycloak_user" "user"

After creating one or more users and adding them to the group resource "keycloak_user_groups" "user_groups".

This point is working perfectly.

main.tf

resource "keycloak_user" "user" {
  for_each = var.keycloak_users
  realm_id    = local.keycloak_realm_data
  username = each.key
  enabled  = true
  email      = each.value

  initial_password {
    value     = ""
    temporary = true
  }
}

resource "keycloak_user_groups" "user_groups" {
  for_each = keycloak_user.user
  realm_id    = local.keycloak_realm_data
  user_id  = each.value.id

  group_ids = [
    keycloak_group.group.id
  ]
}

variable.tf

variable “keycloak_users” {
type = map(any)
}

main.tf - root module

  keycloak_users = {
    "user1" = "user1@domain.com"
    "user2" = "user2@domain.com"
    "user3" = "user3@domain.com"
    "user4" = "user4@domain.com"
  }

However, there is a new demand in which in addition to adding the users created in the first keycloak_user resource within the keycloak_user_groups resource I also need to add other users.

With that decided to try to use the concat function as evidenced below:

main.tf

resource "keycloak_user" "user" {
  for_each = var.keycloak_users
  realm_id    = local.keycloak_realm_data
  username = each.key
  enabled  = true
  email      = each.value

  initial_password {
    value     = ""
    temporary = true
  }
}

resource "keycloak_user_groups" "user_groups" {
  for_each = var.keycloak_user_ext == null ? keycloak_user.user : concat(keycloak_user.user, var.keycloak_user_ext)
  realm_id    = local.keycloak_realm_data
  user_id  = each.value.id

  group_ids = [
    keycloak_group.group.id
  ]
}

I created a new variable that is not mandatory and created an if function, if it is filled in it will concatenate the value obtained from keycloak_user.user and the value of the variable var.keycloak_user_ext

The var.keycloak_user_ext variable can be 1 or more users.

variable.tf

variable "keycloak_user_ext" {
  default = ""
}

When trying to run this run, I ended up getting the following error:

╷
│ Error: Invalid function argument
│ 
│   on ../../modules/squad/main.tf line 77, in resource "keycloak_user_groups" "user_groups":
│   77:   for_each = var.keycloak_user_ext == null ? keycloak_user.user : concat(keycloak_user.user, var.keycloak_user_ext)
│     ├────────────────
│     │ while calling concat(seqs...)
│     │ keycloak_user.user is object with 4 attributes
│ 
│ Invalid value for "seqs" parameter: all arguments must be lists or tuples; got object.

Could you please help solve this problem?

Grateful

The concat function expects a list-of-lists. The code sample demonstrates this, although the documentation is a bit terse.

So this expression:

concat(keycloak_user.user, var.keycloak_user_ext)

Has two problems:

  1. keycloak_user.user is (essentially) a map of 4 keycloak_user resources (since you defined it with the for_each); and
  2. var.keycloak_user_ext is just a simple string (as far as Terraform knows).

In the body of keycloak_user_groups.user_groups, you look at each.value.id, from which we can infer that you want to iterate over multiple things, each thing having a name and an id attribute.

I’ll assume that a keycloak_user resource has an id attribute, although its documentation is unclear, at least to me.

We also need to convert your var.keycloak_user_ext string into 0 or more thing with names and id attributes. I’ll assume that you’re expecting comma-separated values, but how are you obtaining the id? I’ll further assume that you’re passing "user1=id1,user2=id2", etc.

Finally, you can only use concat with lists; the most similar operation for maps is merge.

So, doing all this in a locals block…

Split "user1=id1,user2=id2" into [ "user1=id1", "user2=id2" ]:

extra_user_id_strings = split( ",", var. keycloak_user_ext )

Split each of those on the = sign into two-element lists:

extra_user_id_lists = [
  for user_id_string in local.extra_user_id_strings :
  split( "=", user_id_string )
]

Then construct the map { user1 = "id1", user2 = "id2" }:

extra_user_id_map = {
  for user_id_list in local.extra_user_id_lists :
  user_id_list[0] => {
    name = user_id_list[0]
    id   = user_id_list[1]
  }
}

Finally, merge the “real” keycloak_users with our “synthetic” ones from above:

all_users = merge(
  keycloak_user.user,
  local.extra_user_id_map
)

At this point, you can do the group associations across both the original / real users, and the extra users:

resource "keycloak_user_groups" "user_groups" {
  for_each = local.all_users

  realm_id = local.keycloak_realm_data
  user_id  = each.value.id

  group_ids = [
    keycloak_group.group.id
  ]
}