Terraform:如何从对象列表创建API Gateway端点和方法?

时间:2019-10-15 15:06:38

标签: terraform aws-api-gateway terraform-provider-aws

我想创建一个Terraform(v0.12 +)模块,该模块输出具有Lambda集成的AWS API Gateway。我不太了解如何(或什至有可能)遍历地图列表以动态输出资源。

用户应该能够像这样实例化模块:

rollup({
input: '/tmp/test.ts',
treeshake: true,
plugins: [
    resolve(),
    commonjs(),
    sourcemaps(),
    typescript({
    ...tsconfig.compilerOptions,
    include: ["/tmp/*.ts" ],
})]})

'somePackageName/common' is imported by ../../../../../private/tmp/test.ts, but could not be resolved – treating it as an external dependency界面,我想输出三个资源:

  1. 一个node_modules,其中somePackageName/common
  2. 一个dedupe,其中module "api_gateway" { source = "./apig" endpoints = [ { path = "example1" method = "GET" lambda = "some.lambda.reference" }, { path = "example1" method = "POST" lambda = "some.lambda.reference" }, { path = "example2" method = "GET" lambda = "another.lambda.reference" } ] }
  3. 引用了以下内容的endpointsaws_api_gateway_resource

Terraform的path_part = endpoint[i].path属性似乎不够强大,无法处理此问题。我知道Terraform还支持aws_api_gateway_method循环和http_method = endpoint[i].method循环,但是我找不到使用此类表达式进行资源声明的任何示例。

2 个答案:

答案 0 :(得分:1)

让我们首先写出该endpoints变量的声明,因为其余的答案取决于该变量的定义方式:

