terraform multiple NSG using csv - terraform

I am trying to follow the solution given here How I can avoid multiple loops with Terraform for_each and dynamic for resource azurerm_network_security_group?
Here is my module: Module-Azure-Nsg-V3/main.tf
locals {
# read csv file
list_of_csv_lines = csvdecode(file(var.csv_file_name))
# collect all nsg names , (Unique)
all_nsg_names = distinct([for item in local.list_of_csv_lines : item.nsg_name])
# collect all nsg names other syntax , (Unique)
all_nsg_names_again = distinct(local.list_of_csv_lines[*].nsg_name)
# Loop over all unique names ---> for item in local.all_nsg_names :
# create a key of nsg name item ----> "${item}" =>
# And to now fill value in that dictionary item key,
# Loop over list of csv lines
# pick the line
# if nsg_name matches item
combine = {
for item in local.all_nsg_names :
"${item}" => [
for line in local.list_of_csv_lines : line
if item == line.nsg_name
]
}
}
resource "azurerm_network_security_group" "this" {
for_each = local.combine
name = each.key
location = var.location
resource_group_name = var.resource_group_name
dynamic "security_rule" {
for_each = each.value
content {
name = security_rule.value["nsg_name"]
priority = security_rule.value["priority"]
direction = security_rule.value["direction"]
access = security_rule.value["access"]
protocol = security_rule.value["protocol"]
source_port_range = security_rule.value["source_port_range"]
destination_port_range = security_rule.value["destination_port_range"]
source_address_prefix = security_rule.value["source_address_prefix"]
destination_address_prefix = security_rule.value["destination_address_prefix"]
}
}
}
output "all-nsg-names-in-output" {
value = local.all_nsg_names
}
output "all-nsg-names-in-output-again" {
value = local.all_nsg_names_again
}
output "combine-output"{
value = local.combine
}
Here is my module: Module-Azure-Nsg-V3/variables.tf
variable "csv_file_name" {
type = string
default = null
}
variable "resource_group_name" {
description = "Name of the resource Group"
type = string
default = null
}
variable "location" {
description = "enviroment in which you are working"
type = string
default = null
}
Now I am using above mention module to create NSG.
Here is my nsg_rules.csv
nsg_name,rulename,priority,direction,access,protocol,source_port_range,destination_port_range,source_address_prefix,destination_address_prefix 
nsg01,Rule01,100,Inbound,Allow,Tcp,80,*,*,*
nsg01,Rule02,110,Inbound,Allow,Tcp,443,*,*,*
nsg02,Rule01,100,Inbound,Allow,Tcp,80,*,*,*
nsg02,Rule02,110,Inbound,Allow,Tcp,443,*,*,*
nsg03,Rule01,100,Inbound,Allow,Tcp,80,*,*,*
This is variables.tf file
variable "csv_file_name" {
type = string
default = "nsg_rules.csv"
}
variable "location" {
description = "enviroment in which you are working"
type = string
default = "eastus"
}
variable "resource_group_name" {
description = "Name of the resource Group"
type = string
default = "terraform_rg"
}
And main.tf
module "Module-Azure-Nsg-V3" {
source = "./Module-Azure-Nsg-V3"
csv_file_name = var.csv_file_name
location = var.location
resource_group_name = var.resource_group_name
}
output "display_this" {
value = module.Module-Azure-Nsg-V3.all-nsg-names-in-output
}
output "display_this_again" {
value = module.Module-Azure-Nsg-V3.all-nsg-names-in-output-again
}
output "all-nsg-rules" {
value = module.Module-Azure-Nsg-V3.combine-output
}
But I get errors.
╷
│ Error: Invalid index
│
│ on Module-Azure-Nsg-V3/main.tf line 46, in resource "azurerm_network_security_group" "this":
│ 46: destination_address_prefix = security_rule.value["destination_address_prefix"]
│ ├────────────────
│ │ security_rule.value is object with 10 attributes
│
│ The given key does not identify an element in this collection value.

destination_address_prefix is actually "destination_address_prefix " - you have extra space in your csv file after destination_address_prefix.

Related

Object Merge Failure

