Trouble resolving subnet-id using tf .12

I have defined two subnets like this:

resource "aws_subnet" "archer-public-1" {
  vpc_id                  = aws_vpc.archer.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = "true"
  availability_zone       = "${var.AZ1}"
}

resource "aws_subnet" "archer-public-2" {
  vpc_id                  = aws_vpc.archer.id
  cidr_block              = "10.0.2.0/24"
  map_public_ip_on_launch = "true"
  availability_zone       = "${var.AZ2}"
}

Now I want to create an EC2 instance and deploy each instance in each Public subnet defined above. I tried this with no success:

resource "aws_instance" "nginx" {
  count = 2
  ami           = var.AMIS[var.AWS_REGION]
  instance_type = "t2.micro"

  # the VPC subnet
  subnet_id = "aws_subnet.archer-public-${count.index}.id"  <=== ?????????
  #subnet_id = aws_subnet.archer-public-2.id
...
}

The error I’m getting is this:

Error: Error launching source instance: InvalidSubnetID.NotFound: The subnet ID 'aws_subnet.archer-public-1.id' does not exist
    status code: 400, request id: 26b4f710-e968-484d-a17a-6faa5a9d15d5

The above subnet_id assignment doesn’t work… removing the double-quotes doesn’t either… So how can I dynamically deploy that EC2 in each public Subnet defined above?

Could you show the error you’re receiving?

Error message now posted above. It’s interpolating the values correctly for the subnet name but it’s not fetching the ID for it.

1 Like

Well that’s because you’ve said the subnet id is the string “aws_subnet.archer-public-${count.index}.id” with the index interpolated into it.

You’re much better off making your base data (i.e. the AZs, CIDR blocks) into variable data and then for_each’ing the dependent resources.

e.g.

locals {
  subnets = {
    pub_a = {
      cidr = "10.0.0.0/20"
      az = "us-east-2a"
    },
    pub_b = {
      cidr = "10.0.16.0/20"
      az = "us-east-2b"
    },
    pub_c = {
      cidr = "10.0.32.0/20"
      az = "us-east-2c"
    }
  }
}

resource "aws_subnet" "subnet" {
    for_each = local.subnets
    ...
    cidr_block = each.value.cidr
    availability_zone = each.value.az
}

resource "aws_route_table_association" "internet_rt_assoc" {
    for_each = local.subnets
    subnet_id = aws_subnet.subnet[each.key].id
    ...
}

Given that these two subnets are following a systematic pattern, I agree that this seems like a good candidate for for_each.

For the sake of completeness, there’s another answer that can be used in (rarer) situations where the objects in question are not configured systematically but you need to select one dynamically anyway:

locals {
  subnets = {
    public_1 = aws_subnet.archer_public_1
    public_2 = aws_subnet.archer_public_2
  }
}

This explicitly defines a lookup table for subnets by your arbitrary keys, allowing referring to them by those keys:

resource "aws_instance" "nginx" {
  for_each = local.subnets

  # ...

  subnet_id = each.value.id
}

The for_each mechanism is a way to construct a lookup table like this automatically by systematically creating many resource instances from a single resource block, but it’s possible to construct such a lookup table manually in rarer situations where the objects themselves are not configured systematically.

Both are fantastic examples above, thanks very much. One other question: what’s the advantage of using locals{} construct instead of var {} construct? Just wondering, don’t see any distinction between them.

Hi @jjtdepaul,

A variable block defines an input variable that should be set by the caller of your module, similar in principle to a parameter to a function in a general-purpose programming language.

A locals block, on the other hand, defines a reusable value that is internal to the module where you define it, similar to a local variable in a general-purpose programming language.

When your values are constants anyway it is in principle possible to use a variable block with a default set as equivalent to an entry in a locals block. However, for the example I shared in my previous comment a locals block would be required because the local value is derived from other objects in the same module, whereas input variables can refer (indirectly) only to objects in the parent module.

(If you are currently working only in a single-module configuration without any module blocks then this distinction can feel rather arbitrary indeed, but for a root module a variable block is intended to have its value set by -var or -var-file arguments on the command line, whereas a locals entry is always defined only in the configuration.)

Makes perfect sense, thanks very much.