FAIZAN
FAIZAN

Reputation: 333

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
}



Upvotes: 2

Views: 1366

Answers (4)

EARonan
EARonan

Reputation: 88

For anyone coming across this in 2025, there's no solution to this problem as terraform simply doesn't support it. See https://github.com/hashicorp/terraform/issues/26697

The only solution to this I can come up with is to use an "external" data resource (data "external" "generator") that points to a script you write, consumes your configuration data, and generates a terraform file you can then consume in your TF modules.

Upvotes: 0

eksm27
eksm27

Reputation: 7

while workaround this steps in my environment, the second iteration got failed.

 paths = {
      for path, ops in local.api_ops_by_path : path => {
        for op in ops : lower(op.http_method) => {
          x-amazon-apigateway-integration = {

Error message is given below

but the for op in ops : op.http_method is not working

error is : Can't access attributes on a primitive-typed value (string).

My guess is, looks the op is print only value of dict. not as key : value

Upvotes: 0

Martin Atkins
Martin Atkins

Reputation: 74584

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:

Upvotes: 1

Quassnoi
Quassnoi

Reputation: 425713

That's impossible in Terraform.

In a Terraform state, objects depend on resources, not on instances of resources.

You're using aws_api_gateway_resource.api_resource[each.value].id as an input.

Had you used it as an input in another resource, the dependency would be created on aws_api_gateway_resource.api_resource, not on a particular instance of aws_api_gateway_resource.api_resource.

Since a resource cannot depend on itself, Terraform throws the error you're seeing.

Upvotes: 0

Related Questions