terraform-aws-modules/terraform-aws-alb

listeners must have the same map

alisson276 opened this issue · 3 comments

Description

It's not possible to use different listeners action directives, because the map must match. What can I do to bypass that situation?

The only apprach I can think is to replace this:

for_each = try([each.value.forward], [])

by this:

for_each = try(each.value.forward, null) != null ? [each.value.forward] : []

on all ocurrences (what I actually have done on my fork)

Versions

  • Module version [Required]: 9.1.0

  • Terraform version: v1.6.3

  • Provider version(s):
    • registry.terraform.io/hashicorp/aws v5.23.1

Reproduction Code [Required]

Steps to reproduce the behavior:
variables.tf

variable "load_balancers" {
  type = map(object({
    name                       = optional(string)
    create_security_group      = optional(bool, false)
    enable_deletion_protection = bool
    load_balancer_type         = string
    ip_address_type            = string
    preserve_host_header       = bool
    vpc_id                     = string
    subnets                    = set(string)
    security_groups            = set(string)
    target_groups = optional(map(object({
      arn    = string
      weight = optional(number)
    })), {})
    listeners = map(object({
      port            = number
      protocol        = string
      action_type     = string
      certificate_arn = optional(string)
      ssl_policy      = optional(string)
      redirect = optional(object({
        port        = number
        protocol    = string
        status_code = string
      }))
      fixed_response = optional(object({
        content_type = string
        message_body = string
        status_code  = number
      }))
    }))
    tags = optional(map(string))
  }))
}

prod.tfvars

load_balancers = {
  my-alb-prod = {
    enable_deletion_protection = true
    load_balancer_type         = "application"
    ip_address_type            = "dualstack"
    preserve_host_header       = true
    vpc_id                     = "<SENSITIVE_INFORMATION>"
    subnets = [<SENSITIVE_INFORMATION>]
    security_groups = [<SENSITIVE_INFORMATION>]
    listeners = {
      http = {
        port        = 80
        protocol    = "HTTP"
        action_type = "redirect"
        redirect = {
          port        = 443
          protocol    = "HTTPS"
          status_code = "HTTP_301"
        }
      },
      https = {
        port            = 443
        protocol        = "HTTPS"
        certificate_arn = "<SENSITIVE_INFORMATION>"
        ssl_policy      = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
        action_type     = "fixed-response"
        fixed_response = {
          content_type = "text/plain"
          message_body = ""
          status_code  = 400
        }
      }
    }
    tags = {
      Name = "my-alb-prod"
    }
  }
}

main.tf

module "alb" {
  source = "terraform-aws-modules/alb/aws"
  for_each = var.load_balancers

  name                       = coalesce(each.value.name, each.key)
  create_security_group      = each.value.create_security_group
  enable_deletion_protection = each.value.enable_deletion_protection
  load_balancer_type         = each.value.load_balancer_type
  ip_address_type            = each.value.ip_address_type
  preserve_host_header       = each.value.preserve_host_header
  vpc_id                     = each.value.vpc_id
  subnets                    = each.value.subnets
  security_groups            = each.value.security_groups
  target_groups              = each.value.target_groups
  listeners                  = each.value.listeners
  tags                       = each.value.tags
}

Expected behavior

It should create two listeners, one HTTP, which only redirects to HTTPS, and one HTTPS, which have a default action of returning an empty string.

Actual behavior

I've tried two ways of doing this. First, my listeners variable was a map(any). However, I've received an error when running a plan

The given value is not suitable for var.load_balancers declared at
│ variables.tf:1,1-26: element "hole19-prod": attribute "listeners": all map
│ elements must have the same type.

After this I've created the fixed mapping you can see on variables.tf, but now the error is:

╷
│ Error: Attempt to get attribute from null value
│ 
│   on ../../modules/aws-networking-alb/main.tf line 124, in resource "aws_lb_listener" "this":
│  124:         content_type = default_action.value.content_type
│     ├────────────────
│     │ default_action.value is null
│ 
│ This value is null, so it does not have any attributes.
╵
╷
│ Error: Attempt to get attribute from null value
│ 
│   on ../../modules/aws-networking-alb/main.tf line 185, in resource "aws_lb_listener" "this":
│  185:         status_code = default_action.value.status_code
│     ├────────────────
│     │ default_action.value is null
│ 
│ This value is null, so it does not have any attributes.

Terminal Output Screenshot(s)

Additional context

thats because you've defined it this way

listeners = map(object({
      port            = number
      protocol        = string
      action_type     = string
      certificate_arn = optional(string)
      ssl_policy      = optional(string)
      redirect = optional(object({
        port        = number
        protocol    = string
        status_code = string
      }))
      fixed_response = optional(object({
        content_type = string
        message_body = string
        status_code  = number
      }))

Don't do that and the problem is resolved - this is why, unfortunately, we typically use any for complex types

You're right 😅, using any instead of map(any) solves the issue.

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.