Help On Dynamic Blocks

I am newbie and I am trying to create the security group resource as explained below and getting the following error. Can any expert please guide me.

locals {
	Security_Group_Config = {
		# 1st Security Group Details
		Resource_Type		=	"Security Group"
		Security_Group_List	=	[
			{
				Name = "SG_Database_RDS"
				Description = "Allow only specific traffic to hit RDS Databases."
				Ingress_Rules_List =	[
					{
						from_port   = "8080"
						to_port     = "8080"
						protocol    = "tcp"
						cidr_blocks	= "1.2.3.4/32"
						description = "Testing Security Group Inbound Description"
					},
					{
						from_port   = "8081"
						to_port     = "8081"
						protocol    = "tcp"
						cidr_blocks	= "1.2.3.4/32"
						description = "Testing Security Group Inbound Description"
					}
				]
				Egress_Rules_List	=	[
					{
						from_port   = "0"
						to_port     = "0"
						protocol    = "0"
						cidr_blocks	= "1.2.3.4/32"
						description = "Testing Security Group Outbound Description"
					},
					{
						from_port   = "1"
						to_port     = "1"
						protocol    = "2"
						cidr_blocks	= "1.2.3.4/32"
						description = "Testing Security Group Outbound Description"
					}
				]
			},
			{
				Name = "SG_Database_Redshift"
				Description = "Allow only specific traffic to hit RDS Databases."
				Ingress_Rules_List =	[
					{
						from_port   = "8080"
						to_port     = "8080"
						protocol    = "tcp"
						cidr_blocks	= "1.2.3.4/32"
						description = "Testing Security Group Inbound Description"
					},
					{
						from_port   = "8081"
						to_port     = "8081"
						protocol    = "tcp"
						cidr_blocks	= "1.2.3.4/32"
						description = "Testing Security Group Inbound Description"
					}
				]
				Egress_Rules_List	=	[
					{
						from_port   = "0"
						to_port     = "0"
						protocol    = "0"
						cidr_blocks	= "1.2.3.4/32"
						description = "Testing Security Group Outbound Description"
					},
					{
						from_port   = "1"
						to_port     = "1"
						protocol    = "2"
						cidr_blocks	= "1.2.3.4/32"
						description = "Testing Security Group Outbound Description"
					}
				]
			}
		]
		
	}
  
}

	module "Security_Group_Module" {
		source 						= 	"./Networking/SecurityGroup"
		Input_VPC_ID 				=	"${module.Module_VPC.Output_Private_VPC_1_id}"
		Input_Standard_Tags			=	local.Standard_Tags
		Input_Resource_Type			=	local.Security_Group_Config.Resource_Type
		Input_Security_Group		=	local.Security_Group_Config	
	}

and My SecurityGroup_main.tf is something like :-

resource "aws_security_group" "TF_SG_1" {
	vpc_id		=	var.Input_VPC_ID

	dynamic "SG_Config" {
		for_each	=	var.Input_Security_Group.Security_Group_List
		iterator	=	"SG_Cnt"
		content {
				name		=	SG_Cnt.Name
				description	=	SG_Cnt.Description
				tags = merge(
				var.Input_Standard_Tags,
				{
					Name 			= 	SG_Cnt.Name
					Resource_Type	=	var.Input_Resource_Type
				},
				)
			 
				lifecycle {
					ignore_changes = [tags.Created_On]
				}
			
			dynamic "Ingress_Config" {
				for_each 	=	SG_Cnt[value].Ingress_Rules
				iterator	=	"Ingress_Cnt"
				content {
							from_port   = Ingress_Cnt.value.from_port
							to_port     = Ingress_Cnt.value.to_port
							protocol    = Ingress_Cnt.value.protocol
							cidr_blocks	= Ingress_Cnt.value.cidr_blocks
							description = Ingress_Cnt.value.description				
						}
			}
		}
	}
}

Hi @ManishGupta8,

You mentioned getting an error but I don’t see a specific error message in your question, so I’m going to have to guess what it was.

