In a project I’m working on in Terraform, I’ve got several feature flags in a module. These flags relate to whether this module should turn on a system in a cloud provider, or not, and looks like this:
variable "turn_on_feature_x" {
description = "Setting this to 'yes' will enable Feature X. Any other value will disable it. (Default 'yes')"
value = "yes"
}
variable "turn_on_feature_y" {
description = "Setting this to 'yes' will enable Feature Y. Any other value will disable it. (Default 'no')"
value = "no"
}
When I call the module, I then can either leave the feature with the default values, or selectively enable or disable them, like this:
module "region1" {
source = "./my_module"
}
module "region2" {
source = "./my_module"
turn_on_feature_x = "no"
turn_on_feature_y = "yes"
}
module "region3" {
source = "./my_module"
turn_on_feature_y = "yes"
}
module "region4" {
source = "./my_module"
turn_on_feature_x = "no"
}
# Result:
# region1 has X=yes, Y=no
# region2 has X=no, Y=yes
# region3 has X=yes, Y=yes
# region4 has X=no, Y=no
When I then want to use the feature, I have to remember a couple of key parts.
- Normally this feature check is done with a “count” statement, and the easiest way to use this is to use the ternary operator to check values and return a “1” or a “0” for if you want the value used.
Ternary operators look like this:var.turn_on_feature_x == "yes" ? 1 : 0
which basically means, if the value of the variableturn_on_feature_x
is set to “yes”, then return 1 otherwise return 0.
This can get a bit complex, particularly if you want to check several flags a few times, like this:var.turn_on_feature_x == "yes" ? var.turn_on_feature_y == "yes" ? 1 : 0 : 0
. I’ve found that wrapping them in brackets helps to understand what you’re getting, like this:(
var.turn_on_feature_x == "yes" ?
(
var.turn_on_feature_y == "yes" ?
1 :
0
) :
0
) - If you end up using a count statement, the resulting value must be treated as an 0-indexed array, like this:
some_provider_service.my_name[0].result
This is because, using the count value says “I want X number of resources”, so Terraform has to treat it as an array, in case you actually wanted 10 instead of 1 or 0.
Here’s an example of that in use:
resource "aws_guardduty_detector" "Region" {
count = var.enable_guardduty == "yes" ? 1 : 0
enable = true
}
resource "aws_cloudwatch_event_rule" "guardduty_finding" {
count = (var.enable_guardduty == "yes" ? (var.send_guardduty_findings_to_sns == "yes" ? 1 : (var.send_guardduty_findings_to_sqs == "yes" ? 1 : 0)) : 0)
name = "${data.aws_caller_identity.current.account_id}-${data.aws_region.current.name}-${var.sns_guardduty_finding_suffix}"
event_pattern = <<PATTERN
{
"source": [
"aws.guardduty"
],
"detail-type": [
"GuardDuty Finding"
]
}
PATTERN
}
resource "aws_cloudwatch_event_target" "sns_guardduty_finding" {
count = (var.enable_guardduty == "yes" ? (var.send_guardduty_findings_to_sns == "yes" ? 1 : 0) : 0)
rule = aws_cloudwatch_event_rule.guardduty_finding[0].name
target_id = aws_sns_topic.guardduty_finding[0].name
arn = aws_sns_topic.guardduty_finding[0].arn
}
resource "aws_cloudwatch_event_target" "sqs_guardduty_finding" {
count = (var.enable_guardduty == "yes" ? (var.send_guardduty_findings_to_sqs == "yes" ? 1 : 0) : 0)
rule = aws_cloudwatch_event_rule.guardduty_finding[0].name
target_id = "SQS"
arn = aws_sqs_queue.guardduty_finding[0].arn
}
One thing that bit me rather painfully around this was that if you change from an uncounted resource, like this:
resource "some_tool" "this" {
some_setting = 1
}
To a counted resource, like this:
resource "some_tool" "this" {
count = var.some_tool == "yes" ? 1 : 0
some_setting = 1
}
Then, Terraform will promptly destroy some_tool.this
to replace it with some_tool.this[0]
, because they’re not the same referenced thing!
Fun, huh? 😊
Featured image is “Sydney Observatory I” by “Newtown grafitti” on Flickr and is released under a CC-BY license.