Terraform combine lookup and splat syntax - terraform

I'm trying to provision multiple Azure virtual machines with Terraform and then get the MSI identity ID for entitlements. I started with this MSI example (https://www.terraform.io/docs/providers/azurerm/authenticating_via_msi.html) that works for me and am trying to add count, i.e.
resource "azurerm_virtual_machine" "virtual_machine" {
count = "5"
name = "test"
....
identity = {
type = "SystemAssigned"
}
.....
}
resource "azurerm_virtual_machine_extension" "virtual_machine_extension" {
count = "5"
name = "test"
location = "${var.location}"
resource_group_name = "test"
virtual_machine_name = "${element(azurerm_virtual_machine.virtual_machine.*.name, count.index)}"
publisher = "Microsoft.ManagedIdentity"
type = "ManagedIdentityExtensionForWindows"
type_handler_version = "1.0"
settings = <<SETTINGS
{
"port": 50342
}
SETTINGS
}
output "vm_principals" {
# original had --- "${lookup(azurerm_virtual_machine.virtual_machine.identity[0], "principal_id"}"
value = ["${azurerm_virtual_machine.virtual_machine.*.identity[0]}"]
}
Problem is that I get an output array as following:
vm_principals = [
{
principal_id = xxxxxxxxxxxxx,
type = SystemAssigned
},
{
principal_id = yyyyyyyyyyyyy,
type = SystemAssigned
}
]
What I'm trying to get is
vm_principals = [
xxxxxxxxxxxxxxxxx,
yyyyyyyyyyyyyyyyy
]
I tried the obvious variations, but I suspect this is a Terraform limitation.
value = ["${lookup(azurerm_virtual_machine.virtual_machine.*.identity[0], "principal_id)}"]
Any thoughts?

Unfortunately, you're right. I believe you're hitting this issue. However, this will probably be solved in v0.12 of Terraform as it will introduce a revamped language (HCL). See this Hashicorp blog article for more details.

Here's how I dealt with this issue
resource "azurerm_virtual_machine" "kubenode" {
count = "3"
...
}
For a azurerm_virtual_machine resource named kubenode, you can do this:
${azurerm_virtual_machine.kubenode.*.identity.0.principal_id}
Which will return a list of principal ids. you can then do:
${azurerm_virtual_machine.kubenode.*.identity.0.principal_id[count.index]}
For example in a role-assignment scenario:
resource "azurerm_role_assignment" "kubenode-subscription-reader-role" {
count = "${azurerm_virtual_machine.kubenode.count}"
scope = "${data.azurerm_subscription.primary.id}"
role_definition_name = "Reader"
principal_id = "${azurerm_virtual_machine.kubenode.*.identity.0.principal_id[count.index]}"
}

Related

Terraform deployment for 'Work pace based Application Insight' on Azure

I have been trying to figure out a way to prepare a terraform template for my app service/az function where I can connect it to application Insight while creating them through Terraform. Well the it worked, BUT the application Insight shows
Migrate this resource to Workspace-based Application Insights to gain support for all of the capabilities of Log Analytics, including Customer-Managed Keys and Commitment Tiers. Click here to learn more and migrate in a few clicks.
How do I acheive it from terraform? As from the documentation page of terraform there is no mention of such setup. Appreciate you help on this.
Here is the terraform code for az-function
resource "azurerm_linux_function_app" "t_funcapp" {
name = "t-function-app"
location = local.resource_location
resource_group_name = local.resource_group_name
service_plan_id = azurerm_service_plan.t_app_service_plan.id
storage_account_name = azurerm_storage_account.t_funcstorage.name
storage_account_access_key = azurerm_storage_account.t_funcstorage.primary_access_key
site_config {
application_stack {
java_version = "11"
}
remote_debugging_enabled = false
ftps_state = "AllAllowed"
}
app_settings = {
APPINSIGHTS_INSTRUMENTATIONKEY = "${azurerm_application_insights.t_appinsights.instrumentation_key}"
}
depends_on = [
azurerm_resource_group.t_rg,
azurerm_service_plan.t_app_service_plan,
azurerm_storage_account.t_funcstorage,
azurerm_application_insights.t_appinsights
]
}
Here is the terraform code for app insight
resource "azurerm_application_insights" "t_appinsights" {
name = "t-appinsights"
location = local.resource_location
resource_group_name = local.resource_group_name
application_type = "web"
depends_on = [
azurerm_log_analytics_workspace.t_workspace
]
}
output "instrumentation_key" {
value = azurerm_application_insights.t_appinsights.instrumentation_key
}
output "app_id" {
value = azurerm_application_insights.t_appinsights.app_id
}
You must create a Log Analytics Workspace and add it to your Application Insights.
For example
resource "azurerm_log_analytics_workspace" "example" {
name = "workspace-test"
location = local.resource_location
resource_group_name = local.resource_group_name
sku = "PerGB2018"
retention_in_days = 30
}
resource "azurerm_application_insights" "t_appinsights" {
name = "t-appinsights"
location = local.resource_location
resource_group_name = local.resource_group_name
workspace_id = azurerm_log_analytics_workspace.example.id
application_type = "web"
}
output "instrumentation_key" {
value = azurerm_application_insights.t_appinsights.instrumentation_key
}
output "app_id" {
value = azurerm_application_insights.t_appinsights.app_id
}
Hope this helps!

