I am trying to create multiple users using terraform. For now I am declaring them as locals but later on i will be using json file to create multiple users in my azuread environment.
Here is how i declare the locals:
locals {
users = [
[
"user1",
"Example User1",
"Password#1234#"
],
[
"user2",
"Example User2",
"Password#09876#"
]
]
}
But when I use the below code I am getting an error:
resource "azuread_user" "test" {
for_each = local.users
user_principal_name = "${each.value[0]}#tenantname.OnMicrosoft.com"
display_name = each.value[1]
mail_nickname = each.value[0]
password = each.value[2]
}
Error:
╷
│ Error: Invalid for_each argument
│
│ on main.tf line 18, in resource "azuread_user" "test":
│ 18: for_each = local.users
│ ├────────────────
│ │ local.users is tuple with 2 elements
│
│ The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type tuple.
╵
Will really appreciate any help on how to resolve this?
You have to covert it to a map:
resource "azuread_user" "test" {
for_each = {for idx, user in local.users: idx => user}
user_principal_name = "${each.value[0]}#M365B109047.OnMicrosoft.com"
display_name = each.value[1]
mail_nickname = each.value[0]
password = each.value[2]
}
As you have been told in the other answer, you need it to be a map, but before doing a for as in the other answer, it would use terraform's own tomap () function.
resource "azuread_user" "test" {
for_each = tomap({users=local.users})
user_principal_name = "${each.value[0]}#M365B109047.OnMicrosoft.com"
display_name = each.value[1]
mail_nickname = each.value[0]
password = each.value[2]
Related
When deploying resources, the template terraform gave uses for_each. This poses as a problem as it will give
Error: Invalid for_each argument
│
│ on /home/baiyuc/workspaces/billow/src/GoAmzn-LambdaStackTools/configurations/terraform/sync.tf line 410, in resource "aws_route53_record" "subdomain_cert_validation":
│ 410: for_each = {
│ 411: for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
│ 412: name = dvo.resource_record_name
│ 413: record = dvo.resource_record_value
│ 414: type = dvo.resource_record_type
│ 415: }
│ 416: }
│ ├────────────────
│ │ aws_acm_certificate.cert.domain_validation_options is a set of object, known only after apply
The "for_each" map includes keys derived from resource attributes that cannot be determined until apply, and so Terraform cannot determine the full set of keys that will identify the instances of this resource.
When working with unknown values in for_each, it's better to define the map keys statically in your configuration and place apply-time results only in the map values.
Alternatively, you could use the -target planning option to first apply only the resources that the for_each value depends on, and then apply a second time to fully converge.
error when using terraform import.
I found a potential solution that suggests using count in this type of scenarios, but it didn't go into details. Anyone can give any details on how to do so?
The code of interest is for resource "aws_route53_record" "subdomain_cert_validation":
data "aws_route53_zone" "root_domain" {
name = "${var.root_domain}."
private_zone = false
}
resource "aws_acm_certificate" "cert" {
depends_on = [aws_route53_record.sub-zone]
domain_name = var.domain
validation_method = "DNS"
}
resource "aws_route53_zone" "core-domain" {
name = var.domain
count = var.root_domain == var.domain ? 0 : 1 # If the two are the same, do not create this resource.
tags = {
Environment = var.stack_tag
}
}
resource "aws_route53_record" "sub-zone" {
depends_on = [aws_route53_zone.core-domain]
zone_id = data.aws_route53_zone.root_domain.zone_id
name = var.domain
type = "NS"
ttl = "30"
count = var.root_domain == var.domain ? 0 : 1 # If the two are the same, do not create this resource.
records = var.root_domain == var.domain ? [] : [
aws_route53_zone.core-domain[0].name_servers[0],
aws_route53_zone.core-domain[0].name_servers[1],
aws_route53_zone.core-domain[0].name_servers[2],
aws_route53_zone.core-domain[0].name_servers[3],
]
}
resource "aws_route53_record" "subdomain_cert_validation" {
depends_on = [aws_acm_certificate.cert]
for_each = {
for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
type = each.value.type
ttl = 600
zone_id = var.root_domain == var.domain ? data.aws_route53_zone.root_domain.zone_id : aws_route53_zone.core-domain[0].zone_id
}
resource "aws_acm_certificate_validation" "core" {
certificate_arn = aws_acm_certificate.cert.arn
validation_record_fqdns = [for record in aws_route53_record.subdomain_cert_validation : record.fqdn]
}
this issue is pretty common when using iteration and is caused by trying to use keys that will be dynamically generated at apply time. You need to make sure that your keys are statically defined so they're known at apply time, the value of the map can then be dynamic. Some good reference, for this issue and solutions are here
for_each example and solution
for_each example and solution
I can't figure out how to dynamically connect disks and networks to virtual machines if they are counted
I expect i will pass variables like this:
module "vps-test" {
source = "../module"
count = 3
server_name = "vpstest"
server_image = "debian-11"
server_type = "cx21"
server_datacenter = "fsn1-dc14"
labels = { groups = "test_offline.test_vps" }
server_network = {
backend_network = {
subnet_id = (data.terraform_remote_state.htz_network.outputs.main-subnet-id)
ip = "" #DHCP
}
custom_network = {
subnet_id = "1867414"
ip = ""
}
}
volumes = {
firts_volume = {
name = "volume1"
size = "20"
}
second_volume = {
name = "volume1"
size = "20"
}
}
hetzner_cloud_token = var.hetzner_cloud_offline_main_api_token
cloud_init_file_path = "../module/scripts/user.yaml"
}
and the module will create 3 identical VMs, where each will have 2 disks and 2 networks
It's Hetznere cloud provider, here is my simple code:
resource "hcloud_server" "vps" {
count = var.server_count
name = var.server_count > 1 ? "${var.server_name}-${count.index}" : var.server_name
image = var.server_image
server_type = var.server_type
datacenter = var.server_datacenter
user_data = data.template_file.ansible_user_data.rendered
labels = var.labels
}
resource "hcloud_volume" "volume" {
for_each = var.volumes
name = tostring(each.value["name"])
size = tonumber(each.value["size"])
server_id = hcloud_server.vps.id
automount = true
format = var.volume_filesystem
}
resource "hcloud_server_network" "network" {
for_each = var.server_network
server_id = hcloud_server.vps.id
subnet_id = each.value["subnet_id"]
ip = tostring(each.value["ip"])
}
Errors:
│ Error: Missing resource instance key
│
│ on ../module/resource.tf line 15, in resource "hcloud_volume" "volume":
│ 15: server_id = hcloud_server.vps.id
│
│ Because hcloud_server.vps has "count" set, its attributes must be accessed on specific instances.
│
│ For example, to correlate with indices of a referring resource, use:
│ hcloud_server.vps[count.index]
╵
╷
│ Error: Missing resource instance key
│
│ on ../module/resource.tf line 22, in resource "hcloud_server_network" "network":
│ 22: server_id = hcloud_server.vps.id
│
│ Because hcloud_server.vps has "count" set, its attributes must be accessed on specific instances.
│
│ For example, to correlate with indices of a referring resource, use:
│ hcloud_server.vps[count.index]
if using recommends from error log
│ Error: Reference to "count" in non-counted context
│
│ on ../module/resource.tf line 15, in resource "hcloud_volume" "volume":
│ 15: server_id = hcloud_server.vps[count.index].id
│
│ The "count" object can only be used in "module", "resource", and "data" blocks, and only when the "count" argument is set.
╵
╷
│ Error: Reference to "count" in non-counted context
│
│ on ../module/resource.tf line 22, in resource "hcloud_server_network" "network":
│ 22: server_id = hcloud_server.vps[count.index].id
│
│ The "count" object can only be used in "module", "resource", and "data" blocks, and only when the "count" argument is set.
but if server_id = hcloud_server.vps[0].id (or any specific index) - working
where is the correct way
Since you are using both count and for_each you need to iterate over both of them. Basically it means you need a double for loop. One way to do it in TF is with the help of setproduct:
resource "hcloud_volume" "volume" {
for_each = {for idx, val in setproduct(range(var.server_count), keys(var.volumes)): idx => val}
name = tostring(var.volume[each.value[1]].name)
size = tonumber(var.volume[each.value[1]].size)
server_id = hcloud_server.vps[each.value[0]].id
automount = true
format = var.volume_filesystem
}
resource "hcloud_server_network" "network" {
for_each = {for idx, val in setproduct(range(var.server_count), keys(var.server_network)): idx => val}
server_id = hcloud_server.vps[each.value[0]].id
subnet_id = var.server_network[each.value[1]].subnet_id
ip = tostring(var.server_network[each.value[1]].ip)
}
I have created a terraform template that creates 3 file shares on a storage account using a for_each loop that is working perfectly.
I am trying to assign RBAC role assignments, scoped to each file share using a for_each loop, however I keep getting the following error and I am not sure how to achieve this?
main.tf
###########################
# RESOURCE GROUP CREATION #
###########################
resource "azurerm_resource_group" "rg" {
name = var.rg.name
location = var.rg.location
# tag is a test to see if I can get them to use a variable map
tags = "${var.tags}"
}
############################
# STORAGE ACCOUNT CREATION #
############################
resource "azurerm_storage_account" "storage_account" {
name = var.storage_account.name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = var.storage_account.account_tier
account_replication_type = var.storage_account.account_replication_type
allow_nested_items_to_be_public = false
azure_files_authentication {
directory_type = var.storage_account.directory_type
active_directory {
storage_sid = var.storage_account.storage_sid
domain_name = var.storage_account.domain_name
domain_sid = var.storage_account.domain_sid
domain_guid = var.storage_account.domain_guid
forest_name = var.storage_account.forest_name
netbios_domain_name = var.storage_account.netbios_domain_name
}
}
}
########################################
# STORAGE ACCOUNT FILE SHARES CREATION #
########################################
resource "azurerm_storage_share" "file_shares" {
for_each = var.file_shares
name = each.value.name
storage_account_name = azurerm_storage_account.storage_account.name
quota = each.value.quota
}
########################
# RBAC ROLE ASSIGNMENT #
########################
resource "azurerm_role_assignment" "rbac" {
for_each = var.rbac
scope = azurerm_storage_share.file_shares.*.id
role_definition_name = each.value.role_definition_name
principal_id = each.value.principal_id
}
variables.tf
#######################################
# STORAGE ACCOUNT FILE SHARE SETTINGS #
#######################################
variable "file_shares" {
description = "storage account file share settings"
default = {
profiles = {
name = "profiles"
quota = "5120"
}
o365 = {
name = "o365"
quota = "5120"
}
msix = {
name = "msix"
quota = "5120"
}
}
}
#################################
# RBAC ROLE ASSIGNMENT SETTINGS #
#################################
variable "rbac" {
description = "rbac assignment to storage account, principal id is the object id of the security group listed in Azure AD"
default = {
back_office = {
role_definition_name = "Storage File Data SMB Share Contributor"
principal_id = "e93a67c7-4bfc-4bbd-a720-b26d9291fa28"
}
front_office = {
role_definition_name = "Storage File Data SMB Share Contributor"
principal_id = "0280b0c9-295a-4d75-b8d0-a092cf52dabc"
}
dev_dev = {
role_definition_name = "Storage File Data SMB Share Contributor"
principal_id = "512be349-5444-45b0-80f5-8e59046a0175"
}
dev_prod = {
role_definition_name = "Storage File Data SMB Share Contributor"
principal_id = "0a676556-cf96-4318-b229-503808da7e1c"
}
admins = {
role_definition_name = "Storage File Data SMB Share Elevated Contributor"
principal_id = "b0bde374-eb5d-4967-9a4f-cdd41fd7bb23"
}
}
}
error
╵
╷
│ Error: Unsupported attribute
│
│ on storage_account/main.tf line 51, in resource "azurerm_role_assignment" "rbac":
│ 51: scope = azurerm_storage_share.file_shares.*.id
│
│ This object does not have an attribute named "id".
╵
╷
│ Error: Unsupported attribute
│
│ on storage_account/main.tf line 51, in resource "azurerm_role_assignment" "rbac":
│ 51: scope = azurerm_storage_share.file_shares.*.id
│
│ This object does not have an attribute named "id".
╵
╷
│ Error: Unsupported attribute
│
│ on storage_account/main.tf line 51, in resource "azurerm_role_assignment" "rbac":
│ 51: scope = azurerm_storage_share.file_shares.*.id
│
│ This object does not have an attribute named "id".
╵
╷
│ Error: Unsupported attribute
│
│ on storage_account/main.tf line 51, in resource "azurerm_role_assignment" "rbac":
│ 51: scope = azurerm_storage_share.file_shares.*.id
│
│ This object does not have an attribute named "id".
╵
╷
│ Error: Unsupported attribute
│
│ on storage_account/main.tf line 51, in resource "azurerm_role_assignment" "rbac":
│ 51: scope = azurerm_storage_share.file_shares.*.id
│
│ This object does not have an attribute named "id".
╵
##[warning]Can't find loc string for key: TerraformPlanFailed
##[error]Error: TerraformPlanFailed 1
You need to use the flatten function. Add the following code to your locals :
locals {
rbac_assignment = flatten([
for rbac_key, rbac in var.rbac : [
for file_key, file in azurerm_storage_share.file_shares : {
rbac_key = rbac_key
file_key = file_key
scope = azurerm_storage_share.file_shares[file_key].resource_manager_id
role_definition_name = rbac.role_definition_name
principal_id = rbac.principal_id
}
]
])
}
}
Then in your azurerm_role_assignment resource, use the following for_each:
resource "azurerm_role_assignment" "rbac" {
for_each = { for rbac_assignment in local.rbac_assignment : "${rbac_assignment.rbac_key}.${rbac_assignment.file_key}" => rbac_assignment }
scope = each.value.scope
role_definition_name = each.value.role_definition_name
principal_id = each.value.principal_id
}
I also changed the attribute reference of the file share, instead of using the id, it must use the resource_manager_id.
I want to assign my Terraform Service Principal "KV Secrets User", "KV Contributor" RBAC roles
My code is given below
Storing SQL PWD in the vault
resource "azurerm_key_vault_secret" "app-pwd" {
name = "sql-pass"
value = azurerm_mssql_server.primary.administrator_login_password
key_vault_id = data.azurerm_key_vault.my-kv.id
}
resource "azurerm_role_assignment" "kv-iam" {
for_each = data.azurerm_role_definition.builtin[each.key]
scope = var.subscription_id
role_definition_name = local.role_name[each.value]
principal_id = data.azurerm_client_config.current.id
}
provider "azurerm" {
tenant_id = var.tenant_id
client_id = var.client_id
client_secret = var.client_secret
subscription_id = var.subscription_id
features {}
}
data "azurerm_key_vault" "my-kv" {
name = "testhalvault"
resource_group_name = "Dev-Rg"
}
data "azurerm_client_config" "current" {}
data "azurerm_role_definition" "builtin" {
for_each = toset(local.role_name)
name = format("%s", each.key)
}
locals {
role_name = [
"Key Vault Secrets User",
"Key Vault Administrator"
]
}
I get the below error while doing TF Plan
│
Error: Reference to "each" in context without for_each
│
│ on resources.tf line 54, in resource "azurerm_role_assignment" "kv-iam":
│ 54: for_each = data.azurerm_role_definition.builtin[each.key]
│
│ The "each" object can be used only in "module" or "resource" blocks, and
│ only when the "for_each" argument is set.
╵
Operation failed: failed running terraform plan (exit 1)
How do i assign multiple RBAC Built-In Roles to my TF SP
Normally you would do the following:
for_each = data.azurerm_role_definition.builtin
Curently I'm trying to build dev and production environment without duplicating resource blocks. I have found that I can crate map of objects and use for loop for this.
For this I have created this piece of code that was
variable "sqlserver" {
type = map(object({
name = string
username = string
password = string
}))
}
sqlserver = {
"dev" = {
name = "devsonovasqlserver"
username = "dev_username"
password = "biaJB8wQJb4n!RwG"
}
"prd" = {
name = "testexamplesqlsonova"
username = "prd_username"
password = "biaJB8wQJb4asdan!RwG"
}
}
resource "azurerm_sql_server" "sql_server" {
for_each = var.sqlserver
name = each.value["name"]
resource_group_name = var.dev_main_rg
location = var.location
version = "12.0"
administrator_login = each.value["username"]
administrator_login_password = each.value["password"]
}
This sadly raise Error like
╷
│ Error: Incorrect attribute value type
│
│ on main.tf line 56, in resource "azurerm_sql_server" "dev_sql_server":
│ 56: name = var.sqlserver.name
│ ├────────────────
│ │ var.sqlserver.name is a object, known only after apply
│
│ Inappropriate value for attribute "name": string required.
╵
Your code is valid. When I copy it to a project of my own it works fine. I guess you have something else in your files that make it work different from what is shown here.