Terraform-provider-aws: aws_lb don't support computed values in subnet_mappings

Created on 15 Mar 2018  ·  8Comments  ·  Source: hashicorp/terraform-provider-aws

Terraform Version

Terraform v0.11.3

  • provider.aws v1.11.0
  • provider.template v1.0.0

Affected Resource(s)

Please list the resources as a list, for example:

  • aws_lb

Terraform Configuration Files

module "NLB-public" {
  source                               = "../../modules/ec2/nlb/public/"
  nlb_name                             = "${local.environment}-public"
  nlb_enable_deletion_protection       = true
  nlb_enable_cross_zone_load_balancing = true

  nlb_subnet_mappings = [
    {
      subnet_id     = "${element(module.VPC.public_subnets, 0)}"
      allocation_id = "${module.EIP-public-nlb.eip_id}"
    },
  ]
resource "aws_lb" "nlb" {
  name                             = "${var.nlb_name}"
  internal                         = false
  load_balancer_type               = "network"
  enable_deletion_protection       = "${var.nlb_enable_deletion_protection}"
  enable_cross_zone_load_balancing = "${var.nlb_enable_cross_zone_load_balancing}"
  tags                             = "${var.nlb_tags}"
  subnet_mapping                   = ["${var.nlb_subnet_mappings}"]
}

Expected Behavior

Terraform should be able to create the aws_lb with subnet_mapping using the output of other resources like aws_subnet and aws_eip.

Actual Behavior

When using computed values like the output of aws_eip and aws_subnet, the module fails. But if I pass static strings in the same way, it works.

Error: module.NLB-public.aws_lb.nlb: "subnet_mapping.0.subnet_id": required field is not set

Steps to Reproduce

  1. terraform apply

Important Factoids

None

bug servicelbv2 terraform-0.12 upstream-terraform

Most helpful comment

Mine allows me to pass in values from data:

resource aws_lb default {
  name               = "${var.defaultname}"
  internal           = false
  load_balancer_type = "application"
  security_groups    = ["${data.aws_security_group.lb.id}"]

  enable_deletion_protection = "${var.enable_deletion_protection_lb}"

  subnet_mapping {
    subnet_id = "${data.aws_subnet.publ_a.id}"
  }

  subnet_mapping {
    subnet_id = "${data.aws_subnet.publ_b.id}"
  }
}

BUT...
every plan that I run it wants to delete and recreate my lb because it says the subnet mapping is changing when it is not.

-/+ module.accounts.module.staging.module.us-west-2.module.sso.aws_lb.default (new resource required)
      id:                                       "arn:aws:elasticloadbalancing:us-west-2:redacted:loadbalancer/redacted/redacted/redacted" => <computed> (forces new resource)
      access_logs.#:                            "0" => <computed>
      arn:                                      "arn:aws:elasticloadbalancing:us-west-2:redacted:loadbalancer/redacted/redacted/redacted" => <computed>
      arn_suffix:                               "redacted/redacted/redacted" => <computed>
      dns_name:                                 "redacted-redacted.us-west-2.elb.amazonaws.com" => <computed>
      enable_deletion_protection:               "false" => "false"
      enable_http2:                             "true" => "true"
      idle_timeout:                             "60" => "60"
      internal:                                 "false" => "false"
      ip_address_type:                          "ipv4" => <computed>
      load_balancer_type:                       "application" => "application"
      name:                                     "redacted" => "redacted"
      security_groups.#:                        "1" => "1"
      security_groups.3485198027:               "sg-redactedc" => "sg-redactedc"
      subnet_mapping.#:                         "0" => "2" (forces new resource)
      subnet_mapping.2175746748.allocation_id:  "" => ""
      subnet_mapping.2175746748.subnet_id:      "" => "subnet-redacteda" (forces new resource)
      subnet_mapping.3360964316.allocation_id:  "" => ""
      subnet_mapping.3360964316.subnet_id:      "" => "subnet-redactedb" (forces new resource)
      subnets.#:                                "2" => <computed>
      tags.%:                                   "1" => "1"
      tags.Name:                                "redacted" => "corp-sso"
      vpc_id:                                   "vpc-redacted" => <computed>
      zone_id:                                  "redacted" => <computed>

edit:

Mine might be a separate issue. I hard-coded the values in and it still keeps recreating the LB saying I'm going from 0 subnet mappings to 2. Do you think I should make a new issue on this or is this maybe related?

adding the following has been my workaround for now.

lifecycle {
    ignore_changes = ["subnet_mapping"]
  }

All 8 comments

this is also something I am hitting and would love to see fixed/improved.

very much +1 as well.

Mine allows me to pass in values from data:

resource aws_lb default {
  name               = "${var.defaultname}"
  internal           = false
  load_balancer_type = "application"
  security_groups    = ["${data.aws_security_group.lb.id}"]

  enable_deletion_protection = "${var.enable_deletion_protection_lb}"

  subnet_mapping {
    subnet_id = "${data.aws_subnet.publ_a.id}"
  }

  subnet_mapping {
    subnet_id = "${data.aws_subnet.publ_b.id}"
  }
}

BUT...
every plan that I run it wants to delete and recreate my lb because it says the subnet mapping is changing when it is not.

-/+ module.accounts.module.staging.module.us-west-2.module.sso.aws_lb.default (new resource required)
      id:                                       "arn:aws:elasticloadbalancing:us-west-2:redacted:loadbalancer/redacted/redacted/redacted" => <computed> (forces new resource)
      access_logs.#:                            "0" => <computed>
      arn:                                      "arn:aws:elasticloadbalancing:us-west-2:redacted:loadbalancer/redacted/redacted/redacted" => <computed>
      arn_suffix:                               "redacted/redacted/redacted" => <computed>
      dns_name:                                 "redacted-redacted.us-west-2.elb.amazonaws.com" => <computed>
      enable_deletion_protection:               "false" => "false"
      enable_http2:                             "true" => "true"
      idle_timeout:                             "60" => "60"
      internal:                                 "false" => "false"
      ip_address_type:                          "ipv4" => <computed>
      load_balancer_type:                       "application" => "application"
      name:                                     "redacted" => "redacted"
      security_groups.#:                        "1" => "1"
      security_groups.3485198027:               "sg-redactedc" => "sg-redactedc"
      subnet_mapping.#:                         "0" => "2" (forces new resource)
      subnet_mapping.2175746748.allocation_id:  "" => ""
      subnet_mapping.2175746748.subnet_id:      "" => "subnet-redacteda" (forces new resource)
      subnet_mapping.3360964316.allocation_id:  "" => ""
      subnet_mapping.3360964316.subnet_id:      "" => "subnet-redactedb" (forces new resource)
      subnets.#:                                "2" => <computed>
      tags.%:                                   "1" => "1"
      tags.Name:                                "redacted" => "corp-sso"
      vpc_id:                                   "vpc-redacted" => <computed>
      zone_id:                                  "redacted" => <computed>

edit:

Mine might be a separate issue. I hard-coded the values in and it still keeps recreating the LB saying I'm going from 0 subnet mappings to 2. Do you think I should make a new issue on this or is this maybe related?

adding the following has been my workaround for now.

lifecycle {
    ignore_changes = ["subnet_mapping"]
  }

very much +1 as well.

@GeoffMillerAZ, Can you show what you did to get around this in the meantime?

@armon @mitchellh are there any plans to have this fixed in the future releases?

+1 on this getting this fixed.

Hi folks 👋 This issue is resolved in Terraform 0.12, which supports new functionality in the configuration language aimed at solving problems like these. The new dynamic block syntax can be used to dynamically generate configuration blocks and their arguments. These can be combined with the new null value, which can be used to omit arguments as if they were not defined in the configuration at all.

For example, given this configuration:

# main.tf

terraform {
  required_providers {
    aws = "2.20.0"
  }
  required_version = "0.12.5"
}

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

data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_vpc" "test" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "test" {
  count = 2

  availability_zone = "${element(data.aws_availability_zones.available.names, count.index)}"
  cidr_block        = "10.0.${count.index}.0/24"
  vpc_id            = aws_vpc.test.id
}

module "test1" {
  source = "./module1"

  subnet_mappings = [for subnet in aws_subnet.test :
    {
      allocation_id = null
      subnet_id     = subnet.id
    }
  ]
}

module "test2" {
  source = "./module1"

  subnet_mappings = [
    {
      allocation_id = null
      subnet_id     = aws_subnet.test[0].id
    },
    {
      allocation_id = null
      subnet_id     = aws_subnet.test[1].id
    },
  ]
}

# module1/main.tf

variable "subnet_mappings" {
  type = list(object({
    allocation_id = string
    subnet_id     = string
  }))
}

resource "aws_lb" "test" {
  internal = true
  name     = "test"

  dynamic "subnet_mapping" {
    # The for_each argument is a hardcoded list in this illustrative example,
    # however it can be sourced from a variable or local value as well as
    # support multiple argument values as a map.
    for_each = var.subnet_mappings

    content {
      allocation_id = subnet_mapping.value.allocation_id
      subnet_id     = subnet_mapping.value.subnet_id
    }
  }
}

Produces the following apply output:

$ terraform apply
data.aws_availability_zones.available: Refreshing state...

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

