Dynamic data source in Terraform 12 - azure

I'm creating alerts (azurerm_monitor_scheduled_query_rules_alert) in Azure using Terraform. You can include a list of action groups (i.e. the groups that you send the alerts to).
Within the TFVars file I will pass in a variable value of a list of the names of the action groups. However, the alert module needs the ID's of the resources, not the names. So I have a data source that would get the info of an action group. The Alert resource can then refer to the data source to acquire the azure resource id.
This works fine if I have just one action group, but the size of the list with action group names can vary. I'm trying to figure out how I can convert all action group names to id for ingestion by the resource.
resource "azurerm_monitor_scheduled_query_rules_alert" "tfTestAlertExample" {
for_each = {for alert in var.scheduled_query_alerts : alert.name => alert}
name = each.value["name"]
location = data.azurerm_resource_group.resource_group.location
resource_group_name = data.azurerm_resource_group.resource_group.name
action {
# --This part here. How do I get make this dynamic?--
action_group = [
data.azurerm_monitor_action_group.action_group.id
]
email_subject = each.value["email_subject"]
custom_webhook_payload = "{}"
}
data_source_id = ................ etc
So in the above example, there will only be one action{} block, but the Action_group list within that needs to be dynamic, with ID's retrieved from a data source. Or maybe there's another way of doing this that I've not considered.
Any help would be greatly appreciated.

If you just want to convert the list of action group names to its Ids, you can do it like this:
# declare the variables
variable "action_group_names" {
default = ["nancyAG1","nancyAG2"]
}
# retrieve the Id of action group
data "azurerm_monitor_action_group" "example" {
count = length(var.action_group_names)
resource_group_name = "existingRG"
name = element(var.action_group_names,count.index)
}
# output the result to the terminal
output "groups_id" {
value = data.azurerm_monitor_action_group.example[*].id
}
Then pass Ids to the resource like this:
resource "azurerm_monitor_scheduled_query_rules_alert" "example" {
name = format("%s-queryrule", var.prefix)
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
action {
action_group = data.azurerm_monitor_action_group.example[*].id
email_subject = "Email Header"
custom_webhook_payload = "{}"
}
Check the action_group Ids.

Related

How to extract generated attribute from a previously defined resource?

This is not related to aws but to a technique of extracting data from a resource collection; therefore the content is most likely not correct relative to aws provider. I just used some words from that provider to prove the idea.
Given that the aws_instance.web resources are created as a collection by use of a for_each loop like described below:
resource "aws_instance" "web" {
for_each = {for k,v in var.input_var: k => v if v.enabled}
name = each.key
ami = each.value.ami
instance_type = each.value.instance_type
}
resource "aws_db_instance" "db" {
for_each = var.another_map
aws_instance_id = aws_instance.web[index(aws_instance.web[*].name, each.value.name)].id
}
At creation of the first collection of resources, to each element is assigned a unique read-only id by terraform/provider. Given that var.input_var.key is always unique, results that also aws_instance.web.name will always be unique for each element created.
In the second resources block, I also use a for_each loop to cycle through all elements of var.another_map. I want to attribute to aws_instance_id, the generated id from the first resources collection. So I need to first find the element from aws_instance.web where the name of it is equal to each.value.name while creating aws_db_instance.db and than extract the id form it.
I tried several ways to achieve this. The closest one is the one exposed above: aws_instance.web[index(aws_instance.web[*].name, each.value.name)].id.
So there are two questions that arise from this:
What is the type of aws_instance.web (a list of objects, a map of objects, an object which contains a map)?
How would a correct syntax would look like for matching the element and extracting the id from it?
Researching on Matt Schuchard's answer and expanding on it, the output of first resource creation is object(map(object)) where the keys of the generated map are the same keys of the input variable var.input_var.
eg: given the input variable
input_var = {
"website1" = {
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
other_var = "some value"
},
"webst2" = {
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
other_var = "some other value"
}
}
resource "aws_instance" "web" block would produce a aws_instance.web variable with contents like:
aws_instance.web = {
"website1" = {
id = 43262 # (read-only) generated by `aws_instance` resource block
name = "website1"
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
# other entries generated by `aws_instance` resource block
},
"webst2" = {
id = 43263 # (read-only) generated by `aws_instance` resource block
name = "webst2"
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
# other entries generated by `aws_instance` resource block
}
}
So when trying to access a specific element from aws_instance.web in the second resource block (aws_db_instance.db), one can access it by its key, rather than trying to match its name attribute.
Therefore, the line aws_instance_id = aws_instance.web[index(aws_instance.web[*].name, each.value.name)].id should be replaced with aws_instance_id = aws_instance.web[each.value.name].id, if and only if the set of names is a subset of the keys of aws_instance.web (I will represent this like each.value.name[*] ⊆ aws_instance.web.keys[*])

how can i get a list of azure vm ids in terraform and then read them back one value at a time?

I need to get a list of all VM ids in an Azure subscription using Terraform then read them back one by one and feed one id at a time to a module to perform some tasks on it, how can i do this?
You can use the feature multiple instances of the data source to get the list of all the VM ids. See the data source azurerm_virtual_machine, it requires the name and the resource group name. So if the VMs in the same group, you just need to create a list variable for all the VM names, then the data block will be like this:
variable "vm_names" {
type = list(string)
default = [
...
]
}
data "azurerm_virtual_machine" "example" {
count = length(var.vm_names)
name = element(var.vm_names, count.index)
resource_group_name = var.resource_group_name
}
output "vm_ids" {
value = data.azurerm_virtual_machine.example.*.id
}
The output is all the VM ids.

Terraform looking local vars that are calculated using mapping when using count to create multiple resources

I’m using mapped variables in order to create local vars based on longer variable names, I'm using them where we would have abbreviations or where the resource wants a sanitized or shortened version of a value used elsewhere.
Eg
variable "env-short" {
description = "create a shortened version of the name of use in resource naming"
type = "map"
default = {
"Proof Of Concept" = "poc"
"User Acceptance Testing" = "uat"
"Production" = "prd"
}
}
variable "org-short" {
description = "create a shortened version of the name of use in resource naming"
type = map(string)
default = {
"My Big Company" = "MBC"
"My Little Company" = "MLC"
}
}
variable "loc-short" {
description = "create a shortened version of the name of use in resource naming"
type = map(string)
default = {
"UK South" = "UKS"
"UK West" = "UKW"
"North Europe" = "NEU"
"West Europe" = "WEU"
}
}
And use corresponding variables for their full length mapping equiverlants.
Now I could use as is within a resource block by something like
Name = “${lower(“${var.loc-short[$var.location]}”)-${lower(“${var.org-short[$var.organisation]}”)-${lower(“${var.env-short[$var.environment]}”)-myresource”
But like all good coders I like to keep things neat and readable by declaring local variables that I can then refer to.
locals {
org-short = "${lower("${var.org-short["${var.organisation}"]}")}"
loc-short = "${lower("${var.loc-short["${var.location}"]}")}"
env-short = "${lower("${var.env-short["${var.environment}"]}")}"
# I also create additional for commonly used configurations of them
name-prefix = "${lower("${var.org-short["${var.organisation}"]}")}-${lower("${var.loc-short["${var.location}"]}")}"
name-prefix-storage = "${lower("${var.org-short["${var.organisation}"]}")}${lower("${var.loc-short["${var.location}"]}")}"
}
This works really great and keeps things neat tidy and readable.
resource "provisioner_example" "test" {
location = var.location
name = “${local.loc-short}-${local.env-short}-my resource”
I would like however to be able to use this format when I start creating multiple resources using the count functionality.
resource "provisioner_example" "test" {
count = length(var.location)
location = var.location[count.index]
name = “${local.loc-short[count.index]}-${local.env-short}-my resource”
Terraform then complains that the index is invalid in the locals lookup, varlocation is tuple with 2 elements,| var.loc-short is map of string with 4 elements. The given key does not identify an element in this collection value: string required.
Now I know I can work around this by getting rid of the locals variables andincluding the variable calculation directly
name =”${lower("${var.loc-short["${var.locations[count.index]}"]}")}-${local.env-short}-my resource"
But to me it then makes the code seem more messy and less structured.
Any ideas on how I can pass the count index value to the map lookup?

proper way to use nested variables in terraform

In my terraform script, I have
resource "azuread_application" "main" {
count = "${length(var.sp_names)}"
name = "${sp_prefix}-${var.sp_names[count.index]}"
available_to_other_tenants = false
}
resource "azuread_service_principal" "main" {
count = "${length(var.sp_names)}"
application_id = "${azuread_application.main.["${sp_prefix}"-"${var.sp_names[count.index]}"].application_id}"
}
when I ran terraform init I get the following error:
An attribute name is required after a dot.
what is the right way to use nested variables and a list object?
In order for a resource to be represented as a map of instances rather than a list of instances, you need to use for_each instead of count:
resource "azuread_application" "main" {
for_each = { for n in var.sp_names : n => "${var.sp_prefix}-${n}" }
name = each.value
available_to_other_tenants = false
}
The for_each expression above is a for expression that transforms your list or set of names into a mapping from the given names to the prefixed names. In the other expressions in that block, each.key would therefore produce the original given name and each.value the prefixed name.
You can then similarly use for_each to declare the intent "create one service principal per application" by using the application resource's map itself as the for_each expression for the service principal resource:
resource "azuread_service_principal" "main" {
for_each = azuread_application.main
application_id = each.value.application_id
}
In this case, the azuread_application.main value is a map from unprefixed names to objects representing each of the declared applications. Therefore each.key in this block is the unprefixed name again, but each.value is the corresponding application object from which we can access the application_id value.
If your var.sp_names had a string "example" in it, then Terraform would interpret the above as a request to create two objects named azuread_application.main["example"] and azuread_service_principal.main["example"], identifying these instances by the var.sp_names values. This is different to count where the instances would have addresses like azuread_application.main[0] and azuread_service_principal.main[0]. By using for_each, we ensure that adding and removing items from var.sp_names will add and remove corresponding instances from those resources, rather than updating existing ones that happen to share the same numeric indices.
I am assuming you are using a version older that 0.12.x. If not the answer from Martin is the best one.
You need to leverage the splatting.
resource "azuread_service_principal" "main" {
count = "${length(var.sp_names)}"
application_id = "${azuread_application.main.*.application_id}"
}

Terraform retrieve CIDR/Prefix from existing VNETs/subnets

In Terraform, I want to create a route table for an existing subnet. To achieve the desired end result, I need to pull the CIDR/Prefix for the VNET. The VNET CIDR value is not known beforehand, the only values I know before launch is the VNET's name and Resource Group.
I would like to take the VNET CIDR/Prefix and insert it as a destination in the route table.
data "azurerm_virtual_network" "vnet" {
name = "${var.vnet_name}"
resource_group_name = "${var.vnet_rg}"
}
module "routetable" {
source = "modules/routetable"
route_table_name = "${var.route_table_name}"
resource_group_name =
"${data.azurerm_resource_group.vnet.name}"
location = "eastus"
route_prefixes = ["0.0.0.0/0", "${EXISTING_VNET_CIDR_HERE}"]
route_nexthop_types = ["VirtualAppliance", "VirtualAppliance"]
route_names = ["route1", "route2"]
}
just use data you are getting from the vnet:
${data.azurerm_virtual_network.vnet.address_spaces}
the only issue - assress_spaces is an array (i think its called list in terraforms terms).

Resources