Terraform Azure provider - How do I add via terraform more than one key and/or secret for my key vault? - azure-keyvault

All the examples I saw provide 1 key and or q secret.
Is there a way to add another one (or more)?

To add multiple keys or secrets for your key vault, you just need to add the resources azurerm_key_vault_key and azurerm_key_vault_secret multiple times.
It's recommended to create such resources in the loop. Terraform offers several different looping constructs, each intended to be used in a slightly different scenario:
count parameter: loop over resources.
for_each expressions: loop over resources and inline blocks within a resource.
for expressions: loop over lists and maps.
For example, create one or more keys and secrets with count parameters.
variable "key_lists" {
type = list(string)
default = ["key1","key2","key3"]
}
variable "secret_maps" {
type = map(string)
default = {
"name1"= "value1"
"aaa" = "111"
"bbb" = "222"
}
}
resource "azurerm_key_vault_key" "generated" {
count = length(var.key_lists)
name = var.key_lists[count.index]
key_vault_id = azurerm_key_vault.example.id
key_type = "RSA"
key_size = 2048
key_opts = [
"decrypt",
"encrypt",
"sign",
"unwrapKey",
"verify",
"wrapKey",
]
}
resource "azurerm_key_vault_secret" "example" {
count = length(var.secret_maps)
name = keys(var.secret_maps)[count.index]
value = values(var.secret_maps)[count.index]
key_vault_id = azurerm_key_vault.example.id
}
You could read this blog for more Terraform loop tips.

Related

How to call a value in map element only when it matches another var

I am using the Terraform provider mrparkers/keycloak to attempt to assign Keycloak groups a list of users.
The snippet below creates realms, groups, and users correctly, but I am stumped on the final line for calling a list of users which should belong to the group being looped through.
Anything to point me in the right direction would be hugely appreciated. :)
vars
variable "realms" {
description = "realms"
type = set(string)
default = ["mrpc"]
}
variable "mrpc-groups" {
type = map(object({
name = string
realm = string
members = set(string)
}))
default = {
"0" = {
realm = "mrpc"
name = "mrpc-admins"
members = ["hellfire", "hellfire2"]
},
"1" = {
realm = "mrpc"
name = "mrpc-mods"
members = ["hellfire2"]
}
}
}
variable "mrpc-users" {
type = map(object({
username = string
email = string
first_name = string
last_name = string
realm = string
}))
default = {
"0" = {
realm = "mrpc"
username = "hellfire"
email = "bla#bla.bla"
first_name = "hell"
last_name = "fire"
}
"1" = {
realm = "mrpc"
username = "hellfire2"
email = "bla2#bla.bla"
first_name = "hell2"
last_name = "fire2"
}
}
}
resources
resource "keycloak_realm" "realm" {
for_each = var.realms
realm = each.value
}
resource "keycloak_group" "group" {
for_each = var.mrpc-groups
realm_id = each.value["realm"]
name = each.value["name"]
depends_on = [keycloak_realm.realm]
}
resource "keycloak_user" "user" {
for_each = var.mrpc-users
realm_id = each.value["realm"]
username = each.value["username"]
email = each.value["email"]
first_name = each.value["first_name"]
last_name = each.value["last_name"]
}
resource "keycloak_group_memberships" "group_members" {
for_each = keycloak_group.group
realm_id = each.value["realm_id"]
group_id = each.value["name"]
members = [ "hellfire2" ]
# i want this to be var.mrpc-groups.*.members (* used incorrectly here i think)
# if
# var.mrpc-groups.*.name == each.value["name"]
#
# so that the correct member list in the vars is used when the matching group is being looped over
# any method to get the final outcome is good :)
}
We can use the distinct and flatten functions in conjunction with a for expression within a list constructor to solve this:
distinct(flatten([for key, attrs in var.mrpc_groups : attrs.members]))
As tested locally, this will return the following for your values exactly as requested in the question indicated by var.mrpc-groups.*.members:
members = [
"hellfire",
"hellfire2",
]
The for expression iterates through the variable mrpc_groups map and returns the list(string) value assigned to the members key within each group's key value pairs. The lambda/closure scope variables are simply key and attrs because the context is unclear to me, so I was unsure what a more descriptive name would be.
The returned structure would be a list where each element would be the list assigned to the members key (i.e. [["hellfire", "hellfire2"], ["hellfire2"]]). We use flatten to flatten the list of lists into a single list comprised of the elements of each nested list.
There would still be duplicates in this flattened list, and therefore we use the distinct function to return a list comprised of only unique elements.
For the additional question about assigning the members associated with the group at the current iteration, we can simply implement the following:
members = flatten([for key, attrs in var.mrpc_groups : attrs.members if attrs.name == each.value["name"]])
This will similarly iterate through the map variable of var.mrpc_groups, and construct a list of the members list filtered to only the group matching the name of the current group iterated through keycloak_group.group. We then flatten again because it is also a nested list similar to the first question and answer.
Note that for this additional question it would be easier for you in general and for this answer if you restructured the variable keys to be the name of the group instead of as a nested key value pair.

Iterating Two Maps Inside A Resource using for_each

I'm Trying to create azure SRV record based on some condition, for this multiple target is needed for a single SRV record. If I iterate inside dynamic each.key and each.value is referring the key value outside the dynamic block. is there a way to iterate this record block for required number of time. Also the count is not supported in record block. Could there be any other ways to achieve this without loop also fine.
NOTE: this is a pseudo code for reference.
resource "azurerm_dns_srv_record" "srv_dns_record" {
for_each = { for key, value in var.clusters : key => value if some condition }
name = name
zone_name = azurerm_dns_zone.cluster_dns_zone[each.key].name
resource_group_name = azurerm_resource_group.main.name
ttl = 60
dynamic "record" {
for_each = var.targets
content {
priority = 1
weight = 5
port = 443
target = each.key
}
}
Thanks!
from the docs
The iterator argument (optional) sets the name of a temporary variable that represents the current element of the complex value. If omitted, the name of the variable defaults to the label of the dynamic block ("setting" in the example above).
so to refer to the value of the iterated field in the dynamic block you should use the label of the block not the each word
resource "azurerm_dns_srv_record" "srv_dns_record" {
for_each = { for key, value in var.clusters : key => value if some condition }
name = name
zone_name = azurerm_dns_zone.cluster_dns_zone[each.key].name
resource_group_name = azurerm_resource_group.main.name
ttl = 60
dynamic "record" {
for_each = var.targets
content {
priority = 1
weight = 5
port = 443
target = record.key
}
}
If you want to iterate over var.targets in side a dynamic block, it should be:
target = record.key

How to use terraform for_each to separate map of objects and get IDs of resources?

in my project I have a two clients and each have a couple of databases. How I can create separate failover group for each client with specific client databases?
This is variables for each client:
variable "client" {
type = map (any)
default = {
# Client1 Param
"client1" =
"databases" = {
"db1" = "true",
"db2" = "true",
"db3" = "true",
},
},
# Client2 param
"client2" = {
"databases" = {
"db1" = "true",
"db2" = "false",
},
}
Terraform code to create DBs:
locals {
client_databases = flatten([
for client_key, client in var.client : [
for database, enabled in client.databases : {
client_name = client_key
database_name = database
database_enabled = enabled
}
]
])
}
# ## Create Client Specific Database(s)
resource "azurerm_mssql_database" "client_primary" {
for_each = { for databases in local.client_databases :
"${databases.client_name}-${databases.database_name}" => databases
if databases.database_enabled == "true"
}
name = "${each.key}"
server_id = azurerm_mssql_server.db["primary"].id
elastic_pool_id = azurerm_mssql_elasticpool.db["primary"].id
}
Code to create failover group:
resource "azurerm_sql_failover_group" "db" {
for_each = var.client
name = "${lower(azurerm_resource_group.db.name)}-${each.key}"
resource_group_name = azurerm_resource_group.db.name
server_name = azurerm_mssql_server.db["primary"].name
databases = [for database in azurerm_mssql_database.client_primary : database.id]
partner_servers {
id = azurerm_mssql_server.db["secondary"].id
}
read_write_endpoint_failover_policy {
mode = var.db_failover_policy_mode
grace_minutes = var.db_failover_policy_grace
}
In this case I create two failover group each of them have all databases:
client1-db1
client1-db2
client1-db3
client2-db1
client2-db2
How I can create two failover group with client own databases
Failover group 1:
client1-db1
client1-db2
client1-db3
Failover group 2:
client2-db1
client2-db2
Of course all other needed resources are created!
It sounds like your goal is to declare a separate failover group per client, and then have each failover group include all of the databases for that client.
Your var.client seems to already be in a suitable shape for use in for_each for resources that should have one instance per client, so there's no need to transform it further for use in that resource, as you've already seen:
resource "azurerm_sql_failover_group" "db" {
for_each = var.client
# ...
The remaining problem is to generate a suitable value for databases which uses the provider-chosen id attribute for each `azurerm_mssql_database. That requires reproducing the same compound instance keys used to identify the individual databases:
databases = [
for db_name, enabled in each.value.databases :
azurerm_mssql_database.client_primary["${each.key}:${db_name}"].id
if enabled == "true"
]
Your use of string values to represent boolean values is a little confusing here. I assume you did that because type = map(any) was not able to automatically find a suitable single type to use for all elements when you mixed types.
You can avoid that problem by telling Terraform the precise type constraint you expect, which will then allow Terraform to understand how to interpret the given data structure:
variable "client" {
type = map(
object({
databases = map(bool)
})
)
}
The above type constraint removes all of the ambiguity about what any is supposed to mean in this context, and thus allows using the more appropriate types for this data structure.
If you do this then you'll need to remove all of your == "true" comparisons elsewhere in the module, because true is not equal to "true" in Terraform. The following is an alternative version of the final example I shared above that's written to work with properly-typed boolean values:
databases = [
for db_name, enabled in each.value.databases :
azurerm_mssql_database.client_primary["${each.key}:${db_name}"].id
if enabled
]
Using exactly-specified type constraints will typically make a module easier to use and to maintain in future, by making it clear exactly what it requires and ensuring that the values appearing inside the module will always be of the prescribed types.

Alert Creation for All VMs under same subscription in Azure using Terraform

**I am trying to deploy below terraform alert for all VMs under my subscription, and subscription id I've kept it in variables file. But its throwing below error.
Also how can I define type as MultipleResourceMultipleMetricCriteria for this below template as in ARM templates we can define this type as odata.type but when I try odata_type in terraform its not accepting it.
also is there any way I can grab subscription id at runtime using query instead of passing subscription id in variables file. Is there any quickstart template site for the terraform for reference.
Error: **Can not parse "scopes.0" as a resource id: Cannot parse Azure ID: parse "{subscription_id is getting printed here}":** invalid URI for request**
on metric.tf line 1, in resource "azurerm_monitor_metric_alert" "example":
1: resource "azurerm_monitor_metric_alert" "example" {
terraform.tf file
resource "azurerm_monitor_metric_alert" "example" {
name = "example-metricalert"
resource_group_name = "MyTemp"
scopes = ["${var.subscription_id}"]
description = "Action will be triggered when Transactions count is greater than 50."
target_resource_type = "Microsoft.Compute/virtualMachines"
criteria {
metric_namespace = "Microsoft.Compute/virtualMachines"
metric_name = "Percentage CPU"
aggregation = "Total"
operator = "GreaterThan"
threshold = 50
}
action {
action_group_id = "/subscriptions/xxxxxxxx/resourceGroups/xxxxx/providers/Microsoft.Insights/actionGroups/xxxxx"
}
}
You can use Data Source: azurerm_subscription to access information about an existing Subscription.
terraform.tf file will look like this:
data "azurerm_subscription" "current" {
subscription_id = var.subscription_id
}
resource "azurerm_monitor_metric_alert" "example" {
name = "example-metricalert"
resource_group_name = "MyTemp"
scopes = [data.azurerm_subscription.current.id]
description = "Action will be triggered when Transactions count is greater than 50."
target_resource_type = "Microsoft.Compute/virtualMachines"
criteria {
metric_namespace = "Microsoft.Compute/virtualMachines"
metric_name = "Percentage CPU"
aggregation = "Total"
operator = "GreaterThan"
threshold = 50
}
action {
action_group_id = "/subscriptions/xxxxxxxx/resourceGroups/xxxxx/providers/Microsoft.Insights/actionGroups/xxxxx"
}
}

Using Terraform how can I create a user for each database as well as for each namespace?

My Terraform script currently creates 2 databases for a set of namespaces (1 or more). I now need to create a user for each respective database, and I am having trouble figuring out the correct method to do this.
This is what I have currently...
variable "namespaces" {
type = set(string)
}
variable "databases" {
type = set("server", "analyzer")
}
resource "postgresql_database" "server_databases" {
for_each = toset(var.namespaces)
name = "server_${each.key}"
}
resource "postgresql_database" "analyzer_databases" {
for_each = toset(var.namespaces)
name = "analyzer_${each.key}"
}
resource "random_password" "postgres_password" {
length = 12
}
resource "postgresql_role" "read_only_user" {
name = "readonlyuser"
login = true
password = random_password.postgres_password.result
skip_reassign_owned = true
}
resource "postgresql_grant" "readonly_tables" {
depends_on = [postgresql_database.server_databases, postgresql_database.analyzer_databases]
for_each = toset(var.namespaces)
database = "server_${each.key}"
object_type = "table"
privileges = ["SELECT"]
role = "readonlyuser"
schema = "public"
}
The problem here is database = "server_${each.key}" will only create a user for my server database in each namespace. I am pretty sure I need a nested for_each but I am not sure how to achieve this.
I think it should even be possible to loop over the postgresql_database resources instead of having 2 separate resource's defined

Resources