Search code examples
terraform

Terraform map(string) how to use


I want to create a module for define override settings for this cloudflare ressource https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone_settings_override

variable "settings" {
  type = map(string)
  default = {}
}

So I thought about not defining every possible variable, instead just using a map of strings to just put that in the settings block what I want. Sure, I cannot test if its a allowed variable but i'm fine with it. So I thought I cloud do something like the following pseudo code, but something is missing and I dont know what

resource "cloudflare_zone_settings_override" "this" {
  zone_id = cloudflare_zone.this.id
  for each = var.settings
  settings {
    each.key = each.value
  }
}

Solution

  • This provider has defined settings as a nested block, which means that its schema is rigid and cannot be populated dynamically.

    You will therefore need to explicitly assign each nested argument that you want to support in your module. There is no way to assign dynamically based on the keys in your map, because the provider expects to be able to validate the arguments statically.

    Because you'll need to specify the specific arguments you want to support anyway, it'll be easier (and also more consistent with the provider's design) to declare your variable as being an object type with optional attributes, rather than a map:

    variable "settings" {
      type = object({
        brotli        = optional(string)
        challenge_ttl = optional(number)
        minify        = optional(object({
          css  = string
          html = string
          js   = string
        }))
        # ...etc...
      })
      default = {}
    }
    

    The advantage of declaring this as an object type rather than as a map is that you can then assume that all of the declared attributes will always be present in the object, although the optional ones might be null. You can then assign them directly because null in a resource argument is always equivalent to omitting the argument entirely.

    resource "cloudflare_zone_settings_override" "this" {
      zone_id = cloudflare_zone.this.id
    
      settings {
        brotli        = var.settings.brotli
        challenge_ttl = var.settings.challenge_ttl
    
        dynamic "minify" {
          for_each = var.settings.minify[*]
          content {
            css  = minify.value.css
            html = minify.value.html
            js   = minify.value.js
          }
        }
    
        # ...etc...
      }
    }
    

    If you are writing a shared module (that is: one intended to be called from another module using a module block) then another option you could consider is to design your module to just export cloudflare_zone.this.id as an output value and let the calling module define its own resource "cloudflare_zone_settings_override" block; if you would just be passing the values from the caller verbatim into this resource anyway then you haven't really created any abstraction and so defining the resource directly in the caller would be equivalent and considerably simpler.