terraformaws-api-gatewayinfrastructure-as-code

Create AWS_API_GATEWAY_RESOURCES in a loop


I am trying to create the aws_api_gateway resources using terraform, to avoid the hassle of adding the same code again and again, am trying to create a loop which create multiple resources for me in the order i specify. But, I am facing difficulties as I am new to this and it is always complaining of cyclic dependency.

The cyclic dependency is caused in "parent_id" while creating resources

Any Help would be greatly appreciated.

Here's what I have coded:

locals {
  api_endpoints = [
    {
      path           = "v0"
      http_method    = "GET"
      integration_uri = "${uri_path}/v0"
    },
    {
      path           = "v0/expert/gen/btags"
      http_method    = "POST"
      integration_uri = "${uri_path}/v0/expert/gen/btags"
    },
    {
      path           = "v0/expert/generate/tags"
      http_method    = "POST"
      integration_uri = "${uri_path}/v0/expert/gen/tags"
    },
    {
      path           = "v0/expert/search"
      http_method    = "POST"
      integration_uri = "${uri_path}/v0/expert/search"
    },
  ]
  resources_hierarchy = {
      "v0" = aws_api_gateway_rest_api.gatewayh.root_resource_id
      "expert" = "v0"
      "gen" = "expert"
      "search" = "expert"
      "btags" = "gen"
      "tags" = "gen"
    }
}

resource "aws_api_gateway_resource" "api_resource" {
  for_each = local.resources_hierarchy

  rest_api_id = aws_api_gateway_rest_api.gatewayh.id
  parent_id   = each.value == aws_api_gateway_rest_api.gatewayh.root_resource_id ? each.value : aws_api_gateway_resource.api_resource[each.value].id
  path_part   = each.key
}




Solution

  • The full Amazon API Gateway schema is challenging to construct dynamically in Terraform because it ends up requiring resources that depend on themselves. Dependencies in Terraform are between resource blocks as a whole rather than the individual instances of them, because Terraform must evaluate for_each or count before knowing which instances exist and those expressions can themselves have dependencies.

    However, you can avoid that problem by using the OpenAPI-based method for describing your API, which follows a flat structure similar to what you've shown as your input. The API Gateway API internally transforms that into the corresponding individual resources, resource methods, integration methods, etc, so the result is the same but constructed in a way that doesn't involve as many resources and doesn't involve any dependencies between the objects.

    For example:

    locals {
      api_ops_by_path = tomap({
        for op in local.api_endpoints : op.path => op
      })
    }
    
    resource "aws_api_gateway_rest_api" "example" {
      # ...
    
      body = jsonencode({
        openapi = "3.0.1"
        info = {
          title   = "example"
          version = "1.0"
        }
        paths = {
          for path, ops in local.api_ops_by_path : path => {
            for op in ops : lower(op.http_method) => {
              x-amazon-apigateway-integration = {
                httpMethod = op.http_method
                uri        = op.integration_uri
                # ...
              }
            }
          }
        }
      })
    }
    

    The above uses for expressions to derive a data structure that follows the OpenAPI spec for describing methods associated with paths, and uses the x-amazon-apigateway-integration extension, specific to Amazon API Gateway, to describe what each operation integrates with. It then uses jsonencode to encode that resulting data structure into a form that API Gateway can parse and analyze.

    When you define an API Gateway REST API with a body argument, you don't need to use any other resources to describe the schema; the information in body describes the same tree of resources in a different way. You will still need resources for the deployment, stages, etc, though, because they are independent of the actual API definition.

    OpenAPI uses a flat structure -- all paths listed together as a single map, rather than resources nested inside one another -- and so that's a better fit both for the way your input data is structured and for describing this in a form that doesn't cause problems for Terraform's dependency graph.


    References: