Reputation: 18124
Is there a way to merge the following list of objects
variable "common_variables" {
type = list(object({ var_name = string, var_value = string, workspace_name = string }))
default = [
{
"var_name" = "location"
"var_value" = "West US"
"workspace_name" = "env-1"
}
]
}
variable "custom_variables" {
type = list(object({ var_name = string, var_value = string, workspace_name = string }))
default = [
{
"var_name" = "location"
"var_value" = "West Europe"
"workspace_name" = "env-1"
}
]
}
locals {
# custom_variables should replace common_variables
merged_variables = concat(var.common_variables, var.custom_variables)
}
output "merged_variables" {
value = local.merged_variables
}
Any custom_variables
should replace common_variables
that match on workspace_name
& var_name
.
So the output I want is:
merged_variables = [
{
"var_name" = "location"
"var_value" = "West Europe"
"workspace_name" = "env-1"
}
]
Upvotes: 6
Views: 42107
Reputation: 423
Not sure if it's what you want, but you can use map(string)
instead of list(object...))
.
Here's how:
variable "custom_variables" {
type = map(string)
default = {}
}
locals{
common_variables = merge(
{
Environment = local.env
Deployment_type = "terraform"
var_value = "West Europe"
},
var.custom_variables
)
}
Then, if you set the variable in terraform.tfvars
or any var file like var_value
, it should overwrite your commom_variables or add the new one.
For example
custom_variables={
"var_value" = "West US"
}
That's basically the same thing @martin-atkins said.
Upvotes: 0
Reputation: 1893
Use setunion
https://www.terraform.io/docs/language/functions/setunion.html
Usage:
locals {
all_users = setunion(var.restricted_users, var.admin_users)
}
Variable types used for this case
variable "restricted_users" {
type = set(object({
email = string
name = string
roles = set(string)
}))
default = []
}
variable "admin_users" {
type = set(object({
email = string
name = string
roles = set(string)
}))
default = []
}
Upvotes: 11
Reputation: 6600
Here is a solution that preserves the order (the essential trick is boldly stolen from Martin's answer)
locals {
common_variables_map = { for v in var.common_variables : "${v.workspace_name}/${v.var_name}" => v }
custom_variables_map = { for v in var.custom_variables : "${v.workspace_name}/${v.var_name}" => v }
common_keys = [ for v in var.common_variables : "${v.workspace_name}/${v.var_name}" ]
custom_keys = [ for v in var.common_variables : "${v.workspace_name}/${v.var_name}" ]
all_keys = distinct(concat(common_keys, custom_keys))
merged = [
for k in local.all_keys:
contains(common_variables_map, k) ? common_variables_map[k] : custom_variables_map[k]
]
}
As an alternative you might want to consider restructuring your variables a bit and to provide them through input files. If you provide parameters to terraform like this
terraform apply -var-file="common.tfvars" -var-file="custom.tfvars"
then variables declared in both files will be taken from the file listed last. For this to work you of cause need to break down the single list you have into individual parameters. In addition you might want to use a different file for different cases, e.g. use a different file depending on the workspace (it might be an option to wrap the call to terraform with a batch or shell-script or a make-file to make this more convenient).
Upvotes: 0
Reputation: 74064
The easiest way to do a merge-and-replace is to work with a map rather than a list, so I think I'd start by projecting the two lists into maps where the keys uniquely identify them by the values you want to use to decide what to override, using for
expressions:
locals {
common_variables_map = { for v in var.common_variables : "${v.workspace_name}/${v.var_name}" => v }
custom_variables_map = { for v in var.custom_variables : "${v.workspace_name}/${v.var_name}" => v }
}
We can then use the maps with merge
to get the overriding behavior you want, and then convert back to a list with values
if necessary:
locals {
merged_variables_map = merge(
local.common_variables_map,
local.custom_variables_map,
)
merged_variables = toset(values(merged_variables_map))
}
It's important to note that converting to a map like this will lose the original ordering of the given lists, because maps are not ordered. The items after merging may therefore be in a different order than the items in var.common_variables
or var.custom_variables
, and so I made that explicit by converting the result for local.merged_variables using toset
. If you consider that okay because the inputs are considered to be ordered anyway, you could make that more explicit to your module's caller by using a set type instead of a list type for the variables too:
variable "common_variables" {
type = set(object({ var_name = string, var_value = string, workspace_name = string }))
default = [
{
"var_name" = "location"
"var_value" = "West US"
"workspace_name" = "env-1"
}
]
}
variable "custom_variables" {
type = set(object({ var_name = string, var_value = string, workspace_name = string }))
default = [
{
"var_name" = "location"
"var_value" = "West Europe"
"workspace_name" = "env-1"
}
]
}
Sets are not ordered either, so marking a variable as having a set type lets the caller know that the order of the objects is not significant. Terraform can automatically convert from a list to a set (by discarding the ordering), so this won't change the syntax for using the module.
Upvotes: 4