-
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
Support use cases with conditional logic #1604
Comments
Here are 2 examples: https://gist.github.com/chrisferry/780140d709bfad51038c |
OMG, I could rant on about this issue for a long while. One litany of clear and concise use cases are found in implementing a concept as a terraform module. There are even community modules which exemplify this.. two modules for essentially the same thing, one provides an ELB, one does not. I tend to want to write terraform source as I do with Saltstack: as a giant jinja template. This affords me a whole lot of flexibility while ensuring the application (Salt) ends up with a machine-readable format. Terraform sort of has this type of pre-processing (with interpolation), but Saltstack's implementation leverages the concept of a pluggable renderer system.. this pre-processor renders the template to give to salt for processing. The renderer can be jinja, mako, or any one of a few different systems (made available as modules). The user/developer experience has been exceptional, and I am thankful for the power it lends, while still providing a declarative system. In contrast, using terraform has felt cumbersome and restrictive in the expression of one's needs (especially when I have gone to encapsulate a working POC into a module). Thank you for opening this dicussion! |
This is a bit of a stretch on the topic of this issue, but a couple of times I've found myself wishing for an iteration construct to allow me to create a set of resources that each map one-to-one to an item in a list. I've found and then promptly forgotten a number of examples (having dismissed them as impossible), but one that stayed in my mind was giving EC2 instances more memorable local hostnames and then creating Route53 records for each of them. resource "aws_instance" "app_server" {
# (...)
count = 5
provisioner "remote-exec" {
inline = [
"set-hostname-somehow appname-${join(\"-\", split(\".\", self.private_ip))}"
]
}
}
foreach "${aws_instance.app_server.*}" {
resource "aws_route53_record" "app_server-${item.private_ip}" {
zone_id = "${something_defined_elsewhere}"
name = "appname-${join(\"-\", split(\".\", self.private_ip))}.mydomain.com"
type = "A"
records = ["${item.private_ip}"]
}
} In my imagination, this creates a set of resources named things like While I was sketching this out I also came up with an alternative formulation that might end up leading to a simpler mental model: resource "aws_instance" "app_server" {
# (...)
count = 5
provisioner "remote-exec" {
inline = [
"set-hostname-somehow appname-${join(\"-\", split(\".\", self.private_ip))}"
]
}
child_resource "aws_route53_record" "hostname" {
zone_id = "${something_defined_elsewhere}"
name = "appname-${join(\"-\", split(\".\", parent.private_ip))}.mydomain.com"
type = "A"
records = ["${parent.private_ip}"]
}
} In this formulation, rather than generically supporting iteration over all lists we can just create a family of child resources for each "parent" resource. This feels conceptually similar to how per-instance provisioners work. I'm imagining that the child resource would interpolate like |
When your environment always looks the same (e.g. for a long standing/running app), the declarative language of terraform is expressive enough for most needs. However, my use case (frequent spin ups/tear downs of AWS VPCs of a similar general structure but with plenty of instance/subnet variation) means that terraform is not the "start" of my pipeline. I need to combine some configuration, logic, and templates on the fly each time to define my desired environment before terraform can ingest it. Right now, I'm planning on writing something custom (rake and erb?) to generate the needed terraform json and go from there because I don't see template-esque logic as a job for terraform's declarative config language, It already can use json as an incoming interchange format, so wouldn't it be more versatile to just leverage any of the existing template renderers out there as @ketzacoatl described? It would keep logic out of the configuration and keep the terraform language simple/clean. Appreciate this discussion and I'm open to learning about a better way to solve these kinds of problems. |
And I just realized why my suggested approach is flawed in some cases. The power of terraform is using derived data at runtime as variables elsewhere. If you render separately ahead of time in some cases, you lose that. The |
The power in terraform, IMHO, is that we have the flexibility to choose how much we do before , in TF, and after TF runs. In most cases, you need to start working with some wrapper to create the JSON you want, when the existing interpolation syntax won't get you what you want. I personally, have avoided this as I would rather keep the before limited to a CI / admin who defines details in the |
Last December in AMS Dockercon I had asked @mitchellh if there would be any plans to add such logic control in Terraform DSL, he explained his view on keeping Terraform as simple as possible maybe adding a little algebraic functionality (which has already been merged) and standard string operations. I am glad there are second thoughts on this, but it is a decision that needs a lot of input and real world justification, so thatnks @phinze for bringing this up. I have two real world scenarios I 've faced where an
|
Other scenario,
|
A simple scenario - Optionally use Atlas artifacts to deploy infrastructure - and fallback to just AMI strings (if not using atlas). Something like -
|
👍 I like the idea of even a simple
There are lots of times where I'm building the same infrastructure for dev/stage/prod but don't need things in dev/stage as are needed in prod. |
Even simpler, doesn't break the current syntax and prevents complexity (Inspired by Ansible):
|
@franklinwise that's pretty nice! I like that it preserves the declarative syntax. |
Preserving the declarative syntax seems like priority number one in my opinion. I like the 'when' solution. 👍 |
Yes, I too like this suggested syntax.. |
This seems inadequate as it only supports the conditional creation of resources. In my usage, I've found myself wanting conditional expressions (not statements) several times. The use case has been creating heterogenous groups of ec2 instances. I.e., I'd like to create 12 instances where the first 4 are of type m3.large and the remaining are c4.xlarge. I've hacked around this for the time being by using a lookup table and creating an entry for each index, but it's pretty nasty. For clarification, what I'm looking for is something like:
|
It might make sense to consider these use cases separately, if only for the goal of getting the simpler implemented faster than the more complicated and nuanced case. |
Expanding on the referenced aws spot instances - I'd like to be able to express "spin up N instances in at least M availability zones, bidding the current bid price * X, But fallback to on-demand for any az where the bid price is > Y, or spot instance is unavailable". That seems complex (I have implemented it via a custom ruby script now), so maybe it's less a "we need conditionals" argument and more a "it would be nice if the aws provider abstracted instance types and did the "right thing"). Maybe the whole conditional thing could be handled by some sort of "call to external" hook to work the logic? It's a cop-out, in a way, but perhaps the most flexible in the end. If the suggested "when" could call an arbitrary script, with current context, you could kinda wedge in anything you needed as a shim, in only the spots where declarative is problematic. Can terraform do external calls like that already? I came into this from the side googling for spot instance solutions and seeing if terraform had support, so I'm not super familiar with current functionality there. |
👍 Really like @franklinwise suggestion. I think this is the way to go if control statements are added. |
Just to add another use case similar to rafikk's example. It would be more flexible if I could do the following: |
@RJSzynal We have the same thing in our configuration. I really think support conditional logic inside of interpolation blocks is a must. |
tl;dr my five cents is that I'm for the high-level idea of better conditional / looping support, but would like to stick to keeping it declarative. I like the child resource proposal by @apparentlymart (far more than the foreach, which feels too imperative). That being said, for that specific example I feel like the resource isn't really a child. What may be better is a top-level "group" construct, which supports I'm also +1 for some basic conditional structure that gets interpolated. I don't like the imperative style "if then else" that's been recommended. I'd rather stick with a more procedural style thing that we already have, e.g., I agree with @rafikk with the lookup table being too verbose:
I'm also not fond of the if block surrounding a resource, as things start looking too imperative. A better approach to stick with the declarative format would be something like:
I think setting |
👍 |
@thegedge sorry I just noticed your response to my earlier example even though you posted it a while back. The special thing I was imagining for "child resources" is that they'd always have an implied dependency on their parent, so if you delete the instance then that always deletes the record along with it... perhaps "child" is the wrong word, but I was going for "this thing only exists to support the thing it's nested inside". |
@apparentlymart Ah yes, that would be nice. Could that also be solved with a "also destroy dependent resources" flag to Anyways, I'm also going to add in an example from my team, since I didn't do that in my last comment and that's what @phinze was interested in seeing! We want our app developers to build things on top of a terraformed "cloud", but we want them to be able to this with a minimal configuration that doesn't require much/any terraform knowledge. Many of our apps follow a similar recipe: rails app that sometimes needs redis, memcache, an S3 bucket, a place to run the rails app, and/or a database, maybe some other things. We'd like to construct a module that allows our app developers to conditionally select what they need for their app. It would look something like this from the app developer's perspective:
|
awaiting for TF .8 |
IT'S HERE! YAHOOOOOOOOOOOOOOOOOOO!!! |
Closing this as it has landed in Terraform 0.8.x :) |
@stack72 I assume you refer to the new ternary operator? While that covers many of the use cases, it won't help with optionally specifying attributes, or "sub hashes" inside the resources. For example optionally declaring multiple listeners in an ELB. Would be nice to have an issue for tracking those, too. But great work, the ternary operator for sure makes things easier especially in generic modules! |
Given the age and large potential scope of this issue, I guess it makes sense for us to close it out and capture some more-specific use-cases in other issues, since this one was originally motivated with just collecting information on patterns people were using, and didn't have a tight enough scope that it would ever likely be closed by any real implementation work. The trick there, of course, is that we previously intentionally consolidated several issues describing other use-cases over here, so there's a bunch of closed issues linked from here that capture some real use-cases we presumably don't want to "lose". FWIW though, I understand that improved conditional stuff is still on the radar even if this particular issue is closed. |
@oillio yeah, as far as I understand, that would cover the sub-resource/block case! Even after that one use case would be conditional individual attributes, although I haven't personally been so much in need of those compared to sub-resources. |
Trying to get consistent output of a module using count to dictate the version of a resource to create. module code:
failing output code:
Apparently if any ternary values don't exist, the entire ternary will fail silently. the output above does not display at all. However if I'm using lifecycle_enabled = false and change the output to:
Here because all these values exist the conditional parses correctly. Is this expected behavior? Ultimately I'm looking for a way to consolidate the output so if I'm overlooking a more direct method that'd be great. |
Hi @aaroncaito! That issue is being tracked over in hashicorp/hil#50. It's something I'd like to fix before too long since I know it's annoying and makes the conditional operator not as useful as it could otherwise be. It requires some adjustment to how the interpolation syntax evaluator works, so it's not a quick fix but something that is on my radar to take a look at. |
thanks @apparentlymart in this case it'd be even better if we didn't need unique resource names when only one will be created. my |
My workaround for the above S3 with LC enabled/disabled use case:
Edit: the coalesce is superfluous:
However, this usage of
https://www.terraform.io/docs/configuration/interpolation.html#join_delim_list_ |
@pdecat your workaround is similar to another workaround I saw recently:
|
I have a case where I want to be able to define the AWS instance type in a variable, and - along side it - the size of the root volume. Since on AWS only EBS volumes can have a size, this becomes a problem. If I specify the instance type as a type that doesn't have EBS backed storage then no value will work for the volume_size, and AWS will always complain that you cannot do that. That is...
(applies to both aws_instance, and aws_opsworks_instance, although it's strange that they don't share the code) My workaround? Change the terraform code so that if the parameters within |
@gerph I am likely to do a similar hack to allow a single aws_launch_configuration to either have |
I have two variables lob and role. As of now it is like this: target = "${var.role == "hzc" ? "TCP:11410" : "HTTP:80/"}" But my requirement is: if lob==anything and role==hzc then "TCP:11410 else if lob==kolk and role==kata then "HTTPS:443/index.html" else "TCP:8443" How to map this in conditional statement? |
@arkaprava-jana I assume by "anything" you mean "is defined" and not the text "anything"? You could nest conditionals to get something like:
|
Yes you are right. Looking for nested conditional example only. |
I'm trying to get clever with the ternary operator and conditionals, but I think I have the wrong idea about them. I have the following code in an ec2 module I've built on top of the ec2_instance resource. I have a var I've created called "total_instances". I've tried the following:
I've also tried:
If the count of instances I pass to the module = 1, then I just want the instance to be tagged as "server-ops", but if the count I pass from my main.tf to the module is greater than 1, I want it to assign "server1-ops" as the name. Any help or clarification would be very helpful, thank you! |
@armenr I think the problem is yourr syntax... it should be A format() example would be: |
@jaygorrell - Thanks! I'll fiddle with this and let you know how that works out. Thank you! |
I don't know if I am late to the party, but I am currently facing similar issues to what is being discussed here: One use case that does not work with the current implementation is assigning pre-allocated Floating IPs vs allocating them. The scenario is like this: Production has a fixed amount of servers with pre-allocated IP addresses that need to remain the same all the time. Testing has the same infrastructure but can have more/less servers and Floating IPs are allocated and released on demand. (I use openstack as an example here, but I imagine there are quite many parallel scenarios regardless of platform) resource "openstack_networking_floatingip_v2" "tf_instance_ips" {
count = "${length(var.floating_ips) > 0 ? 0 : var.count}"
pool = "${var.floating_ip_pool_name}"
}
resource "openstack_compute_floatingip_associate_v2" "tf_instance_ips" {
count = "${var.count}"
floating_ip = "${length(var.floating_ips) > 0 ? element(var.floating_ips, count.index) : element(openstack_networking_floatingip_v2.tf_instance_ips.*.address, count.index)}"
instance_id = "${element(openstack_compute_instance_v2.tf_instance.*.id, count.index)}"
} This will throw if "${length(var.floating_ips) > 0}" {
resource "openstack_compute_floatingip_associate_v2" "tf_instance_ips" {
count = "${var.count}"
floating_ip = "${element(var.floating_ips, count.index)}"
instance_id = "${element(openstack_compute_instance_v2.tf_instance.*.id, count.index)}"
}
}
if "${length(openstack_networking_floatingip_v2.tf_instance_ips.*.address) > 0}" {
resource "openstack_networking_floatingip_v2" "tf_instance_ips" {
count = "${length(var.floating_ips) > 0 ? 0 : var.count}"
pool = "${var.floating_ip_pool_name}"
}
resource "openstack_compute_floatingip_associate_v2" "tf_instance_ips" {
count = "${var.count}"
floating_ip = "${element(openstack_networking_floatingip_v2.tf_instance_ips.*.address, count.index)}"
instance_id = "${element(openstack_compute_instance_v2.tf_instance.*.id, count.index)}"
}
} PS: The workaround for the above use case is to use coalescelist function, but it is sub-optimal for example in cases where type of the input value changes conditionally or when depending on condition different sets of resources need to be spawned, while remaining resources are not different. resource "openstack_compute_floatingip_associate_v2" "tf_instance_ips" {
count = "${var.count}"
floating_ip = "${element(coalescelist(var.floating_ips, openstack_networking_floatingip_v2.tf_instance_ips.*.address), count.index)}"
instance_id = "${element(openstack_compute_instance_v2.tf_instance.*.id, count.index)}"
} |
how do i get something like this in terraform |
@mthak Something similar to that will be supported by Terraform 0.12 which will be released later this summer: https://www.hashicorp.com/blog/terraform-0-1-2-preview |
I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. |
It's been important from the beginning that Terraform's configuration language is declarative, which has meant that the core team has intentionally avoided adding flow-control statements like conditionals and loops to the language.
But in the real world, there are still plenty of perfectly reasonable scenarios that are difficult to express in the current version of Terraform without copious amounts of duplication because of the lack of conditionals. We'd like Terraform to support these use cases one way or another.
I'm opening this issue to collect some real-world example where, as a config author, it seems like an
if
statement would really make things easier.Using these examples, we'll play around with different ideas to improve the tools Terraform provides to the config author in these scenarios.
So please feel free to chime in with some specific examples - ideally with with blocks of Terraform configuration included. If you've got ideas for syntax or config language features that could form a solution, those are welcome here too.
(No need to respond with just "+1" / :+1: on this thread, since it's an issue we're already aware is important.)
The text was updated successfully, but these errors were encountered: