I'm completely new to terraform and I'm trying to learn and write a TF code to automate Azure VM deployment. I'm trying to cover each parts as modules (except rg) rather than keeping it in a single main.tf file.
My intention is to create 1 vnet (TESTVNET) and create multiple subnets in same Vnet, where I can define the subnet name and address in my tfvars file.
I'm able to reach till creation on VNet, but cant loop through the defined subnets
Please go through my code. File Main.tf
resource "azurerm_resource_group" "resource_group" {
name = var.RGname
location = var.RGlocation
}
module "VNET" {
source = "./Modules/NetworkConfig"
name = var.VNETname
address_space = var.address_space
location = var.RGlocation
resource_group_name = azurerm_resource_group.resource_group.name
}
module "SUBNETS" {
source = "./Modules/SubnetConfig"
Subnetlist = var.Subnetlist
virtual_network_name = module.VNET.vnet_name
resource_group_name = azurerm_resource_group.resource_group.name
depends_on = [azurerm_resource_group.resource_group, module.VNET.vnet]
}
Variables.tf (of main)
variable "RGlocation" {
}
variable "RGname" {
}
variable "VNETname" {
}
variable "address_space" {
}
variable "Subnetlist" {
type = map(object({
name = list(string)
address = list(string)
}))
}
File main.tfvars
RGlocation = "westus"
RGname = "TEST-RG1"
VNETname = "TEST-VNET-01"
address_space = "10.0.0.0/16"
Subnetlist = {
"list" = {
name = ["TESTSUBNET","TESTSUBNET1","TESTSUBNET2"]
address = ["10.0.1.0/24","10.0.2.0/24","10.0.3.0/24"]
}
}
File Subnets.tf (module)
resource "azurerm_subnet" "SUBNETS" {
for_each=var.Subnetlist
name=each.value.name
address_prefixes=each.value.address
resource_group_name = var.resource_group_name
virtual_network_name = var.virtual_network_name
}
File variable.tf (subnet module)
variable "resource_group_name" {
}
variable "virtual_network_name" {
}
variable "Subnetlist" {
type = map(object({
name = list(string)
address = list(string)
}))
}
Below if the error that I'm getting
╷
│ Error: Incorrect attribute value type
│
│ on Modules\SubnetConfig\Subnet.tf line 3, in resource "azurerm_subnet" "SUBNETS":
│ 3: name=each.value.name
│ ├────────────────
│ │ each.value.name is list of string with 3 elements
│
│ Inappropriate value for attribute "name": string required.
Could anyone please tell me how to resolve it? Also, please do let me know if this is not the right approach.
The way you iterate over Subnetlist is incorrect - you only get the value of "list" key, ending up with a bundle of subnets instead of individual items. Make it a map of individual subnet objects instead:
variable "Subnetlist" {
type = map(object({
name = string
address = string
}))
}
Then pass it in tfvars like:
Subnetlist = {
"s1" = { name = "TESTSUBNET", address = "10.0.1.0/24" },
"s2" = { name = "TESTSUBNET1", address = "10.0.2.0/24" },
"s3" = { name = "TESTSUBNET2", address = "10.0.3.0/24" }
}
Finally consume it in the module like this:
resource "azurerm_subnet" "SUBNETS" {
for_each = var.Subnetlist
name = each.value.name
address_prefixes = each.value.address
resource_group_name = var.resource_group_name
virtual_network_name = var.virtual_network_name
}
Related
I am caught in a bit of a loop on this one. Need to provide the azure_windows_virtual_machine with a list of network interface IDs. The network interfaces are created using a separate resource block. In my variable definition for the windows vm, I provide an argument for the name[s] of said network interfaces so that we can correctly associate the nics that we want with each virtual machine. If we have 100 nics and 90 VMs, some of the VMs could get two NICs, so we want to be sure we provide some link between NIC name and VM name.
The network interface names are therefore a list(string).
I have been trying to use the values function to get the list of NIC IDs (given the names), but running into a failure: "The each object can be used only in "module" or "resource" blocks, and only when the "for_each" argument is set."
If I use a data source in the resource block, which seemed most logical, it fails too because I have a list(string) specified for the network_interface_names argument, but the data source cannot take that. It of course needs a single string. But it's never going to be a single string, it's always going to be a list (since we can have more than one NIC per VM).
I think the correct answer is to maybe create the list of IDs beforehand - trick is that it would need to almost be dynamic for each defined VM - because each VM will have a different list of network_interface_names. We therefore need to generate the new list on the fly for each VM.
Variables
variable "resource_groups" {
description = "Resource groups"
type = map(object({
location = string
}))
}
variable "virtual_networks" {
description = "virtual networks and properties"
type = map(object({
resource_group_name = string
address_space = list(string)
}))
}
variable "subnets" {
description = "subnet and their properties"
type = map(object({
resource_group_name = string
virtual_network_name = string
address_prefixes = list(string)
}))
}
variable "nic" {
description = "network interfaces"
type = map(object({
subnet_name = string
resource_group_name = string
}))
}
variable "admin_password" {
type = string
sensitive = true
}
variable "admin_user" {
type = string
sensitive = true
}
variable "windows_vm" {
description = "Windows virtual machine"
type = map(object({
network_interface_names = list(string)
resource_group_name = string
size = string
timezone = string
}))
}
INPUTS
resource_groups = {
rg-eastus-dev1 = {
location = "eastus"
}
}
virtual_networks = {
vnet-dev1 = {
resource_group_name = "rg-eastus-dev1"
address_space = ["10.0.0.0/16"]
}
}
subnets = {
snet-01 = {
resource_group_name = "rg-eastus-dev1"
virtual_network_name = "vnet-dev1"
address_prefixes = ["10.0.1.0/24"]
}
}
nic = {
nic1 = {
subnet_name = "snet-01"
resource_group_name = "rg-eastus-dev1"
}
}
admin_password = "s}8cpH96qa.1BQ"
admin_user = "padmin"
windows_vm = {
winvm1 = {
network_interface_names = ["nic1"]
resource_group_name = "rg-eastus-dev1"
size = "Standard_B2s"
timezone = "Eastern Standard Time"
}
}
MAIN
resource "azurerm_resource_group" "rgs" {
for_each = var.resource_groups
name = each.key
location = each.value["location"]
}
data "azurerm_resource_group" "rgs" {
for_each = var.resource_groups
name = each.key
depends_on = [
azurerm_resource_group.rgs
]
}
resource "azurerm_virtual_network" "vnet" {
for_each = var.virtual_networks
name = each.key
resource_group_name = each.value["resource_group_name"]
address_space = each.value["address_space"]
location = data.azurerm_resource_group.rgs[each.value["resource_group_name"]].location
}
resource "azurerm_subnet" "subnet" {
for_each = var.subnets
name = each.key
resource_group_name = each.value["resource_group_name"]
virtual_network_name = each.value["virtual_network_name"]
address_prefixes = each.value["address_prefixes"]
depends_on = [
azurerm_virtual_network.vnet
]
}
data "azurerm_subnet" "subnet" {
for_each = var.subnets
name = each.key
virtual_network_name = each.value["virtual_network_name"]
resource_group_name = each.value["resource_group_name"]
depends_on = [
azurerm_resource_group.rgs
]
}
resource "azurerm_network_interface" "nics" {
for_each = var.nic
ip_configuration {
name = each.key
subnet_id = data.azurerm_subnet.subnet[each.value["subnet_name"]].id
private_ip_address_allocation = "Dynamic"
}
location = data.azurerm_resource_group.rgs[each.value["resource_group_name"]].location
name = each.key
resource_group_name = each.value["resource_group_name"]
depends_on = [
azurerm_resource_group.rgs,
azurerm_subnet.subnet
]
}
data "azurerm_network_interface" "nics" {
for_each = var.nic
name = each.key
resource_group_name = each.value["resource_group_name"]
depends_on = [
azurerm_resource_group.rgs
]
}
resource "azurerm_windows_virtual_machine" "windows_vm" {
for_each = var.windows_vm
admin_password = var.admin_password
admin_username = var.admin_user
location = data.azurerm_resource_group.rgs[each.value["resource_group_name"]].location
name = each.key
network_interface_ids = values(data.azurerm_network_interface.nics[each.value["network_interface_names"]].id)
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
resource_group_name = each.value["resource_group_name"]
size = each.value["size"]
timezone = each.value["timezone"]
source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2019-Datacenter"
version = "latest"
}
}
Current Error on Plan
╷
│ Error: Invalid index
│
│ on main.tf line 75, in resource "azurerm_windows_virtual_machine" "windows_vm":
│ 75: network_interface_ids = values(data.azurerm_network_interface.nics[each.value["network_interface_names"]].id)
│ ├────────────────
│ │ data.azurerm_network_interface.nics is object with 1 attribute "nic1"
│ │ each.value["network_interface_names"] is list of string with 1 element
│
│ The given key does not identify an element in this collection value: string required.
Possible Solution - But Not working
Provide a map, keyed off the VM name, of NIC IDs. Then, in the windows_vm resource, take that map and try to get the list of NIC ID values.
locals {
nic_ids {
[for k, v in var.windows_vm : k => v {data.azurerm_network_interface.nics[v.network_interface_names]}.id]
}
}
resource "azurerm_windows_virtual_machine" "windows_vm" {
for_each = var.windows_vm
admin_password = var.admin_password
admin_username = var.admin_user
location = data.azurerm_resource_group.rgs[each.value["resource_group_name"]].location
name = each.key
network_interface_ids = values(local.nic_ids[each.key])
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
resource_group_name = each.value["resource_group_name"]
size = each.value["size"]
timezone = each.value["timezone"]
source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2019-Datacenter"
version = "latest"
}
}
First of all, you do not need to call data after creation of each resource. The resource itself will contain all the information that you need. So you should eliminate all data sources in your code and use resource directly.
But returning to the error you provided. One way to generate the list dynamically, would be:
network_interface_ids = [for ni_name in each.value["network_interface_names"]: azurerm_network_interface.nics[ni_name].id]
I'm trying to create multiplane vms using for each function in terraform.
Resource Group
resource "azurerm_resource_group" "rg" {
name = "${var.prefix}-rg"
location = "east us 2"
tags = var.tags
}
VNET
resource "azurerm_virtual_network" "vnet" {
name = "${var.prefix}-network-1"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
tags = var.tags
}
Subnet
resource "azurerm_subnet" "subnet" {
name = "${var.prefix}-network-subnet-1"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.2.0/24"]
}
Variables for NICS
variable "nics" {
type = map
default = {
nic3 = {
name = "ubuntu-test-3"
}
nic4 = {
name = "ubuntu-test-4"
}
}
}
NICS
resource "azurerm_network_interface" "nics" {
for_each = var.nics
name = each.value.name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
ip_configuration {
name = "${each.value.name}-conf-1"
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = "Dynamic"
}
tags = var.tags
}
Variables for VMS
variable "vms" {
description = "Virtual Machines"
type = map
default = {
vm3 = {
name = "ubuntu-test-3"
size = "Standard_DS1_v2"
}
vm4 = {
name = "ubuntu-test-4"
size = "Standard_DS1_v2"
}
}
}
and the block for the VM ( not completed - i wrote only the section that i have issue with )
resource "azurerm_virtual_machine" "vms" {
for_each = var.vms
name = each.value.name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
vm_size = each.value.size
tags = var.tags
network_interface_ids = [
azurerm_network_interface.nics[each.value].id,
]
The issue is with this section
network_interface_ids = [
azurerm_network_interface.nics[each.value].id,
]
I'm getting ERROR
│ Error: Invalid index
│
│ on main.tf line 247, in resource "azurerm_virtual_machine" "vms":
│ 247: azurerm_network_interface.nics[each.value].id,
│ ├────────────────
│ │ azurerm_network_interface.nics is object with 2 attributes
│ │ each.value is object with 2 attributes
│
│ The given key does not identify an element in this collection value: string required.
Also tried with
network_interface_ids = [
azurerm_network_interface.nics[each.key].id,
]
and got ERROR
│ Error: Invalid index
│
│ on main.tf line 249, in resource "azurerm_virtual_machine" "vms":
│ 249: azurerm_network_interface.nics[each.key].id,
│ ├────────────────
│ │ azurerm_network_interface.nics is object with 2 attributes
│ │ each.key is "vm3"
│
│ The given key does not identify an element in this collection value.
╵
╷
│ Error: Invalid index
│
│ on main.tf line 249, in resource "azurerm_virtual_machine" "vms":
│ 249: azurerm_network_interface.nics[each.key].id,
│ ├────────────────
│ │ azurerm_network_interface.nics is object with 2 attributes
│ │ each.key is "vm4"
│
│ The given key does not identify an element in this collection value
What I'm doing wrong ?
Replicated the same scenario and able to create resources.
Made couple of changes for the existing code base provided
Added **nic = "nic" value at vms block
Updated network_interface_ids = [azurerm_network_interface.nics[each.value.nic].id,]
Here is the code snippet.
Step1:
Main tf code as below
provider "azurerm" {
features {}
}
variable "prefix" {
default = "rg_swarna"
}
resource "azurerm_resource_group" "rg" {
name = "${var.prefix}-rg"
location = "West US"
// tags = var.tags
}
resource "azurerm_virtual_network" "vnet" {
name = "${var.prefix}-network-1"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
// tags = var.tags
}
resource "azurerm_subnet" "subnet" {
name = "${var.prefix}-network-subnet-1"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.2.0/24"]
}
resource "azurerm_network_interface" "nics" {
for_each = var.nics
name = each.value.name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
ip_configuration {
name = "${each.value.name}-conf-1"
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = "Dynamic"
}
//tags = var.tags
}
resource "azurerm_virtual_machine" "vms" {
for_each = var.vms
name = each.value.name
vm_size = "Standard_DS1_v2"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
network_interface_ids = [azurerm_network_interface.nics[each.value.nic].id,]
storage_os_disk {
name = "myosdisk${each.value.name}"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
os_profile {
computer_name = "TestDemo"
admin_username = "azureuser"
admin_password = "*****#123"
}
os_profile_linux_config {
disable_password_authentication = false
}
}
Step2:
variable tf file
variable "allowed_subnet_ids" {
type = list(string)
description = "access"
}
variable "nics" {
type = map
default = {
nic3 = {
name = "ubun3"
}
nic4 = {
name = "ubun4"
}
}
}
variable "vms" {
description = "VM"
type = map
default = {
vm3 = {
name = "ubun3"
size = "Standard_DS1_v2"
nic = "nic3"
}
vm4 = {
name = "ubuntu4"
size = "Standard_DS1_v2"
nic = "nic4"
}
}
}
variable "allowed_ips" {
type = list(string)
description = "IP addresses"
}
variable "sku" {
type = string
description = "SKU"
}
variable "resource_group_name" {
type = string
description = "resource_group_name"
}
variable "location" {
type = string
description = "location"
}
Step3:
terraform plan
terraform apply -auto-approve
Here are the reference screenshots
Here is the output from above code
In order for this to work, you would need to modify the variable for VMs slightly:
variable "vms" {
description = "Virtual Machines"
type = map
default = {
vm3 = {
name = "ubuntu-test-3"
size = "Standard_DS1_v2"
nic = "nic3"
}
vm4 = {
name = "ubuntu-test-4"
size = "Standard_DS1_v2"
nic = "nic4"
}
}
}
Then, in the VM resource block:
resource "azurerm_virtual_machine" "vms" {
for_each = var.vms
name = each.value.name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
vm_size = each.value.size
tags = var.tags
network_interface_ids = [
azurerm_network_interface.nics[each.value.nic].id,
]
}
Alternatively, you could try with resource chaining with for_each [1], but then you would have to refactor the resource block a bit:
resource "azurerm_virtual_machine" "vms" {
for_each = azurerm_network_interface.nics
name = each.value.name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
vm_size = var.vm_size # or set it to be equal to "Standard_DS1_v2"
tags = var.tags
network_interface_ids = [
each.value.id,
]
}
Then, you would also have to define a new variable called vm_size:
variable "vm_size" {
type = string
description = "VM size."
default = "Standard_DS1_v2"
}
In the second case, you could remove the variable vms completely.
[1] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#chaining-for_each-between-resources
│ Error: Unsupported attribute
│
│ on nsgs.tf line 27, in resource "azurerm_network_security_rule" "example":
│ 27: network_security_group_name = module.nsg-networkcore.nsg_name
│ ├────────────────
│ │ module.nsg-networkcore is a object, known only after apply
│
│ This object does not have an attribute named "nsg_name".
Everything but the network security rules get created when running TF Apply
boilerplate
|_ providers.tf
|_ locals.tf
|_ resource_groups.tf
|_ networking_vnets.tf
|_ nsgs.tf
|_ subnets.tf
|_ terraform.tfvars
|_ variables.tf
modules
|_ resource-group
|_ main.tf
|_ outputs.tf
|_ variables.tf
|_ virtual-network
|_ main.tf
|_ outputs.tf
|_ variables.tf
|_ subnet
|_ main.tf
|_ outputs.tf
|_ variables.tf
|_ nsg
|_ main.tf
|_ outputs.tf
|_ variables.tf
modules/terraform-azure-module-resourcegroup/main.tf
resource "azurerm_resource_group" "rg" {
name = var.resource_group_name
location = var.location
tags = var.tags
}
modules/terraform-azure-module-resourcegroup/outputs.tf
# Output Variables of the module
output "resource_group_name" {
value = azurerm_resource_group.rg.name
description = "name of resource group"
}
output "location" {
value = azurerm_resource_group.rg.location
description = "location of resource group"
}
modules/terraform-azure-module-resourcegroup/variables.tf
# Input Variables of the module
variable "resource_group_name" {
type = string
description = "name of resource group"
}
variable "location" {
type = string
description = "location of resource group"
}
variable "tags" {
type = map(any)
default = {}
}
modules/terraform-azure-module-network/virtual-network/main.tf
# Create the Virtual Network
resource "azurerm_virtual_network" "vnet" {
name = var.vnet_name
location = var.vnet_location
resource_group_name = var.resource_group_name
address_space = var.vnet_address_space
dns_servers = var.dns_servers
}
modules/terraform-azure-module-network/virtual-network/outputs.tf
# Vnet Outputs
output "vnet_id" {
value = azurerm_virtual_network.vnet.id
description = "Virutal Network id"
}
output "vnet_name" {
description = "The Name of the newly created vNet"
value = azurerm_virtual_network.vnet.name
}
output "vnet_location" {
description = "The location of the newly created vNet"
value = azurerm_virtual_network.vnet.location
}
output "vnet_address_space" {
value = azurerm_virtual_network.vnet.address_space
description = "Virutal Network address_space"
}
output "dns_servers" {
value = azurerm_virtual_network.vnet.dns_servers
description = "Virutal Network dns_servers"
}
output "resource_group_name" {
value = azurerm_virtual_network.vnet.resource_group_name
description = "Virutal Network resource_group_name"
}
modules/terraform-azure-module-network/virtual-network/variables.tf
# Vnet Variables
variable "vnet_location" {
type = string
description = "Location of environment"
}
variable "resource_group_name" {
type = string
description = "name of resource group"
}
variable "vnet_name" {
type = string
description = "Name of Virtual Network"
}
variable "vnet_address_space" {
type = list(any)
description = "Address space of Virtual Network"
}
variable "dns_servers" {
type = list(any)
description = "Dns servers for Virtual Network"
}
modules/terraform-azure-module-network/virtual-network/subnet/main.tf
# Create the Subnet
resource "azurerm_subnet" "subnet" {
name = var.subnet_name
resource_group_name = var.resource_group_name
virtual_network_name = var.vnet_name
address_prefixes = var.subnet_address_prefixes
}
modules/terraform-azure-module-network/virtual-network/subnet/outputs.tf
output "subnet_name" {
value = azurerm_subnet.subnet.name
}
output "subnet_id" {
value = azurerm_subnet.subnet.id
}
output "subnet_address_prefixes" {
value = azurerm_subnet.subnet.address_prefixes
}
modules/terraform-azure-module-network/virtual-network/subnet/variables.tf
variable "subnet_name" {
type = string
}
variable "resource_group_name" {
type = string
description = "name of resource group"
}
variable "subnet_address_prefixes" {
type = list(any)
description = "Address prefixes of Subnet"
}
variable "vnet_name" {
type = string
description = "Name of Virtual Network"
}
modules/terraform-azure-module-network/virtual-network/nsg/main.tf
resource "azurerm_network_security_group" "nsg" {
name = var.nsg_name
location = var.nsg_location
resource_group_name = var.resource_group_name
}
modules/terraform-azure-module-network/virtual-network/nsg/outputs.tf
output "nsg_id" {
value = azurerm_network_security_group.nsg.*.id
}
output "nsg_name" {
value = azurerm_network_security_group.nsg.name
}
modules/terraform-azure-module-network/virtual-network/nsg/variables.tf
variable "resource_group_name" {
type = string
description = "name of resource group"
}
variable "nsg_location" {
type = string
description = "location of resource group"
}
variable "nsg_name" {
type = string
description = "name of nsg group"
subscriptions/boilerplate/providers.tf
# Terraform Block
terraform {
required_version = ">= 1.0.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 2.0"
}
}
}
# Provider Block
provider "azurerm" {
subscription_id = "*"
features {}
}
subscriptions/boilerplate/locals.tf
locals {
resource_group_name = "rg-${var.environment}-${var.project_office}-${var.asset_name}"
vnet_name = "vn-${var.environment}-${var.project_office}-coreservice"
location = var.location
}
subscriptions/boilerplate/resource_groups.tf
module "rg-networkcore" {
# source = "../../modules/terraform-azure-module-resourcegroup"
source = "git::ssh://git#ssh.dev.azure.com/v3/*/*/terraform-azure-module-resourcegroup"
resource_group_name = "rg-d-lxr-network"
resource_group_location = local.location
}
module "rg-ansiblecontroller" {
# source = "../../modules/terraform-azure-module-resourcegroup"
source = "git::ssh://git#ssh.dev.azure.com/v3/*/*/terraform-azure-module-resourcegroup"
resource_group_name = local.resource_group_name
resource_group_location = local.location
tags = var.tags
}
subscriptions/boilerplate/networking_vnets.tf
module "vnet-networkcore" {
source = "git::ssh://git#ssh.dev.azure.com/v3/*/*/terraform-azure-module-network//virtual-network"
vnet_name = local.vnet_name
vnet_location = module.rg-networkcore.location
resource_group_name = module.rg-networkcore.resource_group_name
vnet_address_space = var.vnet_address_space
dns_servers = var.dns_servers
depends_on = [module.rg-networkcore]
}
module "subnet-networkcore" {
source = "git::ssh://git#ssh.dev.azure.com/v3/*/*/terraform-azure-module-network//virtual-network/subnet"
resource_group_name = module.rg-networkcore.resource_group_name
vnet_name = module.vnet-networkcore.vnet_name
subnet_name = var.subnet_name
subnet_address_prefixes = var.subnet_address_prefixes
depends_on = [
module.rg-networkcore,
module.vnet-networkcore
]
}
subscriptions/boilerplate/nsgs.tf
module "nsg-networkcore" {
# source = "../../modules/terraform-azure-module-resourcegroup"
source = "git::ssh://git#ssh.dev.azure.com/v3/*/*/terraform-azure-module-network//virtual-network/nsg"
nsg_name = var.nsg_name
nsg_location = local.location
resource_group_name = local.resource_group_name
# tags = var.tags
depends_on = [
module.subnet-networkcore
]
}
resource "azurerm_network_security_rule" "example" {
name = "test123"
priority = 100
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = module.rg-networkcore.resource_group_name
# network_security_group_name = module.nsg-networkcore.nsg_name
network_security_group_name = module.nsg-networkcore.nsg_name
depends_on = [
module.nsg-networkcore
]
}
subscriptions/boilerplate/terraform.tfvars
location = "Australia Southeast"
environment = "d"
project_office = "lxr"
asset_name = "ansiblecontroller"
tags = {
env = "dev"
project = "ansible controller"
}
vnet_address_space = ["10.1.0.0/16"]
dns_servers = ["1.1.1.1", "4.4.4.4"]
subnet_address_prefixes = ["10.1.0.0/24"]
subnet_name = "Ansible"
nsg_name = "nsg-ansible"
subscriptions/boilerplate/variables.tf
# Resource Group Variables
variable "location" {
type = string
description = "location of resource group"
}
variable "tags" {
type = map(any)
default = {}
}
variable "project_office" {
description = "Project Office Name"
type = string
}
variable "environment" {
description = "Environment Name"
type = string
}
variable "asset_name" {
description = "Project Office Name"
type = string
}
# Vnet & Subnet Variables
variable "vnet_address_space" {
type = list(any)
description = "Address space of Virtual Network"
}
variable "dns_servers" {
type = list(any)
description = "Dns servers for Virtual Network"
}
variable "subnet_address_prefixes" {
type = list(any)
description = "Address prefixes of Subnet"
}
variable "subnet_name" {
type = string
}
# NSG Variables
variable "nsg_name" {
type = string
description = "name of nsg group"
}
Terraform plan/apply works when i use vars, as shown below :
network_security_group_name = var.nsg_name
I'm not sure what the issue is when i reference the module instead, as it does contain an attribute named "nsg_name"
Would appreciate some assistance
Thanks
sorry fellas this was an easy one to fix. I was running terraform get when I should have run terraform init. Rookie mistake
main.tf wrote:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 2.26"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg" {
name = "Product-RG"
location = var.location
}
resource "azurerm_virtual_network" "vnet" {
resource_group_name = azurerm_resource_group.rg.name
name = "Product-VNet"
address_space = [lookup(var.vnetAddress, var.location)]
location = var.location
subnet {
name = "Web-Sub1"
address_prefix = ["${lookup(var.subnetAddress[var.location], "web1")}"]
}
subnet {
name = "Web-Sub2"
address_prefix = [lookup(var.subnetAddress[var.location], "web2")]
}
In Web-Sub1, i originally brought address_prefix like Web-Sub2, but now i'm trying like address_prefix on Web-Sub1 after the error occurred.
An error has occurred as below.
Error: Incorrect attribute value type
on main.tf line 27, in resource "azurerm_virtual_network" "vnet":
27: address_prefix = ["${lookup(var.subnetAddress[var.location], "web1")}"]
Inappropriate value for attribute "address_prefix": string required.
Error: Incorrect attribute value type
on main.tf line 31, in resource "azurerm_virtual_network" "vnet":
31: address_prefix = [lookup(var.subnetAddress[var.location], "web2")]
Inappropriate value for attribute "address_prefix": string required.
variable.tf wrote:
variable "location" {}
variable "vnetAddress" {
type = map
default = {
westus = "192.168.1.0/27"
eastus = "192.168.11.0/27"
}
}
variable "subnetAddress" {
type = map
default = {
westus = {
web1 = "192.168.1.0/27"
web2 = "192.168.1.32/27"
was1 = "192.168.1.64/27"
was2 = "192.168.1.96/27"
db1 = "192.168.1.128/27"
db2 = "192.168.1.160/27"
}
eastus = {
web1 = "192.168.11.0/27"
web2 = "192.168.11.32/27"
was1 = "192.168.11.64/27"
was2 = "192.168.11.96/27"
db1 = "192.168.11.128/27"
db2 = "192.168.11.160/27"
}
}
}
I wonder why there is an error that needs to be written in string format and why I can't bring the data.
You are almost there, just that address_prefix argument needs to be a string and you are passing a list of strings address_prefix = [lookup(var.subnetAddress[var.location], "web2")]
subnet {
name = "Web-Sub1"
address_prefix = lookup(var.subnetAddress[var.location], "web1")
}
subnet {
name = "Web-Sub2"
address_prefix = lookup(var.subnetAddress[var.location], "web2")
}
This should work.
Refer azurerm_virtual_network resource, address_prefix is passed as a string rather a list of strings.
Also for public ip id getting: "
Error: Can not parse "ip_configuration.0.public_ip_address_id" as a
resource id: Cannot parse Azure ID: parse
module.resource.azurerm_public_ip.primary.id: invalid URI for request
"
As the network is a nested module for the resource module, will you please suggest, where I'm missing?
main.tf file:
#Select provider
provider "azurerm" {
subscription_id = "xxxxxxxxxxxxxxxxxxxxxx"
version = "~> 2.2"
features {}
}
module "resource" {
source = "./modules/resource"
resource_group_name = "DevOpsPoc-primary"
location = "southeastasia"
}
module "network" {
source = "./modules/network"
virtual_network = "primaryvnet"
subnet = "primarysubnet"
address_space = "192.168.0.0/16"
address_prefix = "192.168.1.0/24"
public_ip = "backendvmpip"
location = "southeastasia"
primary_nic = "backendvmnic"
#vnet_subnet_id = element(module.network.vnet_subnets, 0)
primary_ip_conf = "backendvm"
}
resource module main.tf file:
resource "azurerm_resource_group" "primary" {
name = "var.resource_group_name"
location = "var.location"
tags = {
environment = "env"
}
}
network module main.tf file:
#Create Virtual Network in Primary Resource Group
resource "azurerm_virtual_network" "primary" {
name = "var.virtual_network"
resource_group_name = "module.resource.azurerm_resource_group.primary.name"
address_space = ["var.address_space"]
location = "module.resource.azurerm_resource_group.primary.location"
tags = {
environment = "env"
}
}
#Create Subnet in Virtual Network
resource "azurerm_subnet" "primary" {
name = "var.subnet"
resource_group_name = "module.resource.azurerm_resource_group.primary.name"
virtual_network_name = "module.resource.azurerm_virtual_network.primary.name"
address_prefix = "var.address_prefix"
# tags = {
# environment = "env"
# }
}
output "subnet_id"{
value = "module.resource.azurerm_subnet.primary.id"
}
#Create public IP address
resource "azurerm_public_ip" "primary" {
name = "var.public_ip"
location = "module.resource.azurerm_resource_group.primary.location"
resource_group_name = "module.resource.azurerm_resource_group.primary.name"
allocation_method = "Dynamic"
tags = {
environment = "env"
}
}
output "public_ip_id"{
value = "module.resource.azurerm_public_ip.id"
}
#Create Network Interface
resource "azurerm_network_interface" "primary" {
name = "var.primary_nic"
location = "module.resource.azurerm_resource_group.primary.location"
resource_group_name = "module.resource.azurerm_resource_group.primary.name"
ip_configuration {
name = "var.primary_ip_conf"
subnet_id = "module.resource.azurerm_subnet.primary.id"
private_ip_address_allocation = "Dynamic"
public_ip_address_id = "module.resource.azurerm_public_ip.primary.id"
}
tags = {
environment = "env"
}
}
There are some places need to be corrected in your codes:
You don't need double quotes"" in variables or expression refers to Interpolation Syntax. For example "var.virtual_network" should be var.virtual_network.
You can directly reference resources in the same main.tf file instead of from the module block. For example, change virtual_network_name = "module.resource.azurerm_virtual_network.primary.name" to virtual_network_name = azurerm_virtual_network.primary.name in the resource "azurerm_subnet" block.
The syntax for referencing module outputs is ${module.NAME.OUTPUT}, where NAME is the module name given in the header of the module configuration block and OUTPUT is the name of the output to reference. You can declare resource group name and location in module "network" instead of using it from the ./modules/network/main.tf file.
Here is the working code and you could get more references in this document:
main.tf file in the root directory
module "resource" {
source = "./modules/resource"
resource_group_name = "DevOpsPoc-primary"
location = "southeastasia"
}
module "network" {
source = "./modules/network"
resource_group_name = module.resource.RGname
location = module.resource.location
virtual_network = "primaryvnet"
subnet = "primarysubnet"
address_space = ["192.168.0.0/16"]
address_prefix = "192.168.1.0/24"
public_ip = "backendvmpip"
primary_nic = "backendvmnic"
#vnet_subnet_id = element(module.network.vnet_subnets, 0)
primary_ip_conf = "backendvm"
}
main.tf in the directory ./modules/resource
variable "resource_group_name" {}
variable "location" {}
resource "azurerm_resource_group" "primary" {
name = var.resource_group_name
location = var.location
}
output "RGname" {
value = "${azurerm_resource_group.primary.name}"
}
output "location" {
value = "${azurerm_resource_group.primary.location}"
}
main.tf in the directory ./modules/network and also declare the variables in the same directory.
#Create Virtual Network in Primary Resource Group
resource "azurerm_virtual_network" "primary" {
name = var.virtual_network
resource_group_name = var.resource_group_name
address_space = var.address_space
location = var.location
}
#Create Subnet in Virtual Network
resource "azurerm_subnet" "primary" {
name = var.subnet
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.primary.name
address_prefix = var.address_prefix
}
output "subnet_id"{
value = azurerm_subnet.primary.id
}
#Create public IP address
resource "azurerm_public_ip" "primary" {
name = var.public_ip
location = var.location
resource_group_name = var.resource_group_name
allocation_method = "Dynamic"
}
output "public_ip_id"{
value = azurerm_public_ip.primary.id
}
#Create Network Interface
resource "azurerm_network_interface" "primary" {
name = var.primary_nic
location = var.location
resource_group_name = var.resource_group_name
ip_configuration {
name = var.primary_ip_conf
subnet_id = azurerm_subnet.primary.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.primary.id
}
}
I had a similar error when setting up an Azure App Service using Terraform.
module.app_service.azurerm_app_service.app_service: Creating...
│ Error: Cannot parse Azure ID: parse "27220": invalid URI for request
│
│ with module.app_service.azurerm_app_service.app_service,
│ on ../../../modules/azure/app-service/main.tf line 1, in resource "azurerm_app_service" "app_service":
│ 1: resource "azurerm_app_service" "app_service" {
Here's how I fixed it:
The issue was that I used the wrong value for the App Service Plan ID in my module.
I was using 27220 as the App Service Plan ID, instead of the actual value of the App Service Plan ID which of this format:
"/subscriptions/fec545cd-bead-43ba-84c6-5738cdc7e458/resourceGroups/MyDevRG/providers/Microsoft.Web/serverfarms/MyDevLinuxASP"
That's all