AWS CDK how to create an API Gateway backed by Lambda from OpenApi spec?

大兔子大兔子 提交于 2020-06-17 02:07:13

问题


I want to use AWS CDK to define an API Gateway and a lambda that the APIG will proxy to.

The OpenAPI spec supports a x-amazon-apigateway-integration custom extension to the Swagger spec (detailed here), for which an invocation URL of the lambda is required. If the lambda is defined in the same stack as the API, I don't see how to provide this in the OpenAPI spec. The best I can think of would be to define one stack with the lambda in, then get the output from this and run sed to do a find-and-replace in the OpenAPI spec to insert the uri, then create a second stack with this modified OpenAPI spec.

Example:

  /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"

Q1. This seems like a chicken-and-egg problem, is the above the only way to do this?

I tried to use the defaultIntegration property of the SpecRestApi CDK construct. The documentation states:

An integration to use as a default for all methods created within this API unless an integration is specified.

This seems like a should be able to define a default integration using a lambda defined in the CDK spec and therefore have all methods use this integration, without needing to know the uri of the lambda in advance.

Thus I tried this:

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();

The OpenAPI spec defined in openapi.yaml does not include a x-amazon-apigateway-integration stanza; it just has a single GET method defined within a standard OpenApi 3 specification.

However, when I try to deploy this, I get an error:

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

This seems like a bug, so I filed one here.

Q2. How do I define an API Gateway and Lambda using CDK and wire the two together via an OpenAPI spec?


回答1:


It looks like what I'm after is tracked by this CDK issue. In the meantime, I was guided by the comment on that issue here and came up with a workaround.

I used https://github.com/spullara/mustache.java to parse my OpenAPI spec file and replace template values in it that referenced the invocation ARN of the API gateway (that itself references the 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();

Note that props is a variable that refers to the Stack props and myLambda is a reference to a SingletonFunction.

My OpenAPI spec looks like this (header and model sections removed):

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"

Also note that when I granted API Gateway permissions to invoke the lambda like this:

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

I still get a 500 error and in the logs I can see an "Invalid permissions on Lambda function" error message. If I add permissions to the Lambda, like this:

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

then I currently need to redeploy the API before the permissions take effect. I'm still working through how to avoid this.



来源:https://stackoverflow.com/questions/62179893/aws-cdk-how-to-create-an-api-gateway-backed-by-lambda-from-openapi-spec

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!