How to get value from module in another module - Terraform (Azure) - 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.

Related

How to pass certain attributes from resources that were created with for_each loop to other resources in Terraform?

I have been trying to figure out what would be the most ideal option to deploy some fundamental, mostly identical resources (vnet, subnet, bastion host, nsg, etc.) resources in Azure, using Terraform.
I have tried it with for_each and it was working just fine until I have faced a problem where I had to pass a value to an attribute from a resource which was created with for_each. Let me show you:
So this is obviously working, nothing wrong with the following resources:
resource "azurerm_subnet" "AzureBastionSubnet" {
for_each = var.bastion_subnet
name = each.value["name"]
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet[each.key].name
address_prefixes = each.value["address_prefixes"]
depends_on = [azurerm_virtual_network.vnet]
}
resource "azurerm_public_ip" "bastion_public_ip" {
for_each = toset(var.public_ip_location)
name = "bastion-public-ip-${each.value}"
location = each.value
resource_group_name = azurerm_resource_group.rg.name
allocation_method = "Static"
sku = "Standard"
depends_on = [azurerm_subnet.AzureBastionSubnet]
}
But the problem starts now when in the following resource I need to pass attribute values from resources which were created with for_each. How on earth do I pass the right attributes from the created bastion subnets and public IPs to the subnet_id and public_ip_address_id?
resource "azurerm_bastion_host" "bastion" {
for_each = toset(var.location_list)
name = "bastion-${each.value}"
location = each.value
resource_group_name = azurerm_resource_group.rg.name
ip_configuration {
name = "configuration"
subnet_id = azurerm_subnet.AzureBastionSubnet.id
public_ip_address_id = azurerm_public_ip.bastion_public_ip.id
}
depends_on = [azurerm_public_ip.bastion_public_ip]
}
Thanks!
I was looking into Terraform's lookup, and also the for loop and I am sure they could make it work but I just cannot seem to figure it out.
You might be creating multiple items of azurerm_subnet.AzureBastionSubnet as you are using for_each here
resource "azurerm_subnet" "AzureBastionSubnet" {
for_each = var.bastion_subnet
name = each.value["name"]
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet[each.key].name
address_prefixes = each.value["address_prefixes"]
depends_on = [azurerm_virtual_network.vnet]
}
So you may want to refer to your individual azurerm_subnet instance by passing your var.bastion_subnet set member, or its map key.
for example:
resource "azurerm_bastion_host" "bastion" {
..
ip_configuration {
...
subnet_id = azurerm_subnet.AzureBastionSubnet["subnet-1"].id
}
depends_on = [azurerm_public_ip.bastion_public_ip]
}
Where subnet-1 is a key in my var.bastion_subnet map.
From Terraform documentation:
Referring to Instances
When for_each is set, Terraform distinguishes between the block
itself and the multiple resource or module instances associated with
it. Instances are identified by a map key (or set member) from the
value provided to for_each.
<TYPE>.<NAME> or module.<NAME> (for example,
azurerm_resource_group.rg) refers to the block. <TYPE>.<NAME>[<KEY>]
or module.<NAME>[<KEY>] (for example,
azurerm_resource_group.rg["a_group"],
azurerm_resource_group.rg["another_group"], etc.) refers to individual
instances.

Terraform deletes Azure resources in subsequent 'apply' without any config change

I was trying to test the scenario of handling external changes to existing resources and then syncing my HCL config to the current state in the next apply. I could achieve that using 'taint' for the modified resource, but TF deleted other resources which were deployed during the first 'apply'. Here is the module code for a VNet with 3 subnets(prod,dmz and app) and 3 NSGs associated. And I tested with modifying one of the NSGs but TF deleted all of the subnets-
VNET-
resource "azurerm_virtual_network" "BP-VNet" {
name = var.Vnetname
location = var.location
resource_group_name = var.rgname
address_space = var.vnetaddress
subnet {
name = "GatewaySubnet"
address_prefix = "10.0.10.0/27"
}
}
Subnet -
resource "azurerm_subnet" "subnets" {
count = var.subnetcount
name = "snet-prod-${lookup(var.snettype, count.index, "default")}-001"
address_prefixes = ["10.0.${count.index+1}.0/24"]
resource_group_name = var.rgname
virtual_network_name = azurerm_virtual_network.BP-VNet.name
}
NSGs-
resource "azurerm_network_security_group" "nsgs" {
count = var.subnetcount
name = "nsg-prod-${lookup(var.snettype, count.index, "default")}"
resource_group_name = var.rgname
location = var.location
--------
}
BastionSubnet-
resource "azurerm_subnet" "bastionsubnet" {
name = "AzureBastionSubnet"
virtual_network_name = azurerm_virtual_network.BP-VNet.name
resource_group_name = var.rgname
address_prefixes = [ "10.0.5.0/27" ]
}
The end result of second apply is -
With just Gateway subnet. It should not have deleted rest of the 4 subnets. Why is this happening?
The solution may confuse you. You can separate the GatewaySubnet from the azurerm_virtual_network block into an azurerm_subnet block. The code looks like this:
resource "azurerm_subnet" "gateway" {
name = "GatewaySubnet"
resource_group_name = var.rgname
virtual_network_name = azurerm_virtual_network.BP-VNet.name
address_prefixes = ["10.0.10.0/27"]
}
I don't know the certain reason, but it solves your issue.

Getting blank azure public ip output from terraform

In my main terraform file I have:
resource "azurerm_resource_group" "rg" {
name = var.rg_name
location = var.location
}
resource "azurerm_public_ip" "public_ip" {
name = "PublicIP"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
domain_name_label = var.domain_name_label
allocation_method = "Dynamic"
}
And in my outputs file I have:
data "azurerm_public_ip" "public_ip" {
name = "PublicIP"
resource_group_name = azurerm_resource_group.rg.name
depends_on = [azurerm_resource_group.rg, azurerm_public_ip.public_ip]
}
output "public_ip" {
value = data.azurerm_public_ip.public_ip.ip_address
}
All the resources including IP get created, however the output is blank. How can I fix this?
Make sure output.tf contains only output tags and main.tf contains resources tags
The following works just fine for me:
Main.tf
resource "azurerm_resource_group" "example" {
name = "resourceGroup1"
location = "West US"
}
resource "azurerm_public_ip" "example" {
name = "acceptanceTestPublicIp1"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
allocation_method = "Static"
tags = {
environment = "Production"
}
}
Output.tf
output "azurerm_public_ip" {
value = azurerm_public_ip.example.ip_address
}
In case you want to have a dependency between resources, use depends_on inside the resource tag.
For example:
depends_on = [azurerm_resource_group.example]
Steps to reproduce:
terraform init
terraform plan
terraform apply
terraform output
Update-
The reason you get blank public IP is since declaring allocation_method = "Dynamic"
From the docs:
Note Dynamic - Public IP Addresses aren't allocated until they're assigned to a
resource (such as a Virtual Machine or a Load Balancer) by design
within Azure.
Full working example with dynamic allocation.
I had the same issue. The actual problem seems to be the dynamic allocation. The IP address is not known until it is actually used by a VM.
In my case, I could solve the issue by adding the VM (azurerm_linux_virtual_machine.testvm) to the depends_on list in the data source:
data "azurerm_public_ip" "public_ip" {
name = "PublicIP"
resource_group_name = azurerm_resource_group.rg.name
depends_on = [ azurerm_public_ip.public_ip, azurerm_linux_virtual_machine.testvm ]
}
Unfortunately, this seems not to be documented in https://www.terraform.io/docs/providers/azurerm/d/public_ip.html

Terraform/HCL in Azure issues

