Each.value is tuple with 2 elements

Hello,

am using csvdecode and passing values to a code block,to create single service account and multiple topic name. am getting the below error. could someone let me know if this can be done, am I doing something wrong?

name,topic-name
test-1,example-1
test-1,example-2
test-2,example-1
test-2,example-2
module "confluent-service-account" {
  for_each     = { for sa in local.service_account_name : sa.name  => sa.topic-name... }
  source       = "./modules/confluent_service_account"
  display_name = each.value.name
  description  = "${each.value.name}-service-account"
}
module "topicname" {
  source = "./modules/confluent_topic"
  for_each     = { for sa in local.service_account_name : sa.name  => sa.topic-name... }
  topic_name = each.value.topic-name
}
│ Error: Unsupported attribute
│ 
│   on main.tf line 33, in module "topicname":
│   33:   topic_name = each.value.topic-name
│     ├────────────────
│     │ each.value is tuple with 2 elements
│ 
│ This value does not have any attributes.
╵

I’m attempting to accomplish something similar to the below code

>{ for sa in local.service_account_name : sa.name => sa.topic-name... }
{
  "test-1" = {
    "example-1",
    "example-2",
  }
  "test-2" = {
    "example-1",
    "example-2",
  {
}

however, I am receiving the result listed below.

{ for sa in local.service_account_name : sa.name => sa.topic-name... }
{
  "test-1" = [
    "example-1",
    "example-2",
  ]
  "test-2" = [
    "example-1",
    "example-2",
  ]
}

Best Regards,
jjkishore

Hi @jjkishore1,

The two closing examples you’ve shared of what you want to accomplish and what your current code does seem to be the same, unless I’m missing something.

I’m going to guess that your intention is to have one instance of module.confluent-service-account for each distinct name value in your CSV, but that for module.topicname you need one instance per row in the CSV file. In other words, each row in your CSV directly represents a “topic”, and each topic is associated with a service account.

That means that the for expression you wrote is appropriate for the for_each in module "confluent-service-account" – you’re using the ... modifier to group the results by the values in the name field and so the result has one entry per distinct name – but it’s not appropriate for module "topicname" because there needs to be a one-to-one relationship between CSV records and instances of that module, and so no grouping is needed.

Instead then, you will need to concatenate the two fields together in some way to create a unique key for each (name, topic-name) pair. For example:

module "topicname" {
  source   = "./modules/confluent_topic"
  for_each = { for sa in local.service_account_name : "${sa.name}:${sa.topic-name}"  => sa }

  topic_name = each.value.topic-name
}

This declares instances with (based on your example CSV file) the following addresses:

  • module.topicname["test-1:example1"]
  • module.topicname["test-1:example2"]
  • module.topicname["test-2:example1"]
  • module.topicname["test-2:example2"]

FWIW, the typical Terraform idiom is to write identifiers with words separated with underscores rather than with dashes. Terraform allows dashes to make it convenient to specify names that will be stored in a remote system whose convention is to use dashes, but for names that are only for use within Terraform the underscore character is the recommended convention.

In other words, I would prefer to use module "confluent_service_account" instead of module "confluent-service-account", although of course that’s not mandatory if you have a strong reason to differ from the idiom.

thank you very much for the response @apparentlymart,

I made the necessary adjustments, but I get an identical account error conflict when I try to provision service_account. Would you kindly tell me how to get around this?

module "confluent-service-account" {
  for_each     = { for sa in local.service_account_name : "${sa.name}:${sa.topic-name}" => sa }
  source       = "./modules/confluent_service_account"
  display_name = each.value.name
  description  = "${each.value.name}-service-account"
}
error creating Service Account "test-1": 409 Conflict: Service name is already in use.

When I apply the following modifications, a tuple error appears.

module "confluent-service-account" {
  for_each     = { for sa in local.service_account_name : sa.name => sa... }
  source       = "./modules/confluent_service_account"
  display_name = each.value.name
  description  = "${each.value.name}-service-account"
}
each.value is tuple with 2 elements

when I don’t group the items with the below code, duplicates are expected use ellipsis

module "confluent-service-account" {
  for_each     = { for sa in local.service_account_name : sa.name => sa }
  source       = "./modules/confluent_service_account"
  display_name = each.value.name
  description  = "${each.value.name}-service-account"
}
│     │ sa.name is "test-1"
│ 
│ Two different items produced the key "test-1" in this 'for' expression. If duplicates are expected, use the ellipsis (...) after the
│ value expression to enable grouping by key.

best regards,
jjkishore

Hi @jjkishore1,

Unfortunately that new error seems to be coming from a provider that one of your modules is using, and I’m not familiar with that provider, so I can’t make any concrete suggestions.

The best I can do is suggest that you look in the source code for that module at the configuration for whichever resource raised that “error creating Service Account”, figure out which of the resource arguments corresponds to what the remote system uses “Service name” to refer to, and make sure it’s using a unique value for each instance of that resource argument.

You’ve only shown me small portions of the error messages and so I can’t tell you which resource in which module you ought to be looking at, but hopefully the first line after the error message summary says “with …” followed by the resource instance address, and then you can find the resource block that declared that resource instance.

Apologies, @apparentlymart ,

In the plan stage two, service accounts are formed; however, for the csv inputs, I anticipate the development of a single service account with several topic linked to it.

name,topic_name
test-1,example-1
test-1,example-2
Terraform will perform the following actions:

  # module.confluent-service-account["test-1:example-1"].confluent_service_account.example-sa will be created
  + resource "confluent_service_account" "example-sa" {
      + api_version  = (known after apply)
      + description  = "test-1-service-account"
      + display_name = "test-1"
      + id           = (known after apply)
      + kind         = (known after apply)
    }

  # module.confluent-service-account["test-1:example-2"].confluent_service_account.example-sa will be created
  + resource "confluent_service_account" "example-sa" {
      + api_version  = (known after apply)
      + description  = "test-1-service-account"
      + display_name = "test-1"
      + id           = (known after apply)
      + kind         = (known after apply)
    }

Even if I give it an empty spot under the name, the plan continues to be generated.

name,topic-name
test-1,example-1
         ,example-2
Terraform will perform the following actions:

  # module.confluent-service-account["    :example-2"].confluent_service_account.example-sa will be created
  + resource "confluent_service_account" "example-sa" {
      + api_version  = (known after apply)
      + description  = "    -service-account"
      + display_name = "    "
      + id           = (known after apply)
      + kind         = (known after apply)
    }

  # module.confluent-service-account["test-1:example-1"].confluent_service_account.example-sa will be created
  + resource "confluent_service_account" "example-sa" {
      + api_version  = (known after apply)
      + description  = "test-1-service-account"
      + display_name = "test-1"
      + id           = (known after apply)
      + kind         = (known after apply)
    }

Hi @jjkishore1,

Based on this plan output it seems that you replaced the for_each expression for the wrong module. The example I shared earlier was for module.topicname, not module.confluent-service-account.

Hello @apparentlymart ,

I obtain the following issue when I group them.

module "confluent-service-account" {
  for_each     = { for sa in local.service_account_name : sa.name => sa... }
  source       = "./modules/confluent_service_account"
  display_name = each.value.name
  description  = "${each.value.name}-service-account"
}
Planning failed. Terraform encountered an error while generating this plan.

╷
│ Error: Unsupported attribute
│ 
│   on main.tf line 10, in module "confluent-service-account":
│   10:   display_name = each.value.name
│     ├────────────────
│     │ each.value is tuple with 2 elements
│ 
│ This value does not have any attributes.
╵
╷
│ Error: Unsupported attribute
│ 
│   on main.tf line 11, in module "confluent-service-account":
│   11:   description  = "${each.value.name}-service-account"
│     ├────────────────
│     │ each.value is tuple with 2 elements
│ 
│ This value does not have any attributes.

Hi @jjkishore1,

Since you are grouping by name anyway, this “tuple with two elements” is presumably the two objects that share the same name value.

The same name is available as a single string as each.key, so you could try writing display_name = each.key instead to make it conform to the apparent type constraints of this provider.

Many thanks for that @apparentlymart , it worked.