Change in output is throwing error

Hello forum!

I had to make a change to my VPC resource block, where I introduced a count condition to only deploy to certain workspaces.

Now, the output has changed and I’m having issues getting it to work again.

# output
private_subnets_cidr_blocks = [
  tolist([
    "10.0.129.0/24",
    "10.0.130.0/24",
    "10.0.131.0/24",
  ]),
]

# example code
resource "aws_route" "test" {
  count                     = terraform.workspace == "test" ? length(data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks) : 0
  route_table_id            = module.vpc[0].private_route_table_ids[0]
  destination_cidr_block    = data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks
  vpc_peering_connection_id = aws_vpc_peering_connection.test[0].id
}

When referencing that output with terraform_remote_state, I get the following error:

β”‚ Error: Incorrect attribute value type
β”‚ 
β”‚   on peering-sharedservices.tf line 117, in resource "aws_route" "test":
β”‚  117:   destination_cidr_block    = data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks
β”‚     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     β”‚ data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks is tuple with 1 element
β”‚ 
β”‚ Inappropriate value for attribute "destination_cidr_block": string required.

Thanks in advance!

What is the code for the actual output block?

ooops forgot to post that! :frowning:

Here it is:

output "private_subnets_cidr_blocks" {
  value = module.vpc[*].private_subnets_cidr_blocks
}

The VPC module is this one.

Try changing your output block to:

output "private_subnets_cidr_blocks" {
  value = one(module.vpc[*].private_subnets_cidr_blocks)
}

I did, and now the error is:

β”‚ Error: Incorrect attribute value type
β”‚ 
β”‚   on peering-sharedservices.tf line 117, in resource "aws_route" "test":
β”‚  117:   destination_cidr_block    = data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks
β”‚     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     β”‚ data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks is list of string with 3 elements
β”‚ 
β”‚ Inappropriate value for attribute "destination_cidr_block": string required.
β•΅
β•·
β”‚ Error: Incorrect attribute value type
β”‚ 
β”‚   on peering-sharedservices.tf line 117, in resource "aws_route" "test":
β”‚  117:   destination_cidr_block    = data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks
β”‚     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     β”‚ data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks is list of string with 3 elements
β”‚ 
β”‚ Inappropriate value for attribute "destination_cidr_block": string required.
β•΅
β•·
β”‚ Error: Incorrect attribute value type
β”‚ 
β”‚   on peering-sharedservices.tf line 117, in resource "aws_route" "test":
β”‚  117:   destination_cidr_block    = data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks
β”‚     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     β”‚ data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks is list of string with 3 elements
β”‚ 
β”‚ Inappropriate value for attribute "destination_cidr_block": string required.

outputs.tf:

output "private_subnets_cidr_blocks" {
  value = one(module.vpc[*].private_subnets_cidr_blocks)
}

aws_route:

resource "aws_route" "test" {
  count                     = terraform.workspace == "sharedservices" ? length(data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks) : 0
  route_table_id            = module.vpc[0].private_route_table_ids[0]
  destination_cidr_block    = data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks
  vpc_peering_connection_id = aws_vpc_peering_connection.test[0].id
}

As destination_cidr_block is expecting a single string I think you actually want destination_cidr_block = data.terraform_remote_state.network_test.outputs.private_subnets_cidr_blocks[count.index]

1 Like

Thanks! That worked :slight_smile:

hey @stuart-c , sorry to bother you again but I’m getting another similar error with a different output.

# output:
output "public_subnets" {
  value = module.vpc[*].public_subnets
}

# Resource consuming it:
resource "aws_autoscaling_group" "bastion" {
  name                      = "bastion-${terraform.workspace}-asg"
  capacity_rebalance        = true
  max_size                  = var.max_size
  min_size                  = var.min_size
  health_check_grace_period = 300
  health_check_type         = "EC2"
  desired_capacity          = var.desired_capacity
  force_delete              = true
  vpc_zone_identifier       = data.terraform_remote_state.network.outputs.public_subnets
}

# Error
β”‚ Error: Incorrect attribute value type
β”‚ 
β”‚   on bastion.tf line 55, in resource "aws_autoscaling_group" "bastion":
β”‚   55:   vpc_zone_identifier       = data.terraform_remote_state.network.outputs.public_subnets
β”‚     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     β”‚ data.terraform_remote_state.network.outputs.public_subnets is tuple with 1 element
β”‚ 
β”‚ Inappropriate value for attribute "vpc_zone_identifier": element 0: string required.
β•΅
make: *** [plan] Error 1

Any suggestions? Thanks!

Hi @manu.nz,

Your expression has two parts that I think suggest the problem:

  • The attribute name public_subnets implies that this is a collection of multiple subnets, rather than a single subnet.
  • The operator [*] constructs a list of values of the attribute on its right side.

Therefore it seems that you are constructing a list of collections rather than just a single collection, which seems to agree with the error message because your first element in that case would be a collection rather than a string as required.

I’m not sure exactly what you were intending to assign to this argument, but since this argument seems to expect a set of strings perhaps the right idea would be to flatten the result (discarding the extra level of list) and then use the toset function to convert the result to be a set of strings.

hi @apparentlymart ,

I had to introduce a count condition to my VPC module:

# before
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 3.18.1"

# after
moved {
  from = module.vpc
  to   = module.vpc[0]
}

module "vpc" {
  count   = terraform.workspace == "dev" || terraform.workspace == "test" || terraform.workspace == "staging" || terraform.workspace == "prod" ? 1 : 0
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 3.18.1"

And that changed all the outputs. So, that’s why I have module.vpc[*].public_subnets, where before it was module.vpc.public_subnets.

The output of that module can be found here.

Hi @manu.nz,

In any situation where you’ve used a condition to select between zero or one instances of something, you need to describe to Terraform how it should handle the situation where there are zero instances.

A concise way to do that is to use the function one, which transforms a list that might have zero or one elements into a single value that might be null. For example:

output "public_subnets" {
  value = one(module.vpc[*].public_subnets)
}

In this case this reads a bit strangely because it’s returning one set of subnets, so it’s the list of sets that the function is changing, not the inner set itself.

The result is therefore either a set of strings describing public subnets or it is null, depending on whether this module is enabled.

1 Like

so it’s the list of sets that the function is changing, not the inner set itself.

Right… that makes sense to me. Thanks for that!

If I use that output, then I need to reference the output this way, correct?

data.terraform_remote_state.network.outputs.public_subnets[count.index]

I assumed you would use this whole set as a single value because you seemed to be assigning it to an argument that itself expects a set of subnet ids.

In that case, the output value directly is already that value now:

data.terraform_remote_state.network.outputs.public_subnets

If you need to use each of these subnets separately, such as if you are declaring something using for_each over the subnets, then the above expression should work as a for_each expression directly, and then you can use each.value inside the resource configuration to obtain the one subnet ID for that particular resource instance.

1 Like