hookdeck / terraform-provider-hookdeck

Terraform provider for Hookdeck
https://registry.terraform.io/providers/hookdeck/hookdeck/latest/docs
Apache License 2.0
5 stars 2 forks source link

Unable to handle concat #75

Open lijok opened 2 weeks ago

lijok commented 2 weeks ago

When using concat (it seems), the hookdeck_connection resource craps out in weird ways.

Example 1:

locals {
  retry_rule = {
    retry_rule = {
      strategy = var.retry_strategy
      count    = var.retry_count
      interval = var.retry_interval_ms
    }
  }
  delay_rule = {
    delay_rule = {
      delay = var.delay_ms
    }
  }
  transform_rule = { transform_rule = { transformation_id = "fake" } }
  filter_rule = {
    filter_rule = merge(
      var.headers_filter != null ? { headers = var.headers_filter } : {},
      var.body_filter != null ? { body = var.body_filter } : {},
      var.query_filter != null ? { query = var.query_filter } : {},
      var.path_filter != null ? { path = var.path_filter } : {},
    )
  }

  add_retry_rule     = true
  add_delay_rule     = var.delay_ms != 0
  add_transform_rule = var.transformations != []
  add_filter_rule = local.filter_rule.filter_rule != {}

  rules = concat(
    local.add_retry_rule ? [local.retry_rule] : [],
    local.add_delay_rule ? [local.delay_rule] : [],
    local.add_filter_rule ? [local.filter_rule] : [],
    local.add_transform_rule ? [local.transform_rule] : [],
  )
}

resource "hookdeck_connection" "this" {
  name           = var.name
  description    = var.description
  source_id      = var.source_id
  destination_id = var.destination_id
  rules          = local.rules
}
Error: attribute "rules": incorrect list element type: attribute "filter_rule": attribute "headers": attributes "boolean", "json", "number", and "string" are required

Example 2 (same as Example 1 but rules[0] is hardcoded)

locals {
  retry_rule = {
    retry_rule = {
      strategy = var.retry_strategy
      count    = var.retry_count
      interval = var.retry_interval_ms
    }
  }
  delay_rule = {
    delay_rule = {
      delay = var.delay_ms
    }
  }
  transform_rule = { transform_rule = { transformation_id = "fake" } }
  filter_rule = {
    filter_rule = merge(
      var.headers_filter != null ? { headers = var.headers_filter } : {},
      var.body_filter != null ? { body = var.body_filter } : {},
      var.query_filter != null ? { query = var.query_filter } : {},
      var.path_filter != null ? { path = var.path_filter } : {},
    )
  }

  add_retry_rule     = true
  add_delay_rule     = var.delay_ms != 0
  add_transform_rule = var.transformations != []
  add_filter_rule = local.filter_rule.filter_rule != {}

  rules = concat(
    [local.retry_rule],
    local.add_delay_rule ? [local.delay_rule] : [],
    local.add_filter_rule ? [local.filter_rule] : [],
    local.add_transform_rule ? [local.transform_rule] : [],
  )
}

resource "hookdeck_connection" "this" {
  name           = var.name
  description    = var.description
  source_id      = var.source_id
  destination_id = var.destination_id
  rules          = local.rules
}

module "transformation" {
  source   = "../transformation"
  for_each = var.transformations == [] ? toset([]) : toset(["this"])

  name            = "${var.source_id}-${var.destination_id}"
  transformations = var.transformations
}
Attribute rules[1].filter_rule value must have at least one child attribute defined, got: {"body":<null>,"headers":<null>,"path":<null>,"query":<null>}
lijok commented 2 weeks ago

Ok found two problems, apparently local.filter_rule.filter_rule != {} evals to true even when filter_rule is {}. Must be some type missmatch in terraform. So I change the add_filter_rule bool to;

  add_filter_rule = anytrue([
    var.headers_filter != null,
    var.body_filter != null,
    var.query_filter != null,
    var.path_filter != null,
  ])

But it still wouldn't work;

Error: attribute "rules": incorrect list element type: attribute "filter_rule": attribute "query": attributes "boolean", "json", "number", and "string" are required

The magic incantation was to hardcode an empty list in the concat;

  rules = concat(
    local.add_retry_rule ? [local.retry_rule] : [],
    local.add_delay_rule ? [local.delay_rule] : [],
    local.add_filter_rule ? [local.filter_rule] : [],
    local.add_transform_rule ? [local.transform_rule] : [],
    [],
  )

Which for whatever reason makes this pass

leggetter commented 2 weeks ago

@lijok - I think the problem you're hitting is that when some of these expressions are set the value results are unknown. And therefore, could be a disallowed value. By adding the empty list, you're guaranteeing that rules will at least be empty.

@alexluong - can you verify?

alexluong commented 2 weeks ago

Hi @lijok, I couldn't quite recreate your errors in my local setup but I did notice an issue with your TF code. Let's simplify by just hard-coding the connection:

resource "hookdeck_connection" "this" {
  name           = var.name
  description    = var.description
  source_id      = var.source_id
  destination_id = var.destination_id
  rules = [
    {
      filter_rule = {}
    }
  ]
}

This will lead to this error

Attribute rules[0].filter_rule value must have at least one child attribute defined, got: {"body":<null>,"headers":<null>,"path":<null>,"query":<null>}

That's because if we declare a filter_rule, it must include at least one of the filter options. I think there is some issue with your TF logic where it led to this unexpected configuration. It seems you were able to debug that issue and overcome this null check but still ran into another error

Error: attribute "rules": incorrect list element type: attribute "filter_rule": attribute "query": attributes "boolean", "json", "number", and "string" are required

I can't quite recreate this error but I can tell that's because the filter_rule.query is wrong. Maybe we're on different TF versioning where the error message is different. Small note but can you share your TF and hookdeck plugin version so I can make sure we're using the same environment?

Back to the main issue, I'd recommend you hard-code first to understand what's the expected value and then use locals / variables after. For example, this is what we expect for the filter rule:

rules = [
  {
    filter_rule = {
      query = {
        json = jsonencode({
          hello = "world"
        })
      }
    }
  }
]

Please refer to the documentation to see what options you have: https://registry.terraform.io/providers/hookdeck/hookdeck/latest/docs/resources/connection#nestedatt--rules--filter_rule

One tip I was employing while testing this out is try logging the variable to understand what the value you're sending is:

resource "hookdeck_connection" "this" {
  name           = var.name
  # description    = var.description
  source_id      = var.source_id
  destination_id = var.destination_id
  # rules          = local.rules
  description = jsonencode(local.rules)
}

Hope this message is helpful and happy to look more into it if you still run into any issues.