Legacy Terraform: How can I map a list of strings to a list of dictionaries for network acl rules?

I am trying to write a terraform file where I pass in a set of CIDRs as a variable, and the default network acl for the vpc allows port 22 traffic from those ACLs. However, I keep getting an error with my solution where terraform claims I am not passing it a list. Although, when I send it to outputs, it is a list, so I am not sure what it is complaining about.

I know there are ways to do this in Terraform 0.12+, but I am using 0.11 since I am working in someone elses code base and while I’d like to upgrade, the project doesn’t call for that much work.

Here is my code.

provider "aws" {
  region = "us-east-1"
}

variable "allow_cidrs" {
  type    = "list"
  default = ["55.55.55.55/32", "44.44.44.44/32"]
}

data "null_data_source" "acl_rules" {
  inputs = {
    rule_no    = "${1000 + count.index}"
    cidr_block = "${var.allow_cidrs[count.index]}"
    protocol   = "6"
    from_port  = 22
    to_port    = 22
    action     = "allow"
  }

  count = "${length(var.allow_cidrs)}"
}

resource "aws_vpc" "vpc" {
  cidr_block = "10.1.0.0/16"
}

# resource "aws_default_network_acl" "default" {
#   default_network_acl_id = "${aws_vpc.vpc.default_network_acl_id}"

#   ingress = "${data.null_data_source.acl_rules.*.outputs}"
# }

output "acl_rules" {
  value = "${data.null_data_source.acl_rules.*.outputs}"
}

If I comment out the aws_default_network_acl, then output displays

Outputs:

acl_rules = [
    {
        action = allow,
        cidr_block = 55.55.55.55/32,
        from_port = 22,
        protocol = 6,
        rule_no = 1000,
        to_port = 22
    },
    {
        action = allow,
        cidr_block = 44.44.44.44/32,
        from_port = 22,
        protocol = 6,
        rule_no = 1001,
        to_port = 22
    }
]

However, if I uncomment the aws_default_network_acl block I get

Error: aws_default_network_acl.default: ingress: should be a list

Which seems bizarre since it certainly looks like a list to me. Perhaps there is another way entirely I can go about this?

For now I am probably going to just go with creating a non default aws_network_acl since I can use aws_network_acl_rule with that one, but it still feels like there is probably a way to transform a list of data in terraform 0.11.

It’s interesting to note that the following code works entirely fine. The only difference is that I am not constructing the acl_rules from a list of strings. So for some reason, the result of "${aws_vpc.vpc.default_network_acl_id}" is definitely the problem.

provider "aws" {
  region = "us-east-1"
}

locals {
  acl_rules = [
    {
      cidr_block = "55.55.55.55/32"
      from_port  = 22
      protocol   = 6
      action     = "allow"
      rule_no    = 1000
      to_port    = 22
    },
    {
      cidr_block = "44.44.44.44/32"
      from_port  = 22
      protocol   = 6
      action     = "allow"
      rule_no    = 1001
      to_port    = 22
    },
  ]
}

resource "aws_vpc" "vpc" {
  cidr_block = "10.1.0.0/16"
}

resource "aws_default_network_acl" "default" {
  default_network_acl_id = "${aws_vpc.vpc.default_network_acl_id}"

  ingress = "${local.acl_rules}"
}

output "acl_rules" {
  value = "${local.acl_rules}"
}

Have you tried to embed it within []?

If I change it to


resource "aws_default_network_acl" "default" {
  default_network_acl_id = "${aws_vpc.vpc.default_network_acl_id}"

  ingress = ["${data.null_data_source.acl_rules.*.outputs}"]
}

I get

Error: aws_default_network_acl.default: "ingress.0.action": required field is not set



Error: aws_default_network_acl.default: "ingress.0.from_port": required field is not set



Error: aws_default_network_acl.default: "ingress.0.protocol": required field is not set



Error: aws_default_network_acl.default: "ingress.0.rule_no": required field is not set



Error: aws_default_network_acl.default: "ingress.0.to_port": required field is not set

It looks like this is an edge case.

resource "aws_default_network_acl" "default" {
  default_network_acl_id = "${aws_vpc.vpc.default_network_acl_id}"

  ingress {
      cidr_block = "${element(data.null_data_source.acl_rules.*.outputs.cidr_block, 0)}"
      from_port  = "${element(data.null_data_source.acl_rules.*.outputs.from_port, 0)}"
      protocol   = ...
      action     = ...
      rule_no    = ...
      to_port    = ...
  }
ingress {
      cidr_block = "${element(data.null_data_source.acl_rules.*.outputs.cidr_block, 1)}"
      from_port  = "${element(data.null_data_source.acl_rules.*.outputs.from_port, 1)}"
      protocol   = ...
      action     = ...
      rule_no    = ...
      to_port    = ...
  }