Terraform - Inconsistent conditional result types - azure

I am trying to use for_each iterate through couple of list in a conditional manner.
If environment is dev => loop through listA and assign role to all management groups in listA
If environment is production => loop through listB and assign role to all management groups in listB
Conditional Role aassignment to management groups in a list:
variable "environment" {
default = "dev"
}
locals {
management_groups = [
"/providers/Microsoft.Management/managementGroups/one",
"/providers/Microsoft.Management/managementGroups/two"
]
management_groups_aux = [
"/providers/Microsoft.Management/managementGroups/three",
"/providers/Microsoft.Management/managementGroups/four"
]
}
resource "azurerm_resource_group" "this" {
name = "myrg"
location = "West Europe"
}
resource "azurerm_user_assigned_identity" "this" {
name = "myuai"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
}
resource "azurerm_role_assignment" "dev" {
for_each = lower(var.environment) == "dev" ? toset(local.management_groups) : {}
scope = each.value
role_definition_name = "Reader"
principal_id = resource.azurerm_user_assigned_identity.this.principal_id
}
resource "azurerm_role_assignment" "production" {
for_each = lower(var.environment) == "production" ? toset(local.management_groups_aux) : {}
scope = each.value
role_definition_name = "Reader"
principal_id = resource.azurerm_user_assigned_identity.this.principal_id
}
This is throwing an erros as below:
Error: Inconsistent conditional result types
on main.tf line 327, in resource "azurerm_role_assignment" "production":
327: for_each = lower(var.environment) == "production" ? toset(local.management_groups_aux) : {}
>! >! │ ├────────────────
local.management_groups_aux is tuple with 2 elements
var.environment will be known only after apply
The true and false result expressions must have consistent types. The given expressions are set of string and object, respectively.

As the error writes, you can't mix sets with maps in your expressions. It should be:
for_each = lower(var.environment) == "production" ? toset(local.management_groups_aux) : []

Related

Get ARNs of subnets and iterate over them

I have created some subnets. I want to share those subnets with other accounts. For that I need to retrieve the ARN of the subnets.
I am able to get a list of ARNs like this
data "aws_subnets" "dev_subnet" {
filter {
name = "vpc-id"
values = [module.vpc.vpc_id]
}
tags = {
Environment = "dev-*"
}
}
data "aws_subnet" "dev_subnet" {
for_each = toset(data.aws_subnets.dev_subnet.ids)
id = each.value
}
output "dev_subnet_arns" {
value = [for s in data.aws_subnet.dev_subnet : s.arn]
}
This results in
+ dev_subnet_arns = [
+ "arn:aws:ec2:ca-central-1:0097747:subnet/subnet-013987fd9651c3545",
+ "arn:aws:ec2:ca-central-1:0477747:subnet/subnet-015d76b264280321a",
+ "arn:aws:ec2:ca-central-1:0091747:subnet/subnet-026cd0402fe283c33",
]
Now I want to take the list of arns of the subnets and associate them with the resource_share_arn
What Im trying is something like this
resource "aws_ram_resource_association" "example" {
for_each = toset(data.aws_subnets.dev_subnet.ids)
resource_arn = each.value
resource_share_arn = aws_ram_resource_share.share_subnets_with_dev_account.arn
}
But this fails since it only gets the subnets ids and thats wrong
error associating RAM Resource Share: MalformedArnException: The specified resource ARN subnet-0c4afd736c18b3c28 is not valid. Verify the ARN and try again.
This also fails
resource "aws_ram_resource_association" "example" {
for_each = toset(data.aws_subnets.dev_subnet.arn)
resource_arn = each.value
resource_share_arn = aws_ram_resource_share.share_subnets_with_dev_account.arn
}
since arn is not an attribute. What am I missing here ?
You need to loop over the ARNs of the subnets and pass the ARN value for the resource_arn:
resource "aws_ram_resource_association" "example" {
for_each = toset([for s in data.aws_subnet.dev_subnet : s.arn])
resource_arn = each.value
resource_share_arn = aws_ram_resource_share.share_subnets_with_dev_account.arn
}
Or another solution would be:
resource "aws_ram_resource_association" "example" {
for_each = toset(values(data.aws_subnet.dev_subnet)[*].arn)
resource_arn = each.value
resource_share_arn = aws_ram_resource_share.share_subnets_with_dev_account.arn
}

Terraform - Reference a for_each resource from another for_each resource

I have a terraform file with the following contents:
resource "aws_iam_group" "developers" {
name = each.value
for_each = toset(var.groups)
}
resource "aws_iam_group_membership" "developers_team" {
name = "Developers Team"
users = [each.value]
for_each = toset(var.group_users)
group = aws_iam_group.developers.name
}
I would like to reference aws_iam_group from aws_iam_group_membership. How would I do that? The current terraform file is not working.
I tried this:
group = aws_iam_group.developers[each.value] //This will not work since it uses the for_each of
its own code block
The variable file is as below:
variable "groups" {
type = list(string)
default = [
"terraform_group1",
"terraform_group2",
"terraform_group3",
]
}
variable "group_users" {
type = list(string)
default = [
"terraform_test_user1",
"terraform_test_user2"
]
}
Edit:
I tried the below, but it is not working
resource "aws_iam_group_membership" "developers_team" {
name = "Developers Team"
users = [for group_user in var.group_users : group_user]
for_each = toset(var.groups)
group = aws_iam_group.developers[each.key]
}
Apparently, this is working:
resource "aws_iam_group" "developer" {
name = "truedeveloper"
}
resource "aws_iam_group_membership" "developers_team" {
name = "Developers_Team"
users = [for group_user in var.group_users : group_user]
for_each = toset(var.groups)
group = aws_iam_group.developer.name
}

Terraform for each loop or for loop not working

I am trying to set role for azure container registry for multiple service principals
variable "custom_role_list" {
type = list(object ({ service_principal_id = string, role = string }) )
}
When i try to set it from resource module, which I am not sure is the correct way?
resource "azurerm_role_assignment" "ad_sp_role_assignment" {
scope = azurerm_container_registry.acr.id
for_each = var.custom_role_list
role_definition_name = each.value.role
principal_id = each.value.service_principal_id
}
Essentially I am trying to set the azure container registry to work with multiple service principal with specific access roles.
Following is the var definition.
custom_role_list = [
{
service_principal_id = aserviceprincipal.id
role = "Contributor"
},
{
service_principal_id = bserviceprincipal.id
role = "Contributor"
}
]
When I execute it I get the following error.
Error: Invalid for_each argument
on ../modules/az-acr/main.tf line 46, in resource "azurerm_role_assignment" "ad_sp_role_assignment":
46: for_each = var.custom_role_list
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 list
of object.
Please if someone can guide will be very much helpful. thanks!
As the error suggests, for_each only supports maps and sets when used with a resource. You're trying to use a list of objects.
Instead, perhaps your variable can be simply of type map, where each service principle is a key and its corresponding role is the value. For example:
variable "custom_role_list" {
type = map
}
The variable definition:
custom_role_map = {
aserviceprincipal.id = "Contributor"
bserviceprincipal.id = "Contributor"
}
And finally use for_each:
resource "azurerm_role_assignment" "ad_sp_role_assignment" {
for_each = var.custom_role_map
scope = azurerm_container_registry.acr.id
role_definition_name = each.value
principal_id = each.key
}
You might find this blog post to help you with using loops and conditionals with Terraform.
You can use a for_each loop with your list of objects by adapting your code to the following:
variable "custom_role_list" {
type = list(object({
service_principal_id = string
role = string
}))
default = [
{
service_principal_id= "27d653c-aB53-4ce1-920",
role = "Contributor"
},
{
service_principal_id= "57d634c-aB53-4ce1-397",
role = "Contributor"
}
]
}
resource "azurerm_role_assignment" "ad_sp_role_assignment" {
for_each = {for sp in var.custom_role_list: sp.service_principal_id => sp}
scope = azurerm_container_registry.acr.id
role_definition_name = each.value.service_principal_id
principal_id = each.value.role
}

Iterate over nested data with for / for_each at resource level

