-
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
Intermediate variables (OR: add interpolation support to input variables) #4084
Comments
Intermediate variables have been a desire of mine too. In one particularly tricky spot (using the CIDR functions for network planning) I worked around this by creating a module whose entire purpose is to pass through a value verbatim: variable "value" {}
output "value" {
value = "${var.value}"
} which I then instantiated for each "intermediate variable" I needed: module "example" {
source = "..."
value = "${cidrsubnet(....blahblahblah)}"
}
/// ...
resource "aws_vpc" "example" {
cidr_block = "${module.example.value}"
} It's clunky, not least because |
I have spent the better of the last 3 days trying out terraform. In practically all my attempts, I have bumped against this shortcoming; input variables are totally static and cannot be baked from other input variables. Maintainability and readability is highly impacted by this limitation. Every time a calculation must be repeated across several resources, the DevOp is tempted to create a variable out of the calculation so that he can use just the variable and not having to "remember/copy" the calculation everywhere. As the repetitions increase, changing the calculation becomes more and more risky because you must catch it in all the files. I have seen some of the propositions (such as the data-driven configuration) and while they all look great to me, they don't look like trivial changes and thus, I am afraid they will come in very late. It looks to me that if the scope of this feature is limited to input variables, it would be a trivial and incredibly useful addition that would immediately raise user adoption and agility/flexibility. I am still trying to figure out how big of a deal breaker this is for our particular scenario. Here's a very simple scenario:
Maybe it should be a whole new type of resource to make it really easy on the interpreter, and also very easy to deprecate once the data-driven configs are production-ready:
Of course these should be included in "${var.*}" so maybe the variable needs a new parameter if it's hard to distinguish static values from interpolations when terraform reads the config:
In the above example, terraform knows that a variable without a default but that contains a "value" parameter must be expanded last through interpolation. Also, I guess if value is there, it should error out if a default is also included. Of course, only variables would be allowed into this new interpolation stage. I don't expect them to work if interpolated from modules or resources. |
In the mean time since I wrote my earlier comment I found a new workaround, that became possible because of the resource "null_resource" "intermediates" {
triggers = {
subnet_cidr = "${cidrsubnet(....blahblahblah)}"
}
}
resource "aws_vpc" "example" {
cidr_block = "${null_resource.intermediates.triggers.subnet_cidr}"
} This hack has some different pros and cons than the module hack I was using before:
A long-term solution to this could potentially build on the architectural change described in #4149. An implication of that change is making the concept of "computed" more pervasive in Terraform, so that the various complex rules around what kinds of interpolations are allowed in different contexts can potentially be simplified into the three cases "allows all interpolations", "allows all interpolations but causes a deferral when computed" and "allows only non-computed interpolations". |
The null_ressource looks great! Definitely opens the door to better maintainability and readability. The doc is is sort of hidden down there in the providers section, which I haven't had a need for so far. Maybe it deserves a quick mention in the variable interpolation doc? |
@apparentlymart amazing, it works! Would have never thought of using null_resource this way, thank you! |
@apparentlymart - having trouble grokking your workaround. In your example you reference ${null_resource.triggers.subnet_cidr}. Should that be ${null_resource.intermediates.triggers.subnet_cidr} ? I'm getting an error missing dependency: null_resource.triggers when I try and use your example, but an invalid syntax error when I include intermediates. Thanks. |
@darrin-wortlehock yes, sorry... you're right. I've corrected the example to use |
I use the template_file resource to do this...
Then you just use rendered to get the value later. Is there anything wrong with doing it this way? I haven't run into an issue in my limited use of it. |
@plombardi89 that's an interesting hack... you're actually creating a template with no interpolations in it, since the interpolations in your string are handled before the template is parsed, and then "rendering" that template. This has a similar effect to my As long as it doesn't include any interpolation markers then it should work just fine. |
@apparentlymart Good point. Not too worried about interpolation markers here, but I guess I could address it with an explicit vars blocks. |
would really like to interpolate input variables.+1 |
A welcome addition, indeed. |
+1 |
@apparentlymart : Referencing your null_resource example, what if there is more than 1
|
@geek876 that is a good question! Something like this may work, but I've not tested it yet: resource "aws_vpc" "example" {
cidr_block = "${lookup(null_resource.intermediates.triggers, var.var_to_choose_cidr)}"
} This presumes that the |
@apparentlymart. Thanks. However, this doesn't work. |
@geek876 I'm sorry, you're right. I'd briefly forgotten the shenanigans that Terraform does to make |
I've been abusing this a bit on some automation scripts. The problem is that you can't use the output of I'd find it very useful to have a let, that can only interpolate, just like variable "environment_names" { default = ["dev","uat"] }
let "environment_count" { value="${length(var.environment_names)}" }
// or even a shorter version
let "environment_count = "${length(var.environment_names)}"
resource "null_resource" "env" {
count="${let.environment_count}"
} That would make, combined with modules taking in lists and maps, my modules much easier to read, without any of the text processing tricks i currently use. |
I guess one could use a template resource here:
|
+1 Terraform desperately needs the ability to interpolate variables in variables. |
+1 |
FYI to those who have been using some of the workarounds and hacks discussed above: in Terraform 0.7.0 it will be possible to replace uses of |
The lack of intermediate variables or input vars interpolation can mitigated with use of the
|
The inability to create a variable whose value is computed from other variables was one of the first limitations that I encountered as a new user to Terraform. Just wanted to offer that perspective as someone just learning Terraform. |
This is also a blocker for me. I'm creating EC2 instances and want a default set of tags. These tags should have the owner, deploy type, and name which I want passed in as variables. Doh! |
Another request for this. A new user to terraform, I really want to use it as a replacement for all my custom Python / Troposphere code. My simple use case (similar to others above) is to define standard tags for resources (customer, project, environment) each of which will be defined as variables. I then want to create an additional 'name_prefix' variable which is a concatenation of these tags. Is this feature on the development pipeline? Would be good to know how best to deal with this right now. |
Running into a similar issue when trying to interpolate an intermediate variable, except I'm using it to reference data in a remote state: In remote state, I have the following data
Inside a module, I want a user to be able to pass the variable "subnet_type", then use it to build a call to the remote state for the subnet_id. I attempt to do this with the following syntax:
if I pass "subnet_type="mgmt", it outputs the following state with terraform plan.
Instead of the data that is referenced by
I figure trying to build the tag out of "format" is hacky, however I'm unsure if there is a supported method to substantiate the "format" function inside of the "data" function. Has anybody got any advice how to approach this issue? I've tried building it into a Null_reference and pass it, but haven't had much luck. edit:Solved for my use case, and documenting in case anybody is looking to do similar. I've changed my output for my subnets remote state to output the following:
Which can then be referenced as a map at I then reference them in the remote state using:
|
+1 |
Seems like Same code that worked in 0.9.4 is now giving me the following error:
Code: resource "null_resource" "puppet" {
triggers {
default_role = "${format("%s::%s", "${var.tier}", "${var.app_name}")}"
}
}
data "template_file" "userdata" {
template = "${file("${path.module}/userdata.tpl")}"
vars {
puppet_roles = "${var.default_puppet ? null_resource.puppet.triggers.default_role : var.puppet_roles}"
}
} Was this change intentional? |
Hi @missingcharacter! Sorry for that regression. It looks like you're hitting the bug that was fixed by #14454. That'll be included in the next release, which will come out very soon. In the mean time, there are some workarounds discussed in #14399, if staying on 0.9.4 for the moment isn't an option. |
@apparentlymart What is the technical difficulty in this issue? It seems to me current architecture can handle this without so much work, can't it? |
Indeed I think the work here is not too hard conceptually:
In this particular case the main blocker is not on this architectural stuff but more on the fact that making this sort of multi-layer change is something we prefer to do carefully and deliberately, and that anything involving configuration warrants a careful design process to ensure that what we produce can be clearly explained and understood. As I've been mentioning in other issues, we are currently in the early stages of some holistic discussion about next steps for the configuration language, and this is one of the use-cases being considered for it. We prefer to approach this holistically so that we can ensure that all of the configuration language features will meld well together to produce something that is, as a whole, as simple as it can be. A current sketch I have for the syntax here is as follows, but this is very early and not a firm plan: locals {
# locals are visible only within the module where they are defined
foo = "bar"
baz = "${local.foo}-baz"
}
module "example" {
source = "./example"
# can pass locals to other modules via variables
foo_baz = "${local.foo}-${local.baz}"
} Not sure yet if "local" is the right term here. Another one floated was Also leaning towards this being a separate construct -- rather than just allowing interpolation in variable defaults -- because that aligns well with the concepts of other languages, where function arguments (populated by the caller) are a distinct idea from local variables (populated by the function code itself). Again, this is a very early sketch, and not necessarily exactly what this will look like. I expect we'll have more to say on this subject once we get to a more firm place on this language design discussion. |
@apparentlymart Once again throughout explanation. Thank you for it. |
I used several bits of information from this thread to solidify an ansible inventory file output from terraform. Created a gist for anyone wanting to save some time: Looking forward to further updates on intermediate variables - the limitation on count not being able to be computed and not being able to create a map of lists were .. difficult to sort out |
meta.tf calls the JavaScript in "calculated_meta", and uses hack hashicorp/terraform#4084, "dennybaa commented on Jul 31, 2016" to create intermediate var meta. This is used to create the meta TXT record in meta.tf, and returned as output in outputs.I.tf. calculated_meta.result.serial is used in soa.tf. Replaced variable meta with additional_meta, and made it a map again, and removed variable serial. Added
Glad to see this getting addressed. It is obviously a missing feature users are asking for. I found this thread on yet another search of how to workaround this and stop repeating complicated expressions. I came up with the file_template work-around somewhat on my own with hints earlier on, but it is clunky. @apparentlymart you got through the noise to the real root of the problem with the locals solution, as it is not a need to alter "input variables", but rather a need to have scratch areas to do some manipulations within a module execution. Bummer it didn't make the 0.10 release, but I'll look for it following. |
@apparentlymart Please update us ASAP regarding when you expect this to make it to store shelves. This has been driving me insane. :) I guess I should throw my current use case out there. Who knows, maybe there is a better way to what I'm doing. :) I've started creating environments with the region encoded into them:
It would be lovely to split those into variables that can be used later. Otherwise I end up having to scatter |
@bhechinger I stumbled upon this a while ago by @apparentlymart. Didn't make the feature freeze for 0.10.0. #15449 |
@nbering oh, thanks for that link, I expect to see status there so I'll watch that instead! |
@bhechinger until that hits this is how I get around this issue currently
and you would use it like |
@JonCubed chapeau, very elegant -- one can argue that this is good enough |
Hi all! Indeed #15449 is the thing to watch to see when this lands. As you can see over there, we weren't able to get it reviewed and merged in time for the 0.10.0 cutoff but now that 0.10.0 is out, once the dust has settled on any urgent fixes we need to deal with, we should be able to get that in. |
TIL about the |
Hi everyone! I'm happy to announce that #15449 has just been merged for inclusion in the next Terraform release. This introduces a new concept called local values, which are distinct from variables. Whereas variables allow a parent module to pass values to a child, local values allow different parts of the same module to share a value by name. This new feature can be used both to avoid repetition of complex expressions and to factor out constants that will be used many times and that should not be overridden by a calling module. For example: # find the current AWS region
data "aws_region" "current" {
current = true
}
locals {
# constant lookup table for AMIs
regional_amis = {
us-east-1 = "ami-7b4d7900"
eu-west-1 = "ami-1446b66d"
ca-central-1 = "ami-c2c779a6"
}
# make the selected AMI available with a concise name elsewhere in this module
ami = "${local.regional_amis[data.aws_region.current.name]}"
}
resource "aws_instance" "example" {
instance_type = "t2.micro"
ami = "${local.ami}" # get the region-specific AMI from the local value above
} Local values, unlike variables, may contain interpolation expressions that refer to variables, resource attributes, etc, and may even refer to each other as long as there are no cyclic dependencies. Thanks to everyone in this issue for sharing their use-cases and for your patience while we got this designed an implemented. Given that this is a long-lived issue with many watchers, I'd like to request that if anyone finds bugs in this new feature once it's included in a release that they open a new top-level issue rather than leaving a comment here, since that way we can keep the notification spam to a minimum and also avoid "crossing the streams" of trying to debug potentially multiple issues in a single flat thread. Thanks! |
Not quite sure how to express this properly.
Input variables today cannot contain interpolations referring to other variables. I find myself bumping up against this now and then where I'd prefer to define a "default" value for an input variable based upon a composition of another input variable and some fixed string.
If we could interpolate values inside default properties of input variables OR, terraform supported some kind of internal transitive intermediate variable which only exists in order to act as a binding point between inputs and other interpolation expressions I could accomplish what I want without having a lot of redundancy in inputs.
Slightly related, but I also long for the ability to reference the input values of a module (not just the outputs) because this is often where I tend to create such bindings. Of course, I can propagate the input to the module all the way through the module and emit as an output but that gets quite repetitive and clunky after a while.
The text was updated successfully, but these errors were encountered: