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
Related
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
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
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
I have a function in R that recursively builds a list of lists of varying depth. The output node may be be
node<-list(right=(0))
or
node<-list(right=list(right=0))
In Rcpp I would like to build a recursively deconstruct the list and return the integer member, 0 in this case.
My problem is checking if node has a named member right
library(Rcpp)
cppFunction(
'
int predict(List node){
if(node["right"]){
return predict(node["right"]);
}
else{
return node;
}
}
}
'
)
I've looked at Dynamic Wrapping to check type and use switch/case, but it doesn't seem to like named lists.
Trying to check for existence of a named value using a subset, e.g. if(node["right"]), will trigger the following error:
Error in predict_bad(node) : Not compatible with requested type: [type=list; target=logical].
To search a List or *Vector for a named element, use .containsElementNamed("name") member function.
For example, we have:
#include<Rcpp.h>
// [[Rcpp::export]]
Rcpp::List predict_list(Rcpp::List node){
// Check if name is present
if(node.containsElementNamed("right")) {
return predict_list(node["right"]);
}
return node;
}
Notice, here we are returning an Rcpp::List, e.g.
node1 = list(right = list(right = 0))
predict_list(node1)
# [[1]]
# [1] 0
To obtain only an integer, we must first subset the list and cast to the appropriate type. The second component, if we are tricky enough, we can allow Rcpp automagic to handle the conversion. (Thanks to Qiang for revealing the prior answer does not have to be positionally limited.)
#include<Rcpp.h>
// [[Rcpp::export]]
int predict_node_val(Rcpp::List node) {
// Check if name is present
if(node.containsElementNamed("right")) {
// Check if element isn't a list.
switch(TYPEOF(node["right"])) {
case REALSXP:
case INTSXP:
return node["right"];
default: // Keep going down the tree
return predict_node_val(node["right"]);
}
}
// Quiet compiler by providing a final output case
return -1;
}
Output:
node1 = list(right = list(right = 0))
node2 = list(right = 0)
predict_node_val(node1)
# [1] 0
predict_node_val(node2)
# [1] 0
There are a few assumptions made above... The first is we will always have a list architecture based on typing. The second is the value we want to retrieve is always listed as "right". The third
You can just get the names and check if there is an element right.
The following code should work:
library(Rcpp)
cppFunction(
'
int predict(List node) {
std::vector<std::string> list_names = node.names();
if (std::find(list_names.begin(), list_names.end(), "right") != list_names.end()) {
if (TYPEOF(node["right"]) == REALSXP) {
return node["right"];
} else {
return predict(node["right"]);
}
} else {
return -1;
}
}
'
)
The results
> node<-list(right=(0))
> predict(node)
[1] 0
> node<-list(right=list(right=0))
> predict(node)
[1] 0
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