Terraform-provider-aws: aws_lb_target_group impossible to be modular (health_check.timeout)

Created on 28 Aug 2018  ยท  4Comments  ยท  Source: hashicorp/terraform-provider-aws

Unfortunately, it seems aws_lb_target_group cannot be modular supporting both TCP and HTTP(S) in the same module.

  health_check {
    path                = "${local.healthcheck_path             }"
    port                = "${local.healthcheck_port             }"
    matcher             = "${local.healthcheck_match_codes      }"
    timeout             = "${local.healthcheck_timeout          }"
    interval            = "${var.healthcheck_interval           }"
    protocol            = "${local.healthcheck_protocol         }"
    healthy_threshold   = "${var.healthcheck_healthy_threshold  }"
    unhealthy_threshold = "${var.healthcheck_unhealthy_threshold}"
  }

If timeout is present and the target group type is TCP the following error always occurs:

* module.client_nlb_target.aws_lb_target_group.scope: : custom timeout is not supported for target_groups with TCP protocol

I have tried using locals to set the value of local.healthcheck_timeout to the following to make the code modular:

  • 0 - errors, fails validation (ValidateFunc: validation.IntBetween(2, 60),)
  • "" - errors, says its a string and an integer is expected
  • 10 - errors, even though this is the default and only accepted value for a TCP healthcheck, it still says that a custom timeout is not supported.
            if t := healthCheck["timeout"].(int); t != 0 && diff.Id() == "" {
                // timeout has a default value, so only check this if this is a network
                // LB and is a first run
                return fmt.Errorf("%s: custom timeout is not supported for target_groups with TCP protocol", diff.Id())
            }

It looks like to make Terraform ignore the value, it should be 0 or blank, both of which fail validation.

https://github.com/hashicorp/terraform/blob/master/vendor/github.com/terraform-providers/terraform-provider-aws/aws/resource_aws_lb_target_group.go

Thanks !

enhancement servicelbv2 terraform-0.12

Most helpful comment

Hi @rlees85 ๐Ÿ‘‹ This issue is resolved in Terraform 0.12, which supports new functionality in the configuration language aimed at solving problems like these. The new dynamic block syntax can be used to dynamically generate configuration blocks and their arguments. These can be combined with the new null value, which can be used to omit arguments as if they were not defined in the configuration at all.

For example, given this configuration:

# main.tf

terraform {
  required_providers {
    aws = "2.20.0"
  }
  required_version = "0.12.5"
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_vpc" "test" {
  cidr_block = "10.0.0.0/16"
}

module "test1" {
  source = "./module1"

  health_check = []
  vpc_id       = aws_vpc.test.id
}

module "test2" {
  source = "./module1"

  health_check = [{
    healthy_threshold   = null
    interval            = null
    matcher             = "200-299"
    path                = "/"
    port                = null
    protocol            = "HTTP"
    timeout             = null
    unhealthy_threshold = null
  }]
  vpc_id = aws_vpc.test.id
}

# module1/main.tf

variable "health_check" {
  type = list(object({
    healthy_threshold   = number
    interval            = number
    matcher             = string
    path                = string
    port                = number
    protocol            = string
    timeout             = number
    unhealthy_threshold = number
  }))
}

variable "vpc_id" {
  type = string
}

resource "aws_lb_target_group" "test" {
  port     = 80
  protocol = "HTTP"
  vpc_id   = var.vpc_id

  dynamic "health_check" {
    for_each = var.health_check

    content {
      healthy_threshold   = health_check.value.healthy_threshold
      interval            = health_check.value.interval
      matcher             = health_check.value.matcher
      path                = health_check.value.path
      port                = health_check.value.port
      protocol            = health_check.value.protocol
      timeout             = health_check.value.timeout
      unhealthy_threshold = health_check.value.unhealthy_threshold
    }
  }
}

