Search code examples
terraforminfrastructure-as-code

Terraform loop : for_each for tuple


Reference: original question Terraform loop : for_each

Similar to the question I have few changes which needs to be processed using the same method as well.

Changes

The value of db_type could be between any of these two

db_type = [["CLUSTER", "LAKE"], ["CLUSTER", "LAKE"], ["CLUSTER", "LAKE"]]

OR

db_type = ["CLUSTER", "CLUSTER", "CLUSTER"]

In this case, when I try to use the same example (changes on db_type only), as per the solution original question following error is observed

╷
│ Error: Incorrect attribute value type
│
│   on database_users.tf line 143, in resource "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.
╵
╷
│ Error: Incorrect attribute value type
│
│   on database_users.tf line 143, in resource "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.
╵
╷
│ Error: Incorrect attribute value type
│
│   on database_users.tf line 143, in resource "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.

Question: What I actually want? How do I achieve that?

  • If I was to change the value of db_type as explained above following should be the outcome
  # mongodbatlas_database_user.user["test_user2-test_cluster2"] will be created
  + resource "mongodbatlas_database_user" "user" {
      + auth_database_name = "admin"
      + aws_iam_type       = "NONE"
      + id                 = (known after apply)
      + ldap_auth_type     = "NONE"
      + password           = (sensitive value)
      + project_id         = "6216f27d3f350c275ea78efb"
      + username           = "test_user2"
      + x509_type          = "NONE"

      + labels {
          + key   = (known after apply)
          + value = (known after apply)
        }

      + roles {
          + collection_name = (known after apply)
          + database_name   = "db_d"
          + role_name       = "readWrite"
        }
      + roles {
          + collection_name = (known after apply)
          + database_name   = "db_e"
          + role_name       = "readWrite"
        }
      + roles {
          + collection_name = (known after apply)
          + database_name   = "db_f"
          + role_name       = "read"
        }

      + scopes {
          + name = "test_cluster2"
          + type = "CLUSTER"
        }
      + scopes {
          + name = "test_cluster2"
          + type = "LAKE"
        }
    }

If the value of db_type = [["CLUSTER", "LAKE"], ["CLUSTER", "LAKE"], ["CLUSTER", "LAKE"]], the Expected Output should be

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"
}
...

If the value of db_type = db_type = ["CLUSTER", "CLUSTER", "CLUSTER"], the Expected Output should be

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"
}

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"
}
...

Solution

  • If I understand correctly, its better to use db_users in the following form:

      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"]]
          },
          test_cluster2 = {
            db_name = ["db_d", "db_e", "db_f"]
            db_role = ["readWrite", "readWrite", "read"]
            db_type = [["CLUSTER"], ["CLUSTER"], ["CLUSTER"]]
          }
        }
      }
    

    then you flatten as:

      
      db_users_flat = merge(flatten([
        for username, clusters in local.db_users : 
          [
             for clustername, cluster in clusters : 
             {
                 for idx, db_types in cluster.db_type: 
                     "${username}-${clustername}-${idx}" => {
                           username = username
                           clustername = clustername
                           cluster = {
                             db_name = cluster.db_name
                             db_role = cluster.db_role
                             db_type = db_types
                           }
                      }
                   }
           ]
      ])...)
    

    and use as:

    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]
          }
        }
    }