-
Notifications
You must be signed in to change notification settings - Fork 9.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Experimental "templatestring" function #34968
Conversation
This doesn't actually do anything yet, but we'll make it do something in a future commit.
We previously updated the other two constructors to work this way, but missed this one. NewSetCmp is now consistent with NewSet and NewSetFunc.
This function complements the existing "templatefile" to deal with the unusual situation of rendering a template that comes from somewhere outside of the current module's source code, such as from a data resource result. We have some historical experience with the now-deprecated hashicorp/template provider and its template_file data source, where we found that new authors would find it via web search and assume it was "the way" to render templates in Terraform, and then get frustrated dealing with the confusing situation of writing a string template that generates another string template for a second round of template rendering. To try to support those who have this unusual need without creating another attractive nuisance that would derail new authors, this function imposes the artificial extra rule that its template argument may only be populated using a single reference to a symbol defined elsewhere in the same module. This is intended to entice folks trying to use this function for something other than its intended purpose to refer to its documentation (once written) and then hopefully learn what other Terraform language feature they ought to have used instead. The syntax restriction only goes one level deep, so particularly-determined authors can still intentionally misuse this function by adding one level of indirection, such as by building template source code in a local value and then passing that local value as the template argument. The restriction is in place only to reduce the chances of someone _misunderstanding_ the purpose of this function; we don't intend to prevent someone from actively deciding to misuse it, if they have a good reason to do so. This new function inherits the same restriction as templatefile where it does not allow recursively calling other template-rendering functions. This is to dissuade from trying to use Terraform templates "at large", since Terraform's template language is not designed for such uses. It would be better to build a Terraform provider that wraps a more featureful template system like Gonja if someone really does need advanced templating, beyond Terraform's basic goals of being able to build small configuration files, etc. Because this function's intended purpose is rendering templates obtained from elsewhere, this function also blocks calls to any of Terraform's functions that would read from the filesystem of the computer where Terraform is running. This is a small additional measure of isolation to reduce the risk of an attacker somehow modifying a dynamically-fetched template to inspire Terraform to write sensitive data from the host computer into a location accessible to the same attacker, or similar. This is currently only a language experiment and so will not yet be available in stable releases of Terraform. Before stabilizing this and committing to supporting it indefinitely we'll want to gather feedback on whether this function actually meets the intended narrow set of use-cases around dynamic template rendering.
Reminder for the merging maintainer: if this is a user-visible change, please update the changelog on the appropriate release branch. |
I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions. |
(This is motivated primarily by #30616, but since it's just an experiment for now merging this PR does not imply closing that issue.)
A long time ago #215 requested a way to parse and render a template from a separate file, and the Terraform of the day didn't have the necessary building blocks for that to be a function, and so I proposed the compromise that became the
hashicorp/template
provider and itstemplate_file
data source.Later on it became clear that Terraform resource types that take filesystem paths as arguments are problematic because the path gets saved as part of the plan, and so it's hard to then apply the plan on a different computer. As part of a general pattern of replacing filename arguments with arguments that take contents of files,
template_file
changed from acceptingfilename
to acceptingtemplate
, with the intention that it would be used like this to get the original functionality of rendering a template from a file:This had the unintentional side-effect of making it possible to write arbitrary expressions that return strings that the provider could parse as templates, rather than only using the
file
function. People began to use this data source not for its intended purpose of rendering content from external files, but for other less-common situations like fetching a template from an S3 bucket and then rendering it.Meanwhile, the
template_file
data source also grew to be a bit of an attractive nuisance, because those new to Terraform would search for ways to render templates in Terraform and would tend to turn up thetemplate_file
data source even though all they really needed was string template expressions, which are built in to the language. These new users would then get incredibly confused trying to write a string template that causes Terraform Core to generate a string template to parse tohashicorp/template
which is then finally rendered. This was made extra confusing when folks inevitably tried to use it to render shell scripts from a template, since they then had three levels of nested interpolation to worry about, making it even more confusing than the typical two levels that arise when trying to render a bash script using a normal template.Because of that experience, once it finally became possible to implement
templatefile
as a built-in function instead of as a provider with a data source, we switched it back to its original design of taking a filename directly and then reading the file from disk itself. That neatly dealt with the attractive nuisance because the function could no longer be accidentally misused by passing it an inline template. However, we then discovered the fact that some folks had usedtemplate_file
for dynamic template generation/rendering instead of just for loading templates from disk as originally intended, and they were naturally displeased that the new solution did not support their use-case.We've been at an impasse on this ever since, because our experience with
template_file
shows that a dynamic template rendering function is likely to be misunderstood by new authors as the way to render small templates (instead of using Terraform's built-in expression syntax) but a vocal minority of users had used dynamic template rendering in a load-bearing way and thus could not move away from the now-deprecatedhashicorp/template
provider.This PR introduces a language experiment that hopes to break that impasse by making a compromise: the function imposes artificial extra constraints on its template argument to try to proactively dissuade people from using it in situations where an inline template would be more appropriate.
With this experiment enabled, it becomes valid to -- for example -- retrieve a template from S3 and dynamically render it:
However, unlike most other functions it isn't valid to set the first argument to anything other than a single reference to a string from elsewhere.
In particular, it will reject attempts to write an escaped template inline even if the resulting template string is valid:
The goal of that restriction is to intentionally divert a new author from this mistake toward the better alternative of just writing the template expression directly:
This restriction does not actually block more questionable use of the function when it's intentional, because it's always possible to factor out the template literal into a local value and then refer to it:
The goal here is not to stop people from doing this sort of thing if they are particularly determined to do it for any reason, and only to reduce the chances of people doing it accidentally because they haven't yet learned enough about Terraform to know what other options they have.
Since this is just an experiment it does not yet have any documentation. My intention with proposing this is to include it in at least one alpha release and ask those who requested dynamic template rendering to try it and see whether it supports their use-cases, as a replacement for the deprecated
hashicorp/template
provider.If successful, I would expect then to write documentation for this function which emphasizes that this is a rather esoteric function that most authors won't need, and to link directly to the string templates documentation and to the
templatefile
function as more likely candidates for common use-cases.My hope then would be that if someone finds out about this function through some means other than our documentation and tries to use it for an unintended purpose, they'll encounter an error message advising them against that usage and will hopefully then refer to our documentation and learn about another feature that better suits their goals.
This should then, hopefully 🤞 serve as a suitable compromise that allows the power users with unusual needs to finally migrate away from
hashicorp/template
but without recreating the learning hazard that thetemplate_file
data source became.I had previously proposed #28700 as an attempt to narrowly solve a related use-case of allowing modules to take parsed-but-not-evaluated templates as arguments.
While meeting that use-case with strings containing template syntax is not ideal due to the need for the module user to write an inline escaped template, the situations where a template needs to be rendered somewhere other than where it's defined are also relatively rare and so I don't think it's justified to implement something as complicated as that earlier prototype for such a rare need. Therefore if we were to move forward with this I would expect to close that prototype PR and document how to solve that use-case as part of the
templatestring
function documentation, while also including a caution to module authors about the usabililty hazards of doing so.