Referencing for_each resource inside itself

I am trying to limit the amount of code I need to write for AWS APIGateway. Here is my thought first then my problem. I want to create a module the will take a SET of strings as a variable that represents the path for the final method in the gateway.

Example:
["path", "to", "{the}", "{GET}", "{method}"]
translates to:
/path/to/{the}/{GET}/{method}

To do this I was trying to create multiple aws_api_gateway_resource with a for_each.

My problem happens when I need to assign a value to the parent_id for the resource. The value will either be the root_resource_id of the aws_api_gateway_rest_api if it is the first element in the set or it will be the previous elements id that is being created. Setting the value for the first element is fine its the subsequent elements that cause a cycle error.

Here is my code for it:

resource "aws_api_gateway_resource" "resource" {
  for_each = var.path
  parent_id = index(var.path, each.value) == 0 ? data.aws_api_gateway_rest_api.api.root_resource_id : aws_api_gateway_resource.resource[var.path[index(var.path, each.value) - 1]].id
  path_part = each.value
  rest_api_id = data.aws_api_gateway_rest_api.api.id
} 

I understand I am referencing the “same” resource with in it self by calling aws_api_gateway_resource.resource[], but I was hoping since it was getting the value from the list of resources it would be ok.

My question is, is this some how possible and if not, is there a better way to create AWS APIGateways so that it does not take so much code?

Hi @jdtommy,

Unfortunately this sort of thing isn’t possible because Terraform tracks dependencies during planning on a per-resource basis, not a per-resource-instance basis: the dependencies are resolved before for_each is evaluated, because for_each itself can depend on other objects.

A key consequence of this is that Terraform can’t represent a tree of objects created from a single resource block. Instead, resource blocks always create a flat set of instances that all have the same dependencies.

I’ve not tried this technique for API gateway in particular, but one thing you could consider is to represent the entire API as a single aws_api_gateway_rest_api resource which has its body argument set to an OpenAPI description of the API.

You could either supply an OpenAPI description statically, or use jsonencode with a data structure constructed dynamically using for expressions and Terraform functions based on your var.path collection. I’m very rusty on OpenAPI so I’m probably not doing this right, but here’s an example illustrating the basic idea:

resource "aws_api_gateway_rest_api" "example" {
  # ... (other rest API arguments) ...

  body = jsonencode({
    openapi = "3.0.0"
    # (other top-level metadata as needed)
    paths = {
      for path in var.path : "/${join("/", path)}" => {
        get = {
          # (OpenAPI operation description object)
        }
        post = {
          # (OpenAPI operation description object)
        }
      }
    }
  })
}

Some of the API Gateway features require special OpenAPI extensions because they are unique to API Gateway, but I believe it’s possible to describe all of the API Gateway features either way… OpenAPI just requires some different syntax.

Sorry I never replied to this. I figured there was a limitation like you described, so the fall back was to go with the OpenAPI solution. Thanks for the info.

Could you share some example how to solve this problem with OpenAPI solution?