main.tf
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# Load balancer ===============================================================

resource "aws_lb" "this" {
  name                       = local.lb_name
  load_balancer_type         = var.load_balancer_type
  internal                   = var.internal
  subnets                    = var.subnet_ids
  security_groups            = length(var.security_group_ids) > 0 ? var.security_group_ids : null
  enable_deletion_protection = var.deletion_protection
  idle_timeout               = local.is_alb ? var.idle_timeout : null

  enable_cross_zone_load_balancing = local.is_nlb ? var.cross_zone_load_balancing : null

  dynamic "access_logs" {
    for_each = var.access_logs_bucket != null ? [true] : []
    content {
      bucket  = var.access_logs_bucket
      prefix  = var.access_logs_prefix
      enabled = true
    }
  }

  tags = merge(var.tags, local.module_tags, {
    Name          = local.lb_name
    resource-type = "load-balancer"
  })

  lifecycle {
    postcondition {
      condition     = self.arn != ""
      error_message = "Load balancer ${self.name} did not receive an ARN after apply."
    }

    postcondition {
      condition     = self.dns_name != ""
      error_message = "Load balancer ${self.name} has no DNS name — check subnet and az coverage."
    }
  }
}

# WAF association --------------------------------------------------------------
# WAFv2 web ACLs only support ALB, API Gateway, and AppSync as targets.

resource "aws_wafv2_web_acl_association" "this" {
  for_each = var.waf_web_acl_arn != null && local.is_alb ? { waf = true } : {}

  resource_arn = aws_lb.this.arn
  web_acl_arn  = var.waf_web_acl_arn
}

# Target groups ================================================================

resource "aws_lb_target_group" "this" {
  for_each = var.target_groups

  name                 = local.target_group_names[each.key]
  port                 = each.value.port
  protocol             = each.value.protocol
  target_type          = each.value.target_type
  vpc_id               = each.value.vpc_id
  deregistration_delay = each.value.deregistration_delay

  health_check {
    enabled             = each.value.health_check.enabled
    path                = each.value.health_check.protocol == "TCP" ? null : each.value.health_check.path
    protocol            = each.value.health_check.protocol
    matcher             = each.value.health_check.protocol == "TCP" ? null : each.value.health_check.matcher
    interval            = each.value.health_check.interval
    timeout             = each.value.health_check.protocol == "TCP" ? null : each.value.health_check.timeout
    healthy_threshold   = each.value.health_check.healthy_threshold
    unhealthy_threshold = each.value.health_check.unhealthy_threshold
  }

  dynamic "stickiness" {
    for_each = each.value.stickiness != null ? [each.value.stickiness] : []
    content {
      enabled         = stickiness.value.enabled
      cookie_duration = stickiness.value.type == "source_ip" ? null : stickiness.value.cookie_duration
      type            = stickiness.value.type
    }
  }

  tags = merge(var.tags, each.value.tags, local.module_tags, {
    Name          = local.target_group_names[each.key]
    resource-type = "load-balancer-target-group"
  })

  lifecycle {
    postcondition {
      condition     = self.arn != ""
      error_message = "Target group ${self.name} did not receive an ARN after apply."
    }
  }
}

# Target group attachments ========================================================

resource "aws_lb_target_group_attachment" "this" {
  for_each = local.target_attachments

  target_group_arn = aws_lb_target_group.this[each.value.target_group_key].arn
  target_id        = each.value.target_id
  port             = each.value.port
}

# Listeners ======================================================================

resource "aws_lb_listener" "this" {
  for_each = var.listeners

  load_balancer_arn = aws_lb.this.arn
  port              = each.value.port
  protocol          = each.value.protocol
  certificate_arn   = each.value.certificate_arn
  ssl_policy        = each.value.certificate_arn != null ? each.value.ssl_policy : null

  dynamic "default_action" {
    for_each = each.value.redirect != null ? [] : [true]
    content {
      type             = each.value.default_action_type
      target_group_arn = each.value.target_group_key != null ? aws_lb_target_group.this[each.value.target_group_key].arn : null
    }
  }

  dynamic "default_action" {
    for_each = each.value.redirect != null ? [each.value.redirect] : []
    content {
      type = "redirect"
      redirect {
        port        = default_action.value.port
        protocol    = default_action.value.protocol
        status_code = default_action.value.status_code
      }
    }
  }

  tags = merge(var.tags, local.module_tags, {
    Name          = local.listener_keys[each.key]
    resource-type = "load-balancer-listener"
  })

  lifecycle {
    postcondition {
      condition     = self.arn != ""
      error_message = "Listener on port ${self.port} did not receive an ARN after apply."
    }
  }
}