当前位置: 首页 > 知识库问答 >
问题:

AWS CDK如何根据OpenApi规范创建由Lambda支持的API网关?

赵明亮
2023-03-14

我想使用 AWS CDK 来定义 API 网关和 APIG 将代理到的 lambda。

OpenAPI规范支持Swagger规范的x-amazon-apigateway-集成自定义扩展(详见此处),为此需要lambda的调用URL。如果lambda与API在同一个堆栈中定义,我看不出如何在OpenAPI规范中提供此内容。我能想到的最好的办法是定义一个包含lambda的堆栈,然后从中获取输出并运行sed在OpenAPI规范中执行查找和替换以插入uri,然后使用此html" target="_blank">修改后的OpenAPI规范创建第二个堆栈。

示例:

  /items:
    post:
      x-amazon-apigateway-integration:
        uri: "arn:aws:apigateway:eu-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-2:123456789012:function:MyStack-SingletonLambda4677ac3018fa48679f6-B1OYQ50UIVWJ/invocations"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        type: "aws_proxy"

这似乎是一个先有鸡还是先有蛋的问题,以上是唯一的方法吗?

我尝试使用SpecRestApi CDK构造的< code>defaultIntegration属性。文件指出:

除非指定了集成,否则将集成用作在此 API 中创建的所有方法的默认值。

这看起来像是应该能够使用CDK规范中定义的lambda来定义一个默认的集成,因此让所有的方法都使用这个集成,而不需要预先知道lambda的uri。

因此,我尝试了这个:

SingletonFunction myLambda = ...

SpecRestApi openapiRestApi = SpecRestApi.Builder.create(this, "MyApi")
                        .restApiName("MyApi")
                        .apiDefinition(ApiDefinition.fromAsset("openapi.yaml"))
                        .defaultIntegration(LambdaIntegration.Builder.create(myLambda)
                                    .proxy(false)
                                    .build())
                        .deploy(true)
                        .build();

openapi.yaml 中定义的开放API规范不包括 x-amazon-apigateway-集成节;它只有一个在标准OpenApi 3规范中定义的GET方法。

然而,当我尝试部署时,我得到了一个错误:

No integration defined for method (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID: 56113150-1460-4ed2-93b9-a12618864582)

这似乎是一个错误,所以我在这里提交了一个。

Q2。如何使用CDK定义API网关和Lambda,并通过OpenAPI规范将两者连接在一起?

共有3个答案

公冶子琪
2023-03-14

看起来我所追求的是这个CDK问题。与此同时,我被这里对这个问题的评论所引导,想出了一个解决办法。

我使用 https://github.com/spullara/mustache.java 来解析我的 OpenAPI 规范文件,并替换其中引用 API 网关的调用 ARN(本身引用 Lambda ARN)的模板值。

Map<String, Object> variables = new HashMap<>();
variables.put("restapi-lambda", String.format("arn:aws:apigateway:%s:lambda:path/2015-03-31/functions/%s/invocations", props.getEnv().getRegion(), myLambda.getFunctionArn()));

Writer writer = new StringWriter();
MustacheFactory mf = new DefaultMustacheFactory();

Object openapiSpecAsObject;
try (Reader reader = new FileReader(new File("myapi.yaml"))) {
    Mustache mustache = mf.compile(reader, "OAS");
    mustache.execute(writer, scopes);
    writer.flush();

    ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
    openapiSpecAsObject = yamlMapper.readValue(writer.toString(), Object.class);

}

SpecRestApi openapiRestApi = SpecRestApi.Builder.create(this, "MyRestApi")
                                                .restApiName("MyRestApi")
                                                .apiDefinition(ApiDefinition.fromInline(openapiSpecAsObject))
                                                .deploy(true)
                                                .build();

请注意,props是一个变量,它引用了堆栈prop,myLambda,它引用了一个SingletonFunction

我的 OpenAPI 规范如下所示(删除了标头和模型部分):

paths:
  /items:
    get:
      summary: List all items.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ItemList'
      x-amazon-apigateway-integration:
        uri: "{{restapi-lambda}}"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        type: "aws_proxy"

还要注意,当我授予API网关权限以调用lambda时,如下所示:

myLambda.grantInvoke(ServicePrincipal.Builder.create("apigateway.amazonaws.com")
                                              .build());

我仍然得到一个500错误,在日志中我可以看到一条“Lambda函数上的无效权限”错误消息。如果我向Lambda添加权限,如下所示:

myLambda.addPermission("PermitAPIGInvocation", Permission.builder()
                                  .action("lambda:InvokeFunction")
                                  .principal(ServicePrincipal.Builder.create("apigateway.amazonaws.com")
                                     .build())
                                  .sourceArn(openapiRestApi.arnForExecuteApi())
                                  .build());

那么我现在需要在权限生效之前重新部署API。我还在研究如何避免这种情况。

周和志
2023-03-14

我想出了一个解决方案,它比这里的其他答案简单一点,因为它不需要阶段变量或多次部署。

首先,将x-amazon-apigateway集成的uri设置为${API_LAMBDA_ARN}之类的变量,并使用与此示例中相同的类型httpMethod

[...]
  "paths": {
    "/pets": {
      "get": {
        "summary": "List all pets",
        "responses": {
          [...]
        },
        "x-amazon-apigateway-integration": {
          "uri": "${API_LAMBDA_ARN}",
          "type": "AWS_PROXY",
          "httpMethod": "POST",
        }
      }
    }
  },
[...]

然后,您可以使用此构造(或等效的TypeScript实现)在构建时替换变量并基于OpenAPI文档创建API Gateway Http API:

from aws_cdk import (
    core,
    aws_iam as iam,
    aws_lambda as _lambda,
    aws_apigatewayv2 as apigateway
)


class OpenApiLambdaStack(core.Stack):
    def __init__(
        self, scope: core.Construct, construct_id: str, **kwargs
    ) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # function that handles api request(s)
        api_lambda = _lambda.Function([...])

        # read openapi document
        with open("openapi.json", "r") as json_file:
            content = json_file.read()
        # replace the variable by the lambda functions arn
        content = content.replace("${API_LAMBDA_ARN}", api_lambda.function_arn)
        openapi = json.loads(content)

        # create apigateway
        http_api = apigateway.HttpApi(self, "OpenApiLambdaGateway")
        # use escape hatches to import OpenAPI Document
        # see: https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html
        http_api_cfn: apigateway.CfnApi = http_api.node.default_child
        http_api_cfn.add_property_override("Body", openapi)
        http_api_cfn.add_property_deletion_override("Name")
        http_api_cfn.add_property_deletion_override("ProtocolType")
        # let it fail on warnings to be sure everything went right
        http_api_cfn.add_property_override("FailOnWarnings", True)

        # construct arn of createad api gateway (to grant permission)
        http_api_arn = (
            f"arn:{self.partition}:execute-api:"
            f"{http_api.env.region}:{http_api.env.account}:"
            f"{http_api.http_api_id}/*/*/*"
        )

        # grant apigateway permission to invoke api lambda function
        api_lambda.add_permission(
            f"Invoke By {http_api.node.id} Permission",
            principal=iam.ServicePrincipal("apigateway.amazonaws.com"),
            action="lambda:InvokeFunction",
            source_arn=http_api_arn,
        )
        
        # output api gateway url
        core.CfnOutput(self, "HttpApiUrl", value=http_api.url)

Python用户也可能对我发布的openapigateway构造感兴趣,以使这个过程更加简单。它支持JSON和YAML。

柳俊彦
2023-03-14

有一个现有的解决方法。以下是方法:

您的OpenAPI文件必须如下所示:

openapi: "3.0.1"
info:
  title: "The Super API"
  description: "API to do super things"
  version: "2019-09-09T12:56:55Z"

servers:
- url: ""
  variables:
    basePath:
      default:
        Fn::Sub: ${ApiStage}

paths:
  /path/subpath:
    get:
      parameters:
      - name: "Password"
        in: "header"
        schema:
          type: "string"
      responses:
        200:
          description: "200 response"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserConfigResponseModel"
      security:
      - sigv4: []
      x-amazon-apigateway-integration:
        uri: 
          Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MySuperLambda.Arn}/invocations"
        responses:
          default:
            statusCode: "200"
        requestTemplates:
          application/json: "{blablabla}"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        type: "aws"

如您所见,此 OpenAPI 模板指的是 ApiStage、AWS::区域和 MySuperLambda.Arn。

关联的cdk文件包含以下内容:

// To pass external string, nothing better than this hacky solution: 
const ApiStage = new CfnParameter(this, 'ApiStage',{type: 'String', default: props.ApiStage})
ApiStage.overrideLogicalId('ApiStage') 

在这里,ApiStage用于道具。例如,它允许我在CI期间将其传递给具有环境变量的cdk应用程序。

const MySuperLambda = new lambda.Function(this, 'MySuperLambda', {
    functionName: "MySuperLambda",
    description: "Hello world",
    runtime: lambda.Runtime.PYTHON_3_7,
    code: lambda.Code.asset(lambda_asset),
    handler: "MySuperLambda.lambda_handler",
    timeout: cdk.Duration.seconds(30),
    memorySize: 128,
    role: MySuperLambdaRole
  });

  const forceLambdaId = MySuperLambda.node.defaultChild as lambda.CfnFunction
  forceLambdaId.overrideLogicalId('MySuperLambda')

在这里,如前所述,我强制CDK覆盖逻辑id,以便在部署之前知道id。否则,cdk将向逻辑ID添加后缀。

const asset = new Asset(this, 'SampleAsset', {
    path: './api-gateway-definitions/SuperAPI.yml',
  });

这允许我将OpenAPI文件直接上传到cdk bucket(不需要创建新的,这太棒了)。

const data = Fn.transform('AWS::Include', {'Location': asset.s3ObjectUrl})

这是云形成魔法的一部分。这是解释Fn::Sub和Fn::GetAtt的地方。我无法让它工作!参考函数。

const SuperApiDefinition = apigateway.AssetApiDefinition.fromInline(data)

从先前读取的文件创建 api 定义。

  const sftpApiGateway = new apigateway.SpecRestApi(this, 'superAPI', {
    apiDefinition: SuperApiDefinition,
    deploy: false
  })

最后,创建SpecRestApi。运行和魔术,这是工作。您可能仍然会遇到400个错误,可能是因为OpenAPI文件中的格式不正确(并且不要使用!Ref)。

我会重新指挥这个吗?呵呵。这几乎是一种解决方法。如果要在 CI 中将 OpenAPI 格式与动态变量一起使用,这将非常有用。无需花费太多精力,只需切换 1 个环境变量,即可在开发和生产中进行部署。

但是,这感觉真的很笨拙,似乎不适合CDK哲学。这是我目前用于部署的内容,但将来可能会更改。我相信一个真正的模板解决方案可能更适合这里,但现在,我真的没有想过。

 类似资料:
  • 我不熟悉swagger文档等,请分享为以下endpoint(spring boot microservice的endpoint)创建开放api规范的好资源或步骤: 任何帮助或参考将不胜感激。谢谢大家。

  • 我想问是否有一种方法可以简单地从不同的API中聚合OpenApi规范? 目前,我们为每个API添加了用户招摇过市的功能,并在其中添加了用于身份验证的自定义逻辑,我们还为不同的可访问性规则添加了规则。 然而,以某种方式聚合所有这些API,并将身份验证、访问逻辑保持在同一位置,会更方便。 例: 我们有两个独立的微服务,有独立的API和独立的地址 API 1localhost:5000 使用Orders

  • IdentityServer实现了以下规范: OpenID Connect OpenID Connect Core 1.0(规范) OpenID Connect Discovery 1.0(规范) OpenID Connect Session Management 1.0 – draft 22(规范) OpenID Connect HTTP-based Logout 1.0 –draft 03(规

  • 当我使用postman时,谷歌似乎没有验证我的请求主体架构,甚至没有验证请求有主体。我错过了什么吗?对我来说,这意味着谷歌在调用x-google-backend之前会验证此类事情,但它总是将请求传递给我的云函数,无论我是否传递有效数据。 我用这个问题作为指导。 user.yaml:

  • 我有一个从swagger 2.0更新到openapi 3.0.0的yaml规范。 文件本身大约有 7,000 行,因此手动验证具有挑战性。 我需要找出哪些标记不再与openapi 3.0.0兼容。如何验证我的模式?有没有我可以使用的命令行工具? 我不想将此代码复制/粘贴到在线某个地方,因为我不想公开所有路由。

  • 我的输入JSON如下所示,但是我不确定如何使用JOLT进行内部数组相关的参数转换。感谢任何帮助,因为我是新的JOLT 以下是我创建的规范文件,但它并不完整 预期输出如下 我正在使用这个库https://github.com/bazaarvoice/jolt