I a trying to merge a variable definition for log analytics with some other values. Don't have the syntax correct here and when I try to use the local value (local.laws) in other areas of the code, it errors out saying "This object does not have an attribute named resource_group_name." Clearly, the merge is not doing what I want. Code below.
Variable Definition
variable "log_analytics_workspace" {
description = "A list of log analytics workspaces and their arguments."
type = list(object({
name = string
resource_group_name = string
location = string
sku = string #Free, PerNode, Premium, Standard, Standalone, Unlimited, CapacityReservation, and PerGB2018. Defaults to PerGB2018.
retention_in_days = number #Possible values are either 7 (Free Tier only) or range between 30 and 730.
daily_quota_gb = number #The workspace daily quota for ingestion in GB. Defaults to -1 (unlimited) if omitted.
reservation_capacity_in_gb_per_day = number
tags = map(string)
}))
}
INPUT
log_analytics_workspace = [
{
name = "law-eastus-dev-01"
resource_group_name = "rg-eastus-dev-01"
location = "eastus"
sku = "PerGB2018"
retention_in_days = 30
daily_quota_gb = 10
reservation_capacity_in_gb_per_day = 100
tags = {
"law" = "DEV"
}
}
]
LOCALS
###LOCALS###
locals {
laws = {
for_each = { for law in var.log_analytics_workspace : law.name => merge(
{
internet_ingestion_enabled = false
internet_query_enabled = false
cmk_for_query_forced = false
}) }
}
}
##Data Source for Resource Groups
data "azurerm_resource_group" "resource_groups" {
for_each = local.laws
name = each.value.resource_group_name
}
Error
│ Error: Unsupported attribute
│
│ on modules/loganalytics/main.tf line 17, in data "azurerm_resource_group" "resource_groups":
│ 17: name = each.value.resource_group_name
│ ├────────────────
│ │ each.value is object with 1 attribute "law-eastus-dev-01"
│
│ This object does not have an attribute named "resource_group_name".
I think you wanted the following:
locals {
laws = { for law in var.log_analytics_workspace : law.name => merge(
{
internet_ingestion_enabled = false
internet_query_enabled = false
cmk_for_query_forced = false
}, law)
}
}
and then
data "azurerm_resource_group" "resource_groups" {
for_each = local.laws
name = each.value.resource_group_name
}

Use Terraform Locals for list object {type = list(object({}))}

Please how do I simplify this configuration using locals, the code works fine as is but gets complex passing the variables manually each time.
VARIABLES:
variable "vm_all" {
type = list(object({}))
default = [
{
name = "vm1"
rg = "rg1"
sn = "sn1"
sn_prefix = ["10.0.1.0/24"]
},
{
name = "vm2"
rg = "rg2"
sn = "sn2"
sn_prefix = ["10.0.2.0/24"]
},
{
name = "vm3"
rg = "rg3"
sn = "sn3"
sn_prefix = ["10.0.3.0/24"]
}
]
}
CURRENT ITERATION USING LOCALS:(requires manually mapping the variables as shown above)
resource "example_resource" "resource1" {
for_each = {for vm_all in var.vm_all: vm_all.name => vm_all }
name = each.value.name
rg = each.value.rg
sn = each.value.sn
sn_prefix = each.value.sn_prefix
}
DESIRED METHOD OF PASSING VARIABLES:
variable "name" {
default = [
"vm1",
"vm2",
"vm3"
]
}
variable "rg_names" {
default = [
"rg1",
"rg2",
"rg3"
]
}
variable "subnets" {
default = [
"sn1",
"sn2",
"sn3"
]
}
variable "subnet_prefixes" {
default = [
"sn_prefix1",
"sn_prefix2",
"sn_prefix3"
]
}
QUESTION: How can I use locals in a more effective way to allow passing the variables as lists shown above and avoid the need to map manually?
You can combine them as follows:
locals {
vm_all = {for idx, name in var.name:
name => {
"name" = name
rg = var.rg_names[idx]
sn = var.subnets[idx]
sn_prefix = [var.subnet_prefixes[idx]]
}
}
}
then
resource "example_resource" "resource1" {
for_each = local.vm_all
name = each.value.name
rg = each.value.rg
sn = each.value.sn
sn_prefix = each.value.sn_prefix
}
Thank you #Marcin, really helpful but I'm not yet there, I feel so close though, I get the following error when I try to created nics using ids from the created subnets:
(The given key does not identify an element in this collection value.)
Error: Invalid index
│
│ on main.tf line 165, in resource "azurerm_network_interface" "nic":
│ 165: subnet_id = azurerm_subnet.subnet[each.value.sn].id
│ ├────────────────
│ │ azurerm_subnet.subnet is object with 10 attributes
│ │ each.value.sn is "subnet7"
│
│ The given key does not identify an element in this collection value.
see the main.tf below:
resource "azurerm_subnet" "subnet" {
for_each = local.vm_all
name = each.value.sn
resource_group_name = each.value.rg
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = each.value.sn_prefix
}
resource "azurerm_network_interface" "nic" {
for_each = local.vm_all
name = each.value.name
location = var.location
resource_group_name = each.value.rg
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.subnet[each.value.sn].id
private_ip_address_allocation = "Dynamic"
}
}

