How to pass bash command output to variable inside TF plan

If my google-fu has failed and this has been asked, please ignore.

I need to create a list of availability zones in my region and spin up private and public subnets in each. I’ve found lots of info on data, templates, and files external to the tf plan itself but these do not answer my use case.

I have attempted using the data.aws_availability_zones.available, along with count to get a list of availability zones my subnets can actually be spun up in. As others have found, the list includes availability zones that subnets are not allowed to spin up in.

So, I wanted to try and pass in a provisioner:local_exec command and have the results flow into a variable list, which works at command line:

"
provisioner “local-exec” {
command = <<EOF
“aws --profile “dev” ec2 --region us-west-1 describe-availability-zones |
grep “ZoneName” |
awk ‘{ print $2 }’ >>>var.AZs”
EOF
}

However, I am still getting the same error:

“Error: error creating subnet: InvalidParameterValue: Value (var.AZs.[count.index]) for parameter availabilityZone is invalid. Subnets can currently only be created in the following availability zones: us-west-1a, us-west-1c. status code: 400, request id:”

This is the script:

provider “aws” {
profile = var.profile
region = var.region
}

resource “aws_vpc” “vpcTest” {
cidr_block = “10.0.0.0/16”
enable_dns_hostnames = true
tags = {
Name = “vpcTest”
}
}

variable “AZs” {
type = list(string)
default = [ “us-west-1a”, “us-west-1c” ]
}

resource “aws_subnet” “TestPublic_subnet” {
provisioner “local-exec” {
command = <<EOF
“aws --profile “myprof” ec2 --region us-west-1 describe-availability-zones |
grep “ZoneName” |
awk '{ print $2,”," }’ >>>var.AZs"

EOF
}
count = 2
vpc_id = “aws_vpc.vpcTest.id”
cidr_block = “10.0.${10+count.index}.0/24”
availability_zone = “var.AZs.[count.index]”
map_public_ip_on_launch = true
tags = {
Name = “PublicSubnet”
}
}

resource “aws_subnet” “TestPrivate_subnet” {
provisioner “local-exec” {
command = <<EOF
“aws --profile “myprof” ec2 --region us-west-1 describe-availability-zones |
grep “ZoneName” |
awk ‘{ print $2 }’ >>>var.AZs”

EOF
}
count = 2
vpc_id = “aws_vpc.vpcTest.id”
cidr_block = “10.0.${20+count.index}.0/24”
availability_zone = “var.AZs.[count.index]”
map_public_ip_on_launch = false
tags = {
Name = “PrivateSubnet”
}
}

resource “aws_security_group” “webtraffic” {
name = “TestAllow-HTTPS”
vpc_id = aws_vpc.vpcTest.id

ingress {
from_port = 443
to_port = 443
protocol = “TCP”
cidr_blocks = [ aws_vpc.vpcTest.cidr_block ]
}
}

In which cases does it fail in your case?

Using your local-exec you’re trying to create a list which also could be created using the data provider while specifying more attributes of it.

Please also try to format your code above within triple backticks.

1 Like

When I remove the 'provisioner “local-exec” command, (and correct my misplaced quotes on my vpc_id, it works. That is because I have a default value. However, I would like to not be bound by the default values.

The provisioner script works on the command line but produces the subnet AZ error as listed above.

Is that what you’re asking?

My point is that the data source for availability zones can do what you’re trying to achieve using the aws cli - filtering and excluding zones is possible.

1 Like

How would I do that in a terraform plan so it pulls the correct AZs for subnets within any region?

That’s the other part of my question - why can’t you use the data source?

# Declare the data source
data "aws_availability_zones" "available" {
  state = "available"
}

# e.g. Create subnets in the first two available availability zones

resource "aws_subnet" "primary" {
  availability_zone = data.aws_availability_zones.available.names[0]

  # ...
}

resource "aws_subnet" "secondary" {
  availability_zone = data.aws_availability_zones.available.names[1]

  # ...
}
1 Like

I tried a variant of that with both [count.index] and the indices.

Indices Error:

  • All the subnets are created in the same availability zone
    • private subnet A us-west-1a
    • private subnet B us-west-1a
    • private subnet C us-west-1a
      -etc.
    • public subnet A us-west-1c
    • public subnet B us-west-1c
    • public subnet C us-west-1c
    • etc.

[count.index] Error:

  • Invalid Index:
    Error: Invalid index

    on main.tf line 39, in resource “aws_subnet” “TestPrivate_subnet”:
    39: availability_zone = data.aws_availability_zones.available.names[count.index]
    |----------------
    | count.index is 7
    | data.aws_availability_zones.available.names is list of string with 2 elements

    The given key does not identify an element in this collection value.

Ultimately, that’s why I was trying to find a workaround. If I name the AZs that can actually take subnets as a default list in the variable, no worries. However, that restricts us to one region and if the AZs that can take subnets change, we have to manually update the TF Plan.

The data.aws_availabilty_zone.available option would be great but it only picks out AZs that are available. It does not filter what can actually be used to spin up subnets. The command below does what I need but I don’t know how to easily make it a part of our TF Plan and pass the info to a variable in the same TF Plan:

“aws --profile “myProf” ec2 --region us-west-1 describe-availability-zones |
grep “ZoneName” |
awk ‘{ print $2 }’ >>>var.AZs”

Hm, I mean the data source provides also filters (as per your preference), so you should get what you’re looking for. I don’t get the difference yet.

Obviously if count.index > max(available.names) it will fail.
However you could also start at the beginning if count gets larger than the list:

locals {
  ml = ["us-west-1a", "us-west-1c", "aaaaa", "bbbbbb"]
}

output oml {
  value = local.ml[0]
}

output oml2 {
  value = local.ml[7%length(local.ml)]
}
1 Like

I will try that and report back.

(thank you for the help!)