Consistent types with optional attributes

Terraform Version: 1.4.6

I have a variable for AWS Security Group rules defined thus:

  type = list(object({
    rule_type                = string
    description              = string
    from_port                = string
    to_port                  = string
    protocol                 = string
    cidr_blocks              = optional(list(string))
    ipv6_cidr_blocks         = optional(list(string))
    source_security_group_id = optional(string)
  }))

If this variable is empty, I want to construct a default value that has the port set to the value of another variable. I attempted to do this with locals in the following manner:

locals {
  default_sg_rules = [
    {
      rule_type   = "ingress"
      description = "Allow listening port from instances"
      from_port   = var.port
      to_port     = var.port
      protocol    = "tcp"
    },
    {
      rule_type   = "egress"
      description = "Allow all out"
      from_port   = 0
      to_port     = 0
      protocol    = "-1"
    }
  ]
  sg_rules = var.sg_rules == null ? local.default_sg_rules : var.sg_rules
}

However, when I do this, I get the following error:

The true and false result expressions must have consistent types. The 'true' value is tuple, but the 'false' value is list of object.

I have tried using tolist to cast the variable as a list, but then get an error stating the false value has cidr_blocks, ipv6_cidr_blocks, and source_security_group_id defined and the true value doesn’t. Even though the value is either null or an empty list and those attributes are optional.

I got around it by defining another variable and doing some hacky ternaries to test for a specific string in the to_port and from_port but would like to make it work more elegantly.

Any thoughts?

HI @mck-rob-van-kleeck,

There’s a couple problems here which combined are probably making this harder for you.

The default_sg_rules local must be a tuple because it contains objects of different types. You have number literals in one value, but references to a string in the other. Making these consistent will help with align the types.

The other problem is that default_sg_rules still does not contain the same type as var.sg_rules, so the result is going to be inconsistent. You need to insert the missing attributes in the literal values as well:


locals {
  default_sg_rules = [
    {
      rule_type                = "ingress"
      description              = "Allow listening port from instances"
      from_port                = var.port
      to_port                  = var.port
      protocol                 = "tcp"
      cidr_blocks              = null
      ipv6_cidr_blocks         = null
      source_security_group_id = null
    },
    {
      rule_type                = "egress"
      description              = "Allow all out"
      from_port                = "0"
      to_port                  = "0"
      protocol                 = "-1"
      cidr_blocks              = null
      ipv6_cidr_blocks         = null
      source_security_group_id = null
    }
  ]
  sg_rules = var.sg_rules == null ? local.default_sg_rules : var.sg_rules
}

@jbardin Thanks for that. The *_port values were initially numbers all around, but I changed them to strings in my testing and failed to change them back.

I could have sworn I attempted this same configuration, and still had errors. However, this worked perfectly.

One thing I do notice is that if I set var.sg_rules to [] instead of null and try to use:

  sg_rules = var.sg_rules == [] ? local.default_sg_rules : var.sg_rules

then local.sg_rules ends up as []. Any insight into why that might be?

[] is an empty tuple, which is not going to be equal to the empty list of the variable’s type. You should never compare to [], use null to indicate the absence of a value, and check the length when you need to determine if a collection has zero elements.

Makes total sense. Thank you for your clarification and help!