For_each with conditional

I’m a bit stumped on this and not sure if my approach is an anti-pattern.

I have currently working something like this:

terraform.tfvars

apps = {
  "nomad" = {
    name = "Nomad"
    domain = "nomad"
    # commented out as not working
    # create_record = false
    },
   "consul" = {
    name = "Consul"
    domain = "consul"
   }
}

apps.tf

resource "cloudflare_access_application" "apps" {
  for_each = var.apps
  
  name = each.value.name
  domain = each.value.domain
  <...>
}

dns.tf

resource "cloudflare_record" "cname" {
  for_each = var.apps
  name = each.value.domain
  <...>
}

variables.tf

variable "apps" {
  type = map(object({
     name = string
     domain = string
     create_record = optional(bool)
  )})
}

locals {
  apps = defaults(var.apps, {
    create_record = true
  })
}

I want to have a map of objects but I want conditionals in the event that I do not want to create a DNS record, or do not want to create an application but want to create a DNS record.

I tried the conditional:

for_each = {
  for name, app in var.apps : name => app
  if app.create_record
}

but then I got an error:

The condition value is null. Conditions must either be true or false.

Ideally I would have liked to have something like a map object with nested objects for app_config and dns_config, but have been unable to get that to work either.

Edit: I have right now about 20 applications that I manage with cloudflare, and while they share many settings, a few may have something different so I’d like to not be super verbose in the terraform.tfvars file if it isn’t necessary.

IMHO it fails within the consul map, as there isn’t a create_record key.
So either catch this or force the key to be filled with true/false values.

locals {
  apps = {
    "nomad" = {
      name   = "Nomad"
      domain = "nomad"
      # commented out as not working
      create_record = true
    },
    "consul" = {
      name   = "Consul"
      domain = "consul"
      #create_record = false
    }
  }
  trans_map = { for name, app in local.apps : name => app if try(app.create_record, false) }
}

output trans_map {
  value = local.trans_map
}

I see what you’re saying, however isn’t that the point of using defaults()>?

If I have 30 instances that I am configuring, and 2 of them have a different value it seems silly to write out create_record = true 28 times and false two times. I would expect to only be explicit with the values that are not default. My guess is that the conditional is being run before the null value is replaced using defaults but I’m not sure if that’s expected behavior

Well, null is neither false nor true so not a boolean. In addition, an optional attribute is not accessible if not set.

It’s up to you to define the default value in case create_record is not set (null).

Can you elaborate?

In variables.tf I have set default values using the experimental defaults() function by following defaults - Functions - Configuration Language | Terraform by HashiCorp

My understanding is that I have set the default value. If I am missing something, that’s why I am posting here for help…

After some testing, I think I found my misunderstanding / issue.

for_each = var.apps

# should be
for_each = local.apps