Давайте начнем с написания объявления этой переменной endpoints
, поскольку остальная часть ответа зависит от того, как он определен следующим образом:
variable "endpoints" {
type = set(object({
path = string
method = string
lambda = string
})
}
Выше сказано, что endpoints
являетсянабор предметов, что означает, что порядок предметов не имеет значения. Порядок не имеет значения, потому что мы все равно будем создавать отдельные объекты в API для каждого из них.
Следующий шаг - выяснить, как перейти от данной структуры данных к структуре, представляющей собой карту, в которой каждыйключ уникален и где каждый элемент сопоставляется с одним экземпляром ресурсов, которые вы хотите создать. Чтобы сделать это, мы должны определить, какое отображение мы намереваемся, и я думаю, что здесь будет:
- Один
aws_api_gateway_resource
для каждого отдельного path
. - Один
aws_api_gateway_method
для каждой отдельной пары path
и method
. - Один
aws_api_gateway_integration
для каждой отдельной пары path
и method
. - Один
aws_api_gateway_integration_response
для каждого отдельного path/
method /
status_code` triple. - Один
aws_api_gateway_method_response
для каждого отдельного path/
method /
status_code` triple.
Так что, похоже, нам здесь нужно три коллекции: first - это набор всех путей, second - карта от пары 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
. Для этого важно убедиться, что он зависит от всех ресурсов шлюза API, которые мы определили выше, поэтому Terraform будет ожидать полной настройки 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
выражений вместе с flatten
и setproduct
функции, являются нашим основным инструментом для проецирования данных из структуры, которая удобна для вызывающей стороны для предоставления во входной переменной структуры (структур), которые нам нужны для for_each
выражений.
AОднако PI Gateway имеет особенно сложную модель данных, поэтому для выражения всех его возможностей на языке Terraform может потребоваться гораздо больше проекций и других преобразований, чем для других служб. Поскольку OpenAPI уже определяет гибкий декларативный язык для определения API-интерфейсов REST, а API-шлюз уже изначально поддерживает его, может быть более простым и гибким сделать, чтобы ваша переменная endpoints
приняла стандартное определение OpenAPI и передала его непосредственно вAPI Gateway, таким образом, получая всю выразительность формата схемы 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 для конструирования схемы OpenAPI путем получения структуры данных из var.endpoints
и, наконец, передачи ее в jsonencode
.