Can we put a condition on a ressource

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