Terraform error with dynamic block in for_each - terraform

I'm trying to instantiate an azure storage_share map using the azuremrm resource storage_share. By design, I need to be able to instantiate more than one storage share with the same block; each of those shares may or may not have an "acl" section.
I was thinking of solving this issue using a for_each in conjuction with a dynamic block, as in the related SE question:
Main.tf
resource "azurerm_storage_share" "storage_share" {
for_each = var.storage_share_map
name = each.key
storage_account_name = azurerm_storage_account.sa.name
quota = each.value.quota
dynamic "acl" {
for_each = each.value.acl
content {
id = acl.value.id
access_policy {
permissions = acl.value.access_policy.permissions
start = acl.value.access_policy.start
expiry = acl.value.access_policy.expiry
}
}
}
The variable would be defined as:
variable "storage_share_map" {
type = map(object({
quota = number,
acl = object({
id = string,
access_policy = object({
expiry = string,
permissions = string,
start = string
})
}),
}))
default = {}
}
and later parametrized in my tests as:
storage_share_map = {
my-share-2 = {
quota = 123,
acl = {
id = "a-id",
access_policy = {
expiry = "ISO8061 UTC TIME"
permissions = "rwdl"
start = "ISO8601 UTC TIME"
},
},
}
However, when testing, terraform returns the following output:
Error: Unsupported attribute
on .terraform\modules\sa\main.tf line 83, in resource "azurerm_storage_share" "storage_share":
83: id = acl.value.id
|----------------
| acl.value is object with 3 attributes
This object does not have an attribute named "id".
Error: Unsupported attribute
on .terraform\modules\sa\main.tf line 83, in resource "azurerm_storage_share" "storage_share":
83: id = acl.value.id
|----------------
| acl.value is "a-id"
This value does not have any attributes.
Error: Unsupported attribute
on .terraform\modules\sa\main.tf line 86, in resource "azurerm_storage_share" "storage_share":
86: permissions = acl.value.access_policy.permissions
|----------------
| acl.value is object with 3 attributes
This object does not have an attribute named "access_policy".
Error: Unsupported attribute
on .terraform\modules\sa\main.tf line 86, in resource "azurerm_storage_share" "storage_share":
86: permissions = acl.value.access_policy.permissions
|----------------
| acl.value is "a-id"
This value does not have any attributes.
As I understand it, the issue here is that the for_each inside the dynamic block is either malformed or misbehaving: acl.value appears to be both valued as the string "a-id" and carrying three attribute (?).
Terraform version 0.12.26
Azurerm version 2.26.0
Any insight would be appreciated.
Related question:
Dynamic block with for_each inside a resource created with a for_each

By iterating in the dynamic block with for_each = each.value.acl, you are iterating over the values in the object type. It appears you really want to iterate over the acl themselves. You would need to adjust your type to:
variable "storage_share_map" {
type = map(object({
quota = number,
acl = list(object({
...
}))
})),
}
You can tell from the error messages that currently it is iterating over id and then access_policy, and failing to find the two requested attributes for each, which is why you have 2*2=4 errors.
You can adjust your input correspondingly to:
storage_share_map = {
my-share-2 = {
quota = 123,
acl = [{
id = "a-id",
access_policy = {
expiry = "ISO8061 UTC TIME"
permissions = "rwdl"
start = "ISO8601 UTC TIME"
},
}],
}
and this will achieve the behavior you desire.
Note that Terraform 0.12 has issues sometimes with nested object type specifications, so omitting the acl with [] may result in crashing under certain circumstances.

Please use square brackets for each.value.acl.
Azure storage share block should look like:
resource "azurerm_storage_share" "storage_share" {
for_each = var.storage_share_map
name = each.key
storage_account_name = azurerm_storage_account.sa.name
quota = each.value.quota
dynamic "acl" {
for_each = [each.value.acl]
content {
id = acl.value.id
access_policy {
permissions = acl.value.access_policy.permissions
start = acl.value.access_policy.start
expiry = acl.value.access_policy.expiry
}
}
}
}

Related

Tag variable with list in aws_s3_bucket terraform resource

I am trying to create a tag variable as part of a module for aws_s3_bucket terraform resource. One of the tag keys should allow a list(string) value. What data type should I use?
I tried the following in variables.tf:
variable "name" {
type = string
}
variable "tags" {
type = object({
Owner = string
DataCategory = list(string)
}))
With this implementation in main.tf:
resource "aws_s3_bucket" "bucket" {
bucket = var.name
tags = var.tags
}
And trying to create a bucket in ./example/main.tf:
module "s3_bucket_test" {
source = "../"
name = "example-bucket"
tags = {
Owner = "My Team"
DataCategory = ["Customer","Finance"]
}
}
But I am getting the error:
Inappropriate value for attribute "tags": element
"DataCategory": string required.

Produce repeating blocks inside a terraform resource

I am fairly new to terraform and trying to create a google_compute_backend_service using terraform and there are multiple backend blocks inside the resource as shown below:
resource "google_compute_backend_service" "app-backend" {
log_config {
enable = "true"
sample_rate = "1"
}
name = "app-backend"
port_name = "http-34070"
project = "my-project"
protocol = "HTTP"
session_affinity = "NONE"
timeout_sec = "30"
backend {
group = "insatnce-group1"
}
backend {
group = "instance-group2"
}
backend {
group = "instance-group3"
}
health_checks = [google_compute_http_health_check.app-http-l7.name]
}
As seen in the code block above the backend block repeats multiple times. I want to make it dynamic so I do not have to write multiple blocks manually.
I tried the following:
Created a variable in the variables.tf file that contains all the instance groups:
variable "groups" {
type = list(object({
name = string
}))
default = [{ name = "instance-group1"},
{ name = "instance-group2"},
{ name = "instance-group3"}]
}
And modified my resource block to this:
resource "google_compute_backend_service" "app-backend" {
log_config {
enable = "true"
sample_rate = "1"
}
name = "app-backend"
port_name = "http-34070"
project = "my-project"
protocol = "HTTP"
session_affinity = "NONE"
timeout_sec = "30"
dynamic "backend" {
for_each = var.groups
iterator = item
group = item.value.name
}
health_checks = [google_compute_http_health_check.app-http-l7.name]
}
However, when I execute terraform plan I get the following error:
Error: Unsupported argument
│
│ on backend_service.tf line 15, in resource "google_compute_backend_service" "app-backend":
│ 15: group = item.value.name
│
│ An argument named "group" is not expected here.
Where am I going wrong? Is there a better way to achieve this?
You can check the dynamic blocks documentation for the syntax. Otherwise, you had the right idea.
dynamic "backend" {
for_each = var.groups
content {
group = backend.value.name
}
}
You can also simplify the variable structure to make this even easier.
variable "groups" {
type = set(string)
default = ["instance-group1", "instance-group2", "instance-group3"]
}
dynamic "backend" {
for_each = var.groups
content {
group = backend.value
}
}

Iterating on type object in terraform

I have the following terraform:
The idea is that I can pass in volumes and either pass binary_data or data and this module will handle accordingly.
However it is not liking the nested loop. I have a feeling it's not liking iterating on the object.
When I run this I get the following error
#variable.tf
variable "volumes" {
type = map(object({
data = map(string)
binary_data = map(string)
}))
description = "configmap backed volume"
}
#main.tf
resource "kubernetes_config_map" "volume" {
for_each = var.volumes
metadata {
name = each.key
namespace = var.namespace
}
dynamic "data" {
for_each = each.value["data"]
content {
each.key = each.value
}
}
dynamic "binary_data" {
for_each = each.value["binary_data"]
content {
each.key = each.value
}
}
}
Error: Argument or block definition required
On ../../../terraform-modules/helm_install/main.tf line 45: An argument or
block definition is required here. To set an argument, use the equals sign "="
to introduce the argument value.
data and binary_data are arguments, not blocks. dynamic blocks apply only to blocks, not to arguments.
This means that you can't create multiple data and binary_data in a single kubernetes_config_map. You would have to apply for_each at the resource level:
resource "kubernetes_config_map" "volume" {
for_each = var.volumes
metadata {
name = each.key
namespace = var.namespace
}
data = each.value["data"]
binary_data = each.value["binary_data"]
}
I haven't verified the above code, thus threat it as an example only.

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
}

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