Understanding attributes as blocks error for aws security group

I am trying to use the attributes as blocks feature in Terraform 0.12.

If I build the following script, Terraform will configure the security group.

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_security_group" "allow_tls" {
  name        = "allow_tls"
  description = "Allow TLS inbound traffic"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port = 443
    to_port   = 443
    protocol  = "tcp"
  }

  egress {
    from_port       = 0
    to_port         = 0
    protocol        = "-1"
    cidr_blocks     = ["0.0.0.0/0"]
  }
}

If I convert the ingress attribute into block format:

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_security_group" "allow_tls" {
  name        = "allow_tls"
  description = "Allow TLS inbound traffic"
  vpc_id      = aws_vpc.main.id

  ingress = [{
    from_port = 443
    to_port   = 443
    protocol  = "tcp"
  }]

  egress {
    from_port       = 0
    to_port         = 0
    protocol        = "-1"
    cidr_blocks     = ["0.0.0.0/0"]
  }
}

Terraform returns an error

Error: Incorrect attribute value type

  on main.tf line 15, in resource "aws_security_group" "allow_tls":
  15:   ingress = [{
  16:     from_port = 443
  17:     to_port   = 443
  18:     protocol  = "tcp"
  19:   }]

Inappropriate value for attribute "ingress": element 0: attributes
"cidr_blocks", "description", "ipv6_cidr_blocks", "prefix_list_ids",
"security_groups", and "self" are required.

Is this expected?

Hi @omerosaienni,

This is indeed the intended behavior, because when you specify it as an attribute the configuration language runtime must use normal type constraint checking to validate the value, rather than block/argument validation. In this case, the ingress argument is defined as being of an object type, and so the value you provide must conform to that type constraint. The type checker requires that an object can only match an object type constrant if it has a value for all of the attributes of that object type.

With that said, the intended purpose of the “attributes as blocks” behavior is not to write out actual values using that syntax. Instead, it’s to preserve a particular quirky design pattern that some providers were using in Terraform 0.11 and prior where a name that is normally considered to be a block would be treated differently if used as an attribute with an explicit empty list value:

  ingress = []

The resource types that follow this pattern generally consider the absence of any ingress blocks as a request to ignore whatever ingress blocks exist and let them be managed elsewhere. Setting it explicitly to [], then, is a loophole to allow the user to explicitly ask Terraform to destroy any existing ingress rules, rather than ignoring them. For that reason, in Terraform’s native syntax [] is the only value that should be used with the attribute syntax; actual ingress blocks should be specified using the block syntax as normal, which then allows Terraform to use the block/argument validation rules, rather than only the type checker.

(The rules are a little different for the JSON syntax due to the ambiguity of that syntax, but you’re using native syntax in this example so I won’t get into those details here.)