I'm creating two public subnets that will each contain a nat gateay. My code, attempts to create these nats per subnet, and then allocate the eip to each. However, since my for each starts the code block, it looks like the allocation id became us-east-* instead of the id of the eip.
Variables.tf:
variable "public_subnet_numbers" {
type = map(number)
description = "Map of AZ to a number that should be used for public subnets"
default = {
"us-east-1a" = 1
"us-east-1b" = 2
#"us-east-1c" = 3
}
}
variable "private_subnet_numbers" {
type = map(number)
description = "Map of AZ to a number that should be used for private subnets"
default = {
"us-east-1a" = 4
"us-east-1b" = 5
#"us-east-1c" = 6
}
}
variable "vpc_cidr" {
type = string
description = "The IP range to use for the VPC"
default = "192.168.0.0/16"
}
Main.tf:
resource "aws_eip" "nat" {
count = 2
vpc = true
lifecycle {
# prevent_destroy = true
}
tags = {
Name = "cf-${var.infra_env}-eip"
Project = "cf.io"
Environment = var.infra_env
VPC = aws_vpc.vpc.id
ManagedBy = "terraform"
Role = "private"
}
}
resource "aws_nat_gateway" "ngw" {
for_each = var.private_subnet_numbers
subnet_id = each.value.id #aws_subnet.public[each.key].id
allocation_id = aws_eip.nat[each.key].id
tags = {
Name = "cf-${var.infra_env}-ngw"
Project = "cf.io"
VPC = aws_vpc.vpc.id
Environment = var.infra_env
ManagedBy = "terraform"
Role = "private"
}
}
Error:
Error: Invalid index
│
│ on ../terraform/modules/networking/gateways.tf line 42, in resource "aws_nat_gateway" "ngw":
│ 42: allocation_id = aws_eip.nat[each.key].id
│ ├────────────────
│ │ aws_eip.nat is tuple with 2 elements
│ │ each.key is "us-east-1a"
│
│ The given key does not identify an element in this collection value: a number is required.
╵
╷
│ Error: Invalid index
│
│ on ../terraform/modules/networking/gateways.tf line 42, in resource "aws_nat_gateway" "ngw":
│ 42: allocation_id = aws_eip.nat[each.key].id
│ ├────────────────
│ │ aws_eip.nat is tuple with 2 elements
│ │ each.key is "us-east-1b"
│
│ The given key does not identify an element in this collection value: a number is required.
You're mixing count and for_each. The easiest way to solve this would be to use for_each in your EIP creation as well, which makes sense because you are creating an EIP for each NAT. That would also make your code work better if you decided to add another subnet later, you wouldn't need to go in and change the count from 2 to 3.
Otherwise, you need to use the index function to convert the each value to an index number.
As Mark B mentioned mixing the count and for_each is not recommended. In your current setup using exclusively for_each is the way to go based on the private_subnet_numbers variable.
In your aws_eip.nat resource change count to for_each
resource "aws_eip" "nat" {
for_each = var.private_subnet_numbers
vpc = true
}
Next in your resource aws_nat_gateway.ngw you should refer to subnet ids using each
resource "aws_nat_gateway" "ngw" {
for_each = var.private_subnet_numbers
subnet_id = aws_subnet.public[each.key].id
....
}
And the code as a whole for clarity
resource "aws_vpc" "vpc" {
... vpc configurations ...
}
resource "aws_subnet" "public" {
for_each = var.private_subnet_numbers
vpc_id = aws_vpc.vpc.id
... subnet configurations ...
}
resource "aws_eip" "nat" {
for_each = var.private_subnet_numbers
vpc = true
lifecycle {
# prevent_destroy = true
}
tags = {
Name = "cf-${var.infra_env}-eip"
Project = "cf.io"
Environment = var.infra_env
VPC = aws_vpc.vpc.id
ManagedBy = "terraform"
Role = "private"
}
}
resource "aws_nat_gateway" "ngw" {
for_each = var.private_subnet_numbers
subnet_id = aws_subnet.public[each.key].id
allocation_id = aws_eip.nat[each.key].id
tags = {
Name = "cf-${var.infra_env}-ngw"
Project = "cf.io"
VPC = aws_vpc.vpc.id
Environment = var.infra_env
ManagedBy = "terraform"
Role = "private"
}
}
Related
I've got a couple of variables set in my variables.tf files which are of type list and I am trying to find the correct syntax on my .tfvars file
variables.tf
variable "subnet_cidrs" {
type = list(string)
description = "A list of Subnets CIDR's - Should consist of minimum 2"
}
variable "aws_region" {
type = string
description = "Region where all AWS Resources will be created"
}
variable "az" {
type = list(string)
description = "Availability-Zones - Should match numbers of CIDRs given and AWS Region"
}
terraform.tfvars
subnet_cidrs = ["192.168.1.100", "192.168.4.100"]
aws_region = "eu-central-1"
az = ["eu-central-1a", "eu-central-1b"]
A typical output i'll receive is :
│ Error: Incorrect attribute value type
│
│ on ..\ec2\ec2.tf line 21, in resource "aws_subnet" "public":
│ 21: availability_zone = var.az
│ ├────────────────
│ │ var.az is a list of dynamic
│
│ Inappropriate value for attribute "availability_zone": string required.
When brackets are removed for example : az = "eu-central-1a", "eu-central-1b"
It will return the following instead Argument definitions must be separated by newlines, not commas. An argument definition must end with a newline.
Edit
ec2.tf
I've made this as short as possible and included only the variables
# Main VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "vpc-${var.name_prefix}"
}
}
# Two Subnets in different AZ - Public IP on launch
resource "aws_subnet" "public" {
count = length(var.subnet_cidrs)
cidr_block = var.subnet_cidrs
availability_zone = var.az
tags = {
Name = "subnet-${var.name_prefix}-${count.index}"
}
}
resource "aws_security_group" "ec2_sg" {
name = var.ec2_name
...
tags = {
Name = "EC2-SG-${var.name_prefix}"
}
}
# VM Key pair
resource "aws_key_pair" "auth" {
key_name = var.key_pair_name
public_key = file("~/.ssh/${var.ssh_file_name}.pub")
}
# EC2 Instance within 2 AZ's
resource "aws_instance" "ec2" {
count = length(var.subnet_cidrs)
tags = {
Name = "ubuntu-${var.name_prefix}-${count.index}"
}
}
Based on the variable definition and the way subnet resource is created, the following change is required:
resource "aws_subnet" "public" {
count = length(var.subnet_cidrs)
cidr_block = var.subnet_cidrs
availability_zone = var.az[count.index] # <---- using count.index here
tags = {
Name = "subnet-${var.name_prefix}-${count.index}"
}
}
This is needed because otherwise the problem will occur as availability_zone requires a single string value and passing only var.az will be an entire list. Using the count.index with the var.az will fetch a single value as is required.
I am trying to create 2 subnets in aws (with terraform) by passing 2 values in single variable.
Getting below error while executing "terraform validate" command
Please guide me how to correctly define list(string) variable data type in terraform module and correctly use it.
│ Error: Invalid value for input variable
│
│ on usage-test.tf line 11, in module "vpc_module":
│ 11: subnet_cidr_block = ["10.0.0.0/24","10.0.1.0/24"]
│
│ The given value is not suitable for module.vpc_module.var.subnet_cidr_block declared at vpc/var-test.tf:21,1-29: string required.
╵
╷
│ Error: Invalid value for input variable
│
│ on usage-test.tf line 12, in module "vpc_module":
│ 12: subnet_az = ["ap-south-1a","ap-south-1b"]
│
│ The given value is not suitable for module.vpc_module.var.subnet_az declared at vpc/var-test.tf:25,1-21: string required.
╵
refer terraform files below:-
variable.tf:
variable "subnet_cidr_block" {
type = list(string)
}
variable "subnet_az" {
type = list(string)
}
main.tf:
resource "aws_subnet" "mysubnet_public" {
vpc_id = aws_vpc.myvpc.id
cidr_block = var.subnet_cidr_block
availability_zone = var.subnet_az
map_public_ip_on_launch = "true"
depends_on = [aws_internet_gateway.mygw]
}
usage.tf
provider "aws" {
region = "ap-south-1"
}
module "vpc_module" {
source = "./vpc"
vpc_cider_block = "10.0.0.0/16"
vpc_name = "myvpc"
route_table_name = "myrt"
subnet_cidr_block = ["10.0.0.0/24","10.0.1.0/24"]
subnet_az = ["ap-south-1a","ap-south-1b"]
# subnet_cidr_block = "10.0.0.0/24"
# subnet_az = "ap-south-1a"
# subnet_public_name = "mysubnet_public"
sg_mgmt_name = "mysg_mgmt"
}
Well, the error is pretty clear. You cannot use a list of strings, rather a single string value, as the provider documentation also shows [1]:
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24" # <---- A single string value, not a list of strings
tags = {
Name = "Main"
}
}
As a hint for the future: the argument is singular, i.e., cidr_block so that usually means it's a single value.
[1] https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet#basic-usage
Thank you #Marko E for your suggestion
after some research found solution for this issue, refer below code.:-
main.tf
#below code is for creating multiple subnets
resource "aws_subnet" "mysubnet_public" {
count = length(var.public_subnet_cidr)
vpc_id = aws_vpc.myvpc.id
cidr_block = element(var.public_subnet_cidr,count.index)
availability_zone = element(var.azs,count.index)
map_public_ip_on_launch = true
tags = {
Name = "Subnet-${count.index+1}"
}
}
#below code is for associating above created multiple subnets to route table
resource "aws_route_table_association" "myroutetableassociation_public" {
count = length(var.public_subnet_cidr)
subnet_id = element(aws_subnet.mysubnet_public[*].id, count.index)
route_table_id = aws_route_table.myroutetable_public.id
}
output.tf
output "mysubnet_public" {
description = "List of IDs of public route tables"
value = aws_subnet.mysubnet_public[*].id
}
output "myroutetableassociation_public" {
value = aws_route_table_association.myroutetableassociation_public[*].id
}
variable.tf
variable "public_subnet_cidr" {
type = list
}
variable "azs" {
type = list
}
usage.tf
provider "aws" {
region = "ap-south-1"
}
module "vpc_module" {
source = "./vpc"
vpc_name = "myvpc"
public_subnet_cidr = ["10.0.0.0/24", "10.0.1.0/24"]
azs = ["ap-south-1a", "ap-south-1b"]
}
I have a VPC module which creates private subnets
resource "aws_subnet" "private" {
for_each = { for index, az_name in local.az_names : index => az_name }
vpc_id = aws_vpc.network.id
cidr_block = cidrsubnet(aws_vpc.network.cidr_block, 8, each.key + 11)
availability_zone = local.az_names[each.key]
tags = {
Name = "${var.env}-private-${local.az_names[each.key]}"
Description = local.az_names[each.key]
Type = "private"
}
}
And an output
output "private_subnet" {
value = aws_subnet.private
}
Now I am trying to create an EFS module which will create a mount target for each private subnet
resource "aws_efs_mount_target" "efs-mt" {
count = length(var.private_subnet)
file_system_id = aws_efs_file_system.efs.id
subnet_id = var.private_subnet[count.index].id
security_groups = var.security_groups
}
main.tf
module "efs" {
source = "./modules/efs"
env = var.env
vpc_id = module.vpc.vpc_id
security_groups = [module.security.efs_sg_ids]
private_subnet = module.vpc.private_subnet
}
But I am getting these errors
│ Error: Invalid index
│
│ on modules/efs/main.tf line 14, in resource "aws_efs_mount_target" "efs-mt":
│ 14: subnet_id = var.private_subnet[count.index].id
│ ├────────────────
│ │ count.index is a number, known only after apply
│ │ var.private_subnet is a string, known only after apply
│
│ This value does not have any indices.
You are mixing count and for_each meta-arguments which is not a good idea. To fix that, you could do the following:
Leave the count meta-argument in the EFS resource but change the output of the VPC module
Use for_each with the EFS module as well
Even though I would suggest the second approach, I will provide both options. In the output in the VPC module, you can switch to:
output "private_subnet" {
value = values(aws_subnet.private)[*].id
}
The values built-in function [1] will return a list of subnet IDs which you can then use as the intended input for the EFS module. In this case, you would have to change the module input variable type from string to list(string):
variable "private_subnet" {
type = list(string)
description = "List of subnet IDs."
}
On the other hand, you could try switching the EFS resource to use for_each instead of count and passing it the output value of the VPC module the way it currently is:
resource "aws_efs_mount_target" "efs-mt" {
for_each = var.private_subnet
file_system_id = aws_efs_file_system.efs.id
subnet_id = each.value.id
security_groups = var.security_groups
}
In this case, you would have to change the module input variable type from string to map(any):
variable "private_subnet" {
type = map(any)
description = "Map of all the values returned by the subnet resource."
}
[1] https://www.terraform.io/language/functions/values
I am trying to update the routing tables of the subnets in VPC A and VPC B to include a route to a VPC peering end-point. This is my terraform code.
main.tf
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 3.14.2"
for_each = local.vpc_list
name = each.key
cidr =each.value.vpc_cidr
azs = each.value.vpc_azs
public_subnets = each.value.vpc_public_subnets
private_subnets = each.value.vpc_private_subnets
enable_nat_gateway = each.value.vpc_enable_nat_gateway
enable_vpn_gateway = each.value.vpc_enable_vpn_gateway
tags = each.value.vpc_tags
public_subnet_tags = each.value.vpc_public_subnet_tags
private_subnet_tags = each.value.vpc_private_subnet_tags
}
resource "aws_vpc_peering_connection" "vpc_peering_conn" {
peer_owner_id = data.aws_caller_identity.current.account_id
peer_vpc_id = module.vpc["vpcB"].vpc_id
vpc_id = module.vpc["vpcA"].vpc_id
auto_accept = true
tags = {
Name = "VPC Peering between ${module.vpc["vpcA"].name} and ${module.vpc["vpcB"].name}."
}
}
data "aws_route_tables" "vpcA_public_subnet_rts" {
depends_on = [ module.vpc ]
vpc_id = module.vpc["vpcA"].vpc_id
filter {
name = "tag:Subnet"
values = ["*public*"]
}
}
resource "aws_route" "route_vpcA" {
count = length(data.aws_route_tables.vpcA_public_subnet_rts.ids)
route_table_id = tolist(data.aws_route_tables.vpcA_public_subnet_rts.ids)[count.index]
destination_cidr_block = "10.10.11.0/24"
vpc_peering_connection_id = aws_vpc_peering_connection.vpc_peering_conn.id
}
data "aws_route_tables" "vpcB_private_subnet_rts" {
depends_on = [ module.vpc ]
vpc_id = module.vpc["vpcB"].vpc_id
filter {
name = "tag:Subnet"
values = ["*private*"]
}
}
resource "aws_route" "route_vpcB" {
count = length(data.aws_route_tables.vpcB_private_subnet_rts.ids)
route_table_id = tolist(data.aws_route_tables.vpcB_private_subnet_rts.ids)[count.index]
destination_cidr_block = "10.10.10.0/24"
vpc_peering_connection_id = aws_vpc_peering_connection.vpc_peering_conn.id
}
locals.tf
locals {
vpc_list = {
"vpcA" = {
vpc_cidr = "10.10.10.0/24",
vpc_azs = ["ap-southeast-1a"],
vpc_public_subnets = ["10.10.10.0/25"],
vpc_private_subnets = ["10.10.10.128/25"],
vpc_enable_nat_gateway = false,
vpc_enable_vpn_gateway = false,
vpc_tags = {
Name= "VPC A"
Terraform = "true"
Environment = "1st VPC"
Facing= "public and private"
},
vpc_public_subnet_tags = {
Subnet = "vpcA_public_subnet"
},
vpc_private_subnet_tags = {
Subnet = "vpcA_private_subnet"
},
},
"vpcB" = {
vpc_cidr = "10.10.11.0/24",
vpc_azs = ["ap-southeast-1b"],
vpc_public_subnets = [],
vpc_private_subnets = ["10.10.11.0/24"],
vpc_enable_nat_gateway = false,
vpc_enable_vpn_gateway = false,
vpc_tags = {
Name= "VPC B"
Terraform = "true"
Environment = "2nd VPC"
Facing= "private"
},
vpc_public_subnet_tags = {
Subnet = "vpcB_public_subnet"
},
vpc_private_subnet_tags = {
Subnet = "vpcB_private_subnet"
},
},
}
}
locals {
routing_table = {
route_peer_con_vpcA = {
vpc_id = module.vpc["vpcA"].vpc_id
route = {
route_peer_to_vpcB = {
cidr_block = "10.10.11.0/24"
}
}
}
route_peer_con_vpcB = {
vpc_id = module.vpc["vpcB"].vpc_id
route = {
route_peer_to_vpcA = {
cidr_block = "10.10.10.0/24"
}
}
}
}
}
When I run the terraform plan or apply I am getting the below error. Does anyone knows how to address the issue or Is there a better way to achieve what I want?
I saw this post "terraform: data.aws_subnet, value of 'count' cannot be computed" but am not sure how to refer to the output of the subnet' s id for the routing table id.
Thanks.
➜ 01-tf-deploy terraform apply --auto-approve
data.aws_region.current: Reading...
data.aws_caller_identity.current: Reading...
data.aws_region.current: Read complete after 0s [id=ap-southeast-1]
data.aws_ami.amzlinux2: Reading...
data.aws_ami.amzlinux2: Read complete after 1s [id=ami-0c802847a7dd848c0]
data.aws_caller_identity.current: Read complete after 1s [id=500295128231]
╷
│ Error: Invalid count argument
│
│ on main.tf line 70, in resource "aws_route" "route_vpcA":
│ 70: count = length(data.aws_route_tables.vpcA_public_subnet_rts.ids)
│
│ The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count
│ depends on.
╵
╷
│ Error: Invalid count argument
│
│ on main.tf line 88, in resource "aws_route" "route_vpcB":
│ 88: count = length(data.aws_route_tables.vpcB_private_subnet_rts.ids)
│
│ The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count
│ depends on.
╵
I'm trying to study Terraform. I have subnets, that are created with "for_each" expression.
variable "privateSubnetCIDR" {
type = list(string)
default = ["10.0.10.0/24","10.0.20.0/24"]
}
resource "aws_subnet" "privatesubnet" {
for_each = toset(var.privateSubnetCIDR)
cidr_block = each.key
vpc_id = aws_vpc.dev_vpc.id
map_public_ip_on_launch = false
availability_zone = element(data.aws_availability_zones.availableAZ.names, index(var.privateSubnetCIDR, each.key))
tags = {
name = "${var.environment}-privatesubnet-${index(var.privateSubnetCIDR, each.key) + 1}"
AZ = element(data.aws_availability_zones.availableAZ.names, index(var.privateSubnetCIDR, each.key))
Environment = "${var.environment}-privatesubnet"
}
}
And I also have NAT routetable, that uses "count".
resource "aws_route_table" "nat_routetable" {
vpc_id = aws_vpc.dev_vpc.id
count = length(var.publicSubnetCIDR)
route {
cidr_block = var.route_cidr
gateway_id = aws_nat_gateway.nat-gateway[count.index].id
}
depends_on = [aws_nat_gateway.nat-gateway]
}
resource "aws_route_table_association" "nat_routeTableAssociation" {
count = length(var.privateSubnetCIDR)
route_table_id = aws_route_table.nat_routetable[count.index].id
subnet_id = aws_subnet.privatesubnet[count.index].id
}
After terraform plan I get an error on the last string.
│ Error: Invalid index
│
│ on modules/network/subnets.tf line 91, in resource "aws_route_table_association" "nat_routeTableAssociation":
│ 91: subnet_id = aws_subnet.privatesubnet[count.index].id
│ ├────────────────
│ │ aws_subnet.privatesubnet is object with 2 attributes
│ │ count.index is 0
│
│ The given key does not identify an element in this collection value. An object only supports looking up attributes by name,
│ not by numeric index.
If I use "count" in subnet definition, all works. But is there any way to use "for_each" in subnet definition and "count" in route_table definition?
You could do that, yes. You need to go via the intermediary var.privateSubnetCIDR though:
aws_subnet.privatesubnet[var.privateSubnetCIDR[count.index]].id
But at that point you should just use a for_each on the aws_route_table_association as well.