How to create conditional vnet based on address space that I give in terraform

Objective: Trying to create azure vnet where address space differ based on environment
Code I am trying:
Variable.tf:
variable "vnet_address_space" {
type = map(any)
default = {
"Dev" = ["xx.xx.0.0/24","xx.xx.0.0/24","xx.xx.0.0/24","xx.xx.0.0/20"]
"Stage" = ["xx.xx.0.0/24","xx.xx.0.0/24","xx.xx.0.0/24","xx.xx.0.0/20"]
"Prod" = ["xx.xx.0.0/24","xx.xx.0.0/24","xx.xx.0.0/24","xx.xx.0.0/20"]
}
}
Main.tf: (updated)
resource "azurerm_virtual_network" "vnet" {
name = var.hub_vnet_name
location = azurerm_resource_group.rg[0].location
resource_group_name = azurerm_resource_group.rg[0].name
for_each = {for k,v in var.vnet_address_space: k=>v if k == "Dev"}
address_space = var.vnet_address_space.Dev
dns_servers = var.dns_servers
tags = {
environment = "${var.env}"
costcentre = "14500"
}
dynamic "ddos_protection_plan" {
for_each = local.if_ddos_enabled
content {
id = azurerm_network_ddos_protection_plan.ddos[0].id
enable = false
}
}
}
However It did not work as intended
Error I get:
on main.tf line 85, in resource "azurerm_virtual_network" "vnet":
│ 85: address_space = [var.vnet_address_space]
│ ├────────────────
│ │ var.vnet_address_space is a map of dynamic, known only after apply
│
│ Inappropriate value for attribute "address_space": element 0: string required.

How to loop correctly in terraform for_each?

Objective: Loop through azure subnets via terraform.
Code That I use:
Main.tf:
resource "azurerm_network_security_group" "nsg" {
name = "nsg-vnet-hub-${var.env}-indoundDNS"
location = azurerm_resource_group.rg[0].location
resource_group_name = azurerm_resource_group.rg[0].name
tags = {
environment = "${var.env}"
costcentre = "12345"
}
}
resource "azurerm_monitor_diagnostic_setting" "nsg" {
for_each = var.subnets
name = lower("${each.key}-diag")
target_resource_id = azurerm_network_security_group.nsg[each.key].id
storage_account_id = azurerm_storage_account.storeacc.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.logws.id
dynamic "log" {
for_each = var.nsg_diag_logs
content {
category = log.value
enabled = true
retention_policy {
enabled = false
}
}
}
}
My root module variable.tf :
variable "subnets" {
type = map(object({
name = string
}))
default = {
"s1" = { name = "dns_snet"},
"s2" = { name = "common_snet"},
"s3" = { name = "gw_snet"},
"s4" = { name = "data_snet"}
}
}
Problem I am facing:
Error:
network_security_group_id = azurerm_network_security_group.nsg[each.key].id
│ ├────────────────
│ │ azurerm_network_security_group.nsg is object with 7 attributes
│ │ each.key is "s3"
│
│ The given key does not identify an element in this collection value
Just updated this post, now I get error as above. I am referring to below documentation
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group
You have only a single instance of azurerm_network_security_group.nsg. Thus there is nothing to iterate over. To fix your error, it should be:
target_resource_id = azurerm_network_security_group.nsg.id

How to skip certain part of a resource from being provisioned using Terraform

# main.tf
resource "azurerm_api_management" "apim_demo" {
name = var.apim_instance_name
location = azurerm_resource_group.apim_rg.location
resource_group_name = azurerm_resource_group.apim_rg.name
publisher_name = var.apim_publisher_name
publisher_email = var.apim_publisher_email
sku_name = var.apim_sku_name
identity {
type = "SystemAssigned"
}
hostname_configuration {
proxy {
default_ssl_binding = true
host_name = "qtech"
key_vault_id = "https://ssl-key-test789.vault.azure.net/secrets/my-ssl-certificate"
negotiate_client_certificate = true
}
proxy {
default_ssl_binding = false
host_name = "ftech"
key_vault_id = "https://ssl-key-test789.vault.azure.net/secrets/my-ssl-certificate2"
negotiate_client_certificate = true
#custom = var.custom_block
#count = var.test_condition ? 1 : 0
}
}
}
# variables.tf
variable "apim_instance_name" {}
variable "apim_publisher_name" {}
variable "apim_publisher_email" {}
variable "apim_sku_name" {}
variable "tenant_id" {
# description "Enter Tenant ID"
}
variable "client_id" {
# description "Enter Tenant ID"
}
variable "subscription_id" {
# description "Enter Subscription ID"
}
variable "client_secret" {
# description "Enter client secret"
}
variable "apim_resource_group_name" {
# description "RG-2"
}
variable "apim_location" {
type = map(any)
default = {
location1 = "eastus"
location2 = "westus"
}
}
#variable "subnets" {
# type = "list"
# default = ["10.0.1.0/24", "10.0.2.0/24"]
#}
variable "test_condition" {
type = bool
default = true
}
variable "custom_block" {
default = null
}
From the above terraform code, I want to avoid/skip the below (second proxy block) part of the resource from being provisioned
proxy {
default_ssl_binding = false
host_name = "ftech"
key_vault_id = "https://ssl-key-test789.vault.azure.net/secrets/my-ssl-certificate2"
negotiate_client_certificate = true
# custom = var.custom_block
# count = var.test_condition ? 1 : 0
}
I did try to use count logic to avoid but I guess it will work on a complete resource block, not on a certain part of a resource block. Anyway, I received the below error using count logic
Error: Unsupported argument
│
│ on apim-instance.tf line 35, in resource "azurerm_api_management" "apim_demo":
│ 35: count = var.test_condition ? 1 : 0
│
│ An argument named "count" is not expected here.
╵
I also try to use null logic to avoid but I guess it will also work on a complete resource block, not on a certain part of a resource block. Anyway, I got the below error using null logic.
│ Error: Unsupported argument
│
│ on apim-instance.tf line 34, in resource "azurerm_api_management" "apim_demo":
│ 34: custom = var.custom_block
│
│ An argument named "custom" is not expected here.
╵
use dynamic block. it will resolve your query.
https://www.terraform.io/docs/language/expressions/dynamic-blocks.html
variable "proxy" {
type = any
default = [
{
default_ssl_binding = true
host_name = "qtech"
key_vault_id = "https://ssl-key-test789.vault.azure.net/secrets/my-ssl-certificate"
negotiate_client_certificate = true
}
{
default_ssl_binding = true
host_name = "qtech"
key_vault_id = "https://ssl-key-test789.vault.azure.net/secrets/my-ssl-certificate"
negotiate_client_certificate = true
}
]
}
use like below.
hostname_configuration {
dynamic "proxy" {
for_each = var.proxy
content {
default_ssl_binding = proxy.value.default_ssl_binding
host_name = proxy.value.host_name
key_vault_id = proxy.value.key_vault_id
negotiate_client_certificate = proxy.value.negotiate_client_certificate
}
}
}
What if you just seperate this code blocks into modules for each stage?

Resources