tleibo
tleibo

Reputation: 63

Unable to create a nested dynamic block

I have a set of 9 security group rules that I need to apply to 4 different sources. I wanted to build it as a module so instead of copy/pasting the same block multiple times, I just need to pass the ports and source as variables.

I have tried to create a module that gets the ports as for_each in a dynamic block and also passes the sources with count since I failed to provide an additional dynamic block with for_each also for the sources.

modules/sg/main.tf

resource "aws_security_group" "test" {
  name = "test2"
  count = length(var.groups)
  vpc_id = var.vpc_id


  dynamic "ingress_tcp" {
    for_each = var.tcp_ports
    content {
      from_port = ingress_tcp.value
      to_port = ingress_tcp.value
      protocol = "tcp"
      security_groups = [var.groups[*].id]
    }
  }
  dynamic "ingress_udp" {
    for_each = var.udp_ports
    content {
      from_port = ingress_udp.value
      to_port = ingress_udp.value
      protocol = "udp"
      security_groups = [var.groups[*].id]
    }
  }
}

main.tf

module "rules" {
  source = "./module/sg"
  vpc_id = var.vpc_id
  name = "tomer-test"
  tcp_ports = var.tcp_ports
  udp_ports = var.udp_ports
  groups = [var.groups[*].id]
}

variables.tf

variable "groups" {
  description = "source groups"
  type = "list"
  default  = [{
    name = "Enforcement-STG",
    id = "sg-c9db2183abcd"
  },
    {
      name = "Managment-STG",
      id = "sg-b0e71dfa123"
  }]
}

variable "name" {
  type = string
}
variable "vpc_id" {
  type = string
  default = ""
}
variable "tcp_ports" {
  description = "tcp ports to open"
  default = [514,1514, 11514, 12514, 6514]
}
variable "udp_ports" {
  description = "tcp ports to open"
  default = [514,1514, 11514, 12514]
}

I accept the output to build a set of rules per source groups, but the root module fails to invoke the module. The error that I'm currently getting is

terraform plan

Error: Unsupported block type

  on module/sg/main.tf line 7, in resource "aws_security_group" "test":
   7:   dynamic "ingress_tcp" {

Blocks of type "ingress_tcp" are not expected here.


Error: Unsupported block type

  on module/sg/main.tf line 16, in resource "aws_security_group" "test":
  16:   dynamic "ingress_udp" {

Blocks of type "ingress_udp" are not expected here.

Upvotes: 6

Views: 10169

Answers (1)

Martin Atkins
Martin Atkins

Reputation: 74064

As the error message suggests, what you tried here is not valid because ingress_tcp is not a block type expected inside an aws_security_group resource. The correct name for this nested block type is ingress:

resource "aws_security_group" "test" {
  count = length(var.groups)

  name = "test2"
  vpc_id = var.vpc_id

  dynamic "ingress" {
    for_each = var.tcp_ports
    content {
      from_port       = ingress.value
      to_port         = ingress.value
      protocol        = "tcp"
      security_groups = var.groups[*].id
    }
  }

  dynamic "ingress" {
    for_each = var.udp_ports
    content {
      from_port       = ingress.value
      to_port         = ingress.value
      protocol        = "udp"
      security_groups = var.groups[*].id
    }
  }
}

If you are using Terraform 0.12.6 or later, you may prefer to write this using resource for_each instead of count, like this:

resource "aws_security_group" "test" {
  for_each = { for g in var.groups : g.name => g }

  name = "test2-${each.key}"
  vpc_id = var.vpc_id

  dynamic "ingress" {
    for_each = var.tcp_ports
    content {
      from_port       = ingress.value
      to_port         = ingress.value
      protocol        = "tcp"
      security_groups = var.groups[*].id
    }
  }

  dynamic "ingress" {
    for_each = var.udp_ports
    content {
      from_port       = ingress.value
      to_port         = ingress.value
      protocol        = "udp"
      security_groups = var.groups[*].id
    }
  }
}

This will have a similar result to your count example, but will produce instances with addresses like aws_security_group.test["Enforcement-STG"] instead of aws_security_group.test[0], which means that when you add and remove elements from var.groups in future Terraform will be able to determine which instance corresponds with each element and only add/remove the individual instances needed.

This map-based resource is likely to be easier to use elsewhere in the configuration too, since you'll be able to easily find the specific security group for each of your symbolic group names.

Upvotes: 5

Related Questions