Terraform number variable validation not throwing error - terraform

I have a network module with variable:
variable "subnetsCount" {
type = number
description = "Define amount of subnets between 2 min and 4 max"
validation {
condition = var.subnetsCount < 2 || var.subnetsCount > 4
error_message = "Variable subnetsCount should be between 2 and 4."
}
default = 2
}
I want to only allow a number value between 2 and 4. When I pass any value greater or smaller say 1 or 10 it doesn't throw any errors why?
1.For example I pass this to subnet:
module "network" {
source = "./network"
subnetsCount = 4
}
then type in terminal terraform apply, yet no errors thrown.

Your condition should be:
condition = var.subnetsCount >= 2 && var.subnetsCount <= 4

Related

Check if variable number is even or odd in Terraform

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

Replace terraform block with variable

How do I replace an entire block of a terraform script with a variable? For example,
resource "azurerm_data_factory_trigger_schedule" "sfa-data-project-agg-pipeline-trigger" {
name = "aggregations_pipeline_trigger"
data_factory_id = var.data_factory_resource_id
pipeline_name = "my-pipeline"
frequency = var.frequency
schedule {
hours = [var.adf_pipeline_schedule_hour]
minutes = [var.adf_pipeline_schedule_minute]
}
}
In the above sample, how to make the entire schedule block configurable using a terraform variable?
I tried this but it isn't working.
schedule = var.schedule
Nope, that is not possible as schedule is a block with arguments and it is not an argument itself. Maps are aggregate types and and they are made of primitive types (e.g., numbers in this case). A more detailed explanation on primitive and aggregate types with examples can be found in [1] (h/t: Matt Schuchard). In such cases, I prefer to do something like this:
variable "schedule" {
type = object({
hours = number
minutes = number
})
description = "Variable to define the values for hours and minutes."
default = {
hours = 0
minutes = 0
}
}
Then, in the resource:
resource "azurerm_data_factory_trigger_schedule" "sfa-data-project-agg-pipeline-trigger" {
name = "aggregations_pipeline_trigger"
data_factory_id = var.data_factory_resource_id
pipeline_name = "my-pipeline"
frequency = var.frequency
schedule {
hours = [var.schedule.hours]
minutes = [var.schedule.minutes]
}
}
[1] https://www.terraform.io/plugin/sdkv2/schemas/schema-types#typemap

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

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

Resources