CptDolphin
CptDolphin

Reputation: 474

Create dynamic block in terraform

I wanted to create google_compute_health_check in terraform and I'm thinking about how to make them the most versatile. My code atm looks like that

application.hcl

inputs = {
  health_checks = {
    tcp-health-check = {
      name                = "tcp-health-check"
      desc                = "Health check via tcp"
      port                = 80
      timeout_sec         = 4
      check_interval_sec  = 30
    }
  }

main terragrunt.hcl

include {
  path = find_in_parent_folders()
}

terraform { source ...}

locals {
  app_vars    = read_terragrunt_config(find_in_parent_folders("application.hcl"))
}

inputs = {
  # and my idea was that every invocation of the module, picks it's own set
  # of health checks that it wants to use
  health_checks = [local.app_vars.inputs.health_checks.tcp-health-check]
}

now the module main.tf looks like so

locals {
  checks = { for check in var.health_checks: check.name => check }
}

resource "google_compute_health_check" "main" {
  for_each           = local.checks
  name               = each.value.name

  timeout_sec        = each.value.timeout_sec
  check_interval_sec = each.value.check_interval_sec

  dynamic tcp_health_check {
    #for_each             = each.value.name == "tcp_health_check" ? each.value : []
    #for_each             = lookup(each.value, "tcp_health_check", [])
    for_each             = contains(keys(each.value), "tcp_health_check") != null ? each.value : {}
      content {
        port               = 80
        #        port               = each.value.port
        #        port_name          = each.value.name
      }
  }

and I'm stuck in the dynamic block - how to make it work so that it only is applied when I pass the tcp health_check, and when I pass, ssh it creates dynamic ssh block (I know there is no ssh block in the code atm, but in future I'll expand the module by whichever healht-check I'll need)


The errors I get are as followed with contains

Error: List longer than MaxItems

  on main.tf line 30, in resource "google_compute_health_check" "main":
  30: resource "google_compute_health_check" "main" {

Attribute supports 1 item maximum, config has 7 declared

ERRO[0011] 1 error occurred:
    * exit status 1

with lookup

Error: ExactlyOne

  on main.tf line 30, in resource "google_compute_health_check" "main":
  30: resource "google_compute_health_check" "main" {

"ssl_health_check": one of
`grpc_health_check,http2_health_check,http_health_check,https_health_check,ssl_health_check,tcp_health_check`
must be specified

ERRO[0005] 1 error occurred:
    * exit status 1

and with == comparison

Error: Inconsistent conditional result types

  on main.tf line 44, in resource "google_compute_health_check" "main":
  44:     for_each             = each.value.name == "tcp_health_check" ? each.value : []
    |----------------
    | each.value is object with 7 attributes
    | each.value.name is "tcp-health-check"

The true and false result expressions must have consistent types. The given
expressions are object and tuple, respectively.

ERRO[0005] 1 error occurred:
    * exit status 1

Ok solved it doing it the other way, but thanks for the answer Marcin

terragrunt.hcl

  inputs = {
    name                = "nat-health-check"
    used_for            = "used for NATs"
    check_interval_sec  = 30
    timeout_sec         = 5
    healthy_threshold   = 1
    unhealthy_threshold = 5
    http_checks         = local.app_vars.inputs.health_checks.nat-http
  }

and modules main.tf

resource "google_compute_health_check" "main" {
  name               = var.name
  timeout_sec        = var.timeout_sec
  check_interval_sec = var.check_interval_sec
  description        = "${var.name} - ${var.used_for}"

  dynamic "http_health_check" {
    for_each = var.http_checks != null ? [1] : []
    content {
      port               = var.http_checks.port
      request_path       = var.http_checks.request_path
      port_specification = var.http_checks.port_specification
    }
  }
}

Upvotes: 1

Views: 1239

Answers (1)

Marcin
Marcin

Reputation: 238081

The following

contains(keys(each.value), "tcp_health_check") != null ? each.value : {}

fails because when this is true, your dynamic block tcp_health_check will be executed more then once, since your each.value is just a map of values. You can't have more then one tcp_health_check block.

The second

for_each             = lookup(each.value, "tcp_health_check", [])

does not fail and is correct. But since it results in false, your tcp_health_check block is not created. This fails as you are not providing any of the alternative blocks grpc_health_check,http2_health_check,http_health_check,https_health_check,ssl_health_check,tcp_health_check.

The last attempt:

for_each             = each.value.name == "tcp_health_check" ? each.value : []

fails because it should be for_each = each.value.name == "tcp_health_check" ? each.value : {}. But if you fix this issue, each.value will again fail as before.

To sum up, you are always doomed fot fail, since when you eliminate your block tcp_health_check as in the second case, you are not providing any alternatives to it. And if your condition is true, each.value will try to create multiple blocks.

The closes solution would be (does not account for false case, where you need to provide alternatives):

  dynamic tcp_health_check {
    for_each           = each.value.name == "tcp_health_check" ? [1] : []
    content {
         port          = each.value.port
         port_name     = each.value.name
      }
  }

Upvotes: 2

Related Questions