darkKnight
darkKnight

Reputation: 83

Variable values in Terraform for aws security groups

I am new to terraform and trying to create an AWS security group with ingress and egress rules. Rather than hardcoding the values and creating multiple ingress and egress blocks, I am trying to make use of terraform lookup function.

main.tf file looks like this:

provider "aws" {
  version                 = "~> 2.0"
  region                  = var.region
  profile                 = var.profile
}

resource "aws_security_group" "this" {
  name = "test-sg"
  description = "test security group"

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      description      = lookup(ingress.value, "description", null)
      from_port        = lookup(ingress.value, "from_port", null)
      to_port          = lookup(ingress.value, "to_port", null)
      protocol         = lookup(ingress.value, "protocol", null)
      cidr_blocks      = lookup(ingress.value, "cidr_blocks", null)
    }
  }

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

  tags = {
    Name = "test-sg"
  }
}

variables.tf file looks like this

variable "ingress_rules" {
  default     = {
    "description" = ["For HTTP", "For SSH"]
    "from_port"   = ["80", "22"]
    "to_port"     = ["80", "22"]
    "protocol"    = ["tcp", "tcp"]
    "cidr_blocks" = ["0.0.0.0/0", "0.0.0.0/0"]
  }
  type        = map(list(string))
  description = "Security group rules"
}

When I run terraform validate it shows configuration is valid, but when I run terraform plan, it shows the following error:

 ingress.value is list of string with 2 elements
 
 Invalid value for "inputMap" parameter: lookup() requires a map as the first
 argument.

After spending a long time still, I am not able to figure out how to solve this error. What is the correct way to pass lookup values to variables.tf file.

Upvotes: 5

Views: 8566

Answers (2)

Akif
Akif

Reputation: 6836

I would implement it as follows:

resource "aws_security_group" "test_security_group" {
  name = "test-sg"
  description = "test security group"

  dynamic "ingress" {
    for_each = var.sg_ingress_rules
    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
    }
  }

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

  tags = {
    Name = "test security group"
  }
}

variables.tf

variable "sg_ingress_rules" {
  description = "Ingress security group rules"
  type        = map
}

my_vars.tfvars

sg_ingress_rules = {
  "1" = {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTP"
  },
  "2" = {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["<my_private_ip>/32"]
    description = "SSH"
  }
}

Hope it helps to understand further!

Upvotes: 2

Matthew Schuchard
Matthew Schuchard

Reputation: 28864

You have constructed your variable's default value as five maps with a string key and list of strings value. You probably wanted a single map with a series of keys and values associated with the various attributes of your ingress rule. You can update the variable value accordingly like:

variable "ingress_rules" {
  default     = {
    "my ingress rule" = {
      "description" = "For HTTP"
      "from_port"   = "80"
      "to_port"     = "80"
      "protocol"    = "tcp"
      "cidr_blocks" = ["0.0.0.0/0"]
    },
    "my other ingress rule" = {
      "description" = "For SSH"
      "from_port"   = "22"
      "to_port"     = "22"
      "protocol"    = "tcp"
      "cidr_blocks" = ["0.0.0.0/0"]
    }
  }
  type        = map(any)
  description = "Security group rules"
}

Now, in your for_each iterator, the value of the first ingress.key will be my ingress rule, and the value of the first ingress.value will be your entire map of keys and strings. Before, the first ingress.key would have been description, and the first value would have been ["For HTTP", "For SSH"]. That is why you were getting that error: you cannot lookup a value with key description from a list of ["For HTTP", "For SSH"]. After making this variable value update, you should have your expected behavior.

Note that you can refine your type further with an object:

default = {
  "my ingress rule" = {
    description = "For HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  },
  "my other ingress rule" = {
    description = "For SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
type = map(object({
  description = string
  from_port   = number
  to_port     = number
  protocol    = string
  cidr_blocks = list(string)
}))

Upvotes: 4

Related Questions