Is there a way to use something like this in Terraform?
count = "${var.I_am_true}"&&"${var.I_am_false}"
This is more appropriate in the actual version (0.12.X)
The supported operators are:
Equality: == and !=
Numerical comparison: >, <, >=, <=
Boolean logic: &&, ||, unary !
https://www.terraform.io/docs/configuration/interpolation.html#conditionals
condition_one and condition two:
count = var.condition_one && var.condition_two ? 1 : 0
condition_one and NOT condition_two:
count = var.condition_one && !var.condition_two ? 1 : 0
condition_one OR condition_two:
count = var.condition_one || var.condition_two ? 1 : 0
The answer by deniszh is pretty close, but I thought I'd clarify it a bit and clean up the syntax.
In Terraform, a boolean true is converted to a 1 and a boolean false is converted to a 0. So if you have two boolean variables, var.foo and var.bar, you can represent AND using simple multiplication:
count = "${var.foo * var.bar}"
In the code above, count will be 1 only if var.foo AND var.bar are both true, as 1 * 1 is 1. In all other cases (1 * 0, 0 * 1, 0 * 0), you get 0.
To represent OR, you can take advantage of the function signum(x), which returns 1 if the x you pass in is a positive number, 0 if x is 0, and -1 if x is a negative number. Taking this into account, here is OR:
count = "${signum(var.foo + var.bar)}"
In the code above, count will be 1 if either var.foo OR var.bar is true and 0 only if both are false (signum(1 + 1) = 1, signum(1 + 0) = 1, signum(0 + 1) = 1, signum(0 + 0) = 0).
Note that to use the techniques above, you must take care to set the variables to a boolean and NOT a string. You want this:
variable "foo" {
# Proper boolean usage
default = true
}
NOT this:
variable "foo" {
# THIS WILL NOT WORK!
default = "true"
}
For more info on how to do a variety of Terraform conditionals, check out Terraform tips & tricks: loops, if-statements, and gotchas and Terraform: Up & Running.
Terraform 0.8 added first class support for conditional logic rather than the previous hacky workarounds.
This uses the classic ternary syntax so now you can do something like this:
variable "env" { default = "development" }
resource "aws_instance" "production_server" {
count = "${var.env == "production" ? 1 : 0}"
...
}
Now this will only create the production_server EC2 instance when env is set to "production".
You can also use it in other places too such as setting a variable/parameter like this:
variable "env" { default = "development" }
variable "production_variable" { default = "foo" }
variable "development_variable" { default = "bar" }
output "example" {
value = "${var.env == "production" ? var.production_variable : var.development_variable}"
}
One thing to be aware of is that Terraform actually evaluates both sides before then choosing the value used in the ternary statement rather than lazily evaluating just the side of the ternary that the logic will trigger.
This means you can't do something like this recent example of me trying to hack around an issue with the aws_route53_zone data source:
variable "vpc" {}
variable "domain" {}
variable "private_zone" { default = "true" }
data "aws_vpc" "vpc" {
filter {
name = "tag-key"
values = [ "Name" ]
}
filter {
name = "tag-value"
values = [ "${var.vpc}" ]
}
}
data "aws_route53_zone" "private_zone" {
count = "${var.private_zone == "true" ? 1 : 0}"
name = "${var.domain}"
vpc_id = "${data.aws_vpc.vpc.id}"
private_zone = "true"
}
data "aws_route53_zone" "public_zone" {
count = "${var.private_zone == "true" ? 0 : 1}"
name = "${var.domain}"
private_zone = "false"
}
output "zone_id" {
value = "${var.private_zone == "true" ? data.aws_route53_zone.private_zone.zone_id : data.aws_route53_zone.public_zone.zone_id}"
}
In the above example this will fail on the plan because either data.aws_route53_zone.private_zone.zone_id or data.aws_route53_zone.public_zone.zone_id is not defined depending on whether public_zone is set to true or false.
There's no binary type defined in Terraform. But you can try to use simple math
E.g.
OR equivalent
count = signum(${var.I_am_true} + ${var.I_am_false})
AND equivalent
count = ${var.I_am_true} * ${var.I_am_false}
Both will work if I_am_true == 1 and I_am_false == 0.
Didn't try both, though.
All the answers are enough but there is another case too.
For example, you have multiple environments like;
master
dev
staging
and you need to set value of OBJECT_ENABLE key based on these environments. You can do this like following:
OBJECT_ENABLE = var.app_env == "master" || var.app_env == "dev" ? "true" : "false"
According to the above condition value of the OBJECT_ENABLE key will be the following;
for master : OBJECT_ENABLE is true
for dev : OBJECT_ENABLE is true
for staging : OBJECT_ENABLE is false
Related
I need to validate if a provided variable number is even or odd in terraform, but I was unable to find a simple solution for it.
variable "my_number" {
type = number
validation {
condition = ???
error_message = "Only even numbers are accepted."
}
}
You can use the modulo operator [1]:
variable "my_number" {
type = number
validation {
condition = var.my_number % 2 == 0
error_message = "Only even numbers are accepted."
}
}
[1] https://developer.hashicorp.com/terraform/language/expressions/operators#a-b-4
I have previously created A type cloudflare record and would like to keep it while adding another one. So I defined a flag to just skip it.
resource "cloudflare_record" "cloudflare-a-record" {
count = var.flag != false ? 1 : 0
zone_id = var.zone_id
name = var.sub_domain
type = "A"
value = aws_eip.my_eip.public_ip
ttl = 1
proxied = false
}
resource "cloudflare_record" "vault-cloudflare-cname-record" {
count = var.flag == false ? 1 : 0
zone_id = var.zone_id
name = cloudflare_record.cloudflare-a-record.hostname
type = "CNAME"
value = aws_eip.my_eip.public_dns
ttl = 1
proxied = false
}
But Terraform deleted this resource with the following message:
cloudflare_record.vault-cloudflare-a-record[0] will be destroyed
(because index [0] is out of range for count)
Is there another way to ignore this resource? Or is the code wrong?
In this case, you cannot use the same flag for two different resources as the variable value will remain the same for both A and CNAME resources. The way I see it there are two possible options with the current code since you are using different conditionals (in A you use == and in CNAME you use !=):
var.flag == false ? 1 : 0 # A record
var.flag != false ? 1 : 0 # CNAME record
This means if the flag = false the A record will be created (as the count will be 1) and the CNAME record will not be created (as the count will be 0). Now, if the flag's value changes to true, then the A record will be deleted (as true == false will return false) and the CNAME record will be created (as true != false will be true). This means that the same flag should not be used for two different resources. You could use the same conditional for both resources, which means that both would be created/deleted together (not sure if that is what you want). A better way would be to define two variables, one for A and one for CNAME record:
variable "create_a_record" {
type = bool
}
variable "create_cname_record" {
type = bool
}
Then, in your code you would change the lines that use the count meta-argument to:
resource "cloudflare_record" "cloudflare-a-record" {
count = var.create_a_record ? 1 : 0
zone_id = var.zone_id
name = var.sub_domain
type = "A"
value = aws_eip.my_eip.public_ip
ttl = 1
proxied = false
}
resource "cloudflare_record" "vault-cloudflare-cname-record" {
count = var.create_cname_record ? 1 : 0
zone_id = var.zone_id
name = cloudflare_record.cloudflare-a-record.hostname
type = "CNAME"
value = aws_eip.my_eip.public_dns
ttl = 1
proxied = false
}
This way you can control if you want to have both created or only one. Also note the following:
count = var.create_a_record ? 1 : 0
count = var.create_cname_record ? 1 : 0
When variables are of type bool (true or false), when using them in conditionals, you do not have to check their equality against another boolean, as the left-most value will anyway be either true or false. So for example, if you set create_a_record = true, that would make the above expression:
count = true ? 1 : 0
and that would evaluate to count = 1. You could also set default values for variables, e.g., if you want to make sure the A record is always there, you can just do this:
variable "create_a_record" {
type = bool
default = true
}
[1] https://www.terraform.io/language/expressions/conditionals
I have a block of terraform code.
data "am_nodes" "tm_nodes" {
count = length(local.l_domains)
ay = local.l_domains[count.index].name
pol = local.am_pool[count.index].resource_id
host_reg = "${local.reg_k}${local.cte_env_map[local.environment]}-pd${local.pI}-mr*"
}
here I want to put a condition like if local.pI value is 0 then ignore entire host_reg
is there any way to achieve this ?
Yes you can do that with the ternary operator, like this:
data "am_nodes" "mt_nodes" {
count = length(local.l_domains)
ay = local.l_domains[count.index].name
pol = local.am_pool[count.index].resource_id
host_reg = local.pl == 0 ? null : "${local.reg_k}${local.cte_env_map[local.environment]}-dp${local.pI}-mr*"
}
I am trying to change the stack name based on the environment. I tried the following:
stack_name = "intl-${var.intl_region}-${var.intl_ctry}-${var.intl_env}-jk-${var.vanity_env == "-np" || var.vanity_env == "-dr"} ? "mstr" : "master"}"
but getting the following error:
Error: Error parsing
/build_workspace/workspace/GCM/PIPE_JENKINS/main.tf: At 33:25: nested
object expected: LBRACE got: ASSIGN
Can some please help me how can I change the stack name based on the environment?
It should be like:
if a == a || b ? "c ": "d"
so:
a==b==c else d
You've got the syntax for the ternary slightly wrong there:
stack_name = intl-${var.intl_region}-${var.intl_ctry}-${var.intl_env}-jk-${var.vanity_env == "-np" || var.vanity_env == "-dr" ? "mstr" : "master"}
Note that the whole ternary statement sits inside the ${} construct.
As a complete example:
variable "intl_region" {
default = "foo"
}
variable "intl_ctry" {
default = "bar"
}
variable "intl_env" {
default = "baz"
}
variable "vanity_env" {}
output "foo" {
value = "intl-${var.intl_region}-${var.intl_ctry}-${var.intl_env}-jk-${var.vanity_env == "-np" || var.vanity_env == "-dr" ? "mstr" : "master"}"
}
And running it:
$ TF_VAR_vanity_env=-np terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
foo = intl-foo-bar-baz-jk-mstr
$ TF_VAR_vanity_env=-dr terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
foo = intl-foo-bar-baz-jk-mstr
$ TF_VAR_vanity_env=quux terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
foo = intl-foo-bar-baz-jk-master
Is there a way to use something like this in Terraform?
count = "${var.I_am_true}"&&"${var.I_am_false}"
This is more appropriate in the actual version (0.12.X)
The supported operators are:
Equality: == and !=
Numerical comparison: >, <, >=, <=
Boolean logic: &&, ||, unary !
https://www.terraform.io/docs/configuration/interpolation.html#conditionals
condition_one and condition two:
count = var.condition_one && var.condition_two ? 1 : 0
condition_one and NOT condition_two:
count = var.condition_one && !var.condition_two ? 1 : 0
condition_one OR condition_two:
count = var.condition_one || var.condition_two ? 1 : 0
The answer by deniszh is pretty close, but I thought I'd clarify it a bit and clean up the syntax.
In Terraform, a boolean true is converted to a 1 and a boolean false is converted to a 0. So if you have two boolean variables, var.foo and var.bar, you can represent AND using simple multiplication:
count = "${var.foo * var.bar}"
In the code above, count will be 1 only if var.foo AND var.bar are both true, as 1 * 1 is 1. In all other cases (1 * 0, 0 * 1, 0 * 0), you get 0.
To represent OR, you can take advantage of the function signum(x), which returns 1 if the x you pass in is a positive number, 0 if x is 0, and -1 if x is a negative number. Taking this into account, here is OR:
count = "${signum(var.foo + var.bar)}"
In the code above, count will be 1 if either var.foo OR var.bar is true and 0 only if both are false (signum(1 + 1) = 1, signum(1 + 0) = 1, signum(0 + 1) = 1, signum(0 + 0) = 0).
Note that to use the techniques above, you must take care to set the variables to a boolean and NOT a string. You want this:
variable "foo" {
# Proper boolean usage
default = true
}
NOT this:
variable "foo" {
# THIS WILL NOT WORK!
default = "true"
}
For more info on how to do a variety of Terraform conditionals, check out Terraform tips & tricks: loops, if-statements, and gotchas and Terraform: Up & Running.
Terraform 0.8 added first class support for conditional logic rather than the previous hacky workarounds.
This uses the classic ternary syntax so now you can do something like this:
variable "env" { default = "development" }
resource "aws_instance" "production_server" {
count = "${var.env == "production" ? 1 : 0}"
...
}
Now this will only create the production_server EC2 instance when env is set to "production".
You can also use it in other places too such as setting a variable/parameter like this:
variable "env" { default = "development" }
variable "production_variable" { default = "foo" }
variable "development_variable" { default = "bar" }
output "example" {
value = "${var.env == "production" ? var.production_variable : var.development_variable}"
}
One thing to be aware of is that Terraform actually evaluates both sides before then choosing the value used in the ternary statement rather than lazily evaluating just the side of the ternary that the logic will trigger.
This means you can't do something like this recent example of me trying to hack around an issue with the aws_route53_zone data source:
variable "vpc" {}
variable "domain" {}
variable "private_zone" { default = "true" }
data "aws_vpc" "vpc" {
filter {
name = "tag-key"
values = [ "Name" ]
}
filter {
name = "tag-value"
values = [ "${var.vpc}" ]
}
}
data "aws_route53_zone" "private_zone" {
count = "${var.private_zone == "true" ? 1 : 0}"
name = "${var.domain}"
vpc_id = "${data.aws_vpc.vpc.id}"
private_zone = "true"
}
data "aws_route53_zone" "public_zone" {
count = "${var.private_zone == "true" ? 0 : 1}"
name = "${var.domain}"
private_zone = "false"
}
output "zone_id" {
value = "${var.private_zone == "true" ? data.aws_route53_zone.private_zone.zone_id : data.aws_route53_zone.public_zone.zone_id}"
}
In the above example this will fail on the plan because either data.aws_route53_zone.private_zone.zone_id or data.aws_route53_zone.public_zone.zone_id is not defined depending on whether public_zone is set to true or false.
There's no binary type defined in Terraform. But you can try to use simple math
E.g.
OR equivalent
count = signum(${var.I_am_true} + ${var.I_am_false})
AND equivalent
count = ${var.I_am_true} * ${var.I_am_false}
Both will work if I_am_true == 1 and I_am_false == 0.
Didn't try both, though.
All the answers are enough but there is another case too.
For example, you have multiple environments like;
master
dev
staging
and you need to set value of OBJECT_ENABLE key based on these environments. You can do this like following:
OBJECT_ENABLE = var.app_env == "master" || var.app_env == "dev" ? "true" : "false"
According to the above condition value of the OBJECT_ENABLE key will be the following;
for master : OBJECT_ENABLE is true
for dev : OBJECT_ENABLE is true
for staging : OBJECT_ENABLE is false