random_id using HMACkey - terraform

I am trying to create multiple resources with different suffix. I have two insight resources which i need in same resource group.
I am using HMAC key to create random_id's.
Although it generates them, i am not able to use them in resource function.
The error that i'm getting:
Because random_id.application_insights_unique_suffix has "count" set, its attributes must be accessed on specific instances.
and here i'm getting an error
${random_id.application_insights_unique_suffix.hex}
locals {
hmac_key = "${var.environmentshort}${var.hmac_key}"
}
resource "random_id" "application_insights_unique_suffix" {
count = "${length(var.application_insights)}"
keepers = {
rand = sha256(local.hmac_key)
}
byte_length = 4
}
resource "azurerm_application_insights" "appi" {
for_each = var.application_insights
name = "${var.productname}-${var.environmentshort}-${var.regionshort}-${random_id.application_insights_unique_suffix.hex}"
location = var.resourcelocation
resource_group_name = var.resourcegpname
application_type = each.value["application_type"]
retention_in_days = each.value["retention_in_days"]
daily_data_cap_in_gb = each.value["daily_data_cap_in_gb"]
tags = merge(var.rgtags,var.ars_tags)
}
would be glad if you could help me find an alternate solution.
Thank you

Since the same variable is used in the random_id resource and the azurerm_application_insights resource, for_each can be used in both cases. That will require a small bit of refactoring:
locals {
hmac_key = "${var.environmentshort}${var.hmac_key}"
}
resource "random_id" "application_insights_unique_suffix" {
for_each = var.application_insights
keepers = {
rand = sha256(local.hmac_key)
}
byte_length = 4
}
resource "azurerm_application_insights" "appi" {
for_each = var.application_insights
name = "${var.productname}-${var.environmentshort}-${var.regionshort}-${random_id.application_insights_unique_suffix[each.key].hex}"
location = var.resourcelocation
resource_group_name = var.resourcegpname
application_type = each.value["application_type"]
retention_in_days = each.value["retention_in_days"]
daily_data_cap_in_gb = each.value["daily_data_cap_in_gb"]
tags = merge(var.rgtags,var.ars_tags)
}
Note that you cannot use count[1] and for_each [2] meta-arguments interchangeably:
Note: A given resource or module block cannot use both count and for_each.
[1] https://www.terraform.io/language/meta-arguments/count
[2] https://www.terraform.io/language/meta-arguments/for_each

Related

Terraform to create azure subnet delegation for selected subnets using for_each

I want some help on creating delegation on selected azure subnets. My code is as per below details.
Variables defined in my variable file
variable “subnets” {
type = map(any)
}
My tfvar file contains below values
subnets = {
mlops-aue-snt-aks = [“10.255.232.0/24”]
mlops-aue-snt-stg = [“10.255.233.0/26”]
mlops-aue-snt-kv = [“10.255.233.128/27”]
AzureBastionSubnet = [“10.255.233.160/27”]
mlops-aue-snt-shd = [“10.255.234.0/25”]
mlops-aue-snt-db1 = [“10.255.235.0/26”]
mlops-aue-snt-db2 = [“10.255.235.64/26”]
mlops-aue-snt-aci = [“10.255.235.128/26”]
}
This is my code for subnet
resource “azurerm_subnet” “azr_subnet” {
for_each = var.subnets
name = each.key
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = each.value
enforce_private_link_endpoint_network_policies = true
}
All subnets are created with this and it is all fine but now I have a requirement to add service_delegation for mlops-aue-snt-db1 and mlops-aue-snt-db2 and different for other subnets. I am not sure how to achieve this with my existing code. I can’t separate out subnets from the code as it will force to delete existing ones and create new which is not recommended. I did read some posts about using dynamic block to make changes but not sure how to implement it for selective subnets as per my requirement.
Can anyone please suggest how to achieve this in terraform?
You can do this with dynamic blocks and by changing your subnets a bit.
subnets = {
mlops-aue-snt-aks = {
cidr = [“10.255.232.0/24”]
service_delegation = false
}
# the rest in same format
mlops-aue-snt-db1 = {
cidr = [“10.255.235.0/26”]
service_delegation = true
}
mlops-aue-snt-db2 = {
cidr = [“10.255.235.64/26”]
service_delegation = true
}
# ...
}
then
resource "azurerm_subnet" "azr_subnet" {
for_each = var.subnets
name = each.key
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = each.value.cidr
enforce_private_link_endpoint_network_policies = true
dynamic "delegation" {
for_each = each.value.service_delegation == "true" ? [1] : []
content {
name = "delegation"
service_delegation {
name = "Microsoft.ContainerInstance/containerGroups"
actions = ["Microsoft.Network/virtualNetworks/subnets/join/action", "Microsoft.Network/virtualNetworks/subnets/prepareNetworkPolicies/action"]
}
}
}
}
Check out this repo. This is not my work and I am not taking credit for it. I have just decided to share it so that it can be useful for devs looking for a solution for this common problem.

How to output ID from one resource which contains map variable

I'm new in Terraform and I have silly question.I want to create NAT gateway and I need ID of one subnet:
resource "azurerm_subnet" "this" {
for_each = var.subnet_prefixes
name = each.key
resource_group_name = var.subnetRGname
virtual_network_name = azurerm_virtual_network.this.name
address_prefixes = each.value
}
variable subnet_prefixes {
type = map
default = {
"PublicSubnet"=["10.0.1.0/24"]
"PrivateSubnet"=["10.0.2.0/24"]
}
}
output "publicsubnet" {
value = map ("private", azurerm_subnet.this.id)
/* {
for s in azurerm_subnet.this : s.name => private.id
} */
}
In first block I'm creating subnets from "subnet_prefixes" map variables.
But then I want to output only "PublicSubnet" ID so I can join it to my NAT gateway.
Is there any simple way to do this?
Thank you very much for your help.
EDIT:
Well I tried to do something about it and it kinda works while using count, but it is not what I expected:
resource "azurerm_subnet" "this" {
count = length(var.subnet_prefixes)
name = var.subnetnames[count.index]
resource_group_name = var.subnetRGname
virtual_network_name = azurerm_virtual_network.this.name
address_prefixes = [var.subnet_prefixes[count.index]]
}
variable subnet_prefixes {
type = list(string)
default = ["10.0.1.0/24","10.0.2.0/24"]
}
variable subnetnames {
type = list(string)
default = ["SubnetA","SubnetB"]
}
output "publicsubnet" {
value = azurerm_subnet.this.*.id
}
module "NAT" {
source = "./Modules/NAT"
NATname = "${var.RGName}-NAT"
NATRGlocation = module.Resource_Group.resource_group_location
NATRGname = module.Resource_Group.resource_group_name
publicIPid = module.VNET.publicIP
publicsubnetID = module.VNET.publicsubnet[0]
}
I'm just wondering, as you can see on main module i need to specify index of subnet, and I wanted to export only ID that somehow refers to name like "Public*". Is there any way to do it, or maybe could you please tell me how to improve this code?
Since you've used for_each in your azurerm_subnet based on map of var.subnet_prefixes, you can refer to individuals subnets using keys from the map.
So public subnet id should be be:
output "publicsubnet" {
value = azurerm_subnet.this["PublicSubnet"].id
}

looping over local map of bojects with for expression on a resouce that already has a for_loop

