Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to dereference set values #27949

Closed
ablackrw opened this issue Mar 1, 2021 · 7 comments
Closed

Unable to dereference set values #27949

ablackrw opened this issue Mar 1, 2021 · 7 comments
Labels
bug new new issue not yet triaged

Comments

@ablackrw
Copy link

ablackrw commented Mar 1, 2021

Terraform Version

Terraform v0.14.7

Terraform Configuration Files

terraform {
  required_version = "~> 0.14.0"
}

locals {
  mapping = toset([
  {
    "device_name" = "/dev/xvda"
    "ebs" = tomap({
      "delete_on_termination" = "true"
      "encrypted" = "false"
      "iops" = "0"
      "snapshot_id" = "snap-04a9c6683b1b9509b"
      "throughput" = "0"
      "volume_size" = "2"
      "volume_type" = "standard"
    })
    "no_device" = ""
    "virtual_name" = ""
  },
])
}

output "device" {
  value = local.mapping.device_name
}

Debug Output

Crash Output

Expected Behavior

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

Plan: 0 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + device  = "/dev/xvda"

Actual Behavior

Error: Unsupported attribute

  on testcase.tf line 25, in output "device":
  25:   value = local.mapping.device_name
    |----------------
    | local.mapping is set of object with 1 element

This value does not have any attributes.

Steps to Reproduce

  1. terraform init
  2. terraform plan

Additional Context

The value of local.mapping is a stand-in for a data.aws_ami object (from the v3.30 hashicorp/aws provider). It may be possible to work around this issue on the provider side, but this inability to look up values from sets likely will trip people up.

References

@ablackrw ablackrw added bug new new issue not yet triaged labels Mar 1, 2021
@ablackrw
Copy link
Author

ablackrw commented Mar 1, 2021

The 'simple' workaround for this issue is to use the following splat expression

output "device" {
  value = local.mapping.*.device_name
}

However, the following 'full' test case

terraform {
  required_version = "~> 0.14.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.30"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

locals {
  arch    = { x86_64 = "t3a", arm64 = "t4g" }
}

data "aws_ami" "amzl2" {
  for_each    = local.arch
  most_recent = true

  filter {
    name   = "name"
    values = ["amzn2-ami-minimal-hvm-*-${each.key}-ebs"]
  }

  filter {
    name   = "architecture"
    values = [each.key]
  }

  owners = ["amazon"]
}

output "sizes" {
  value = {for k, v in local.arch: k=> data.aws_ami.amzl2[k].block_device_mappings.*.ebs.volume_size}
}

resource "aws_instance" "template" {
  for_each               = local.arch
  ami                    = data.aws_ami.amzl2[each.key].id
  instance_type          = "${each.value}.small"
  root_block_device {
    volume_size = data.aws_ami.amzl2[each.key].block_device_mappings.*.ebs.volume_size
  }
}

fails with the following message:

Error: Incorrect attribute value type

  on testcase.tf line 45, in resource "aws_instance" "template":
  45:     volume_size = data.aws_ami.amzl2[each.key].block_device_mappings.*.ebs.volume_size
    |----------------
    | data.aws_ami.amzl2 is object with 2 attributes
    | each.key is "x86_64"

Inappropriate value for attribute "volume_size": number required.


Error: Incorrect attribute value type

  on testcase.tf line 45, in resource "aws_instance" "template":
  45:     volume_size = data.aws_ami.amzl2[each.key].block_device_mappings.*.ebs.volume_size
    |----------------
    | data.aws_ami.amzl2 is object with 2 attributes
    | each.key is "arm64"

Inappropriate value for attribute "volume_size": number required.

If however the aws_instance.template block is removed or commented out, the following output is produced:

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

Plan: 0 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + sizes = {
      + arm64  = [
          + "2",
        ]
      + x86_64 = [
          + "2",
        ]
    }

This failure is not reproducible if the for_each on the data.aws_ami objects are simplified away.

@alisdair
Copy link
Contributor

alisdair commented Mar 1, 2021

Hi @ablackrw, thanks for reporting this.

I think the underlying issue here is that the data source attribute you're referencing returns a set (i.e. a zero-or-more sized collection), and it looks like you're trying to bind it to an attribute which is a number. In the synthetic example from the original report, your workaround of using the splat operator results in a collection, too:

Changes to Outputs:
  + device = [
      + "/dev/xvda",
    ]

Compare to your expected result:

Changes to Outputs:
  + device  = "/dev/xvda"

I'm not sure what your intention is with the real example. But because the set value could contain multiple block device mappings, your Terraform code has to define which of those mappings is the one you want to use as the root block device's volume size. My recommendation would be to post in the Terraform community forum about this use case, where hopefully someone with experience with this problem will be able to advise on how best to do that.

That task is something that could probably be helped by the one function in #27454, depending on your approach. However, I don't think there's a bug in Terraform here, so I'm going to close this issue for now. Let me know if I've misunderstood!

@alisdair alisdair closed this as completed Mar 1, 2021
@ablackrw
Copy link
Author

ablackrw commented Mar 2, 2021

I think the initial test case is somewhat valid, though incorrectly structured.

If the output block is replaced with the following

output "normal" {
  value = local.mapping.0.device_name
}

output "alt" {
  value = local.mapping[0].device_name
}

output "hack" {
  value = local.mapping.*.device_name[0]
}

the actual behavior becomes

Error: Invalid index

  on testcase.tf line 25, in output "normal":
  25:   value = local.mapping.0.device_name
    |----------------
    | local.mapping is set of object with 1 element

This value does not have any indices.


Error: Invalid index

  on testcase.tf line 29, in output "alt":
  29:   value = local.mapping[0].device_name
    |----------------
    | local.mapping is set of object with 1 element

This value does not have any indices.

with the expected behavior becoming

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

Plan: 0 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + alt    = "/dev/xvda"
  + hack   = "/dev/xvda"
  + normal = "/dev/xvda"

The goal with the real example is to create an aws_instance object using a sub-attribute from the data.aws_ami object. In the data source documentation, it indicates that the sub-attributes can be accessed by index as shown in the 'normal' output.

@alisdair
Copy link
Contributor

alisdair commented Mar 2, 2021

As you are seeing from these error messages, set values cannot be accessed by index, as they have no logical order. This means that expressions like mapping[0] do not make sense and are a sign that there's a logic bug in the configuration.

It looks like in your real example, you're only considering the case when the block_device_mappings attribute is a set with one element. What should your Terraform configuration do if the AMI has multiple block device mappings? Or zero?

@ablackrw
Copy link
Author

ablackrw commented Mar 2, 2021

This discussion may point to a documentation or design bug in the hashicorp/aws provider.

The block_device_mappings set is expected to contain at least one element. If it did not have any elements, it would be defining a virtual machine image that lacks an associated file system. As such, things would likely fall apart quickly. In this particular case, the entity described by the data.aws_ami object is expected to contain only a single definition. If more than one element was expected in the set, you would need to identify which one of the elements should be associated with the root_block_device structure in the aws_instance definition. This would possibly be done via pattern matching on the device_name value in the set element. The remainder of the set elements would likely be referenced in other structures (likely ebs_block_device).

@alisdair
Copy link
Contributor

alisdair commented Mar 2, 2021

That sounds correct to me! I tried to find information about this in the AWS provider docs before replying to this issue to no avail, so adding docs explaining what to expect from this set and how to use it in practice would be very valuable.

@ghost
Copy link

ghost commented Apr 1, 2021

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@ghost ghost locked as resolved and limited conversation to collaborators Apr 1, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug new new issue not yet triaged
Projects
None yet
Development

No branches or pull requests

2 participants