variable "endpoints" {
  type = set(object({
    path   = string
    method = string
    lambda = string
  })
}

上面说endpoints是一组对象,这意味着项目的顺序并不重要。顺序无关紧要,因为无论如何我们将在API中为每个对象创建单独的对象。

下一步是弄清楚如何从给定的数据结构转移到一个结构,该结构中的每个键都是唯一的,每个元素都映射到您要产生的资源的一个实例。为此,我们必须定义我们想要的映射,我认为这里是:

  • 每个不同的aws_api_gateway_resource有一个path
  • 每个aws_api_gateway_methodpath对分别有一个method
  • 每个aws_api_gateway_integrationpath对分别有一个method
  • 每个aws_api_gateway_integration_response方法path/ status_code三元组各有一个/
  • 每个aws_api_gateway_method_response方法path/ status_code三元组各有一个/

因此,这里似乎需要三个集合:首先是所有路径的集合,其次是从path + method对到描述该方法的对象的映射,以及第三是我们要建模的端点和状态代码的每种组合。

locals {
  response_codes = toset({
    status_code         = 200
    response_templates  = {} # TODO: Fill this in
    response_models     = {} # TODO: Fill this in
    response_parameters = {} # TODO: Fill this in
  })

  # endpoints is a set of all of the distinct paths in var.endpoints
  endpoints = toset(var.endpoints.*.path)

  # methods is a map from method+path identifier strings to endpoint definitions
  methods = {
    for e in var.endpoints : "${e.method} ${e.path}" => e
  }

  # responses is a map from method+path+status_code identifier strings
  # to endpoint definitions
  responses = {
    for pair in setproduct(var.endpoints, local.response_codes) :
    "${pair[0].method} ${pair[0].path} ${pair[1].status_code}" => {
      method              = pair[0].method
      path                = pair[0].path
      method_key          = "${pair[0].method} ${pair[0].path}" # key for local.methods
      status_code         = pair[1].status_code
      response_templates  = pair[1].response_templates
      response_models     = pair[1].response_models
      response_parameters = pair[1].response_parameters
    }
  }
}

定义了这两个派生集合后,我们现在可以写出资源配置:

resource "aws_api_gateway_rest_api" "example" {
  name = "example"
}

resource "aws_api_gateway_resource" "example" {
  for_each = local.endpoints

  rest_api_id = aws_api_gateway_rest_api.example.id
  parent_id   = aws_api_gateway_rest_api.example.root_resource_id
  path_part   = each.value
}

resource "aws_api_gateway_method" "example" {
  for_each = local.methods

  rest_api_id = aws_api_gateway_resource.example[each.value.path].rest_api_id
  resource_id = aws_api_gateway_resource.example[each.value.path].resource_id
  http_method = each.value.method
}

resource "aws_api_gateway_integration" "example" {
  for_each = local.methods

  rest_api_id = aws_api_gateway_method.example[each.key].rest_api_id
  resource_id = aws_api_gateway_method.example[each.key].resource_id
  http_method = aws_api_gateway_method.example[each.key].http_method

  type                    = "AWS_PROXY"
  integration_http_method = "POST"
  uri                     = each.value.lambda
}

resource "aws_api_gateway_integration_response" "example" {
  for_each = var.responses

  rest_api_id = aws_api_gateway_integration.example[each.value.method_key].rest_api_id
  resource_id = aws_api_gateway_integration.example[each.value.method_key].resource_id
  http_method = each.value.method
  status_code = each.value.status_code

  response_parameters = each.value.response_parameters
  response_templates  = each.value.response_templates

  # NOTE: There are some other arguments for
  # aws_api_gateway_integration_response that I've left out
  # here. If you need them you'll need to adjust the above
  # local value expressions to include them too.
}

resource "aws_api_gateway_response" "example" {
  for_each = var.responses

  rest_api_id = aws_api_gateway_integration_response.example[each.key].rest_api_id
  resource_id = aws_api_gateway_integration_response.example[each.key].resource_id
  http_method = each.value.method
  status_code = each.value.status_code

  response_models     = each.value.response_models
}

您可能还需要一个aws_api_gateway_deployment。为此,重要的是要确保它依赖于我们上面定义的 all API网关资源,以便Terraform在尝试部署API之前要等到API完全配置后:

resource "aws_api_gateway_deployment" "example" {
  rest_api_id = aws_api_gateway_rest_api.example.id

  # (whatever other settings are appropriate)

  depends_on = [
    aws_api_gateway_resource.example,
    aws_api_gateway_method.example,
    aws_api_gateway_integration.example,
    aws_api_gateway_integration_response.example,
    aws_api_gateway_method_response.example,
  ]
}

output "execution_arn" {
  value = aws_api_gateway_rest_api.example.execution_arn

  # Execution can't happen until the gateway is deployed, so
  # this extra hint will ensure that the aws_lambda_permission
  # granting access to this API will be created only once
  # the API is fully deployed.
  depends_on = [
    aws_api_gateway_deployment.example,
  ]
}

除了

API网关详细信息之外,这种情况的一般过程是:

  • 定义您的输入。
  • 弄清楚如何从输入到每个资源每个实例具有一个元素的集合。
  • 编写local表达式以描述从输入到重复集合的投影。
  • resource块,其中for_each引用适当的本地值作为其重复值。

for expressions以及flattensetproduct函数是我们从结构投影数据的主要工具,方便调用者在结构中输入输入变量我们需要for_each表达式。

尽管API网关具有特别复杂的数据模型,所以要在Terraform语言中表达其所有可能性,可能需要比其他服务更多的投影和其他转换。因为OpenAPI已经定义了一种用于定义REST API的灵活的声明性语言,并且API Gateway已经本身支持它,所以使您的endpoints变量采用标准的OpenAPI定义并将其直接传递给它可能更加直接和灵活。 API网关,从而获得了OpenAPI模式格式的所有表现力,而不必自己在Terraform中实现所有细节:

variable "endpoints" {
  # arbitrary OpenAPI schema object to be validated by API Gateway
  type = any
}

resource "aws_api_gateway_rest_api" "example" {
  name = "example"
  body = jsonencode(var.endpoints)
}

即使您仍然希望将endpoints变量用作更高级别的模型,也可以考虑使用Terraform语言通过从中派生数据结构来构造 var.endpoints,最后将其传递给jsonencode

答案 1 :(得分:0)

有一个配置文件(json)

#configuration.json
{
  "lambda1": {
    "name": "my-name",
    "path": "my-path",
    "method": "GET"
  },
 "lambda2": {
    "name": "my-name2",
    "path": "my-path2",
    "method": "GET"
  },

}

以及以下地形


locals {
  conf = jsondecode(file("${path.module}/configuration.json"))
  name="name"
}

data "aws_caller_identity" "current" {}


resource "aws_lambda_permission" "apigw_lambda" {
  for_each      = local.conf
  statement_id  = "AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = each.value.name
  principal     = "apigateway.amazonaws.com"

  # More: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html
  source_arn = "arn:aws:execute-api:${var.region}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.api.id}/*/${aws_api_gateway_method.methods[each.key].http_method}${aws_api_gateway_resource.worker-path[each.key].path}"
}

resource "aws_api_gateway_rest_api" "api" {
  name        = local.name
  description = "an endpoints...."
  endpoint_configuration {
    types = ["REGIONAL"]
  }
  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_api_gateway_resource" "country-endpoint" {
  rest_api_id = aws_api_gateway_rest_api.api.id
  parent_id   = aws_api_gateway_rest_api.api.root_resource_id
  path_part   = local.country-code # https.exmaple.com/stage/uk
  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_api_gateway_resource" "worker-path" {
  for_each    = local.conf
  rest_api_id = aws_api_gateway_rest_api.api.id
  parent_id   = aws_api_gateway_resource.country-endpoint.id
  path_part   = each.value.path # https.exmaple.com/stage/uk/path_from_json
  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_api_gateway_method" "methods" {
  for_each      = local.conf
  http_method   = each.value.method
  resource_id   = aws_api_gateway_resource.worker-path[each.key].id
  rest_api_id   = aws_api_gateway_rest_api.api.id
  authorization = "NONE"
}


resource "aws_api_gateway_integration" "lambda-api-integration-get-config" {
  for_each      = local.conf
  # The ID of the REST API and the endpoint at which to integrate a Lambda function
  resource_id   = aws_api_gateway_resource.worker-path[each.key].id
  rest_api_id   = aws_api_gateway_rest_api.api.id
  # The HTTP method to integrate with the Lambda function
  http_method = aws_api_gateway_method.methods[each.key].http_method
  # AWS is used for Lambda proxy integration when you want to use a Velocity template
  type = "AWS_PROXY"
  # The URI at which the API is invoked
  uri = data.terraform_remote_state.workers.outputs.lambda_invoke[each.key]
  integration_http_method = "POST"
}