Skip to content

enabled parameter to avoid logical statements in count #21953

@romachalm

Description

@romachalm

Terraform v0.12.2

Use-cases

We often use count to disable a resource (above all in modules) from a boolean var, with statements like count = var.enabled ? 1 : 0 or count = var.enabled ? 1 : length([some list of resources or datasources])

Here are among others some code snippets from terraform-aws-vpc module :

resource "aws_subnet" "private" {
  count = var.create_vpc && length(var.private_subnets) > 0 ? length(var.private_subnets) : 0

  vpc_id            = local.vpc_id
  cidr_block        = var.private_subnets[count.index]
  availability_zone = element(var.azs, count.index)

  tags = merge(
    {
      "Name" = format(
        "%s-${var.private_subnet_suffix}-%s",
        var.name,
        element(var.azs, count.index),
      )
    },
    var.tags,
    var.private_subnet_tags,
  )
}

resource "aws_network_acl" "public" {
  count = var.create_vpc && var.public_dedicated_network_acl && length(var.public_subnets) > 0 ? 1 : 0

  vpc_id     = element(concat(aws_vpc.this.*.id, [""]), 0)
  subnet_ids = aws_subnet.public.*.id

  tags = merge(
    {
      "Name" = format("%s-${var.public_subnet_suffix}", var.name)
    },
    var.tags,
    var.public_acl_tags,
  )
}

resource "aws_network_acl_rule" "public_inbound" {
  count = var.create_vpc && var.public_dedicated_network_acl && length(var.public_subnets) > 0 ? length(var.public_inbound_acl_rules) : 0

  network_acl_id = aws_network_acl.public[0].id

  egress      = false
  rule_number = var.public_inbound_acl_rules[count.index]["rule_number"]
  rule_action = var.public_inbound_acl_rules[count.index]["rule_action"]
  from_port   = var.public_inbound_acl_rules[count.index]["from_port"]
  to_port     = var.public_inbound_acl_rules[count.index]["to_port"]
  protocol    = var.public_inbound_acl_rules[count.index]["protocol"]
  cidr_block  = var.public_inbound_acl_rules[count.index]["cidr_block"]
}

I have also witnessed some int variable used as count = var.enabled * length([some list of resources or datasources]).

It makes the triggers more complex to understand, as count actually embeds both a boolean expression to enable the provisionning, together with the number of instances of the resource. It sounds like a twist of the initial expected count usage.

Attempted Solutions

I have thought initially about having count accepting booleans by automatically converting true->1 and false->0.
I had a quick discussion with go-cty developer that explained to me the intentional decision not to convert bool to number to avoid confusions, and indeed, I am aligned that the code should stay readable by segregating the scopes.
Therefore, I attempt this proposal below.

Proposal

Introducing a new parameter enabled in resources accepting bool values or logical statements to provision the resource (default) or not, independently from count value (which can be 0 by the way).
It will increase the readibility of the code by avoiding sometines complex conditional operators ?:

The previous examples would become :

resource "aws_subnet" "private" {
  enabled = var.create_vpc
  count   = length(var.private_subnets)

  vpc_id            = local.vpc_id
  cidr_block        = var.private_subnets[count.index]
  availability_zone = element(var.azs, count.index)

  tags = merge(
    {
      "Name" = format(
        "%s-${var.private_subnet_suffix}-%s",
        var.name,
        element(var.azs, count.index),
      )
    },
    var.tags,
    var.private_subnet_tags,
  )
}

resource "aws_network_acl" "public" {
  enabled = var.create_vpc && var.public_dedicated_network_acl && length(var.public_subnets) > 0

  vpc_id     = element(concat(aws_vpc.this.*.id, [""]), 0)
  subnet_ids = aws_subnet.public.*.id

  tags = merge(
    {
      "Name" = format("%s-${var.public_subnet_suffix}", var.name)
    },
    var.tags,
    var.public_acl_tags,
  )
}

resource "aws_network_acl_rule" "public_inbound" {
  enabled = var.create_vpc && var.public_dedicated_network_acl && length(var.public_subnets) > 0 
  count   = length(var.public_inbound_acl_rules)

  network_acl_id = aws_network_acl.public[0].id

  egress      = false
  rule_number = var.public_inbound_acl_rules[count.index]["rule_number"]
  rule_action = var.public_inbound_acl_rules[count.index]["rule_action"]
  from_port   = var.public_inbound_acl_rules[count.index]["from_port"]
  to_port     = var.public_inbound_acl_rules[count.index]["to_port"]
  protocol    = var.public_inbound_acl_rules[count.index]["protocol"]
  cidr_block  = var.public_inbound_acl_rules[count.index]["cidr_block"]
}

Another use case I often see is to to provision some resources only for prod configurations (audit logs buckets, special routes or firewall rules):

enabled = var.env == "production" || var.env == "qa"

enabled shall preced the interpretation of count interpretation.

IMO, it will make the code more readable by explicitly describing what may prevent the provisionning of count resources. It will also allow to clearly distinguish bool from int variables.

References

I haven't found any ticket on that subject. Sorry if I have missed one.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions