hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io/
Other
42.45k stars 9.51k forks source link

Unable to use for loop with maps #20230

Closed bentterp closed 5 years ago

bentterp commented 5 years ago

Terraform Version

Terraform v0.12.0

Terraform Configuration Files

locals{
  networks = {
    production_a = {
      network_number    = 1
      availability_zone = "us-east-1a"
    }
    production_b = {
      network_number    = 2
      availability_zone = "us-east-1b"
    }
    staging_a = {
      network_number    = 3
      availability_zone = "us-east-1c"
    }
  }
  zones = ["us-east-1a","us-east-1b","us-east-1c"]
}

output "myzones" {
  value = [
    for zone in local.zones:
    zone
  ]
}

output "mymap" {
  value = {
    for net in local.networks:
    net.network_number => net.availability_zone
  }
}

Debug Output

2019/02/05 12:05:48 [INFO] Terraform version: 0.12.0  7216049fdb703b0779193770cb99b34b192c3c26+CHANGES
2019/02/05 12:05:48 [INFO] Go runtime version: go1.11.5
2019/02/05 12:05:48 [INFO] CLI args: []string{"C:\\Users\\FBTE61\\terraform.exe", "apply"}
2019/02/05 12:05:48 [DEBUG] Attempting to open CLI config file: C:\Users\FBTE61\AppData\Roaming\terraform.rc
2019/02/05 12:05:48 [DEBUG] File doesn't exist, but doesn't need to. Ignoring.
2019/02/05 12:05:48 [INFO] CLI command args: []string{"apply"}

Error: Missing key/value separator

  on locals.tf line 28, in output "mymap":
  27:   value = {
  28:     for net in local.networks:

Expected an equals sign ("=") to mark the beginning of the attribute value. If
you intended to given an attribute name containing periods or spaces, write
the name in quotes to create a string literal.

Crash Output

Expected Behavior

I was hoping to get a map as output

Actual Behavior

No output, only errmsg

Steps to Reproduce

terraform apply

Additional Context

Obviously, this is not a configuration that I'm building but my attempt to understand the new for-loop syntax.... I just can't get it to work as in the documentation.

References

apparentlymart commented 5 years ago

Hi @bentterp! Thanks for reporting this.

Are you using a build from latest master here, or are you using one of the older alpha releases? I ask because I think you're seeing here the symptoms of hashicorp/hcl2#51, the fix for which should now be merged in to Terraform.

The alpha releases are several months old at this point, so they contain a number of bugs that have been fixed in the mean time. I'd recommend using a build from master for this sort of testing now, until we get beta1 out, which we're expecting to release soon once we get provider releases in place that are compatible with it.

bentterp commented 5 years ago

@apparentlymart Hi again :-) Sorry, I should have included that right away. I built against the master, and I've just verified again now after pulling so that I'm at this HEAD:

commit 1f4d2f4c5008cbf4e2da61b65f1cb98183bc4884
Merge: 3b18dd7 411df99
Author: James Bardin <j.bardin@gmail.com>
Date:   Tue Feb 5 16:42:01 2019 -0500

    Merge pull request #20235 from hashicorp/jbardin/id

(I can't use the alphas anyways, as they don't have the support for remote azurerm backend with CLI authenticatiion. So that's what got me into building on my own)

apparentlymart commented 5 years ago

Thanks for confirming, @bentterp! We'll take a closer look at this after beta1, since our focus for beta1 is on testing the core<->provider interations, and we'll return to configuration language bugs for a subsequent beta.

In the mean time, if you happen to still have this error message handy, it'd be helpful to know if any specific part of the source code snippet you posted was underlined indicating a more precise position of the error. No worries if you don't still have it, or if nothing was underlined -- we can try to repro this ourselves to see -- but if you happen to have it handy already it may save some time in debugging this later on.

bentterp commented 5 years ago
$ terraform plan

Error: Missing key/value separator

  on locals.tf line 20, in locals:
  19:   mymap = {
  20:     for name,net in local.networks:

Expected an equals sign ("=") to mark the beginning of the attribute value. If
you intended to given an attribute name containing periods or spaces, write
the name in quotes to create a string literal.

"for name,net in local.networks:" - name is underscored (markdown won't let me underscore, though), ie the first word after "for" so "for" is interpreted as an attribute and not recognized as a reserved keyword.

bentterp commented 5 years ago

Meanwhile, in case others run into this, I have worked around it by creating two lists and then zipping them:

  mymap = zipmap(
    [for name,net in local.networks: name],
    [for name,net in local.networks: net.availability_zone],
  )

When the bug is fixed, this config snippet can be changed to the more correct/efficient code without affecting input or output.

apparentlymart commented 5 years ago

Thanks for that additional context and the workaround, @bentterp!

This still sounds suspiciously like the symptoms of hashicorp/hcl2#51, so I think we may need to revisit that change and see what it has missed for this problem to still be present.

If it is related to hashicorp/hcl2#51 then a different workaround may be to put the for keyword on the same line as the brace:

mymap = { for name,net in local.networks: etc, etc }

(The issue with hashicorp/hcl2#51 was that it was sniffing ahead for the for token after enabling newline-sensitive scanning, so the next token was the newline rather than the for keyword and thus the parser thought it was parsing a normal { foo = bar, baz = beep } sort of construct rather than the for expression construct.)

bentterp commented 5 years ago

That was spot on, @apparentlymart !! If I put the entire map declaration on one line, then it works. But it has to be everything on the same line, including the closing brace:

locals{
  mymap = { for name,net in local.networks: name => net.availability_zone }
}

Any line breaks anywhere in that line causes the map generation to fail

apparentlymart commented 5 years ago

Hi @bentterp! Sorry for the delay here.

It turns out that the previous fix here was insufficient.

The tricky thing with this construct is that { can introduce either an object constructor or an object for expression, and the former uses a newline-sensitive parsing mode (the object attributes are separated by newlines) while the for expression is newline-agnnostic.

Our previous fixed handled the problem where the parser was switching to newline-sensitive mode too early, before even scanning for the for token, and so it wouldn't match if it were on a newline. But that fix didn't handle the similar problem that the parser might already be in newline-sensitive mode due to parsing a block body (as in your case), and it wasn't forcing the mode off before peeking ahead for the for token.

I've merged an upstream change to HCL that fixes the problem by managing the newline sensitivity mode more carefully, so this should now work in latest master, and the fix will be included in the forthcoming betas and final release.

Thanks again for reporting this!

ghost commented 4 years ago

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.