I am trying to write come terraform code that will take a nested object and create resources, normally this would be done with the dynamic block but the resouce in question doesn't support this, and wondering if anyone has any ideas on what might be the best approach to tackle this.
Config would be like this:
dashboards = {
"Test Dashboards" = {
folder_name = "Test Folder"
dashboards = [
{
file. = "test.json"
overwrite = true
},
{
file = "test1.json"
overwrite = false
}
]
}
}
and then in resource creation, it would normally be something like this:
resource "grafana_dashboard" "dashboards" {
for_each = var.dashboards
folder = each.value.folder_name
dynamic "dashboard" {
for_each = each.value.dashboards
content {
config_json = file("../../dashboards/${dashboards.value["file"]}")
overwrite = dashboards.value["overwrite"]
}
}
}
But the dynamic block isn't support with this resource, the only way I just don't want to duplicate config where it might be shared with multiple resources if that makes sense.
Before getting into the actual answer to this question, I think it's important to clear up some disagreement in terminology: You've called your input variable "dashboards", but based on the value you've assigned to it this seems to instead be a collection of folders, where each one contains one or more dashboards.
Therefore I think that what you really need is one instance of grafana_folder
for each element of var.dashboards
, and then one instance of grafana_dashboard
for each distinct dashboard listed across all of the folders.
What you are doing here then is essentially a Grafana-flavored version of the problem described in Flattening nested structures for for_each
, and so you can adapt the approach described in there as follows.
First, let's declare an input variable to make sure we're agreed on what type it has:
# NOTE: I renamed this just to clarify that it's a collection
# of folders, rather than of dashboards directly. You can
# rename it back if you prefer.
variable "dashboard_folders" {
type = map(object({
folder_name = string
dashboards = set(object({
file = string
overwrite = optional(bool, false)
}))
}))
}
Since this variable is already a map and already has one element for each "folder" you want to declare, you can use this value directly with for_each
to declare multiple instances of grafana_folder
:
resource "grafana_folder" "dashboards" {
for_each = var.dashboard_folders
uid = each.key
title = each.value.folder_name
}
The dashboards themselves are trickier because you need to first project this data structure into a shape suitable to describe all of your dashboards at once: it must be a collection with one element per dashboard, rather than one element per folder. So that's where flatten
comes in:
locals {
dashboards = flatten([
for folder_key, folder in var.dashboard_folders : [
for dashboard in folder.dashboards : {
folder_key = folder_key
file = dashboard.file
overwrite = dashboard.overwrite
}
]
])
}
local.dashboards
is now a flat collection of all of the dashboards where each one knows the key of the folder it belongs to.
The only remaining requirement then is to project this into a map where each element has a unique key that Terraform can use to track which resource instance belongs to it. If you can guarantee that each dashboard definitely has distinct file
argument then that could be suitable, and I'm going to assume that's true for this example but if that's not true then you'll need to introduce another value to uniquely track each dashboard within each folder.
resource "grafana_dashboard" "all" {
for_each = {
for dashboard in local.dashboards :
"${dashboard.folder_key}:${dashboard.file}" => dashboard
}
folder = grafana_folder.dashboards[each.value.folder_key].id
config_json = file(each.value.file)
overwrite = each.value.overwrite
}
This uses the folder_key
attribute of each object to connect each dashboard back with its corresponding folder object, and so this should therefore describe a hierarchy in Grafana that's equivalent to the hierarchy described by var.dashboard_folders
.