Produces the following apply output:

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_vpc.test will be created
  + resource "aws_vpc" "test" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = (known after apply)
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
    }

  # module.test1.aws_lb_target_group.test will be created
  + resource "aws_lb_target_group" "test" {
      + arn                                = (known after apply)
      + arn_suffix                         = (known after apply)
      + deregistration_delay               = 300
      + id                                 = (known after apply)
      + lambda_multi_value_headers_enabled = false
      + name                               = (known after apply)
      + port                               = 80
      + protocol                           = "HTTP"
      + proxy_protocol_v2                  = false
      + slow_start                         = 0
      + target_type                        = "instance"
      + vpc_id                             = (known after apply)

      + health_check {
          + enabled             = (known after apply)
          + healthy_threshold   = (known after apply)
          + interval            = (known after apply)
          + matcher             = (known after apply)
          + path                = (known after apply)
          + port                = (known after apply)
          + protocol            = (known after apply)
          + timeout             = (known after apply)
          + unhealthy_threshold = (known after apply)
        }

      + stickiness {
          + cookie_duration = (known after apply)
          + enabled         = (known after apply)
          + type            = (known after apply)
        }
    }

  # module.test2.aws_lb_target_group.test will be created
  + resource "aws_lb_target_group" "test" {
      + arn                                = (known after apply)
      + arn_suffix                         = (known after apply)
      + deregistration_delay               = 300
      + id                                 = (known after apply)
      + lambda_multi_value_headers_enabled = false
      + name                               = (known after apply)
      + port                               = 80
      + protocol                           = "HTTP"
      + proxy_protocol_v2                  = false
      + slow_start                         = 0
      + target_type                        = "instance"
      + vpc_id                             = (known after apply)

      + health_check {
          + enabled             = true
          + healthy_threshold   = 3
          + interval            = 30
          + matcher             = "200-299"
          + path                = "/"
          + port                = "traffic-port"
          + protocol            = "HTTP"
          + timeout             = (known after apply)
          + unhealthy_threshold = 3
        }

      + stickiness {
          + cookie_duration = (known after apply)
          + enabled         = (known after apply)
          + type            = (known after apply)
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_vpc.test: Creating...
aws_vpc.test: Creation complete after 2s [id=vpc-047ef4cec5efc2955]
module.test1.aws_lb_target_group.test: Creating...
module.test2.aws_lb_target_group.test: Creating...
module.test1.aws_lb_target_group.test: Creation complete after 0s [id=arn:aws:elasticloadbalancing:us-east-1:--OMITTED--:targetgroup/tf-20190724074317849500000001/ac44b2a3a2c3d4fe]
module.test2.aws_lb_target_group.test: Creation complete after 0s [id=arn:aws:elasticloadbalancing:us-east-1:--OMITTED--:targetgroup/tf-20190724074317850400000002/a264755154d3b092]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Enjoy! ๐Ÿš€

All 4 comments

Hi @rlees85 ๐Ÿ‘‹ Sorry you're having trouble here. There are some interesting limitations in working with ELBv2 APIs and the health check information certainly has caused issues in the past.

This issue should be resolvable without changes to the AWS provider once Terraform 0.12 has released as it will include support for passing null to attributes, which will avoid triggering the attribute validation. I believe for now (without code changes to the resource) you will need to create two separate aws_lb_target_group configurations and use count to determine which to enable.

Fair enough, thanks for the reply. I'm glad that I'm not missing something obvious.

I just thought I'd raise it in case the validation (2-60) is no longer relevant now the module also supports NLB (or it should be checking against 10 as nil for the NLB timeout value check).

Look forward to 0.12, I think a lot of my modules will be able to be consolidated!

Hi @rlees85 ๐Ÿ‘‹ This issue is resolved in Terraform 0.12, which supports new functionality in the configuration language aimed at solving problems like these. The new dynamic block syntax can be used to dynamically generate configuration blocks and their arguments. These can be combined with the new null value, which can be used to omit arguments as if they were not defined in the configuration at all.

For example, given this configuration:

# main.tf

terraform {
  required_providers {
    aws = "2.20.0"
  }
  required_version = "0.12.5"
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_vpc" "test" {
  cidr_block = "10.0.0.0/16"
}

module "test1" {
  source = "./module1"

  health_check = []
  vpc_id       = aws_vpc.test.id
}

module "test2" {
  source = "./module1"

  health_check = [{
    healthy_threshold   = null
    interval            = null
    matcher             = "200-299"
    path                = "/"
    port                = null
    protocol            = "HTTP"
    timeout             = null
    unhealthy_threshold = null
  }]
  vpc_id = aws_vpc.test.id
}

# module1/main.tf

variable "health_check" {
  type = list(object({
    healthy_threshold   = number
    interval            = number
    matcher             = string
    path                = string
    port                = number
    protocol            = string
    timeout             = number
    unhealthy_threshold = number
  }))
}

variable "vpc_id" {
  type = string
}

resource "aws_lb_target_group" "test" {
  port     = 80
  protocol = "HTTP"
  vpc_id   = var.vpc_id

  dynamic "health_check" {
    for_each = var.health_check

    content {
      healthy_threshold   = health_check.value.healthy_threshold
      interval            = health_check.value.interval
      matcher             = health_check.value.matcher
      path                = health_check.value.path
      port                = health_check.value.port
      protocol            = health_check.value.protocol
      timeout             = health_check.value.timeout
      unhealthy_threshold = health_check.value.unhealthy_threshold
    }
  }
}

Produces the following apply output:

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_vpc.test will be created
  + resource "aws_vpc" "test" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = (known after apply)
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
    }

  # module.test1.aws_lb_target_group.test will be created
  + resource "aws_lb_target_group" "test" {
      + arn                                = (known after apply)
      + arn_suffix                         = (known after apply)
      + deregistration_delay               = 300
      + id                                 = (known after apply)
      + lambda_multi_value_headers_enabled = false
      + name                               = (known after apply)
      + port                               = 80
      + protocol                           = "HTTP"
      + proxy_protocol_v2                  = false
      + slow_start                         = 0
      + target_type                        = "instance"
      + vpc_id                             = (known after apply)

      + health_check {
          + enabled             = (known after apply)
          + healthy_threshold   = (known after apply)
          + interval            = (known after apply)
          + matcher             = (known after apply)
          + path                = (known after apply)
          + port                = (known after apply)
          + protocol            = (known after apply)
          + timeout             = (known after apply)
          + unhealthy_threshold = (known after apply)
        }

      + stickiness {
          + cookie_duration = (known after apply)
          + enabled         = (known after apply)
          + type            = (known after apply)
        }
    }

  # module.test2.aws_lb_target_group.test will be created
  + resource "aws_lb_target_group" "test" {
      + arn                                = (known after apply)
      + arn_suffix                         = (known after apply)
      + deregistration_delay               = 300
      + id                                 = (known after apply)
      + lambda_multi_value_headers_enabled = false
      + name                               = (known after apply)
      + port                               = 80
      + protocol                           = "HTTP"
      + proxy_protocol_v2                  = false
      + slow_start                         = 0
      + target_type                        = "instance"
      + vpc_id                             = (known after apply)

      + health_check {
          + enabled             = true
          + healthy_threshold   = 3
          + interval            = 30
          + matcher             = "200-299"
          + path                = "/"
          + port                = "traffic-port"
          + protocol            = "HTTP"
          + timeout             = (known after apply)
          + unhealthy_threshold = 3
        }

      + stickiness {
          + cookie_duration = (known after apply)
          + enabled         = (known after apply)
          + type            = (known after apply)
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_vpc.test: Creating...
aws_vpc.test: Creation complete after 2s [id=vpc-047ef4cec5efc2955]
module.test1.aws_lb_target_group.test: Creating...
module.test2.aws_lb_target_group.test: Creating...
module.test1.aws_lb_target_group.test: Creation complete after 0s [id=arn:aws:elasticloadbalancing:us-east-1:--OMITTED--:targetgroup/tf-20190724074317849500000001/ac44b2a3a2c3d4fe]
module.test2.aws_lb_target_group.test: Creation complete after 0s [id=arn:aws:elasticloadbalancing:us-east-1:--OMITTED--:targetgroup/tf-20190724074317850400000002/a264755154d3b092]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Enjoy! ๐Ÿš€

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 feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. Thanks!

Was this page helpful?
0 / 5 - 0 ratings