Terraform append string to for_each each.key - amazon-web-services

I want to append some text to the Name tag for each resource created. I want the name to be the 'key' name + "a string"
resource "aws_vpc_peering_connection" "commerce_vpc_pc" {
for_each = local.requester_vpcs
peer_vpc_id = data.aws_vpc.tf_commerce_vpc.id
vpc_id = each.value.vpc_id
auto_accept = true
tags = {
Name = [each.key]+"_pc_commerce"
}
}
This give the error:
│ Error: Invalid operand
│
│ on main.tf line 34, in resource "aws_vpc_peering_connection" "commerce_vpc_pc":
│ 34: Name = [each.key]+"-to-commerce_vpc"
│
│ Unsuitable value for right operand: a number is required.
which makes sense. However is it possible to append some text to the each.key key?

resource "aws_vpc_peering_connection" "commerce_vpc_pc" {
for_each = local.requester_vpcs
peer_vpc_id = data.aws_vpc.tf_commerce_vpc.id
vpc_id = each.value.vpc_id
auto_accept = true
tags = {
Name = "${each.key}_pc_commerce"
}
}
See this another example of provisioning a customer gateway.
################################################################################
# Customer Gateways
################################################################################
resource "aws_customer_gateway" "this" {
for_each = var.customer_gateways
bgp_asn = each.value["bgp_asn"]
ip_address = each.value["ip_address"]
device_name = lookup(each.value, "device_name", null)
type = "ipsec.1"
tags = merge(
{ Name = "${var.name}-${each.key}" },
var.tags,
var.customer_gateway_tags,
)
}

Related

Terraform.tfvars parsing lists for variables.tf file

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.

getting error while using list(string) data type in terraform module

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"]
}

Terraform --Create multiple hosted zoned and assign different records for each zones

I am new to terraform and trying to change the existing script were we used to create 1 route 53 zone and corresponding route 53 record , now the requirement is to add one more zone and 53 record (correspondingly) , i am trying multi level map , i need your help correct my code
tf.vars
variable "facade_hostname" = {
type = "map"
default = {
old_mobile_facade_hostname = "xxx.morgen.nl"
new_mobile_facade_hostname = "xxx.test.nl"
}
}
dns_config = {
old_dns_records = {
mobile_facade = {
name = "xxx.morgen.nl",
ttl = "5",
type = "A",
records = [
"1.2.3.4"]
}
},
new_dns_records = {
mobile_facade = {
name = "xxx.test.nl",
ttl = "5",
type = "A",
records = [
"5.6.7.8"]
}
}
}
varibles.tf
variable "dns_config" {
type = map(object({
name = string
ttl = string
type = string
records = string
}))
default = {}
}
variable "facade_hostname" {
type = map(object({
old_mobile_facade_hostname = string
new_mobile_facade_hostname = string
}))
default = {}
}
and finally my resource creation
resource "aws_route53_zone" "private" {
for_each = var.facade_hostname
count = var.dns_config != "" && var.facade_hostname != "" ? 1 : 0
name = var.facade_hostname
force_destroy = true
vpc {
vpc_id = module.vpc_private.vpc_id
}
}
resource "aws_route53_record" "A" {
for_each = var.facade_hostname
count = var.dns_config != "" && var.facade_hostname!= "" ? 1 : 0
zone_id = aws_route53_zone.private[count.index].zone_id
name = var.dns_config.facade_hostname.name
ttl = var.dns_config.facade_hostname.ttl
type = var.dns_config.facade_hostname.type
records = var.dns_config.facade_hostname.records
allow_overwrite = true
}
Error i am encountering, when running the terraform init
╷
│ Error: Invalid combination of "count" and "for_each"
│
│ on route53.tf line 2, in resource "aws_route53_zone" "private":
│ 2: for_each = var.facade_hostname
│
│ The "count" and "for_each" meta-arguments are mutually-exclusive, only one
│ should be used to be explicit about the number of resources to be created.
╵
╷
│ Error: Invalid combination of "count" and "for_each"
│
│ on route53.tf line 12, in resource "aws_route53_record" "A":
│ 12: for_each = var.facade_hostname
│
│ The "count" and "for_each" meta-arguments are mutually-exclusive, only one
│ should be used to be explicit about the number of resources to be created.
╵
aws-vault: error: exec: Failed to wait for command termination: exit status 1
Thanks
Finally after spending some time , this seems a working solution incase if it helps any one in future , to create couple of hosted zone and create different A record based on hosted zone,
resource "aws_route53_zone" "private" {
for_each = var.mobile_facade_hostname
name = each.key
force_destroy = true
vpc {
vpc_id = module.vpc_private.vpc_id
}
}
resource "aws_route53_record" "A" {
for_each = aws_route53_zone.private
zone_id = each.value["zone_id"]
name = trimsuffix(each.value["name"], ".")
type = "A"
ttl = "5"
records = [var.mobile_facade_hostname[trimsuffix(each.value["name"], ".")]]
My tfvars
mobile_facade_hostname = { "x.y.nl" = "1.2.3.4", "a.b.nl" = "5.6.7.8" }
variables.tf
variable "mobile_facade_hostname" {
type = map(string)
default = {}
}

Attach each EIP to each Nat Gatway in Terraform

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"
}
}

How to set nat_gateway_id in aws_route_table

main.tf
module "vpc" {
source = "../modules/aws/vpc"
env_prefix = "prod"
environment = "production"
env_name = "test"
vpc_cidr = "10.1.0.0/16"
public_subnet_cidrs = {
test-prod-nat = {
subnet = "10.1.15.0/24"
name = "test-prod-nat"
az = "ap-northeast-1a"
}
}
}
nat.tf
resource "aws_nat_gateway" "private" {
for_each = var.public_subnet_cidrs
allocation_id = aws_eip.nat_gateway.id
subnet_id = aws_subnet.public[each.key].id
tags = merge(
local.tags,
{
Name = format("%s_%s_%s", var.env_prefix, var.env_name, "nat-gateway")
}
)
lifecycle {
prevent_destroy = false
}
}
route_table.tf
/**
* for private subnet
*/
resource "aws_route_table" "private" {
vpc_id = aws_vpc.dandori.id
tags = merge(
local.tags,
{
Name = format("%s_%s", var.env_prefix, var.env_name)
}
)
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = [
for v in aws_nat_gateway.private : v.id
]
}
lifecycle {
prevent_destroy = false
}
}
When I run the terraform plan after creating the above tf file, I get the following Error
【Error】
╷
│ Error: Incorrect attribute value type
│
│ on ../modules/aws/vpc/route_table.tf line 55, in resource "aws_route_table" "private":
│ 55: nat_gateway_id = [
│ 56: for v in aws_nat_gateway.private : v.id
│ 57: ]
│ ├────────────────
│ │ aws_nat_gateway.private is object with 1 attribute "test-prod-nat"
│
│ Inappropriate value for attribute "nat_gateway_id": string required.
route_table.tf and nat.tf will be files in the module
I'm trying to set the nat_gateway_id in route_table.tf using the for loop method, but I can't set it correctly as shown in the Error message.
What should I do to solve this problem?
Please give me some advice.
If you want to create a route table for each aws_nat_gateway.private, then it should be:
resource "aws_route_table" "private" {
for_each = aws_nat_gateway.private
vpc_id = aws_vpc.dandori.id
tags = merge(
local.tags,
{
Name = format("%s_%s", var.env_prefix, var.env_name)
}
)
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = each.value["id"]
}
lifecycle {
prevent_destroy = false
}
}