Create resource if variable equals something (with count)

I am trying to create Cloudwatch alarm but only if engine_mode variable equals to provisioned. length and lookup can’t work together. By default following statement create alarm regardless:
count = local.instance_count < 1 ? 1 : local.instance_count
while this wants to destroy it if it is set to provisioned:
count = local.instance_count < 1 || (local.instance_count >= 1 && local.engine_mode == "provisioned") ? 1 : 0

in locals I have:

locals {
  engine_mode          = lookup(var.configuration, "engine_mode", "provisioned")
  instance_count       = lookup(var.configuration, "instance_count", 1)
}

configuration variable is sourcing from tfvars per environment:

environment_vars = {
  db = {
    instance_count           = 0
    engine_mode              = "serverless"
  }
}

Hi @horizn,

It sounds like you have this code split across two modules, with one element from environment_vars being passed into the child module to become var.configuration.

If you haven’t already, I’d suggest writing an explicit object type constraint for that input variable so that Terraform has more information about what you’re intending to do, and so can hopefully give you some more direct feedback if something is missing:

variable "configuration" {
  type = object({
    engine_mode    = optional(string, "provisioned")
    instance_count = optional(number, 1)
  })
}

Using optional attributes with defaults as I’ve shown here will also remove the need to define local values to deal with the attributes not being set, because Terraform will automatically set them to the default values as the object enters the module.

So then your count would be something like this:

  count = var.configuration.instance_count < 1 ? 1 : var.configuration.instance_count

I’m suggesting this because I think the problem is in some of the intermediate code you haven’t shared. Perhaps the variable declaration has an incomplete type constraint and so your objects never have instance_count attributes, for example. I’m hoping that what I’ve suggested here will either make it work as you expected immediately or will cause Terraform to return an error message that will give a better clue as to what’s missing from your configuration.

Hi @apparentlymart

variable within rds module:

variable "configuration" {
  type        = map(string)
  default     = {}
}

locals are above.

aws_rds_cluster:

resource "aws_rds_cluster" "this" {
  cluster_identifier              = var.name
  engine                            = local.engine
  engine_version                  = local.engine_version
  engine_mode                     = local.engine_mode
...

aws_rds_cluster_instance:

resource "aws_rds_cluster_instance" "this" {
  count = local.instance_count

  engine                       = local.engine
  identifier                   = "${var.name}-${count.index}"
  cluster_identifier           = aws_rds_cluster.this.id
  db_subnet_group_name         = aws_db_subnet_group.this.name
  db_parameter_group_name      = element(aws_db_parameter_group.this.*.name, count.index)
  instance_class               = lookup(var.configuration, "instance_class", "db.t3.small")
  promotion_tier               = lookup(var.configuration, "promotion_tier", count.index * 10)

and then there is an alarm:

resource "aws_cloudwatch_metric_alarm" "cpu" {
  count = local.instance_count < 1 ? 1 : local.instance_count
  alarm_name                = local.instance_count < 1 ? "DB-${var.name}-CPU" : "DB-${var.name}-${count.index}-CPU"
  comparison_operator       = "GreaterThanOrEqualToThreshold"
  evaluation_periods        = lookup(var.configuration, "alarm_cpuutilization_evaluation_periods", "3")
  datapoints_to_alarm       = lookup(var.configuration, "alarm_cpuutilization_evaluation_periods", "3")
  period                    = lookup(var.configuration, "alarm_cpuutilization_period", "300")
  threshold                 = lookup(var.configuration, "alarm_cpuutilization_threshold", "85")

  dimensions = {
    DBInstanceIdentifier = local.instance_count < 1 ? var.name : "${var.name}-${count.index}"
  }
}

This doesn’t make any change.

Hi @horizn,

You are using this data structure as if it is an object with specific attribute names and each attribute having its own type, so you should declare the variable as being of an object type, not as map(string).

Here is the a declaration based on the attribute names I can see in your other snippets:

variable "configuration" {
  type = object({
    engine_mode    = optional(string, "provisioned")
    instance_count = optional(number, 1)
    instance_class = optional(string, "db.t3.small")
    promotion_tier = optional(number)

    alarm_cpuutilization_evaluation_periods = optional(number, 3)
    alarm_cpuutilization_period             = optional(number, 300)
    alarm_cpuutilization_threshold          = optional(number, 85)
  })
  default = {}
}

resource "aws_rds_cluster_instance" "this" {
  count = local.instance_count

  engine                       = local.engine
  identifier                   = "${var.name}-${count.index}"
  cluster_identifier           = aws_rds_cluster.this.id
  db_subnet_group_name         = aws_db_subnet_group.this.name
  db_parameter_group_name      = element(aws_db_parameter_group.this.*.name, count.index)
  instance_class               = var.configuration.instance_class
  promotion_tier               = coalesce(var.configuration.promotion_tier, count.index * 10)
  # ...
}

resource "aws_cloudwatch_metric_alarm" "cpu" {
  count = var.configuration.instance_count < 1 ? 1 : var.configuration.instance_count

  alarm_name                = var.configuration.instance_count < 1 ? "DB-${var.name}-CPU" : "DB-${var.name}-${count.index}-CPU"
  comparison_operator       = "GreaterThanOrEqualToThreshold"
  evaluation_periods        = var.configuration.alarm_cpuutilization_evaluation_periods
  datapoints_to_alarm       = var.configuration.alarm_cpuutilization_evaluation_periods
  period                    = var.configuration.alarm_cpuutilization_period
  threshold                 = var.configuration.alarm_cpuutilization_threshold

  dimensions = {
    DBInstanceIdentifier = var.configuration.instance_count < 1 ? var.name : "${var.name}-${count.index}"
  }
}

If you don’t properly describe the data types that you are working with then Terraform can make incorrect assumptions about what you intended, leading to confusing error messages or unexpected behavior.


Once you have your variable’s type constraint declared correctly, I think you’ll be able to incorporate the extra rule about engine_mode (that you described in your original post) like this:

  count = (
    var.configuration.engine_mode == "provisioned" ?
    max(var.configuration.instance_count, 1) :
    0
  )

This expression has two parts:

  • var.configuration_engine_mode == "provisioned" ? ... : 0 forces the result to be zero if the engine_mode is anything other than "provisioned".
  • max(var.configuration.instance_count, 1) chooses the largest value of the two arguments. That means that if var.configuration.instance_count is less than 1 then the result will be 1, because that will be the larger of the two values.

Notice that with the variable declared properly as having an object type it’s not necessary to use lookup, because Terraform guarantees that all of the declared attributes will always be present and will insert the default values for optional attributes automatically where necessary.