Terraform will perform the following actions:

  # aws_subnet.test[0] will be created
  + resource "aws_subnet" "test" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "us-east-1a"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.0.0/24"
      + id                              = (known after apply)
      + ipv6_cidr_block                 = (known after apply)
      + ipv6_cidr_block_association_id  = (known after apply)
      + map_public_ip_on_launch         = false
      + owner_id                        = (known after apply)
      + vpc_id                          = (known after apply)
    }

  # aws_subnet.test[1] will be created
  + resource "aws_subnet" "test" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = "us-east-1b"
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.1.0/24"
      + id                              = (known after apply)
      + ipv6_cidr_block                 = (known after apply)
      + ipv6_cidr_block_association_id  = (known after apply)
      + map_public_ip_on_launch         = false
      + owner_id                        = (known after apply)
      + vpc_id                          = (known after apply)
    }

  # aws_vpc.test will be created
  + resource "aws_vpc" "test" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = (known after apply)
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
    }

  # module.test1.aws_lb.test will be created
  + resource "aws_lb" "test" {
      + arn                        = (known after apply)
      + arn_suffix                 = (known after apply)
      + dns_name                   = (known after apply)
      + enable_deletion_protection = false
      + enable_http2               = true
      + id                         = (known after apply)
      + idle_timeout               = 60
      + internal                   = true
      + ip_address_type            = (known after apply)
      + load_balancer_type         = "application"
      + name                       = "test"
      + security_groups            = (known after apply)
      + subnets                    = (known after apply)
      + vpc_id                     = (known after apply)
      + zone_id                    = (known after apply)

      + subnet_mapping {
          + subnet_id = (known after apply)
        }
      + subnet_mapping {
          + subnet_id = (known after apply)
        }
    }

  # module.test2.aws_lb.test will be created
  + resource "aws_lb" "test" {
      + arn                        = (known after apply)
      + arn_suffix                 = (known after apply)
      + dns_name                   = (known after apply)
      + enable_deletion_protection = false
      + enable_http2               = true
      + id                         = (known after apply)
      + idle_timeout               = 60
      + internal                   = true
      + ip_address_type            = (known after apply)
      + load_balancer_type         = "application"
      + name                       = "test"
      + security_groups            = (known after apply)
      + subnets                    = (known after apply)
      + vpc_id                     = (known after apply)
      + zone_id                    = (known after apply)

      + subnet_mapping {
          + subnet_id = (known after apply)
        }
      + subnet_mapping {
          + subnet_id = (known after apply)
        }
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_vpc.test: Creating...
aws_vpc.test: Creation complete after 1s [id=vpc-0d36d77bb34cecd44]
aws_subnet.test[1]: Creating...
aws_subnet.test[0]: Creating...
aws_subnet.test[0]: Creation complete after 1s [id=subnet-08e6644b5a81f85ad]
aws_subnet.test[1]: Creation complete after 1s [id=subnet-083865752394ecc45]
module.test2.aws_lb.test: Creating...
module.test1.aws_lb.test: Creating...
module.test2.aws_lb.test: Still creating... [10s elapsed]
module.test1.aws_lb.test: Still creating... [10s elapsed]
module.test1.aws_lb.test: Still creating... [20s elapsed]
module.test2.aws_lb.test: Still creating... [20s elapsed]
module.test1.aws_lb.test: Still creating... [30s elapsed]
module.test2.aws_lb.test: Still creating... [30s elapsed]
module.test2.aws_lb.test: Still creating... [40s elapsed]
module.test1.aws_lb.test: Still creating... [40s elapsed]
module.test1.aws_lb.test: Still creating... [50s elapsed]
module.test2.aws_lb.test: Still creating... [50s elapsed]
module.test1.aws_lb.test: Still creating... [1m0s elapsed]
module.test2.aws_lb.test: Still creating... [1m0s elapsed]
module.test1.aws_lb.test: Still creating... [1m10s elapsed]
module.test2.aws_lb.test: Still creating... [1m10s elapsed]
module.test1.aws_lb.test: Still creating... [1m20s elapsed]
module.test2.aws_lb.test: Still creating... [1m20s elapsed]
module.test1.aws_lb.test: Still creating... [1m30s elapsed]
module.test2.aws_lb.test: Still creating... [1m30s elapsed]
module.test1.aws_lb.test: Still creating... [1m40s elapsed]
module.test2.aws_lb.test: Still creating... [1m40s elapsed]
module.test2.aws_lb.test: Still creating... [1m50s elapsed]
module.test1.aws_lb.test: Still creating... [1m50s elapsed]
module.test2.aws_lb.test: Still creating... [2m0s elapsed]
module.test1.aws_lb.test: Still creating... [2m0s elapsed]
module.test1.aws_lb.test: Still creating... [2m10s elapsed]
module.test2.aws_lb.test: Still creating... [2m10s elapsed]
module.test1.aws_lb.test: Still creating... [2m20s elapsed]
module.test2.aws_lb.test: Still creating... [2m20s elapsed]
module.test2.aws_lb.test: Creation complete after 2m23s [id=arn:aws:elasticloadbalancing:us-east-1:067819342479:loadbalancer/app/test/e96b1fef116b21ac]
module.test1.aws_lb.test: Creation complete after 2m23s [id=arn:aws:elasticloadbalancing:us-east-1:067819342479:loadbalancer/app/test/e96b1fef116b21ac]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

Enjoy! 🚀

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 feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. Thanks!

Was this page helpful?
0 / 5 - 0 ratings