0

I need help to run for_each on terraform for following variable set

locals {
  db_users = {
    test_user1 = {      #user
      test_cluster1 = { #cluster
        db_name = ["db_a", "db_b", "db_c"]
        db_role = ["readWrite", "read", "readWrite"]
        db_type = ["CLUSTER", "CLUSTER", "CLUSTER"]
      },
      test_cluster2 = {
        db_name = ["db_a", "db_b", "db_c"]
        db_role = ["readWrite", "read", "readWrite"]
        db_type = ["CLUSTER", "CLUSTER", "CLUSTER"]
      }
    },
    test_user2 = {
      test_cluster1 = {
        db_name = ["db_d", "db_e", "db_f"]
        db_role = ["readWrite", "readWrite", "read"]
        #db_type = [["CLUSTER", "LAKE"], ["CLUSTER", "LAKE"], ["CLUSTER", "LAKE"]]
        db_type = ["CLUSTER", "CLUSTER", "CLUSTER"]
      },
      test_cluster2 = {
        db_name = ["db_d", "db_e", "db_f"]
        db_role = ["readWrite", "readWrite", "read"]
        db_type = ["CLUSTER", "CLUSTER", "CLUSTER"]
      }
    }
  }

The db_type can be both based on the situation. If we have multiple db_type then both value should be associated with the final resource.

I also tried flatten of the variable with following

value = flatten([
  for ip_key, ip in local.db_users : [
    for a, b in ip : [
      for index in range(length(b.db_name)) : {
        username = ip_key
        user_index = index
        roles = {
          role_name     = b.db_role[index]
          database_name = b.db_name[index]
        }
        scopes = {
          type = b.db_type[index]
          name = a
        }
      }
    ]
  ]
])

Output after flattening the value

[
  {
    "roles" = {
      "database_name" = "db_a"
      "role_name" = "readWrite"
    }
    "scopes" = {
      "name" = "test_cluster1"
      "type" = "CLUSTER"
    }
    "username" = "test_user1"
  },
  {
    "roles" = {
      "database_name" = "db_b"
      "role_name" = "read"
    }
    "scopes" = {
      "name" = "test_cluster1"
      "type" = "CLUSTER"
    }
    "username" = "test_user1"
  },
   ...
  {
    "roles" = {
      "database_name" = "db_d"
      "role_name" = "readWrite"
    }
    "scopes" = {
      "name" = "test_cluster1"
      "type" = "CLUSTER"
    }
    "username" = "test_user2"
  },
  {
    "roles" = {
      "database_name" = "db_e"
      "role_name" = "readWrite"
    }
    "scopes" = {
      "name" = "test_cluster1"
      "type" = "CLUSTER"
    }
    "username" = "test_user2"
  },
   ...
]

Looking into the type of the value, it is as follows

tuple([
    object({
        roles: object({
            database_name: string,
            role_name: string,
        }),
        scopes: object({
            name: string,
            type: string,
        }),
        username: string,
    }),
    ...
    object({
        roles: object({
            database_name: string,
            role_name: string,
        }),
        scopes: object({
            name: string,
            type: string,
        }),
        username: string,
    }),
])

Question: What I want to achieve???

  • I want to create the resource from this particular variable

The resource which is responsible for this is the following. There can be as many roles and scopes in the same block as possible. For this we can you dynamic block setup

resource "users" "user" {
  username = var.username
  roles {
    database_name = var.database_name
    role_name     = var.role_name
  }
  roles {
    database_name = var.database_name
    role_name     = var.role_name
  }
  ...
  scopes {
    name = var.cluster
    type = var.type
  }
  scopes {
    name = var.cluster
    type = var.type
  }
  ...
}

and the resource should finally look like the following value.

Resource No. 1

username=test_user1
role = {
  db_name=db_a
  role=readWrite
}
role = {
  db_name=db_b
  role=read
}
role = {
  db_name=db_c
  role=readWrite
}
scope = {
  name = test_cluster1
  type = "cluster"
}
scope = {
  name = test_cluster1
  type = "lake"
}

Resource No. 2

username=test_user1
role = {
  db_name=db_d
  role=readWrite
}
role = {
  db_name=db_e
  role=read
}
role = {
  db_name=db_f
  role=readWrite
}
scope = {
  name = test_cluster2
  type = "cluster"
}
scope = {
  name = test_cluster2
  type = "lake"
}

Resource No. 3

username=test_user1
role = {
  db_name=db_a
  role=readWrite
}
role = {
  db_name=db_b
  role=read
}
role = {
  db_name=db_c
  role=readWrite
}
scope = {
  name = test_cluster1
  type = "cluster"
}
scope = {
  name = test_cluster1
  type = "lake"
}

Resource No. 4

username=test_user2
role = {
  db_name=db_d
  role=readWrite
}
role = {
  db_name=db_e
  role=read
}
role = {
  db_name=db_f
  role=readWrite
}
scope = {
  name = test_cluster2
  type = "cluster"
}
scope = {
  name = test_cluster2
  type = "lake"
}

Exact Source code used. ( I know this is not correct). here username, database_name, role_name, name, and type all should be a string value

resource "users" "user" {
  for_each = local.db_users
  username = each.key
  dynamic "roles" {
    for_each = each.value
    content {
      database_name = each.value.db_name
      role_name     = each.value.db_role
    }
  }
  dynamic "scopes" {
    for_each = each.value
    content {
      name = each.key
      type = each.value.db_type
    }
  }
}

Error

│ Error: Unsupported attribute
│
│   on database_users.tf line 25, in resource "mongodbatlas_database_user" "user":
│   25:       database_name = each.value.db_name
│     ├────────────────
│     │ each.value is object with 2 attributes
│
│ This object does not have an attribute named "db_name".
╷
│ Error: Unsupported attribute
│
│   on database_users.tf line 26, in resource "mongodbatlas_database_user" "user":
│   26:       role_name     = each.value.db_role
│     ├────────────────
│     │ each.value is object with 2 attributes
│
│ This object does not have an attribute named "db_role".
╵
╷
│ Error: Unsupported attribute
│
│   on database_users.tf line 33, in resource "mongodbatlas_database_user" "user":
│   33:       type = each.value.db_type
│     ├────────────────
│     │ each.value is object with 2 attributes
│
│ This object does not have an attribute named "db_type".
╵

PS: considering the above flatten value is not used. How to solve this using the same db_users contents?

PPS: I am also okay with using of flatten value as long as it serves my purpose.

9
  • Ok, so what is the problem? Did you try using for_each or count to create your resource "users" "user"? Commented Feb 28, 2022 at 7:16
  • Also what is type = "lake"? Your local db_users does not have any lake in it? Commented Feb 28, 2022 at 7:24
  • Yes I, did try using for_each but I did not succeed while trying to get the exact output as I required as shown in the resource section. the type = lake is the type of cluster. so basically if I have a clusterA with type=cluster and clusterB type=lake both the cluster should be added on the scope of the database. Commented Feb 28, 2022 at 7:55
  • "I did not succeed " - is not specific. Can you provide exact code that you tried and the detailed error message you obtained? Commented Feb 28, 2022 at 8:01
  • I have updated the question now for your relevance Commented Feb 28, 2022 at 8:12

1 Answer 1

2

Your db_users should be flattened in a different way, namely:

locals {
 
    db_users_flat = merge([
    for username, clusters in local.db_users : 
      {
         for clustername, cluster in clusters : 
           "${username}-${clustername}" => {
                 username = username
                 clustername = clustername
                 cluster = cluster
            }
       }
  ]...) # please do NOT remove the dots
 
}

then

resource "users" "user" {
  for_each = local.db_users_flat
  username = each.value.username
  dynamic "roles" {
    for_each = range(length(each.value.cluster.db_name))
    content {
      database_name = each.value.cluster.db_name[roles.key]
      role_name     = each.value.cluster.db_role[roles.key]
    }
  }
  dynamic "scopes" {
    for_each = range(length(each.value.cluster.db_type))
    content {
      name = each.value.clustername
      type = each.value.cluster.db_type[scopes.key]
    }
  }
}
Sign up to request clarification or add additional context in comments.

7 Comments

Hi, this seems to work but I have few doubts and queries here if you dont mind answering. In the type of scope section how do you associate the value for multiple db_type. (The value added might not be a proper way to pass, I would love your suggestion on that as well) for example, if my db_type has both values let's say db_type = [["CLUSTER", "LAKE"], ["CLUSTER", "LAKE"], ["CLUSTER", "LAKE"]] by this what I mean is, my db_user should have access to db of both cluster as well as lake at the same time if both the keys are present
@dempti The answer is based on your example db_users. If it has different format of db_type then it will not work.
just tried this value db_type = [["CLUSTER", "LAKE"], ["CLUSTER", "LAKE"], ["CLUSTER", "LAKE"]] but it seems to fail with the following error ╷ │ Error: Incorrect attribute value type │ │ on database_users.tf line 143, in resource "mongodbatlas_database_user" "user": │ 143: type = each.value.cluster.db_type[scopes.key] │ ├──────────────── │ │ each.value.cluster.db_type is tuple with 3 elements │ │ Inappropriate value for attribute "type": string required. What changes are required if I was to use the value that shared on this comment? I will try as suggested
Also what does ... represent on the flatten step?
really appreciate your help @Marcin if you could upvote my question as well or edit it as per understanding would be great.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.