Hello,
Is it possible to execute a block in a ressource with specific condition ?
For exemple on the ressource “logdna_view”, I can execute
resource “logdna_view” “my_view” {
name = “name”
query = “query”
}
or
resource “logdna_view” “my_view” {
name = “name”
query = “query”
email_channel {
emails = “email”
immediate = true
}
The two will works seperatly, but can I merge the two ressource in one like this ?
resource “logdna_view” “my_view” {
name = “name”
query = “query”
if (val.enabled ? email_channel {
emails = “email”
immediate = true
}):“avoid this blog”
Like that, if val.enabled is true I execute the block email_channel{}, and if not, I don’t execute it
Thanks for your help
Hi @Dominique,
When thinking about problems like this it’s important to remember that Terraform modules are declarations of what should exist and are not exactly “code to be executed” in the way you might think of general-purpose programming languages.
So a different way to state the problem is: how can I decide based on a dynamic rule how many email_channel
blocks to declare?
The mechanism for dynamically deciding how many blocks to declare is dynamic
blocks, which are essentially a rule for generating zero or more blocks of a particular type.
The most direct form of this would be to expose the decision about how to generate these directly to the user of your module:
variable "email_channels" {
type = list(object({
emails = string
immediate = optional(bool, false)
}))
}
resource “logdna_view” “my_view” {
name = “name”
query = “query”
dynamic "email_channel" {
for_each = var.email_channels
content {
emails = email_channel.value.emails
immediate = email_channel.value.immediate
}
}
}
If you want to simplify this so that the user of your module only needs to set a boolean variable then you can modify the above to generate the for_each
argument using a more complex expression, such as a conditional expression choosing between a zero-element list and a one-element list, or anything else that produces a list with one element per block you want to generate.
1 Like
Hi @apparentlymart ,
Thanks for your return, but there is one thing I don’t understand :
To be more specific, I read a Yaml file and then want to execute the block ‘email_channels’ only if the structure is présent :
alert_email_test:
- name: "email test"
query: "query"
category: "TEST - TEST"
email_channels:
emails: ["email1@gmail.com","email2@mail.com"]
immediate: "false"
operator: "absence"
terminal: "true"
timezone: "Pacific/Samoa"
triggerinterval: "24h"
triggerlimit: 15
And the ressource code with the dynamic block :
resource "logdna_view" "my_alert_mail_test" {
for_each = { for x in coalesce(local.input.alert_email_test, []) : x.name => x }
name = each.value.name
query = each.value.query
categories = ["each.value.category"]
dynamic "email_channel" {
for_each = each.value.email_channels
content {
emails = [for mails in each.value.email_channels.emails : mails]
immediate = each.value.email_channels.immediate
operator = each.value.email_channels.operator
terminal = each.value.email_channels.terminal
timezone = each.value.email_channels.timezone
triggerinterval = each.value.email_channels.triggerinterval
triggerlimit = each.value.email_channels.triggerlimit
}
}
}
The result not wanted is it create 7 ressources, because of the “for_each = each.value.email_channels”. I think because there is 7 element in it.
# logdna_view.my_alert_mail_test["email test"] will be created
+ resource "logdna_view" "my_alert_mail_test" {
+ categories = [
+ "each.value.category",
]
+ id = (known after apply)
+ name = "email test"
+ query = "query"
+ email_channel {
+ emails = [
+ "email1@gmail.com",
+ "email2@mail.com",
]
+ immediate = "false"
+ operator = "absence"
+ terminal = "true"
+ timezone = "Pacific/Samoa"
+ triggerinterval = "24h"
+ triggerlimit = 15
}
+ email_channel {
+ emails = [
+ "email1@gmail.com",
+ "email2@mail.com",
]
+ immediate = "false"
+ operator = "absence"
+ terminal = "true"
+ timezone = "Pacific/Samoa"
+ triggerinterval = "24h"
+ triggerlimit = 15
}
.....
I am confused on how to saw “please create the ‘email_channels’ block only if it is present”.
Thanks for your help
Hi @Dominique,
email_channels
in your example would become an object value in Terraform, like this:
{
"emails" = ["email1@gmail.com","email2@mail.com"]
"immediate" = "false"
"operator" = "absence"
"terminal" = "true"
"timezone" = "Pacific/Samoa"
"triggerinterval" = "24h"
"triggerlimit": 15
}
Therefore when you use this value with your dynamic
blocks’ for_each
Terraform thinks you are intending to declare one email_channel
block for each attribute of this object.
You instead want to generate one block if the object is present or zero blocks if the object is absent. One way to do this in Terraform is to use the splat operator to convert your single value into a list, like this:
dynamic "email_channel" {
for_each = try(each.value.email_channels[*], [])
content {
emails = email_channel.value.emails
immediate = email_channel.value.immediate
operator = email_channel.value.operator
terminal = email_channel.value.terminal
timezone = email_channel.value.timezone
triggerinterval = email_channel.value.triggerinterval
triggerlimit = email_channel.value.triggerlimit
}
}
The for_each
expression above is handling two different situations:
each.value.email_channels[*]
will return a single-element list containing each.value.email_channels
if that value is not null, or will return an empty list if that value is null. However, this expression would fail if the email_channels
attribute is missing from each.value
.
- The
try(..., [])
catches that error and makes the result be an empty list in that case too, so there will also be zero blocks if the attribute is entirely absent.
I also changed each.value.email_channels
in your example to email_channel.value
. Inside a dynamic "email_channel"
block the email_channel
symbol represents the current element of the for_each
collection, and so email_channel.value
is the value of the current element, similar to each.value
for the resource-level for_each
.
1 Like
Hi @apparentlymart ,
Thanks for your return and explanation, that works now !
That works perfectly fine when I have only one ressource on the YAML file, but now that doesn’t works when I have more than one ressource (that worked before with non dynamic bloc).
For exemple with
alert_email:
- name: "EMAIL 1"
query: "host:schematics"
category: "TEST - TEST"
or
alert_email:
- name: "EMAIL 1"
query: "host:schematics"
category: "TEST - TEST"
email_channel:
emails: ["email1@gmail.com","email2@mail.com"]
immediate: "false"
operator: "absence"
terminal: "true"
timezone: "Pacific/Samoa"
triggerinterval: "24h"
triggerlimit: 15
That works fine
But with :
alert_email:
- name: "EMAIL 1"
query: "host:schematics"
category: "TEST - TEST"
- name: "EMAIL 2"
query: "host:schematics"
category: "TEST - TEST"
email_channel:
emails: ["email1@gmail.com","email2@mail.com"]
immediate: "false"
operator: "absence"
terminal: "true"
timezone: "Pacific/Samoa"
triggerinterval: "24h"
triggerlimit: 15
I have this error
Error: Error in function call
│
│ on maint.tf line 61, in resource "logdna_view" "my_alert_mail":
│ 61: for_each = { for x in coalesce(local.input.alert_email, []) : x.name => x }
│ ├────────────────
│ │ while calling coalesce(vals...)
│ │ local.input.alert_email is tuple with 2 elements
│
│ Call to function "coalesce" failed: all arguments must have the same type.
The code is
resource "logdna_view" "my_alert_mail" {
depends_on = [logdna_category.my_category]
for_each = { for x in coalesce(local.input.alert_email, []) : x.name => x }
#for_each = try(local.input.alert_email[*], [])
name = each.value.name
query = each.value.query
categories = [each.value.category]
dynamic "email_channel" {
for_each = try(each.value.email_channel[*], [])
content {
emails = email_channel.value.emails
immediate = email_channel.value.immediate
operator = email_channel.value.operator
terminal = email_channel.value.terminal
timezone = email_channel.value.timezone
triggerinterval = email_channel.value.triggerinterval
triggerlimit = email_channel.value.triggerlimit
}
}
Thanks for your help
Hi @apparentlymart ,
Is it possible to use the coalesce() function with tuple ?
I want to execute the For Loop only if there is some element on it. This is strange because that worked fine before the Dynamic Bloc. Maybe because it is not a static map now ?
Error: Error in function call
│
│ on main.tf line 34, in resource "logdna_view" "my_alert_mail":
│ 34: for_each = { for x in coalesce(local.input.view_alert, []) : x.name => x }
│ ├────────────────
│ │ while calling coalesce(vals...)
│ │ local.input.view_alert is tuple with 21 elements
│
│ Call to function "coalesce" failed: all arguments must have the same type.
I tried to use the tomap() function without succes :
Error: Error in function call
│
│ on main.tf line 34, in resource "logdna_view" "my_alert_mail":
│ 34: for_each = { for x in coalesce(tomap(local.input.view_alert), []) : x.name => x }
│ ├────────────────
│ │ while calling coalesce(vals...)
│
│ Call to function "coalesce" failed: all arguments must have the same type.
Thanks for your help