How Do I Avoid Repeating A Variable In Terraform? - terraform

Terraform doesn't allow you to interpolate variables within the variables file otherwise you get the error:
Error: Variables not allowed
on variables.tf line 9, in variable "resource_group_name": 9:
default = "${var.prefix}-terraform-dev_rg"
Variables may not be used here.
This then means I end up duplicating the value of the prefix in my variables.tf file when I try to create the name for the resource group.
Is there a nice way around this to avoid duplicating the value of the variable?
variables.tf
variable "prefix" {
description = "The prefix used for all resources in this plan"
default = "terraform-dev"
}
variable resource_group_name {
type = "string"
default = "terraform-dev_rg"
}
variable resource_group_location {
type = "string"
default = "eastus"
}
main.tf
# Configure the Microsoft Azure Provider
provider "azurerm" {
version = "=1.28.0"
}
# Create a resource group
resource "azurerm_resource_group" "resource-group" {
name = var.resource_group_name
location = var.resource_group_location
}
#Create an application gateway with web app firewall
module "firewall" {
source = "./firewall"
resource_group_name = var.resource_group_name
resource_group_location = var.resource_group_location
}
./firewall/variables.tf
#Passed down from the root variables.tf
variable "prefix" {}
variable "resource_group_name" {}
variable "resource_group_location" {}
./firewall/main.tf
# Create a virtual network for the firewall
resource "azurerm_virtual_network" "firewall-vnet" {
name = "${var.prefix}-waf-vnet"
address_space = ["10.0.0.0/16"]
resource_group_name = var.resource_group_name
location = var.resource_group_location
}

Try to use local values,
https://www.terraform.io/docs/configuration/locals.html
variable "prefix" {
description = "The prefix used for all resources in this plan"
default = "terraform-dev"
}
variable resource_group_location {
type = "string"
default = "eastus"
}
locals {
resource_group_name = "${var.prefix}_rg"
}
resource "azurerm_resource_group" "resource-group" {
name = local.resource_group_name
location = var.resource_group_location
}

Terraform does not support variables inside a variable.
If you want to generate a value based on two or more variables then you can try Terraform locals (https://www.terraform.io/docs/configuration/locals.html).
Locals should help you here to achieve goal.
something like
variables.tf
variable "prefix" {
description = "The prefix used for all resources in this plan"
default = "terraform-dev"
}
variable resource_group_location {
type = "string"
default = "eastus"
}
main.tf
locals {
resource_group_name = "${var.prefix}_rg"
}
# Configure the Microsoft Azure Provider
provider "azurerm" {
version = "=1.28.0"
}
# Create a resource group
resource "azurerm_resource_group" "resource-group" {
name = local.resource_group_name
location = var.resource_group_location
}
Hope this helps.
Please read similar discussion here -https://stackoverflow.com/questions/58841060/terraform-variables-within-variables/58841360?noredirect=1#comment129460631_58841360

Related

How to get value from module in another module - Terraform (Azure)

Im trying to get value from one module and use it in another module.
I have module - vnet
resource "azurerm_virtual_network" "vnet" {
name = var.vnet_name
resource_group_name = var.resource_group_name
location = var.location
address_space = var.address_space
}
resource "azurerm_subnet" "subnet" {
name = "${var.vnet_name}-subnet"
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = var.subnet_prefixes
}
and the output is :
output "subnet_id" {
value = "${azurerm_subnet.subnet.id}"
}
output "vnet_name" {
value = "${azurerm_virtual_network.vnet.name}"
}
from this module i would like to get the vnet name and the subnet id for my other module that im using to create a nic.
nic module
module "vnet" {
source = "../vnet"
}
resource "azurerm_network_interface" "nic" {
name = "${module.vnet.vnet_name}-nic"
location = "east us 2"
resource_group_name = "null"
ip_configuration {
name = " "
subnet_id = module.vnet.subnet_id
private_ip_address_allocation = "Dynamic"
}
}
this way is working BUT the terraform plan , planning to create 2 resource per each resource because the way im using to get the values .
under nic module im using again the vnet module so its will create second vnet.
my main.tf is
resource "azurerm_resource_group" "rg" {
name = var.resource_group.name
location = var.resource_group.location
}
module "ib151w-vnet" {
source = "./modules/vnet"
resource_group_name = azurerm_resource_group.rg.name
vnet_name = "ib151w-vnet"
address_space = var.address_space
subnet_prefixes = var.subnet_prefixes
}
module "ib151w-nic" {
source = "./modules/nic"
name = "nic-test-123"
location = "east us 2"
resource_group_name = "ib151w"
}
the question is how can i get the vnet name and subnet id to use inside the nic module ?
i know there is alot of better ways to establish my request but im
just learning terraform and trying this specific way :)
how can i get the vnet name and subnet id to use inside the nic module
You have to explicitly pass those values in the root module:
module "ib151w-nic" {
source = "./modules/nic"
name = "nic-test-123"
location = "east us 2"
resource_group_name = "ib151w"
vnet_name = module.vnet.vnet_name
subnet_id = module.vnet.subnet_id
}
Also you have to modify your vnet module to make vnets and subents conditional. For example, add variable in the vent module:
variable "should_create_vnet_and_subnet" {
default = true
}
then make the resource conditional:
resource "azurerm_virtual_network" "vnet" {
count = should_create_vnet_and_subnet == true ? 1 : 0
name = var.vnet_name
resource_group_name = var.resource_group_name
location = var.location
address_space = var.address_space
}
resource "azurerm_subnet" "subnet" {
count = should_create_vnet_and_subnet == true ? 1 : 0
name = "${var.vnet_name}-subnet"
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = var.subnet_prefixes
}
And the rest. Basically you have to rewrite your entire vnet module around conditional resources.
There are a lot of things to reconsider here but I would like to stick to your query only as you have requested.
How to get value from the module in another module - Terraform
As I can see you are already using two modules for vnet(which includes subnet) and nic and also using two modules interface calls to use them. You can simply use variables in your nic module and then at the interface level you can pass the outputs from vnet module as an attribute to your nic module.
Refer to the below code.
# main.tf or MODULE INTERFACES
## Default variables ##
variable "resource_group_name" {
type = string
description = "(optional) resource group name in which resources will created"
default = "stack-over-flow-query"
}
variable "location" {
type = string
description = "(optional) location where resources would be created."
default = "east us 2"
}
################################
resource "azurerm_resource_group" "rg" {
name = var.resource_group_name
location = var.location
}
module "ib151w-vnet" {
source = "./modules/vnet"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
vnet_name = "ib151w-vnet"
address_space = ["10.0.0.0/16"]
subnet_prefixes = ["10.0.1.0/24"]
}
module "ib151w-nic" {
source = "./modules/nic"
name = "${module.ib151w-vnet.vnet_name}-nic-test-123"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
subnet_id = module.ib151w-vnet.subnet_id
ip_configuration = "stackoverflow"
}
## NIC Module
resource "azurerm_network_interface" "nic" {
name = var.name
location = var.location
resource_group_name = var.resource_group_name
ip_configuration {
name = var.ip_configuration
subnet_id = var.subnet_id
private_ip_address_allocation = var.private_ip_address_allocation
}
}
## Required variable definitions with defaults(best practice installation in your situation)
Using the same outputs as yours in the vnet module.
output "vnet_name" {
value = azurerm_virtual_network.vnet.name
}
output "subnet_id" {
value = azurerm_subnet.subnet.id
}
Note: "${}"aka interpolation is not required when using terraform referencing without variables or any unknown value to terraform.
There can be a lot more ways to have a module like this but I would suggest at least a few things to try and do hands-on.
Use looping in your module to make multiple resources with one interface call [ only when necessary and make sense ]
https://developer.hashicorp.com/terraform/tutorials/configuration-language/for-each
Warning: extra looping can increase complexity.
Use conditions to control your module behavior.
https://developer.hashicorp.com/terraform/language/expressions/conditionals
And the most important when to use a module.
https://developer.hashicorp.com/terraform/language/modules/develop#when-to-write-a-module
I hope this helps and as I have stated this only answers your query, not some best practices or best vnet-nic module.

Terraform Data source is not picking subnet or resource group properly

I started writing terraform to automate the iac for provisioning VMs in Azure. However I wrote the entire code but am unable to use the existing subnet/vnet/resource group properly.
main.tf
# Configure the Microsoft Azure Provider
provider "azurerm" {
# The "feature" block is required for AzureRM provider 2.x.
# If you're using version 1.x, the "features" block is not allowed.
#version = "~>2.20.0"
features {}
subscription_id = var.subscription_id
tenant_id = var.tenant_id
client_id = var.client_id
client_secret = var.client_secret
}
#terraform {
# backend "azurerm" {
# snapshot = true
#}
#}
# Refer to resource group
data "azurerm_resource_group" "nwrk_group" {
name = var.nwrk_resource_group
}
data "azurerm_resource_group" "resource_group" {
name = var.resource_group
}
# Refer to a subnet
data "azurerm_subnet" "subnet" {
name = var.nwrk_subnet_name
virtual_network_name = var.nwrk_name
resource_group_name = data.azurerm_resource_group.nwrk_group.name
}
# Refer to Network Security Group and rule
data "azurerm_network_security_group" "nwrk_security_group" {
name = var.nwrk_security_grp
resource_group_name = data.azurerm_resource_group.nwrk_group.name
}
module "vm" {
source = "../modules/windows_vm"
node = var.node
node_username = var.node_username
node_password = var.node_password
tags = var.tags
deployment_environment = var.deployment_environment
nwrk_group_location = data.azurerm_resource_group.resource_group.location
nwrk_group_name = data.azurerm_resource_group.resource_group.name
subnet_id = data.azurerm_subnet.subnet.id
nwrk_security_group_id = data.azurerm_network_security_group.nwrk_security_group.id
resource_group_location = data.azurerm_resource_group.resource_group.location
resource_group_name = data.azurerm_resource_group.resource_group.name
}
terraform.tfvars
tags = {
project = "SEPS_Terraform"
environment = "test_tfm"
}
deployment_environment = "DEV"
node_username = "saz76test"
node_password = "SA82nd2"
nwrk_subnet_name = "SUBNET_45_0"
node = {
general_info = {
name = "gateway.test.com"
private_ip = "153.78.51.92"
vm_template = "Standard_B2s"
disk_type = "StandardSSD_LRS"
nwrk_resource_group = "SWS_LAB_36_192"
nwrk_name = "SUB_VNET_36_192"
nwrk_security_group = "N-Untrusted"
nwrk_subnet_name = "SUB_51_0"
}
os_image = {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2019-DataCenter"
version = "latest"
}
storage_disk = {
type = "StandardSSD_LRS"
size = 256
}
}
variables.tf
variable "subscription_id" {
type = string
description = "Azure subscription id to provision infra."
}
variable "tenant_id" {
type = string
description = "Azure subscription tenant id"
}
variable "client_id" {
type = string
description = "App id to authenticate to azure."
}
variable "client_secret" {
type = string
description = "App password to authenticate to azure"
}
variable "resource_group" {
type = string
description = "Resource group in which resources will be added other than network resources"
}
variable "nwrk_resource_group" {
type = string
description = "Resource group for network resources"
}
variable "nwrk_name" {
type = string
description = "VPC network name where the network resources belong to"
}
variable "nwrk_subnet_name" {
type = string
description = "Subnet of the VPC network"
}
variable "nwrk_security_grp" {
type = string
description = "Security group to which the network belong to"
}
variable "tags" {
type = map(string)
description = "Tags to attach to resources"
}
variable "deployment_environment" {
type = string
description = "Environment these VMs belong to"
}
variable "node" {
type = map(map(string))
description = "web node with specifications."
}
variable "node_username" {
type = string
description = "Login username for node"
}
variable "node_password" {
type = string
description = "Login password for node"
}
module_code:
# Create network interface
resource "azurerm_network_interface" "nic" {
name = "${var.node["general_info"]["name"]}_nic"
location = var.nwrk_group_location
resource_group_name = var.nwrk_group_name
ip_configuration {
name = "${var.node["general_info"]["name"]}_nicConfiguration"
subnet_id = var.subnet_id
private_ip_address_allocation = "Static"
private_ip_address = var.node["general_info"]["private_ip"]
}
tags = var.tags
}
# Connect the security group to the network interface
resource "azurerm_network_interface_security_group_association" "example" {
network_interface_id = azurerm_network_interface.nic.id
network_security_group_id = var.nwrk_security_group_id
}
resource "azurerm_windows_virtual_machine" "vm" {
name = var.node["general_info"]["name"]
location = var.resource_group_location
resource_group_name = var.resource_group_name
network_interface_ids = [azurerm_network_interface.nic.id]
size = var.node["general_info"]["vm_template"]
computer_name = var.node["general_info"]["name"]
admin_username = var.node_username
admin_password = var.node_password
os_disk {
name = "${var.node["general_info"]["name"]}-osDisk"
caching = "ReadWrite"
storage_account_type = var.node["general_info"]["disk_type"]
}
source_image_reference {
publisher = var.node["os_image"]["publisher"]
offer = var.node["os_image"]["offer"]
sku = var.node["os_image"]["sku"]
version = var.node["os_image"]["version"]
}
tags = var.tags
}
output "vm_id" {
value = azurerm_windows_virtual_machine.vm.id
}
output "vm_name" {
value = azurerm_windows_virtual_machine.vm.name
}
output "vm_ip_address" {
value = azurerm_network_interface.nic.private_ip_address
}
My code is above one which am trying to execute init working but plan is failing to do. Can someone please help me on this what I am missing. ?? The error is getting like it.
Error :
Warning: Value for undeclared variable
│
│ The root module does not declare a variable named "nwrk_security_group" but a value was found in file "subscription.tfvars". If you meant to use
│ this value, add a "variable" block to the configuration.
│
│ To silence these warnings, use TF_VAR_... environment variables to provide certain "global" settings to all configurations in your organization.
│ To reduce the verbosity of these warnings, use the -compact-warnings option.
╵
╷
│ Warning: Resource targeting is in effect
│
│ You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the
│ current configuration.
│
│ The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when
│ Terraform specifically suggests to use it as part of an error message.
╵
╷
│ Error: Error: Subnet "SUBNET_45_0" (Virtual Network "SUB_VNET_36_192" / Resource Group "SWS_LAB_36_192") was not found
│
│ with data.azurerm_subnet.subnet,
│ on main.tf line 31, in data "azurerm_subnet" "subnet":
│ 31: data "azurerm_subnet" "subnet" {
│
╵
╷
│ Error: Error: Network Security Group "NSG" (Resource Group "SWS_LAB_36_192") was not found
│
│ with data.azurerm_network_security_group.nwrk_security_group,
│ on main.tf line 38, in data "azurerm_network_security_group" "nwrk_security_group":
│ 38: data "azurerm_network_security_group" "nwrk_security_group" {
Subscription.tfvars
subscription_id = "fdssssssssssssss"
client_id = "sdsdsdsdsdsdsdsdsdsdsdsd"
client_secret = ".dssssssssssssssssss
tenant_id = "asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf"
resource_group = "SWS_LAB_36_192"
nwrk_resource_group = "SWS_LAB_36_192"
nwrk_name = "SUB_VNET_36_192"
nwrk_security_group = "N-Untrusted"
There could potentially be many different problems because I am not sure what the outlook of the root module and child modules are, but as per the error you are getting, it seems that the value defined for the variable in the subscription.tfvars is not being declared anywhere and the one that is supposed to be declared is missing, the data source does not return anything, hence there is the error from the child module as well. Currently it is defined as:
variable "nwrk_security_grp" {
type = string
description = "Security group to which the network belong to"
}
If you take a look at the values in subscription.tfvars, there is no nwrk_security_grp, but there is a nwrk_security_group. One option to fix this would probably be to change the name of the variable in the variables.tf:
variable "nwrk_security_group" {
type = string
description = "Security group to which the network belong to"
}
In that case, you would have to adapt the data source to use the new variable name:
data "azurerm_network_security_group" "nwrk_security_group" {
name = var.nwrk_security_group
resource_group_name = data.azurerm_resource_group.nwrk_group.name
}
Alternatively (and probably easier), you can change the name of the variable you are assigning the value to in subscription.tfvars:
nwrk_security_grp = "N-Untrusted" # it was nwrk_security_group
What I would strongly suggest going forward is to keep the naming convention for the variables the same because this way you will get into a lot of issues.

Using terraform, how to create multiple resources of same type with unique and unidentical names using list/count for azure?

Here is a basic example for what I am trying to achieve. I have two files (main.tf) and (variable.tf), I want to create two resource groups and in the variables file is a list of names which I want the resource groups to occupy. First name of the first resource group and similarly going forward.
So help me out on how to achieve it. I am using terraform v0.13.
main.tf file:
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "test" {
count = 2
name = var.resource_group_name
location = var.location
}
variable.tf file:
variable "resource_group_name" {
description = "Default resource group name that the network will be created in."
type = list
default = ["asd-rg","asd2-rg"]
}
variable "location" {
description = "The location/region where the core network will be created.
default = "westus"
}
You just need to change the resource group block like this:
resource "azurerm_resource_group" "test" {
count = 2
name = element(var.resource_group_name, count.index)
location = var.location
}
You can use the for_each syntax to create multiple resource of similar type. It requires a set (of unique values) to iterate over, hence convert your variable resource_group_name to set.
variable.tf
variable "resource_group_name" {
description = "Default resource group name that the network will be created in."
type = list(string)
default = ["asd-rg","asd2-rg"]
}
variable "location" {
description = "The location/region where the core network will be created.
default = "westus"
}
main.tf
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "test" {
name = each.value // value from iteration
location = var.location
for_each = toset(var.resource_group_name) // convert list to set and iterate over it
}
Edit: variable can be of type string, list or map. This needs to be converted to set to be used with for_each
for you can use the combination of length and count to create multiple resources of the same type with unique and unidentical names.
You just need to change the resource group block like this:
resource "azurerm_resource_group" "test" {
count = length(var.resource_group_name)
name = element(concat(var.resource_group_name, [""]), count.index)
location = var.location
}

Terraform on Azure - Deploy multiple subnet

I'm trying to implement a Terraform script to create multiple subnets.
resource "azurerm_subnet" "test_subnet" {
name = "testSUBNET"
resource_group_name = "${local.resource_group_name}"
virtual_network_name = "${azurerm_virtual_network.lab_vnet.name}"
address_prefix = "10.0.1.0/24"
}
Is there a way to do a for-each or a loop on a variable in order to create them at the same time?
You can achieve this using a variable and count index as follows:
variable "subnet_prefix" {
type = "list"
default = [
{
ip = "10.0.1.0/24"
name = "subnet-1"
},
{
ip = "10.0.2.0/24"
name = "subnet-2"
}
]
}
resource "azurerm_subnet" "test_subnet" {
name = "${lookup(element(var.subnet_prefix, count.index), "name")}"
count = "${length(var.subnet_prefix)}"
resource_group_name = "${local.resource_group_name}"
virtual_network_name = "${azurerm_virtual_network.lab_vnet.name}"
address_prefix = "${lookup(element(var.subnet_prefix, count.index), "ip")}"
}
There is also preview feature available for-each in the new version
If you are using Terraform 12 this can be achieved using the for-each capability or the count capability
count should be used if you are looking to create almost identical resources.
for-each should be used to create multiple of each instance based on a different map or set of values.
Using an list of strings and the toset() function to convert this is a neat way to achieve this
variable "subnet_ids" {
type = list(string)
}
resource "aws_instance" "server" {
for_each = toset(var.subnet_ids)
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = each.key # note: each.key and each.value are the same for a set
tags = {
Name = "Server ${each.key}"
}
}
Or you could achieve this by using something like the below:
resource "azurerm_resource_group" "rg" {
for_each = {
a_group = "eastus"
another_group = "westus2"
}
name = each.key
location = each.value
}
If you are looking to achieve this with Terraform 11 the count and variable capabilities are the only way other than code duplication. (Rajat Arora has mentioned)
I would strongly recommended using Terraform 12 as the providers for Terraform 11 will be unsupported in the not to far future and if you can save yourself from refactoring now, you should!

Could not read output attribute from remote state datasource

I am new to terraform so I will attempt to explain with the best of my ability. Terraform will not read in the variable/output from the statefile and use that value in another file.
I have tried searching the internet for everything I could find to see if anyone how has had this problem and how they fixed it.
###vnet.tf
#Remote State pulling data from bastion resource group state
data "terraform_remote_state" "network" {
backend = "azurerm"
config = {
storage_account_name = "terraformstatetracking"
container_name = "bastionresourcegroups"
key = "terraform.terraformstate"
}
}
#creating virtual network and putting that network in resource group created by bastion.tf file
module "quannetwork" {
source = "Azure/network/azurerm"
resource_group_name = "data.terraform_remote_state.network.outputs.quan_netwk"
location = "centralus"
vnet_name = "quan"
address_space = "10.0.0.0/16"
subnet_prefixes = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
subnet_names = ["subnet1", "subnet2", "subnet3"]
tags = {
environment = "quan"
costcenter = "it"
}
}
terraform {
backend "azurerm" {
storage_account_name = "terraformstatetracking"
container_name = "quannetwork"
key = "terraform.terraformstate"
}
}
###resourcegroups.tf
# Create a resource group
#Bastion
resource "azurerm_resource_group" "cm" {
name = "${var.prefix}cm.RG"
location = "${var.location}"
tags = "${var.tags}"
}
#Bastion1
resource "azurerm_resource_group" "network" {
name = "${var.prefix}network.RG"
location = "${var.location}"
tags = "${var.tags}"
}
#bastion2
resource "azurerm_resource_group" "storage" {
name = "${var.prefix}storage.RG"
location = "${var.location}"
tags = "${var.tags}"
}
terraform {
backend "azurerm" {
storage_account_name = "terraformstatetracking"
container_name = "bastionresourcegroups"
key = "terraform.terraformstate"
}
}
###outputs.tf
output "quan_netwk" {
description = "Quan Network Resource Group"
value = "${azurerm_resource_group.network.id}"
}
When running the vnet.tf code it should read in the output from the outputs.tf which is stored in the azure backend storage account statefile file and use that value for the resource_group_name in the quannetwork module. Instead it creates a resource group named data.terraform_remote_state.network.outputs.quan_netwk. Any help would be greatly appreciated.
First, you need to input a string for the resource_group_name in your module quannetwork, not the resource group Id.
Second, if you want to quote something in the remote state, do not just put it in the Double quotes, the right format below:
resource_group_name = "${data.terraform_remote_state.network.outputs.quan_netwk}"

Resources