Terraform how to access resource local name as a variable inside the resource block itself - terraform

resource "aws_subnet" "goodsubnet" {
vpc_id = "VPCID"
cidr_block = "x.x.x.x/x"
availability_zone = "xyz"
tags =
{
tagname1 = "$something"
}
}
I want the tag "tagname1" to dynamically have the value of the resource local name i.e "goodsubnet" Is there a variable I can use?
Thanks

That is not possible the way you want to do it. However, Terraform has a concept of variables, which you can use to assign values to arguments. That could be either a local value [1] or an input variable [2]. For example, an input variable definition:
variable "subnet_name_tag" {
type = string
description = "Tag name for a subnet."
}
Then, in your code you would do:
resource "aws_subnet" "goodsubnet" {
vpc_id = "VPCID"
cidr_block = "x.x.x.x/x"
availability_zone = "xyz"
tags =
{
tagname1 = var.subnet_name_tag
}
}
Alternatively, you could define a local value:
locals {
subnet_tag_name = "goodsubnet"
}
Followed by:
resource "aws_subnet" "goodsubnet" {
vpc_id = "VPCID"
cidr_block = "x.x.x.x/x"
availability_zone = "xyz"
tags =
{
tagname1 = local.subnet_name_tag
}
}
[1] https://developer.hashicorp.com/terraform/language/values/locals
[2] https://developer.hashicorp.com/terraform/language/values/variables

Related

terraform do not get variable passed through modules

I want to provision a simple terraform vpc and subnet.
I created a module and declared all the variable required in the module variable.tf
and passed wrote all the needed values through terraform.tfvars
but when I run terraform apply I get a prompt to insert a value for avail_zone
main.tf
module "myapp-subnet" {
source = "./modules/subnet"
subnet_cidr_block = var.subnet_cidr_block
avail_zone = "us-east-1a"
env_prefix = "dev"
vpc_id = aws_vpc.myapp-vpc.id
default_route_table_id = aws_vpc.myapp-vpc.default_route_table_id
}
modules/subnet/main.tf
resource "aws_subnet" "myapp-subnet-1" {
vpc_id = var.vpc_id
cidr_block = var.subnet_cidr_block
availability_zone = var.avail_zone
tags = {
Name: "${var.env_prefix}-subnet-1"
}
}
resource "aws_internet_gateway" "myapp-igw"{
vpc_id = var.vpc_id
tags = {
Name: "${var.env_prefix}-igw"
}
}
# using existing route table
resource "aws_default_route_table" "default-rtb" {
default_route_table_id = var.default_route_table_id
route{
cidr_block = "0.0.0.0/10"
gateway_id = aws_internet_gateway.myapp-igw.id
}
tags = {
Name: "${var.env_prefix}-main-rtb"
}
}
module/subnet/variables.tf
variable vpc_cidr_blocks {}
variable subnet_cidr_block {}
variable avail_zone {}
variable env_prefix{}
variable vpc_id {}
variable default_route_table_id {}
terraform.tfvars
vpc_cidr_blocks = "xxxx"
subnet_cidr_block = "xxxx"
avail_zone = "xxxxxx"
env_prefix = "dev"
myip = "xxxxxxx"
instance_type="t2.micro"
public_key_location= "/home/xxxxxxxx/.ssh/id_rsa.pub"

Getting the list of subnet ids created with for_each

How may I get the list of subnet ids created with for_each (need at the bottom of my script):
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
provider "aws" {
region = "eu-west-1"
}
data "aws_availability_zones" "azs" {
state = "available"
}
locals {
az_names = data.aws_availability_zones.azs.names
}
variable "vpc_cidr" {
default = "10.0.0.0/16"
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
}
resource "aws_subnet" "private" {
for_each = {for idx, az_name in local.az_names: idx => az_name}
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, each.key)
availability_zone = local.az_names[each.key]
}
module "eks" {
source = "terraform-aws-modules/eks/aws"
# I need to get the list of subnet ids (aws_subnet.private) here
subnet_ids = []
}
Since the aws_subnet resource was created with for_each, you could reference the values to create a list using for [1]:
subnet_ids = [for k, v in aws_subnet.private : aws_subnet.private[k].id]
Just tested with the same code you have and terraform plan shows no errors.
[1] https://www.terraform.io/language/expressions/for#for-expressions
While using a for is correct, a shorter expression would be to use values function with a splat operator:
subnet_ids = values(aws_subnet.private)[*].id

Referencing a created subnet and associate it with a given EIP for a NLB (aws provider)

I'm trying to parametrize the creation of a NLB, and provision in the same plan the necessary public subnets.
The subnets are specified as a variable of the plan:
variable "nlb_public_subnets" {
type = list(object({
name = string
network_number = number
availability_zone = string
elastic_ip = string
}))
default = [
{
name = "sftp_sub_A"
network_number = 1
availability_zone = "eu-west-1a"
elastic_ip = "X.Y.Z.T"
},
{
name = "sftp_sub_B"
network_number = 2
availability_zone = "eu-west-1b"
elastic_ip = "XX.YY.ZZ.TT"
}
]
}
variable "common_tags" {
description = "A map containing the common tags to apply to all resources"
type = map(string)
default = {}
}
locals {
vpc_id = "dummy"
base_cidr = "10.85.23.0/24"
publicSubnets = { for s in var.nlb_public_subnets :
s.name => {
name = s.name
cidr_block = cidrsubnet(var.base_public_subnet_cidr_block, 6,
s.network_number )
availability_zone = s.availability_zone
elastic_ip = s.elastic_ip
}
}
}
I'm specifying a name, a network number (to compute the cidr block), an availability zone, and an elastic IP to map to when creating the NLB.
Here I'm creating the subnets:
#Comment added after solution was given
#This will result in a Map indexed by subnet.name provided in var.nlb_public_subnets
resource "aws_subnet" "sftp_nlb_subnets" {
for_each = { for subnet in local.publicSubnets :
subnet.name => subnet
}
cidr_block = each.value.cidr_block
vpc_id = local.vpc_id
availability_zone = each.value.availability_zone
tags = {
Name = each.key
Visibility = "public"
Purpose = "NLB"
}
}
Now I need to create my NLB, and this is where I'm struggling on how to associate the freshly created subnets with the Elastic IP provided in the configuration:
resource "aws_lb" "sftp" {
name = var.name
internal = false
load_balancer_type = "network"
subnets = [for subnet in aws_subnet.sftp_nlb_subnets: subnet.id]
enable_deletion_protection = true
tags = merge(var.common_tags,{
Name=var.name
})
dynamic "subnet_mapping" {
for_each = aws_subnet.sftp_nlb_subnets
content {
subnet_id = subnet_mapping.value.id
allocation_id = ????Help???
}
}
}
Could I somehow look up the configuration object with the help of the subnet name in the tags?
UPDATE1
Updated the dynamic block, as it had a typo.
UPDATE2
#tmatilai nailed the answer!
Here's the modified aws_lb block:
#
#This will result in a Map indexed by subnet.name provided in var.nlb_public_subnets
data "aws_eip" "nlb" {
for_each = local.publicSubnets
public_ip = each.value.elastic_ip
}
resource "aws_lb" "sftp" {
name = var.name
internal = false
load_balancer_type = "network"
subnets = [for subnet in aws_subnet.sftp_nlb_subnets : subnet.id]
enable_deletion_protection = true
tags = merge(var.common_tags, {
Name = var.name
})
dynamic "subnet_mapping" {
#subnet_mapping.key will contain subnet.name, so we can use it to access the Map data.aws_eip.nlb (also indexed by subnet.name) to get the eip allocation_id
for_each = aws_subnet.sftp_nlb_subnets
content {
subnet_id = subnet_mapping.value.id
allocation_id = data.aws_eip.nlb[subnet_mapping.key].id
}
}
}
The trick is to realize that both aws_subnet.sftp_nlb_subnets and data.aws_eip.nlb are a Map, indexed by the key of local.publicSubnets. This allows us to use this common key (the subnet name) in the map aws_subnet.sftp to look up information in the data (data.aws_eip.nlb) obtained from the original input, local.publicSubnets.
Thanks. This is a neat trick.
Passing the IP address of the elastic IPs sounds strange. If you create the EIPs elsewhere, why not pass the (allocation) ID of them instead?
But with this setup, you can get the allocation ID with the aws_eip data source:
data "aws_eip" "nlb" {
for_each = local.publicSubnets
public_ip = each.value.elastic_ip
}
resource "aws_lb" "sftp" {
# ...
dynamic "subnet_mapping" {
for_each = aws_subnet.sftp_nlb_subnets
content {
subnet_id = subnet_mapping.value.id
allocation_id = data.aws_eip.nlb[subnet_mapping.key].id
}
}
}
But maybe it would make more sense to create the EIPs also here. For example something like this:
resource "aws_eip" "nlb" {
for_each = local.publicSubnets
vpc = true
}
resource "aws_lb" "sftp" {
# ...
dynamic "subnet_mapping" {
for_each = aws_subnet.sftp_nlb_subnets
content {
subnet_id = subnet_mapping.value.id
allocation_id = aws_eip.nlb[subnet_mapping.key].id
}
}
}

Dynamic interpolation of all components of cidrsubnet function in Terraform

Is there a way in Terraform where I can interpolate all three components of cidrsubnet function ? The reason I'm asking this is because I want to create VPCs with different CIDR prefix and accordingly the cidrsubnet function takes care of creating subnets for me.
Supposingly, VPC "prod" has CIDR 10.10.0.0/16 and VPC "dev" has CIDR 10.20.0.0/24. I'm looking for a solution wherein the same code will create subnets for me by interpolating values of cidrsubnet function something like below:
# map for different CIDR
variable "VPC_CIDR" {
type = "map"
default = {
"dev" = "10.10.0.0/24"
"prod" = "10.10.0.0/16"
}
}
variable "PRI_SUBNET_COUNT" {
default = "1"
}
# intended logic which interpolates netnum (impractical code)
if var.VPC_CIDR = "prod"
netnum = 4
elif var.VPC_CIDR = "dev"
netnum = 3
else
netnum = 2
resource "aws_subnet" "sub-node-private" {
count = var.PRI_SUBNET_COUNT
cidr_block = cidrsubnet(var.VPC_CIDR, var.netnum, count.index + 2) #all three components interpolated
The above code will create subnet CIDR 10.10.0.0/20 for prod and 10.10.0.0/28 for dev. This way my code remains same, only the variables get interpolated.
N.B: This code is for demonstration purpose. It is known that this is not the practical code for if/else in Terraform.
If the keys of var.VPC_CIDR are selectable by the caller then it may be best to combine the CIDR prefix and the number of new bits to use for its subnets together in the variable. Since the conventional way to name Terraform variables is in lowercase, I'm going to also rename it to vpc_cidr in the following examples.
variable "vpc_cidr" {
type = map(object({
cidr_block = string
subnet_bits = number
}))
default = {
dev = {
cidr_block = "10.10.0.0/24"
subnet_bits = 3
}
prod = {
cidr_block = "10.10.0.0/16"
subnet_bits = 4
}
}
}
variable "pri_subnet_count" {
type = number
default = 1
}
locals {
vpc_subnets = flatten([
for name, vpc in var.vpc_cidr : [
for i in count(var.pri_subnet_count) : {
name = "${name}-${i}"
vpc_name = name
cidr_block = vpc.cidr_block
subnet_bits = vpc.subnet_bits
network_num = i + 2
}
]
])
}
resource "aws_vpc" "example" {
for_each = var.vpc_cidr
cidr_block = each.value.cidr_block
}
resource "aws_subnet" "private" {
for_each = { for s in local.vpc_subnets : s.name => s }
vpc_id = aws_vpc.example[each.value.vpc_name].id
cidr_block = cidrsubnet(each.value.cidr_block, each.value.subnet_bits, each.value.network_num)
# ...
}
If the names "prod" and "dev" are fixed and thus your module will assume they will always be specified, you can derive the subnet_bits values automatically in a way similar to what you described, like this:
variable "vpc_cidr" {
type = object({
# Force caller to provide "dev" and "prod" values, so
# that it will match up with the attributes in
# local.subnet_bits defined below.
dev = string
prod = string
})
value = {
dev = "10.10.0.0/24"
prod = "10.10.0.0/16"
}
}
variable "pri_subnet_count" {
type = number
default = 1
}
locals {
subnet_bits = {
dev = 3
prod = 4
}
vpcs = {
for name, cidr_block in var.vpc_cidr : name => {
cidr_block = cidr_block
subnet_bits = local.subnet_bits[name]
}
}
vpc_subnets = flatten([
for name, vpc in local.vpcs : [
for i in count(var.pri_subnet_count) : {
name = "${name}-${i}"
vpc_name = name
cidr_block = vpc.cidr_block
subnet_bits = vpc.subnet_bits
network_num = i + 2
}
]
])
}
resource "aws_vpc" "example" {
for_each = local.vpcs
cidr_block = each.value.cidr_block
}
resource "aws_subnet" "private" {
for_each = { for s in local.vpc_subnets : s.name => s }
vpc_id = aws_vpc.example[each.value.vpc_name].id
cidr_block = cidrsubnet(each.value.cidr_block, each.value.subnet_bits, each.value.network_num + 2)
# ...
}
The general pattern illustrated above is building the data structure local.vpc_subnets which contains one element for each subnet you want to create. That then allows repeating aws_subnet.private for each element, and gathers together all of the values required to populate the vpc_id and cidr_block arguments on the subnet.

Terraform - populate variable values from same script

I'm very green to terraform; infact this is part of my training.
I'm wondering; is there a way to get terraform to store a specific value (as variable) from the previous command within the same file.
Example:
resource "aws_vpc" "TestVPC"{
cidr_block = "192.168.0.0/16"
instance_tenancy = "default"
enable_dns_hostnames="True"
tags{
Name="TestVpc"
}
}
resource "aws_subnet" "TestSubnet"{
vpc_id = "${var.aws_vpc_id}" ##This is where I'd like to populate the aws_vpc_id from the VPC creation step above.
cidr_block = "192.168.0.0/24"
map_public_ip_on_launch="True"
availability_zone = "us-east-2a"
tags{
Name="TestSubnet"
}
}
Help is greatly appreciated.
Thanks.
You can use the output from the creation of the VPC, ${aws_vpc.TestVPC.id}
Like so:
resource "aws_vpc" "TestVPC" {
cidr_block = "192.168.0.0/16"
instance_tenancy = "default"
enable_dns_hostnames = "True"
tags {
Name = "TestVpc"
}
}
resource "aws_subnet" "TestSubnet" {
vpc_id = "${aws_vpc.TestVPC.id}"
cidr_block = "192.168.0.0/24"
map_public_ip_on_launch = "True"
availability_zone = "us-east-2a"
tags {
Name = "TestSubnet"
}
}

Resources