terraform dynamic block using list of map - dynamic

I have a terraform variable:
variable "volumes" {
default = [
{
"name" : "mnt",
"value" : "/mnt/cvdupdate/"
},
{
"name" : "efs",
"value" : "/var"
},
]
}
and I am trying to create a dynamic block
dynamic "volume" {
for_each = var.volumes == "" ? [] : [true]
content {
name = volume["name"]
}
}
but I get an error when I run plan
name = volume["name"]
│
│ The given key does not identify an element in this collection value.
the desired output would be:
volume {
name = "mnt"
}
volume {
name = "efs"
}
what is wrong with my code?

Since you are using for_each, you should use value. Also you condition is incorrect. It all should be:
dynamic "volume" {
for_each = var.volumes == "" ? [] : var.volumes
content {
name = volume.value["name"]
}
}

As you are creating an if-else like condition to pass value to for loop, the condition needs a value to set. https://developer.hashicorp.com/terraform/language/meta-arguments/for_each
Need to replace [true] with var.volumes to pass the value.
for_each = var.volumes == "" ? [] : var.volumes
And, then set the value in the content block with .value to finally set the values to use.
content {
name = volume.value["name"]
The final working code is below as #marcin posted.
dynamic "volume" {
for_each = var.volumes == "" ? [] : var.volumes
content {
name = volume.value["name"]
}
}

You can simply use for_each = var.volumes[*]:
dynamic "volume" {
for_each = var.volumes[*]
content {
name = volume.value["name"]
}
}
or:
dynamic "volume" {
for_each = var.volumes[*]
content {
name = volume.value.name # <------
}
}

Related

Write a dynamic Terraform block for a load balancer listener rule

I'm new to dynamic blocks and am having some trouble writing rules to listeners on a load balancer that was created using for_each.
Below are the resources I created:
resource "aws_lb_listener" "app_listener_forward" {
for_each = toset(var.app_listener_ports)
load_balancer_arn = aws_lb.app_alb.arn
port = each.value
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
certificate_arn = var.ssl_cert
default_action {
type = "forward"
forward {
dynamic "target_group" {
for_each = aws_lb_target_group.app_tg
content {
arn = target_group.value["arn"]
}
}
stickiness {
enabled = true
duration = 86400
}
}
}
}
resource "aws_lb_listener_rule" "app_https_listener_rule" {
for_each = toset(var.app_listener_ports)
listener_arn = aws_lb_listener.app_listener_forward[each.value].arn
action {
type = "forward"
forward {
dynamic "target_group" {
for_each = aws_lb_target_group.app_tg
content {
arn = target_group.value["arn"]
}
}
}
}
dynamic "condition" {
for_each = var.images
path_pattern {
content {
values = condition.value["paths"]
}
}
}
}
resource "aws_lb_target_group" "app_tg" {
for_each = var.images
name = each.key
port = each.value.port
protocol = "HTTP"
target_type = "ip"
vpc_id = aws_vpc.app_vpc.id
health_check {
interval = 130
timeout = 120
healthy_threshold = 10
unhealthy_threshold = 10
}
stickiness {
type = "lb_cookie"
cookie_duration = 86400
}
}
Below are how the variables are defined:
variable "images" {
type = map(object({
app_port = number
paths = set(string)
}))
{
"app-one" = {
app_port = 3000
paths = [
"/appOne",
"/appOne/*"
]
}
"app-two" = {
app_port = 4000
paths = [
"/appTwo",
"/appTwo/*"
]
}
}
variable "app_listener_ports" {
type = list(string)
default = [
80, 443, 22, 7999, 8999
]
}
Upon executing, I am getting an error dealing with the path_pattern being unexpected:
Error: Unsupported block type
│
│ on alb.tf line 78, in resource "aws_lb_listener_rule" "app_https_listener_rule":
│ 78: path_pattern {
│
│ Blocks of type "path_pattern" are not expected here.
I've tried a few ways to get this dynamic block but am having some difficulty. Any advice would be appreciated.
Thank you!
Try it like this:
dynamic "condition" {
for_each = var.images
content {
path_pattern {
values = condition.value.paths
}
}
}
And change the type of paths from set(string) to list(string).
This is also completely acceptable:
dynamic "condition" {
for_each = var.images
content {
path_pattern {
values = condition.value["paths"]
}
}
}
However, in my opinion here it's better to not use a dynamic block for the condition to maintain readability and maintenance.
condition {
path_pattern {
values = [
"/appOne",
"/appOne/*" ## can also use variables if you prefer !!
]
}
}
I have already answered your original post related to the problem which you had after fixing the dynamic syntax.
Post URL: Error when creating dynamic terraform rule for alb listener rule

Create partition on bigquery table using terraform

Description
I have list of bigquery tables to be created using terraform but I need only the partition for specific tables.
Here is the ex.
locals {
path = "../../../../../../../../db"
gcp_bq_tables = [
"my_table1",
"my_table1_daily",
"my_table2",
"my_table2_daily"
]
}
And, the terraform script to create the tables:
resource "google_bigquery_table" "gcp_bq_tables" {
for_each = toset(local.gcp_bq_tables)
dataset_id = google_bigquery_dataset.gcp_bq_db.dataset_id
table_id = each.value
schema = file("${local.path}/schema/${each.value}.json")
labels = {
env = var.env
app = var.app
}
}
In that I need to create partition on timestamp, type as DAY but the columns are different.
Lets say for my_table1,
The partition column would be my_ts_column_table1 for table1
The partition column would be my_last_modified_column_table2 for table2
How to write the terraform script in this scenario.
My exploration
I find a way to do it in terraform_documentation but not sure for multiple tables and how can be specified the partition columns for both tables.
In this case it might be the best to use dynamic [1] with for_each meta-argument [2] to achieve what you want. The code would have to be changed to:
resource "google_bigquery_table" "gcp_bq_tables" {
for_each = toset(local.gcp_bq_tables)
dataset_id = google_bigquery_dataset.gcp_bq_db.dataset_id
table_id = each.value
schema = file("${local.path}/schema/${each.value}.json")
dynamic "time_partitioning" {
for_each = each.value == "table1" || each.value == "table2" ? [1] : []
content {
type = "DAY"
field = each.value == "table1" ? "my_ts_column_table1" : "my_last_modified_column_table2"
}
}
labels = {
env = var.env
app = var.app
}
}
[1] https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks
[2] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each
I hope this solution can help.
You can configure a json file to create dynamically your tables with partitions.
tables.json file
{
"tables": {
"my_table1": {
"dataset_id": "my_dataset",
"table_id": "my_table",
"schema_path": "folder/myschema.json",
"partition_type": "DAY",
"partition_field": "partitionField",
"clustering": [
"field",
"field2"
]
},
"my_table2": {
"dataset_id": "my_dataset",
"table_id": "my_table2",
"schema_path": "folder/myschema2.json",
"partition_type": "DAY",
"partition_field": "partitionField2",
"clustering": [
"field",
"field2"
]
}
}
Then retrieve your tables from Terraform local file.
locals.tf file :
locals {
tables = jsondecode(file("${path.module}/resource/tables.json"))["tables"]
}
I put a default partition in variables.json file on myDefaultDate field :
variable "time_partitioning" {
description = "Configures time-based partitioning for this table. cf https://www.terraform.io/docs/providers/google/r/bigquery_table.html#field"
type = map(string)
default = {
type = "DAY"
field = "myDefaultDate"
}
}
In the resource.tf file, I used a dynamic bloc :
if the partition exists in the current table from the Json matadata configuration file tables.json, I take it.
Otherwise I take the default partition given by the variables.tf file.
resource.tf file :
resource "google_bigquery_table" "tables" {
for_each = local.tables
project = var.project_id
dataset_id = each.value["dataset_id"]
table_id = each.value["table_id"]
clustering = try(each.value["clustering"], [])
dynamic "time_partitioning" {
for_each = [
var.time_partitioning
]
content {
type = try(each.value["partition_type"], time_partitioning.value["type"])
field = try(each.value["partition_field"], time_partitioning.value["field"])
expiration_ms = try(time_partitioning.value["expiration_ms"], null)
require_partition_filter = try(time_partitioning.value["require_partition_filter"], null)
}
}
schema = file("${path.module}/resource/schema/${each.value["schema_path"]}")
}

terraform variable using block with no argument

I have a sample code below from terraform but I'm having some issues trying to declare a variable that the argument is a block
basic {}
and moving to production will be something like
dedicated {
cku = 2
}
DEV
resource "confluent_kafka_cluster" "basic" {
display_name = "basic_kafka_cluster"
availability = "SINGLE_ZONE"
cloud = "GCP"
region = "us-central1"
basic {} <<<< # I want this block to be declared as variable
# Calling the variable
local.cluster_type["dev"] <<<< # this approach is not supported. how can I call the variable directly if there is no argument?
}
PROD
resource "confluent_kafka_cluster" "dedicated" {
display_name = "dedicated_kafka_cluster"
availability = "MULTI_ZONE"
cloud = "GCP"
region = "us-central1"
# For Production it is using a different block
dedicated {
cku = 2
}
# Calling the variable
local.cluster_type["prod"] <<<<< # this approach is not supported. how can I call the variable directly if there is no argument?
}
Local variables
locals {
cluster_type = {
prod = "dedicated {
cku = 2
}"
dev = "basic {}"
}
}
You have some issues with your script:
confluent_kafka_cluster is deprecated, it should be replaced by confluentcloud_kafka_cluster
To use the environment, you can create confluentcloud_environment:
resource "confluentcloud_environment" "env" {
display_name = var.environment
}
To solve the issue of the block, you can use dynamic with conditions, like this:
dynamic "basic" {
for_each = var.environment == "dev" ? [1] : []
content {}
}
dynamic "dedicated" {
for_each = var.environment == "prod" ? [1] : []
content {
cku = 2
}
}
Your code can be like this:
resource "confluentcloud_environment" "env" {
display_name = var.environment
}
resource "confluentcloud_kafka_cluster" "basic" {
display_name = "basic_kafka_cluster"
availability = "SINGLE_ZONE"
cloud = "GCP"
region = "us-central1"
dynamic "basic" {
for_each = var.environment == "dev" ? [1] : []
content {}
}
dynamic "dedicated" {
for_each = var.environment == "prod" ? [1] : []
content {
cku = 2
}
}
environment {
id = confluentcloud_environment.env.id
}
}
variable "environment" {
default = "dev"
}

terraform azurerm_data_factory_pipeline assing type to the variables

in terraform documentation i found the follow example:
resource "azurerm_data_factory_pipeline" "test" {
name = .....
resource_group_name = ...
data_factory_id = ...
variables = {
"bob" = "item1"
}
but I need to create a boolean variable, in the portal Azure I have the type field.
how can I set the variable like this:
"variables": {
"END": {
"type": "Boolean",
"defaultValue": false
}
}
Based on your question, if you are asking how to create a variable of type boolean in Terraform, that is done like this:
variable "END" {
type = bool
description = "End variable."
default = false
}
You can reference that variable then in the resource definition:
resource "azurerm_data_factory_pipeline" "test" {
name = .....
resource_group_name = ...
data_factory_id = ...
variables = {
"END" = var.END
}
}
Or alternatively you can set it without defining the Terraform variable like this:
resource "azurerm_data_factory_pipeline" "test" {
name = .....
resource_group_name = ...
data_factory_id = ...
variables = {
"END" = false
}
}

Dynamic block with optional data

let say we have this locals:
locals = {
schemas = [
{
name = "is_cool"
attribute_data_type = "Boolean"
mutable = true
required = false
},
{
name = "firstname"
attribute_data_type = "String"
mutable = true
required = false
min_length = 1
max_length = 256
}
]
}
What I would like to achieve is to use dynamic to build schemas and when the schema is a string I would like to add the string_attribute_constraints block.
This is what I did so far but it adds an empty string_attribute_constraints block when the schema is Boolean
dynamic "schema" {
for_each = var.schemas
content {
name = schema.value.name
attribute_data_type = schema.value.attribute_data_type
mutable = schema.value.mutable
required = schema.value.required
string_attribute_constraints {
min_length = lookup(schema.value, "min_length", null)
max_length = lookup(schema.value, "max_length", null)
}
}
}
terraform plan:
+ schema {
+ attribute_data_type = "Boolean"
+ mutable = true
+ name = "is_cool"
+ required = false
+ string_attribute_constraints {}
}
You can use a second nested dynamic block to tell Terraform how many string_attribute_constraints blocks to generate based on your rule:
dynamic "schema" {
for_each = var.schemas
content {
name = schema.value.name
attribute_data_type = schema.value.attribute_data_type
mutable = schema.value.mutable
required = schema.value.required
dynamic "string_attribute_constraints" {
for_each = schema.attribute_data_type == "String" ? [1] : []
content {
min_length = lookup(schema.value, "min_length", null)
max_length = lookup(schema.value, "max_length", null)
}
}
}
}
This works by making the for_each for the nested dynamic be an empty list in the case where we don't want to generate any blocks, and making it a single-element list in the case where we do. Since we need no references to string_attribute_constraints.key or string_attribute_constraints.value inside the block, we can set the value of the single element to anything, and so I just set it to 1 as an arbitrary placeholder.
dynamic "schema" {
for_each = local.my_schema
content {
name = schema.value.name
attribute_data_type = schema.value.attribute_data_type
mutable = schema.value.mutable
required = schema.value.required
dynamic "string_attribute_constraints" {
for_each = schema.value.attribute_data_type == "String" ? [1] : []
content {
min_length = lookup(schema.value, "min_length", 0)
max_length = lookup(schema.value, "max_length", 2048)
}
}
dynamic "number_attribute_constraints" {
for_each = schema.value.attribute_data_type == "Number" ? [1] : []
content {
min_value = lookup(schema.value, "min_value", 0)
max_value = lookup(schema.value, "max_value", 2048)
}
}
}
}