I am new to HCL and Terraform and have having issues with associating a security group and a backend address pool to the network interface. I am creating 2 network interfaces in a single network interface block:
#Create network interface for 2 VMs
resource "azurerm_network_interface" "FrontNetworkInterface" {
count = 2
name = "niFront${count.index}"
location = azurerm_resource_group.PWSDevResourceGroup.location
resource_group_name = azurerm_resource_group.PWSDevResourceGroup.name
ip_configuration {
name = "ipconfFrontVM"
subnet_id = azurerm_subnet.PWSDevSubnet.id
private_ip_address_allocation = "dynamic"
}
}
I have tried associating in various ways that have produced different errors:
ATTEMPT 1:
#Connect security group to the network interface
resource "azurerm_network_interface_security_group_association" "PWSDevSecurityGroupAssoc" {
network_interface_id = azurerm_network_interface.FrontNetworkInterface.id
network_security_group_id = azurerm_network_security_group.PWSDevSecurityGroup.id
}
#Connect 2 backend ips to the load balancer
resource "azurerm_network_interface_backend_address_pool_association" "BackendIPAssoc" {
network_interface_id = azurerm_network_interface.FrontNetworkInterface.id
ip_configuration_name = "bipa"
backend_address_pool_id = azurerm_lb_backend_address_pool.BackendIpPool.id
}
ERRORS:
Error: Missing resource instance key
on front.tf line 85, in resource "azurerm_network_interface_security_group_association" "PWSDevSecurityGroupAssoc":
85: network_interface_id = azurerm_network_interface.FrontNetworkInterface.id
Because azurerm_network_interface.FrontNetworkInterface has "count" set, its
attributes must be accessed on specific instances.
For example, to correlate with indices of a referring resource, use:
azurerm_network_interface.FrontNetworkInterface[count.index]
Error: Missing resource instance key
on front.tf line 91, in resource "azurerm_network_interface_backend_address_pool_association" "BackendIPAssoc":
91: network_interface_id = azurerm_network_interface.FrontNetworkInterface.id
Because azurerm_network_interface.FrontNetworkInterface has "count" set, its
attributes must be accessed on specific instances.
For example, to correlate with indices of a referring resource, use:
azurerm_network_interface.FrontNetworkInterface[count.index]
ATTEMPT 2/3/4 (Using "[count.index]", "[count.index].id", or "[element(azurerm_network_interface.FrontNetworkInterface.*.id, count.index)]" as described in the previous error):
#Connect security group to the network interface
resource "azurerm_network_interface_security_group_association" "PWSDevSecurityGroupAssoc" {
network_interface_id = azurerm_network_interface.FrontNetworkInterface[count.index]
network_security_group_id = azurerm_network_security_group.PWSDevSecurityGroup.id
}
#Connect 2 backend ips to the load balancer
resource "azurerm_network_interface_backend_address_pool_association" "BackendIPAssoc" {
network_interface_id = azurerm_network_interface.FrontNetworkInterface[count.index]
ip_configuration_name = "bipa"
backend_address_pool_id = azurerm_lb_backend_address_pool.BackendIpPool.id
}
ERROR (Same result for [count.index].id and [element(azurerm_network_interface.FrontNetworkInterface.*.id, count.index)]):
Error: Reference to "count" in non-counted context
on front.tf line 85, in resource "azurerm_network_interface_security_group_association" "PWSDevSecurityGroupAssoc":
85: network_interface_id = azurerm_network_interface.FrontNetworkInterface[count.index]
The "count" object can only be used in "module", "resource", and "data"
blocks, and only when the "count" argument is set.
Error: Reference to "count" in non-counted context
front.tf line 91, in resource "azurerm_network_interface_backend_address_pool_association" "BackendIPAssoc":
network_interface_id = azurerm_network_interface.FrontNetworkInterface[count.index]
The "count" object can only be used in "module", "resource", and "data"
blocks, and only when the "count" argument is set.
Also, I am receiving this error on my azurerm_virtual_machine block:
line 162, in resource "azurerm_virtual_machine" "FrontEndVirtualMachines":
162: admin_ssh_key {
Blocks of type "admin_ssh_key" are not expected here.
I am following what is shown here:
https://learn.microsoft.com/en-us/azure/developer/terraform/create-linux-virtual-machine-with-infrastructure
As you can see, the admin_ssh_key block is provided. I tried using version 2.0 as used in the scripts; however, I experienced the same result.
Thanks for your help!! :)
When referencing a resource created with count you still need to add the .id. See the following example. For more information see this link.
provider "azurerm" {
version = "~>2.23.0"
features {}
}
resource "azurerm_resource_group" "example" {
name = "example-resources"
location = "East US"
}
resource "azurerm_virtual_network" "example" {
name = "vnet"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
address_space = ["10.0.0.0/16"]
dns_servers = ["10.0.0.4", "10.0.0.5"]
}
resource "azurerm_subnet" "example" {
name = "example"
resource_group_name = azurerm_resource_group.example.name
virtual_network_name = azurerm_virtual_network.example.name
address_prefixes = ["10.0.1.0/24"]
}
resource "azurerm_network_interface" "example" {
count = 2
name = format("int%s", count.index)
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
ip_configuration {
name = "ip"
subnet_id = azurerm_subnet.example.id
private_ip_address_allocation = "dynamic"
}
}
resource "azurerm_network_security_group" "example" {
name = "acceptanceTestSecurityGroup1"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
security_rule {
name = "test123"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
resource "azurerm_network_interface_security_group_association" "secgroup" {
count = length(azurerm_network_interface.example)
network_interface_id = azurerm_network_interface.example[count.index].id
network_security_group_id = azurerm_network_security_group.example.id
}
I will admit that I haven't read the whole story, but it looks like your attempt #2/3/4 was pretty close. Where you use [count.index], you need to specify a count, otherwise there's no count to index. So if you just add count = 2 to those two resource blocks, it should work.
Better yet, either have the 2 as a variable, or use
count = len(azurerm_network_interface.FrontNetworkInterface)
to ensure you don't end up with mismatched numbers when you change the 2 later on.

Accessing the output from module via index

I am trying to create 2 VMs on Azure using Terraform.
I create 2 NICs like
variable "internalips" {
description = "List of Internal IPs"
default = ["10.0.2.10", "10.0.2.11"]
type = "list"
}
resource "azurerm_network_interface" "helloterraformnic" {
count = 2
name = "nic-${count.index}"
location = "West US"
resource_group_name = "myrg"
ip_configuration {
name = "testconfiguration1"
subnet_id = "${azurerm_subnet.helloterraformsubnet.id}"
private_ip_address_allocation = "static"
private_ip_address = "${element(private_ip_address, count.index)}"
}
}
Now I want to use them in module azurerm_virtual_machine
resource "azurerm_virtual_machine" "helloterraformvm" {
count = 2
name = "${element(elasticmachines, count.index)}"
location = "West US"
resource_group_name = "myrg"
network_interface_ids = "${element(azurerm_network_interface.helloterraformnic, count.index)}"
....
}
This gives me an error
Failed to load root config module: Error loading azure/rg.tf: Error
reading config for azurerm_virtual_machine[helloterraformvm]:
azurerm_network_interface.helloterraformnic: resource variables must
be three parts: TYPE.NAME.ATTR in:
${element(azurerm_network_interface.helloterraformnic, count.index)}
How can I use the above created NICs using index ?
First thinking to use length function to get the counts more than hard coding it.
from
count = 2
change to
count = "${length(var.internalips)}"
For your problem, you need to tell the resource which attribute you want to get the value.
network_interface_ids = "${element(azurerm_network_interface.helloterraformnic.id, count.index)}"
Refer:
terraform Interpolation Syntax
terraform azurerm_virtual_machine Attributes Reference

Resources