gruntwork-io / terratest

Terratest is a Go library that makes it easier to write automated tests for your infrastructure code.
https://terratest.gruntwork.io/
Apache License 2.0
7.46k stars 1.32k forks source link

Works with Terraform; Fails with Terratest: "each.value cannot be used in this context" #1194

Open dyba opened 1 year ago

dyba commented 1 year ago

Hello!

I'm at my rope's end and can't figure out why the same Terraform file works fine when I deploy with Terraform, but fails when I deploy with Terragrunt.

In my main.tf file, I am creating a Route53 record:

resource "aws_route53_record" "dns" {
  for_each = {
    for dvo in aws_acm_certificate.web_cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 300
  type            = each.value.type
  zone_id         = data.aws_route53_zone.main.zone_id
}

With Terraform, I run the commands to deploy this:

terraform init
terraform validate
terraform plan -var web_hostname='sample.some-site.com' -var web_hostname_alts='["www.sample.some-site.com"]' -out plan.txt
terraform apply plan.txt

Success! 🥳

Now with Terragrunt:

package test

import (
    "fmt"
    "testing"

    // "github.com/stretchr/testify/assert"

    "github.com/gruntwork-io/terratest/modules/aws"
    "github.com/gruntwork-io/terratest/modules/random"
    "github.com/gruntwork-io/terratest/modules/terraform"
    test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
)

func TestTerraformCertificateModule(t *testing.T) {
    uniqueID := random.UniqueId()
    awsRegion := "us-east-1"

    certDomainName := fmt.Sprintf("%s.daiba-io-test.link", uniqueID)
    certDomainNameAlt := fmt.Sprintf("www.%s.daiba-io-test.link", uniqueID)

    rootFolder := ".."
    terraformFolderRelativeToRoot := "examples/daiba-io-test-link"

    tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot)
    planFilePath := fmt.Sprintf("%s/plan.txt", tempTestFolder)

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: tempTestFolder,

        Vars: map[string]interface{}{
            "web_hostname":      certDomainName,
            "web_hostname_alts": []string{certDomainNameAlt},
        },
    })

    defer terraform.Destroy(t, terraformOptions)

    terraform.Init(t, terraformOptions)

    terraform.Validate(t, &terraform.Options{
        TerraformDir: tempTestFolder,
    })

    terraform.Plan(t, &terraform.Options{
        TerraformDir: tempTestFolder,

        Vars: map[string]interface{}{
            "web_hostname":      certDomainName,
            "web_hostname_alts": []string{certDomainNameAlt},
        },

        PlanFilePath: planFilePath,
    })

    terraform.Apply(t, terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: tempTestFolder,
        PlanFilePath: planFilePath,
    }))

// ...

Ok, here we go...

go test -v -run TestTerraformCertificateModule

// ...
TestTerraformCertificateModule 2022-11-01T18:32:52-07:00 retry.go:91: terraform [apply -input=false -auto-approve -lock=false /var/folders/8c/v6tg8r695tx4qx_rvv53w1340000gn/T/TestTerraformCertificateModule51734770/terraform-aws-certificate/examples/daiba-io-test-link/plan.txt]
TestTerraformCertificateModule 2022-11-01T18:32:52-07:00 logger.go:66: Running command terraform with args [apply -input=false -auto-approve -lock=false /var/folders/8c/v6tg8r695tx4qx_rvv53w1340000gn/T/TestTerraformCertificateModule51734770/terraform-aws-certificate/examples/daiba-io-test-link/plan.txt]
TestTerraformCertificateModule 2022-11-01T18:32:53-07:00 logger.go:66: module.aws_certificate.aws_acm_certificate.web_cert: Creating...
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: module.aws_certificate.aws_acm_certificate.web_cert: Still creating... [10s elapsed]
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: module.aws_certificate.aws_acm_certificate.web_cert: Creation complete after 10s [id=arn:aws:acm:us-east-1:224683637342:certificate/f0ffc0f1-8cb7-43e3-905a-41d0a14f1d6b]
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: ╷
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: │ Error: each.value cannot be used in this context
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: │
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: │   on .terraform/modules/aws_certificate/main.tf line 37, in resource "aws_route53_record" "dns":
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: │   37:   name            = each.value.name
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: │
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: │ A reference to "each.value" has been used in a context in which it
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: │ unavailable, such as when the configuration no longer contains the value in
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: │ its "for_each" expression. Remove this reference to each.value in your
TestTerraformCertificateModule 2022-11-01T18:33:03-07:00 logger.go:66: │ configuration to work around this error.

💥 😱

But it works with Terraform!

Context

Terraform version: 1.3.3 Go: 1.17 Terratest: v0.40.24

I'd appreciate any pointers to what I might be doing wrong. Thank you! 🙏

Sample Code To Reproduce The Problem

denis256 commented 1 year ago

Hi, I need to clone and check execution but so far noticed that in explicit Terraform invocation was used web_hostame but in Terratest is referenced web_hostname, web_hostame != web_hostname

dyba commented 1 year ago

@denis256 Thanks for looking at this. Actually, I made a typo with the manual Terraform command I wrote above. It should be:

terraform init
terraform validate
# hostname, not hostame
terraform plan -var web_hostname='sample.some-site.com' -var web_hostname_alts='["www.sample.some-site.com"]' -out plan.txt
terraform apply plan.txt

These shell commands work fine. The correct variable name is web_hostname. The problem happens when I run Terratest.