I am trying to work out how to iterate over nested variables from a complex object given in the following tfvars file using Terraform 0.12.10:
example.tfvars
virtual_network_data = {
1 = {
product_instance_id = 1
location = "somewhere"
address_space = ["192.168.0.0/23"]
dns_servers = []
custom_tags = {"test":"test value"}
subnets = [
{
purpose = "mgmt"
newbits = 4
item = 0
},
{
purpose = "transit"
newbits = 4
item = 1
}
]
}
}
example.tf
variable "virtual_network_data" {} #Data comes from example.tfvars
variable "resource_group_name" {
default = "my_resource_group"
}
variable "virtual_network_name" {
default = "my_virtual_network"
}
####
resource "azurerm_subnet" "pool" {
for_each = var.virtual_network_data
name = format("%s%s%02d", "subnet_", s.purpose, s.item)
resource_group_name = var.resource_group_name
virtual_network_name = var.virtual_network_name
address_prefix = cidrsubnet(each.value["address_space"], s.newbits, s.item)
}
In example.tf I can use each.value["address_space"] to get to the top level variables, but I can't work out how to get to the items in subnets (s.purpose, s.item & s.newbits).
I have used dynamic blocks, as part of a parent resource (below), which works but in this case, I need to move the subnet into its own resource. Simply put, how do I get the first for_each to behave like the second for_each in the dynamic block?
resource "azurerm_virtual_network" "pool" {
for_each = var.virtual_network_data
name = format("%s%02d", local.resource_name, each.key)
resource_group_name = var.resource_group_name
location = each.value["location"]
address_space = each.value["address_space"]
dns_servers = each.value["dns_servers"]
tags = merge(local.tags, each.value["custom_tags"])
dynamic "subnet" {
for_each = [for s in each.value["subnets"]: {
name = format("%s%s%02d", "subnet_", s.purpose, s.item)
prefix = cidrsubnet(element(each.value["address_space"],0), s.newbits, s.item)
}]
content {
name = subnet.value.name
address_prefix = subnet.value.prefix
}
}
}
Cheeky bonus, is there a way to replace s.item with something like each.key or count.index?
TIA
The technique in this situation is to use other Terraform language features to transform your collection to be a suitable shape for the for_each argument: one element per resource instance.
For nested data structures, you can use flatten in conjunction with two or more for expressions to produce a flat data structure with one element per nested object:
locals {
network_subnets = flatten([
for network_key, network in var.virtual_network_data : [
for subnet in network.subnets : {
network_key = network_key
purpose = subnet.purpose
parent_cidr_block = network.address_space[0]
newbits = subnet.newbits
item = subnet.item
}
]
])
}
Then you can use local.network_subnets as the basis for repetition:
resource "azurerm_subnet" "pool" {
# Each instance must have a unique key, so we'll construct one
# by combining the network key, the subnet "purpose", and the "item".
for_each = {
for ns in local.network_subnets : "${ns.network_key}.${ns.purpose}${ns.item}" => ns
}
name = format("%s%s%02d", "subnet_", each.value.purpose, each.value.item)
resource_group_name = var.resource_group_name
virtual_network_name = var.virtual_network_name
address_prefix = cidrsubnet(each.value.parent_cidr_block, each.value.newbits, each.value.item)
}
There's a similar example in the flatten documentation, as some additional context.
As alternative to flatten trick, you may for_each resource by first parameter inside nested module, then for_each this module by second parameter.

Referencing resource instances created by "for_each" in Terraform

I'm testing the "for_each" resource attribute now available in Terraform 12.6 but can't manage to reference created instances in other resources.
azure.tf
variable "customers" {
type = map(object({name=string}))
}
resource "azurerm_storage_account" "provisioning-datalake" {
for_each = var.customers
name = "mydatalake${each.key}"
resource_group_name = "${azurerm_resource_group.provisioning-group.name}"
location = "${azurerm_databricks_workspace.databricks.location}"
account_kind = "StorageV2"
account_tier = "Standard"
account_replication_type = "GRS"
is_hns_enabled = true
enable_advanced_threat_protection = true
tags = {
environment = var.environment
customer = each.value.name
}
}
resource "azurerm_key_vault_secret" "key-vault-datalake-secret" {
for_each = var.customers
name = "mydatalake-shared-key-${each.key}"
value = azurerm_storage_account.provisioning-datalake[each.key].primary_access_key
key_vault_id = azurerm_key_vault.key-vault.id
tags = {
environment = var.environment
customer = each.value.name
}
}
build.tfvars
environment = "Build"
customers = {
dev = {
name = "Development"
},
int = {
name = "Integration"
},
stg = {
name = "Staging"
}
}
I expect "keyvault-datalake-secret" entries to be created with the matching keys of the generated "provisioning-datalake" resources.
But when I run terraform plan --var-file=build.tfvars, I get the following error:
Error: Invalid index
on azure.tf line 45, in resource "azurerm_key_vault_secret" "key-vault-datalake-secret":
45: value = azurerm_storage_account.provisioning-datalake[each.key].primary_access_key
|----------------
| azurerm_storage_account.provisioning-datalake is object with 52 attributes
| each.key is "stg"
The given key does not identify an element in this collection value.
Error: Invalid index
on azure.tf line 45, in resource "azurerm_key_vault_secret" "key-vault-datalake-secret":
45: value = azurerm_storage_account.provisioning-datalake[each.key].primary_access_key
|----------------
| azurerm_storage_account.provisioning-datalake is object with 52 attributes
| each.key is "int"
The given key does not identify an element in this collection value.
Error: Invalid index
on azure.tf line 45, in resource "azurerm_key_vault_secret" "key-vault-datalake-secret":
45: value = azurerm_storage_account.provisioning-datalake[each.key].primary_access_key
|----------------
| azurerm_storage_account.provisioning-datalake is object with 52 attributes
| each.key is "dev"
The given key does not identify an element in this collection value.
Bug corrected in Terraform 0.12.7

Resources