Terraform Error 'the number of path segments is not divisible by 2' creating Azure Configuration Keys

I'm trying to create an Azure App Configuration service and keys through Terraform, but when I run my Terraform through my pipeline I get an error running terraform plan. This is my tf script for creating the service and keys:
resource "azurerm_app_configuration" "appconf" {
name = var.name
location = var.location
resource_group_name = var.resource_group_name
tags = var.tags
sku = "standard"
}
resource "azurerm_app_configuration_key" "MainAPI" {
configuration_store_id = azurerm_app_configuration.appconf.id
key = "MainAPI"
value = var.picking_api_url
type = "kv"
label = var.environment_name
}
# other keys omitted
This is the error I see:
Error: while parsing resource ID: while parsing resource ID: the number of path segments is not divisible by 2 in "subscriptions/[SubscriptionId]/resourceGroups/[rgName]/providers/Microsoft.AppConfiguration/configurationStores/[AppConfigServiceName]/AppConfigurationKey/MainAPI/Label"
I get this error regardless of whether I explicitly include a label argument for the key in my TF script. I've bumped up my version of the Terraform ARM provider to 2.90 in case it was a bug in the provider, but still get the error.
resource "azurerm_app_configuration_key" depends on azurerm_role_assignment. You need to define a resource for that as well for assigning role to app_configuration.
The following code from the Hashicorp Terraform documentation for azurerm_app_configuration_key demonstrates how to do this:
resource "azurerm_role_assignment" "appconf_dataowner" {
scope = azurerm_app_configuration.appconf.id
role_definition_name = "App Configuration Data Owner"
principal_id = data.azurerm_client_config.current.object_id
}
resource "azurerm_app_configuration_key" "test" {
configuration_store_id = azurerm_app_configuration.appconf.id
key = "appConfKey1"
label = "somelabel"
value = "a test"
depends_on = [
azurerm_role_assignment.appconf_dataowner
]
}

How do I make terraform skip that block while creating multiple resources in loop from a CSV file?

