condition checking in terraform - terraform

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*"
}

Related

How to prevent Terraform from deleting a resource when index [0] is out of range for count

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

Multiple "OR" condition in shorthand IF statement (Terraform) [duplicate]

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

Override variables in Terraform

I've started to use Terraform lately, and i need some help. I hope it's not too basic. I've the following Terraform data structure.
abc_template = {
a = var.a
b = var.b
c = var.c
d = var.d
....
....
....
k = var.k
}
And then i run:
resource "local_file" "aaa" {
count = 1
content = templatefile("${path.module}/templates/abc.tmpl", local.abc_template)
....
....
}
I need to create a new template (xyz_template), Which should be very similar to abc_template, While only a few variables will changed from the original template. What can i do instead of duplicating so many code lines? Is there a way to inherit abc_template, and just to override the relevant variables, Instead of creating xyz_template which might be very similar to abc_template?
Please advise.
You could use a map:
variable "global" {
type = "map"
default = {
name = "TEST"
addr = "Test123"
}
}
output "example" {
value = templatefile("${path.module}/web.tpl", {
global = var.global
})
}
template:
My name is ${global.name}.
And you can override values in a map using the merge() function.
You can use the merge function to produce a new map using a blend of elements from multiple maps.
For example:
locals {
abc_template = {
a = var.a
b = var.b
c = var.c
d = var.d
}
xyz_template = merge(
local.abc_template,
{
d = var.other_d
x = var.x
y = var.y
z = var.z
},
)
}
In the above example, local.xyz_template would have all of the same elements as local.abc_template except that d is overridden, and it would additionally have elements x, y, and z.

Is there a way AND/OR conditional operator in terraform?

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

linq inline string operation

I've a method that returns this exception.
LINQ to Entities does not recognize the method 'System.String stringCutter(System.String)' method, and this method cannot be translated into a store expression.
public List<ProductReqNoDate> GetRequestsQuery()
{
var query = (from r in db.talepler
select new ProductReqNoDate
{
talepEdenBirim = r.talepEdenBirim,
talepNo = r.talepNo,
talepTarihi = r.talepTarihi,
urunAdi = stringCutter((from p in db.urunler
where p.talepNo == r.talepNo
select p.urunAd).FirstOrDefault()) // <--This
}).AsQueryable();
return query.ToList();
}
string stringCutter(string txt)
{
return string.IsNullOrEmpty(txt) ? "" : txt.Length <= 30 ? txt : txt.Substring(0, 30) + "...";
}
when i use this string operations inline, it works. but its too long,
urunAdi = ((from p in db.urunler where p.talepNo == r.talepNo select p.urunAd).FirstOrDefault()).Length <= 30 ?
((from p in db.urunler where p.talepNo == r.talepNo select p.urunAd).FirstOrDefault()) :
((from p in db.urunler where p.talepNo == r.talepNo select p.urunAd).FirstOrDefault()).Substring(0, 30) + "..."
How can i refer (from p in db.urunler where p.talepNo == r.talepNo select p.urunAd).FirstOrDefault()) as txt maybe; so i can use stringCutter method inline like:
urunAdi=string.IsNullOrEmpty(txt) ? "" : txt.Length <= 30 ? txt : txt.Substring(0, 30) + "...";
Is there a way of shortening this code. , thanks
EF is trying to map the function stringCutter into SQL. Which it can't because it does not know about it. The inline version only uses functions that EF knows to map to SQL expressions/function.
IQueryable<> cannot convert your expression to be used with the back-end store. You need to bring a string into memory first, then you can use IEnumerable<>'s projection method to call your method, like this:
return (from r in db.talepler
select new // First, use an anonymous type to read the building blocks
{
r.talepEdenBirim,
r.talepNo,
r.talepTarihi,
urunAdiStr = (from p in db.urunler
where p.talepNo == r.talepNo
select p.urunAd).FirstOrDefault()
}
).AsQueryable()
.ToList() // Force the data into memory
.Select(anon => new ProductReqNoDate {
talepEdenBirim = anon.talepEdenBirim,
talepNo = anon.talepNo,
talepTarihi = anon.talepTarihi,
urunAdi = stringCutter(anon.urunAdiStr) // Now stringCutter is available
}).ToList();

Resources