I’m learning HCL - I get the general idea of how it works, but I’m struggling with a conditional transform of a map of objects:
What I’d like to do is have the error message in the validation block below display a list of keys of the objects that failed the validation.
I get that what I think I want to be doing is have something that can transform a map of some objects, given a condition on the attributes of that object, and return me a list of keys from the map where that condition is true for each of those keys.
Then I can flatten that to a string for the error message.
OK - this example is not ground breakingly vital, but the aspect of the language is - I can see that understanding map transforms is going to be important - could anyone give me a hint please?
Many thanks for any pointers
variable "volumes" {
type = map(object({
name = string
region = optional(string, "uk-west")
size_gb = number
}))
default = {
test_volume = {
name = "test_volume"
size_gb = 5
},
}
validation {
condition = alltrue([
for k, v in var.volumes:
v.size_gb >= 10
])
error_message = "volumes.size_gb needs to be 10GB or larger"
}
}
d’oh! A short while after reading this, I stumbled across the operation I needed:
error_message = format( "Volumes validation failed, size_gb needs to be 10GB or larger. Failed volumes: [%s]", join ("][", [for k, v in var.volumes : k if v.size_gb < 10 ]))
But I will leave the post up in case there’s a better way…
Maybe you did it this way on purpose, but might also see if a set or list of objects might make more sense than a map of objects in this case. I think in either case (whether you do a map or a set or list), you could theoretically run into the issue of creating a duplicate object.
In any event, making some small tweaks, this seems to also work, though which is better is probably a judgement call:
variable "volumes" {
type = list(object({
name = string
region = optional(string, "uk-west")
size_gb = number
}))
default = [
{
name = "test_volume"
size_gb = 5
},
{
name = "test2"
size_gb = 6
},
{
name = "test3"
size_gb = 15
},
]
validation {
condition = alltrue([
for item in var.volumes:
item.size_gb >= 10
])
error_message = format("Volumes validation failed, size_gb needs to be 10GB or larger. Failed volumes: [%s]", join (",", [for item in var.volumes : item.name if item.size_gb < 10 ]))
}
}
You could also avoid having that 10 GB limit hard-coded in three places by using a local variable or variable for the size limit, and then do something like:
validation {
condition = alltrue([
for item in var.volumes:
item.size_gb >= local.size_limit
])
error_message = format(
"Volumes validation failed, size_gb needs to be %dGB or larger. Failed volumes: [%s]",
local.size_limit,
join (",", [for item in var.volumes : item.name if item.size_gb < local.size_limit ])
)
}
Thank you for your reply. Yes, I was toying with list vs map and initially a list seemed better - But I assumed I would also need the key to be able to reference this resource from another (“attach this volume to that compute instance”). That’s the next bit I am about to try…
I have indeed put the limit into a variable to as you suggested - and today, into a data module so it can live with all the other limits for the same provider. Been learning how modules work so I can keep this neat.
My underlying goal here is to learn TF, when I can afford to break stuff, then take my learning back to work and see if I can refactor a rather larger TF setup I inherited…
I’ll happy admit I don’t get on with declarative languages half as well as procedural ones, so this stuff is always a challenge at first.