How could i achieve looping over local.daily map so i could create the backup policy name based on the map value on a resource that already uses a for_each loop ?
On the following example, on the resource azurerm_backup_policy_file_share i would like to populate the name field with the value of local.daily["name"] value.
locals {
regions = [
"centralus",
"northeurope"
]
}
resource "azurerm_resource_group" "recovery_vault" {
name = "recovery-vault-${terraform.workspace}-rg"
location = var.azure_region
tags = {
environment = terraform.workspace
source = "terraform"
service = "Backup Vault"
}
}
resource "azurerm_recovery_services_vault" "vaults" {
for_each = toset(local.regions)
name = "recovery-vault-${terraform.workspace}-${each.key}"
location = each.key
resource_group_name = azurerm_resource_group.recovery_vault.name
sku = "Standard"
soft_delete_enabled = true
}
locals {
daily = [{
name = "Every23h"
frequency = "Daily"
time = "23:00"
count = 30
}
]
}
resource "azurerm_backup_policy_file_share" "daily" {
for_each = azurerm_recovery_services_vault.vaults
name = "need this field to be name retrieved from local.daily"
resource_group_name = each.value["resource_group_name"]
recovery_vault_name = each.value["name"]
timezone = "UTC"
dynamic "backup" {
for_each = local.daily
content {
frequency = backup.value["frequency"]
time = backup.value["time"]
}
}
dynamic "retention_daily" {
for_each = local.daily
content {
count = retention_daily.value["count"]
}
}
}
I guess what you need is setproduct.
It should look something like this:
resource "azurerm_backup_policy_file_share" "daily" {
for_each = toset(setproduct(azurerm_recovery_services_vault.vaults, local.daily))
name = each.value[1].name
resource_group_name = each.value[0]["resource_group_name"]
# ...

Terraform-12 (AWS): Create Subnets according to provided input variables using for/for_each

I want to create a module that can accept inputs for environments(dev,test,prod) and create the number of subnets ("app" and "db" subnet for each environment) with proper tags. e.g Name=dev-app
The module should be flexible to add/delete subnets as input variables are updated.
My template looks like below
variable "environments" {
type = map(object({
app_subnet_cidr = string
db_subnet_cidr = string
}))
default = {
dev = {
app_subnet_cidr = "192.168.219.0/24"
db_subnet_cidr = "192.168.218.0/24"
}
test = {
app_subnet_cidr = "192.168.118.0/24"
db_subnet_cidr = "192.168.119.0/24"
}
}
}
resource "aws_subnet" "this" {
for_each = var.environments
vpc_id = var.vpc_id
cidr_block = {Don't know what to use here}
tags {
Name = {Don't know what to use here}
}
}
I was referring to the below articles.
https://www.hashicorp.com/blog/terraform-0-12-rich-value-types/
Question-2: How "networks" variable could be defined for below module
module "subnets" {
source = "./subnets"
parent_vpc_id = "vpc-abcd1234"
networks = {
production_a = {
network_number = 1
availability_zone = "us-east-1a"
}
production_b = {
network_number = 2
availability_zone = "us-east-1b"
}
staging_a = {
network_number = 1
availability_zone = "us-east-1a"
}
}
}
https://www.hashicorp.com/blog/hashicorp-terraform-0-12-preview-for-and-for-each/
The blog posts you are referring to were previews of forthcoming features not yet finished, so they are not the best reference for the final functionality that shipped. Instead, refer to the official documentation for resource for_each.
The documentation includes an example of declaring a number of azurerm_resource_group resource instances based on a map. We can adapt that example to work with your var.environments, with one resource block describing the "app" subnets, and a separate resource block describing the "db" subnets:
resource "aws_subnet" "app" {
for_each = var.environments
vpc_id = var.vpc_id
cidr_block = each.value.app_subnet_cidr
tags = {
Name = "${each.key}-app"
}
}
resource "aws_subnet" "db" {
for_each = var.environments
vpc_id = var.vpc_id
cidr_block = each.value.db_subnet_cidr
tags = {
Name = "${each.key}-db"
}
}
I'm not sure exactly how your second question relates to the first, but here's how to declare a variable networks so that it would accept a value like the one you showed in the second example.
variable "networks" {
type = map(object({
network_number = number
availability_zone = string
}))
}
If you want to see more about that second example, I'd suggest starting a new separate question on Stack Overflow about it. The convention on Stack Overflow is to have a separate question for each topic, rather than to ask multiple questions at once.

Looping using for or For_each | Terraform 0.12

I need to create multiple subnets in GCP within a network. I am planning to use Terraform 0.12 syntax for the same as follows:
project_name = [
"order-dev",
"ship-dev"
]
variable "project_name" {
type = list(string)
description = "Name of the project"
}
resource "google_compute_subnetwork" "subnetwork" {
name = "${var.project_name}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Is there anyway to use for or for_each expression in this scenario, i am aware of using element and doing this. But want to try a different approach if possible?
variable "project_name" {
type = set(string)
}
resource "google_compute_subnetwork" "subnetwork" {
for_each = var.project_name
name = "${each.key}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Try using the count meta-argument
With your sample, something like this
project_name = [
"order-dev",
"ship-dev"
]
variable "project_name" {
type = list(string)
description = "Name of the project"
}
resource "google_compute_subnetwork" "subnetwork" {
count = length(var.project_name)
name = "${var.project_name[count.index]}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Another option is for_each with key-value pairs, but you only have access to one value and I don't think you can use a list variable like your sample.
resource "google_compute_subnetwork" "subnetwork" {
for_each = {
order = "order-dev"
ship = "ship-dev"
}
name = "${key.value}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Resources:
https://www.terraform.io/docs/configuration/resources.html#count-multiple-resource-instances-by-count
https://blog.gruntwork.io/terraform-tips-tricks-loops-if-statements-and-gotchas-f739bbae55f9
https://www.terraform.io/docs/configuration/resources.html#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings

Resources