Invalid value for "v" parameter: cannot convert tuple to string in for/if, isolating a string data element

I get an error during plan/apply that appears to be due to unresolved details of an object to build. Below is one of 3 errors (the others confirm AZ-letter is “b” and “c”).

│ Error: Invalid function argument

│ on vpc.tf line 63, in resource “aws_nat_gateway” “ngw”:
│ 63: allocation_id = tostring([for eip in aws_eip.nat_ip : eip.id if eip.tags.AZ-letter == each.value.tags.AZ-letter])
│ ├────────────────
│ │ while calling tostring(v)
│ │ aws_eip.nat_ip is object with 3 attributes
│ │ each.value.tags.AZ-letter is “a”

│ Invalid value for “v” parameter: cannot convert tuple to string.

Excerpts of my code suggest the if == comparison should be 2 strings. It appears the ‘aws_eip.nat_ip is object with 3 attributes’ isn’t being evaluated to recognise aws_eip.nat_ip.tags.AZ-letter will be a string value. Please provide guidance (or confirmation this is a bug and suggestions maybe how to work around it)

variable azs {
type = list(string)
default = [“ap-southeast-2a”,“ap-southeast-2b”,“ap-southeast-2c”]
}

locals {
subnets = flatten([for env_name, env_setup in var.envs :
[for subnet_type, octet_offset in var.subnet_defs :
[for az in var.azs :
[for subnet_details in tolist([{
subnet_name = “{var.prefix}-{env_name}-subnet-{subnet_type}-{substr(az,length(az)-1,1)}”,
subnet_ip = “{var.upper_network}.{env_setup.start+(octet_offset+(4*(index(var.azs,az)+1)))}.0”,
az = az,
env = env_name,
area = subnet_type,
az_letter = substr(az,length(az)-1,1)
}]) : subnet_details if (env_setup.active_azs[index(var.azs,az)] && contains(env_setup.reqd_defs, subnet_type))
]
]
]
])
}

resource “aws_subnet” “infra_subnets” {
for_each = {for subnet in local.subnets : subnet.subnet_name => subnet if subnet.env == “mgmt”}
vpc_id = aws_vpc.eos-vpc.id
cidr_block = “{each.value.subnet_ip}/{var.mask}”
availability_zone = each.value.az
tags = {
Name = each.value.subnet_name
Environment = each.value.env
AZ-letter = each.value.az_letter
}
}

resource “aws_eip” “nat_ip” {
for_each = aws_subnet.infra_subnets
domain = “vpc”
tags = {
Name = “{var.prefix}-mgmt-nat_ip-{each.value.tags.AZ-letter}”
AZ-letter = each.value.tags.AZ-letter
}
depends_on = [aws_internet_gateway.igw]
}

resource “aws_nat_gateway” “ngw” {
for_each = aws_subnet.infra_subnets
allocation_id = tostring([for eip in aws_eip.nat_ip : eip.id if eip.tags.AZ-letter == each.value.tags.AZ-letter])
subnet_id = each.value.id
tags = {
Name = “{var.prefix}-mgmt-ngw-{each.value.tags.AZ-letter}”
AZ-letter = each.value.tags.AZ-letter
}
depends_on = [aws_internet_gateway.igw, aws_eip.nat_ip]
}

Further info: I did an ‘apply’ without the offending aws_nat_gateway resource. The aws_eip resources built so received allocated values. Terraform console still through errors with my [for] code, but I was able to refine it to

[for eip in aws_eip.nat_ip : tostring(eip.id) if eip.tags.AZ-letter == “a”]
[
“eipalloc-094005aefca383f8c”,
]
::Workaround option 1::
[for eip in aws_eip.nat_ip : eip.id if eip.tags.AZ-letter == “a”][0]
tostring([for eip in aws_eip.nat_ip : eip.id if eip.tags.AZ-letter == “a”][0])
“eipalloc-094005aefca383f8c”
::Workaround option 2::
join(“”,[for eip in aws_eip.nat_ip : eip.id if eip.tags.AZ-letter == “a”])
“eipalloc-094005aefca383f8c”
Is it expected that the single eip.id result should be treated as a tuple? Shouldn’t tostring() be able to handle this?

tostring() only accepts primitive types as an argument.

Only the primitive types (string, number, and bool) and null can be converted to string.

You are passing a tuple, which is not a primitive type. Hence the error.

Is it expected that the single eip.id result should be treated as a tuple?

Yes it is expected. A for expression returns either a tuple or an object.

A for expression alone can only produce either an object value or a tuple value,

If you know that your tuple returns only one element then you can access that element using a zero-based index like so:

terraform console
> [1,2,3]
[
  1,
  2,
  3,
]
> type([1,2,3])
tuple([
    number,
    number,
    number,
])
> [1,2,3][0]
1

Or, if you want concatenate all elements in a tuple to a string its:

> join("", [1,2,3])
"123"

Can I also suggest using code blocks to format your code. It helps others to help you.

Thanks for the quick and clear explanation, Jamie :slight_smile:
Hopefully this conversation adds to the knowledgebase. I think part of my confusion is that the error seemed to suggest my ‘if’ filter was the problem when it was the results of the [for] loop feeding into the to string() function with an inappropriate data type, as you explained.
I also discovered the one() function which should also produce the anticipated string result. Cheers!

1 Like

Nice. I didn’t know about one().

Hi @barnaby,

In case it’s helpful in future, I’ll note that Terraform included this part in the error message:

while calling tostring(v)

… which means that this error message came from the tostring function itself, rather than from the language’s own expressions.

If you see an error message including a statement like that, you can assume that the problem is with how you called the named function, and so start by reading the documentation for that function to make sure that your usage matches the documentation.

Of course, I only know to look for that due to experience reading Terraform error messages, so I’m not pointing this out to say that you were wrong not to notice it. Only that knowing how Terraform communicates that an error originated in a function will hopefully help you narrow down the cause of similar error messages in the future.