Hi everyone,
In yesterday’s Terraform CLI v1.9.0-alpha20240501 there is an experimental implementation of the long-requested feature of treating a string value fetched from somewhere other than the local filesystem as a Terraform template, and rendering it with a provided set of template variables.
For (very contrived) example:
terraform {
# This experiment opt-in will be required as long
# as this remains experimental. If the experiment
# is successful then this won't be needed in the
# final release.
experiments = [template_string_func]
}
data "aws_s3_object" "example" {
bucket = "example-example-example"
key = "example.tmpl"
}
output "result" {
value = templatestring(data.aws_s3_object.example.body, {
foo = "bar"
})
}
Those who were following earlier discussion about this request may recall that we were concerned that adding this might reintroduce an earlier problem of inexperienced Terraform users finding this function and thinking that it’s the way to render templates, and having a very bad time trying to figure out how to escape a literal template to pass to the function. That usability hazard came up a surprising number of times with the earlier template_file
data source offered by the (now-deprecated) hashicorp/template
provider, and so we were cautious about repeating it.
This experiment therefore includes an attempted compromise: unlike most Terraform functions, this one is more “fussy” about what syntax it will accept for specifying its first argument. Specifically, it requires the first argument to always be a direct reference to some other named object in the current module. In the example above, that reference is data.aws_s3_object.example.body
.
In particular this means that it’s forbidden to write a template expression directly into the first argument, which would suggest that someone is trying to use this function unnecessarily:
# The following is not allowed, as a special case
templatestring("Hello, $${name}", {
name = "Martin"
})
This compromise is here only to help Terraform give better feedback and not to constrain what’s possible. If you really do want to construct a template string dynamically from parts for some reason, you can achieve that by factoring out the construction of the template into a separate local value, which we’re using as a heuristic for “I know what I’m doing, get out of my way”:
locals {
greeting_template = "Hello, $${name}"
}
output "result" {
value = templatestring(local.greeting_template, {
foo = "bar"
})
}
Since the most commonly-reported use-cases for this function were in fetching templates from elsewhere (e.g. data sources) and passing template strings through input variables – both of which involve references to specific single objects – we’re hoping that this compromise will give power users a useful tool while also narrowly avoiding a repeat of the earlier usability hazard.
Before we move forward with this in a stable release, we’d like to gather some examples of it being used both successfully and unsuccessfully, to hopefully give us a signal about whether this is a viable compromise.
If you are interested in using this new function, it would help if you could download the alpha release and try it with something as close as possible to the real situation where you’d use this function.
Since this is only an alpha release we do not recommend using this build in production. Instead, please limit your experiments to development environments that cannot affect any infrastructure whose downtime would be detrimental to you or your organization.
If you choose to participate and have feedback to share, it would be most helpful if you could reply to this topic and include all of the relevant parts of the configuration you tried with. In particular, we’d love to see:
- Whatever enclosing block you’re calling
templatestring
from, so that we can understand the specific use-case. (Specific, real-world examples are helpful for understanding the real impact of adding this feature.) - The configuration of whatever object you’ve referred to in the first argument to
templatestring
. If it’s an input variable to a shared module, it would be helpful to see an example of a typicalmodule
block using the module. - If you run into problems, the full text of any error messages Terraform returned, or any surprising output Terraform returned despite not returning an error.
Thanks in advance for any testing and feedback!