Hi @kstephenson,
I think what you are asking here is how to merge the elements in var.user_data_vars
with the fixed set you’ve assigned to vars
in your example, so that the template can use both.
The most direct answer to your question is that you can use the merge
function to produce a mapping that has the elements from two other maps, like this:
vars = merge(
var.user_data_vars,
{
datacenter = var.short_region
# etc, etc
},
)
Note that you can decide whether the predefined elements will take priority over the custom ones or vice-versa by changing the order of arguments to merge
. Later arguments take priority over earlier ones in the event that multiple maps have elements with the same key.
With that said, I have some additional notes here that might be useful depending on what your underlying “real world” problem is here, as opposed to the specific solution you’ve decided to use within Terraform.
The first thing I want to note is that the template_file
data source has been deprecated since Terraform v0.12 and continues to exist only for backward compatibility with modules written for Terraform v0.11 and earlier. Terraform now has the templatefile
function built in to the Terraform language, and so you can use it to render templates from external files without depending on an external provider and in a way that integrates fully with the rest of the Terraform language, because it’s not interpreting the template in a separate plugin program.
Here’s an equivalent to what you showed in your example, modified as I suggested above, using the templatefile
function instead:
locals {
dba_user_data = templatefile(
"${path.module}/../../../dev/services/${var.db_type}-cluster/templates/${var.db_type}-user-data.tpl",
merge(
var.user_data_vars,
{
datacenter = var.short_region
# etc, etc
},
)
)
}
This uses a local value to assign the template result to a symbol you can use elsewhere in the module, so you can replace references like data.template_file.dba_user_data.rendered
with local.dba_user_data
instead, with the same effect. If you only use this template result in one place in your module, you might instead choose to just inline that templatefile
function call directly in that argument and not declare a separate symbol at all, but that’s a subjective style tradeoff:
resource "aws_instance" "example" {
# ...
user_data = templatefile(
# (...as above...)
)
# ...
}
The other thing I wanted to note is that in your example you have it rendering a template that has a hard-coded path that seems to traverse outside of the module. That’s a pretty atypical pattern because it breaks the expected encapsulation of a module by making it depend on the directory structure around it. Since it seems like your goal here is to let the caller of the module provide a custom template to go with their custom template variables, I would suggest making the path to the templates also be a variable, either by passing in a path to an exact file or by passing in a prefix that you would then append your expected subdirectory structure to:
variable "template_path_prefix" {
type = string
}
locals {
dba_user_data = templatefile(
"${var.template_path_prefix}/${var.db_type}-cluster/templates/${var.db_type}-user-data.tpl",
merge(
# (...as above...)
)
)
}
variable "dba_user_data_template_file" {
type = string
}
locals {
dba_user_data = templatefile(
var.dba_user_data_template_file,
merge(
# (...as above...)
)
)
}
Which of the above to use will probably depend on whether you have other templates under the given prefix that this module also uses, but either way the benefit of this is that the calling module can use it’s own path.module
to select a path within its own area of influence, and thus avoid the surprising encapsulation break:
module "example" {
# ...
template_path_prefix = "${path.module}/services"
}
What you wrote will work fine as long as the directory structures line up, but I’m suggesting this because if you have other folks working on this configuration in future who already have some Terraform experience I expect they would find it surprising for a module to internally assume a particular directory structure above its own directory, rather than being able to see the path prefix or exact filename pass explicitly through the module input variables.