Hi I am trying to create a Terraform script which will take inputs from the user in the form of a CSV file and create multiple Azure resources.
For example if the user wants to create: ResourceGroup>Vnet>Subnet in bulk, he will provide input in CSV format as below:
resourcegroup,RG_location,RG_tag,domainname,DNS_Zone_tag,virtualnetwork,VNET_location,addressspace
csvrg1,eastus2,Terraform RG,test.sd,Terraform RG,csvvnet1,eastus2,10.0.0.0/16,Terraform VNET,subnet1,10.0.0.0/24
csvrg2,westus,Terraform RG2,test2.sd,Terraform RG2,csvvnet2,westus,172.0.0.0/8,Terraform VNET2,subnet1,171.0.0.0/24
I have written the following working main.tf file:
# Configure the Microsoft Azure Provider
provider "azurerm" {
version = "=1.43.0"
subscription_id = var.subscription
tenant_id = var.tenant
client_id = var.client
client_secret = var.secret
}
#Decoding the csv file
locals {
vmcsv = csvdecode(file("${path.module}/computelanding.csv"))
}
# Create a resource group if it doesn’t exist
resource "azurerm_resource_group" "myterraformgroup" {
count = length(local.vmcsv)
name = local.vmcsv[count.index].resourcegroup
location = local.vmcsv[count.index].RG_location
tags = {
environment = local.vmcsv[count.index].RG_tag
}
}
# Create a DNS Zone
resource "azurerm_dns_zone" "dnsp-private" {
count = 1
name = local.vmcsv[count.index].domainname
resource_group_name = local.vmcsv[count.index].resourcegroup
depends_on = [azurerm_resource_group.myterraformgroup]
tags = {
environment = local.vmcsv[count.index].DNS_Zone_tag
}
}
To be continued....
The issue I am facing here what is in the second resource group, the user don't want a resource type, suppose the user want to skip the DNS zone in the resource group csvrg2. How do I make terraform skip that block ?
Edit: What I am trying to achieve is "based on some condition in the CSV file, not to create azurerm_dns_zone resource for the resource group csvrg2"
I have provided an example of the CSV file, how it may look like below:
resourcegroup,RG_location,RG_tag,DNS_required,domainname,DNS_Zone_tag,virtualnetwork,VNET_location,addressspace
csvrg1,eastus2,Terraform RG,1,test.sd,Terraform RG,csvvnet1,eastus2,10.0.0.0/16,Terraform VNET,subnet1,10.0.0.0/24
csvrg2,westus,Terraform RG2,0,test2.sd,Terraform RG2,csvvnet2,westus,172.0.0.0/8,Terraform VNET2,subnet1,171.0.0.0/24
you had already the right thought in your mind using the depends_on function. Although, you're using a count inside, which causes from my understanding, that once the first resource[0] is created, Terraform sees the dependency as solved and goes ahead as well.
I found this post with a workaround which you might be able to try:
https://github.com/hashicorp/terraform/issues/15285#issuecomment-447971852
That basically tells us to create a null_resource like in that example:
variable "instance_count" {
default = 0
}
resource "null_resource" "a" {
count = var.instance_count
}
resource "null_resource" "b" {
depends_on = [null_resource.a]
}
In your example, it might look like this:
# Create a resource group if it doesn’t exist
resource "azurerm_resource_group" "myterraformgroup" {
count = length(local.vmcsv)
name = local.vmcsv[count.index].resourcegroup
location = local.vmcsv[count.index].RG_location
tags = {
environment = local.vmcsv[count.index].RG_tag
}
}
# Create a DNS Zone
resource "azurerm_dns_zone" "dnsp-private" {
count = 1
name = local.vmcsv[count.index].domainname
resource_group_name = local.vmcsv[count.index].resourcegroup
depends_on = null_resource.example
tags = {
environment = local.vmcsv[count.index].DNS_Zone_tag
}
}
resource "null_resource" "example" {
...
depends_on = [azurerm_resource_group.myterraformgroup[length(local.vmcsv)]]
}
or depending on your Terraform version (0.12+ which you're using guessing your syntax)
# Create a resource group if it doesn’t exist
resource "azurerm_resource_group" "myterraformgroup" {
count = length(local.vmcsv)
name = local.vmcsv[count.index].resourcegroup
location = local.vmcsv[count.index].RG_location
tags = {
environment = local.vmcsv[count.index].RG_tag
}
}
# Create a DNS Zone
resource "azurerm_dns_zone" "dnsp-private" {
count = 1
name = local.vmcsv[count.index].domainname
resource_group_name = local.vmcsv[count.index].resourcegroup
depends_on = [azurerm_resource_group.myterraformgroup[length(local.vmcsv)]]
tags = {
environment = local.vmcsv[count.index].DNS_Zone_tag
}
}
I hope that helps.
Greetings

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!

Variable in terraform resource referance

I'm trying create a resource in terraform that will create a number of subnets based on a list variable.
I'm having trouble with references to existing resources. For example in the following code network_security_group_id is hardcoded to azurerm_network_security_group.k8s.id:
variable "resources_large" {
description = "List of Large Networks"
default = [
"k8s",
"storm"
]
}
resource "azurerm_subnet" "large" {
name = "ue-${var.environment}-${var.resources_large[count.index]}-subnet-${replace("${cidrsubnet("${local.subnet_ranges["large"]}", "${var.newbit_size["large"] }", count.index )}", "/[./]/", "-" ) }"
resource_group_name = "ue-${var.environment}-${var.resources_large[count.index]}-rg"
virtual_network_name = "${azurerm_virtual_network.dev.name}"
address_prefix = "${cidrsubnet("${local.subnet_ranges["large"]}", "${var.newbit_size["large"] }", count.index )}"
network_security_group_id = "${azurerm_network_security_group.k8s.id}"
count = "${length(var.resources_large)}"
depends_on = ["azurerm_virtual_network.dev"]
}
This needs to reference existing security groups based on the name in the resources_large list.
What I'd like to have is something which looks likes this:
network_security_group_id = "${azurerm_network_security_group.${var.resources_large[count.index]}.id}"
Which doesn't work, I'm guessing due to the lack of variable interpolation support.
Is there any way to reference other resources based on variable?
Maybe something like this
locals {
sgs = {
k8s = "${azurerm_network_security_group.k8s.id}"
storm = "${azurerm_network_security_group.storm.id}"
}
}
...
network_security_group_id = "${lookup( locals.sgs, var.resources_large[count.index])}"
may work.
If you create the SG using the same counter, it can be just
network_security_group_id = "${element(azurerm_network_security_group.*.id, count.index)}"
HTH

Resources