The main problem I see in the second example you shared is the labels for each of the two dynamic blocks:

	dynamic "SG_Config" {
			dynamic "Ingress_Config" {

The label of a dynamic block must be the name of the type of block you are dynamically generating. The aws_security_group resource type doesn’t expect a block called SG_Config, so I assume that Terraform is telling you that your configuration doesn’t match the provider’s expected schema.

From what you’ve shared in your first example, it looks like your goal is to declare one security group per element of local.Security_Group_Config.Security_Group_List, which you can achieve using resource for_each (to declare multiple instances of the resource itself) rather than dynamic blocks (which generate zero or more nested blocks inside the resource). The ingress and egress nested blocks can still be generated with dynamic blocks, though.

resource "aws_security_group" "TF_SG_1" {
  for_each = {
    for sg in local.Security_Group_Config.Security_Group_List : sg.Name => sg
  }

  vpc_id = var.Input_VPC_ID

  name        = each.value.Name
  description = each.value.Description

  tags = merge(
    var.Input_Standard_Tags,
    {
      Name          = each.value.Name
      Resource_Type = var.Input_Resource_Type
    },
  )

  dynamic "ingress" {
    for_each = each.value.Ingress_Rules_List
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
      description = ingress.value.description
    }
  }

  dynamic "egress" {
    for_each = each.value.Egress_Rules_List
    content {
      from_port   = egress.value.from_port
      to_port     = egress.value.to_port
      protocol    = egress.value.protocol
      cidr_blocks = egress.value.cidr_blocks
      description = egress.value.description
    }
  }
}

An important part of resource for_each is that the keys of the for_each map become part of Terraform’s identifiers for the multiple resource instances, so the above will declare multiple instances of this resource with addresses like this:

  • aws_security_group.TF_SG_1["SG_Database_RDS"]
  • aws_security_group.TF_SG_1["SG_Database_Redshift"]

This is significant because it affects how Terraform will understand future changes to your local.Security_Group_Config.Security_Group_List value: if you change the values of one of the objects but leave the Name argument unchanged then Terraform will understand it as editing the existing object, but if you change the Name argument then Terraform will understand it as destroying the old object and creating a new one alongside it.


Note that the idiomatic Terraform style is to write identifiers all in lower case, rather than mixed case. For example, I’d expect to see local.security_group_config rather than local.Security_Group_Config.

Using a prefix like “input” on all of your input variables is also unnecessary, because the var. prefix already specifies that it’s an input variable and input variables are inputs by definition. If you are bringing experience from CloudFormation, note that while CloudFormation has a single namespace containing all referenceable objects (inputs, resources, etc), Terraform uses a separate namespace for each kind of object – var. for input variables, local. for local values, etc – and so we don’t typically include the type of an object as part of its name. For a similar reason, I’d typically not include “SG” or “security group” in the name of a security group: you’ll always reference it as aws_security_group.name, so its type is already qualified.

Thanks @apparentlymart but after making the changes i am still getting the below error :-

Error Message :-
Error: Invalid ‘for’ expression

on Networking\SecurityGroup\Security_Group_main.tf line 127, in resource “aws_security_group” “TF_SG_1”:
125: for_each = {
126: for sg in var.Input_Security_Group.Security_Group_List
127: }

For expression requires a colon after the collection expression.

Here is the script that i am using :-

locals {
Security_Group_Config = {
# 1st Security Group Details
Resource_Type = “Security Group”
Security_Group_List = [
{
Name = “SG_Database_RDS”
Description = “Allow only specific traffic to hit RDS Databases.”
Ingress_Rules_List = [
{
from_port = “8080”
to_port = “8080”
protocol = “tcp”
cidr_blocks = “1.2.3.4/32”
description = “Testing Security Group Inbound Description”
},
{
from_port = “8081”
to_port = “8081”
protocol = “tcp”
cidr_blocks = “1.2.3.4/32”
description = “Testing Security Group Inbound Description”
}
]
Egress_Rules_List = [
{
from_port = “0”
to_port = “0”
protocol = “0”
cidr_blocks = “1.2.3.4/32”
description = “Testing Security Group Outbound Description”
},
{
from_port = “1”
to_port = “1”
protocol = “2”
cidr_blocks = “1.2.3.4/32”
description = “Testing Security Group Outbound Description”
}
]
},
{
Name = “SG_Database_Redshift”
Description = “Allow only specific traffic to hit RDS Databases.”
Ingress_Rules_List = [
{
from_port = “8080”
to_port = “8080”
protocol = “tcp”
cidr_blocks = “1.2.3.4/32”
description = “Testing Security Group Inbound Description”
},
{
from_port = “8081”
to_port = “8081”
protocol = “tcp”
cidr_blocks = “1.2.3.4/32”
description = “Testing Security Group Inbound Description”
}
]
Egress_Rules_List = [
{
from_port = “0”
to_port = “0”
protocol = “0”
cidr_blocks = “1.2.3.4/32”
description = “Testing Security Group Outbound Description”
},
{
from_port = “1”
to_port = “1”
protocol = “2”
cidr_blocks = “1.2.3.4/32”
description = “Testing Security Group Outbound Description”
}
]
}
]

}

}

Here is my main.tf in root folder :-
/*************************** Create Security Group /
module “Security_Group_Module” {
source = “./Networking/SecurityGroup”
Input_VPC_ID = “${module.Module_VPC.Output_Private_VPC_1_id}”
Input_Standard_Tags = local.Standard_Tags
Input_Resource_Type = local.Security_Group_Config.Resource_Type
Input_Security_Group = local.Security_Group_Config
}
/
****************************************************/

Here is my main.tf in SecurityGroup Folder:-

resource “aws_security_group” “TF_SG_1” {
for_each = {
for sg in var.Input_Security_Group.Security_Group_List
}

vpc_id = var.Input_VPC_ID
name        = each.value.Name
description = each.value.Description
tags = merge(
	var.Input_Standard_Tags,
	{
		Name          = each.value.Name
		Resource_Type = var.Input_Resource_Type
	},
)

dynamic "ingress" {
	for_each = each.value.Ingress_Rules_List
	content {
		from_port   = ingress.value.from_port
		to_port     = ingress.value.to_port
		protocol    = ingress.value.protocol
		cidr_blocks = ingress.value.cidr_blocks
		description = ingress.value.description
	}
}

dynamic "egress" {
	for_each = each.value.Egress_Rules_List
	content {
		from_port   = egress.value.from_port
		to_port     = egress.value.to_port
		protocol    = egress.value.protocol
		cidr_blocks = egress.value.cidr_blocks
		description = egress.value.description
	}
}

}

Here is my variables.tf in SecurityGroup folder:-----
variable “Input_VPC_ID” {}
variable “Input_Standard_Tags” {}
variable “Input_Resource_Type” {}
variable “Input_Security_Group” {}