AWS CDK で API Gateway + Swaggerの環境を構築する
おはようございます。CX事業本部@札幌の佐藤です。
はじめに
AWS CDKではAPI Gatewayを作成する方法として、以下の3種類の方法があります。
@aws-cdk/aws-apigateway
の RestAPI を使う(基本はこれ)@aws-cdk/aws-apigateway
の CfnXXXX を使う(冗長な記述になる)@aws-cdk/aws-sam
の CfnApi を使う(AWS SAMのAWS::Serverless::Api
と同じ)
概要
AWS CDKを使ってサーバーレスなRestAPIのインフラを構築することになりました。AWS SAMだと、AWS::Serverless::Apiを使えば、Swaggerに対応しているんですが、AWS CDKでは以下のIssueにある通り、現状API GatewayのSwaggerが対応されていません。(対応予定ではあります)
https://github.com/aws/aws-cdk/issues/723
RestApiでSwaggerが使えないとなると、CfnRestApiを使うしかないんですが、せっかくCDKを使っているのに低レベルなConstructを使ってしまうと、CDKの抽象化のメリットが薄れてしまうため、なるべくRestApiなどの高レベルなConstructを使いたいところです。どうにかして高レベルConstructを使いつつSwagger対応できないかを調査しました。
ConstructNodeを使って解決
CDKには Core API として、ConstructNode というものがあり、これは CDKによって作成される構成ツリー(CloudFormationの実体)と対話するためのAPIとなっています。これを使うことで、高レベルConstruct から CloudFormation のリソースを抽出して、動的にプロパティを変更して、Swaggerに対応させるということができました。以下は実際のコードです。
CDKのインフラコード
@aws-cdk/aws-apigateway
のRestApi
を使いつつ、Swaggerに対応させます。api.node.findChild('Resource')
でCloudFormationのAWS::APIGateway::RestApi
のリソースを取得し、body
プロパティにSwaggerのJSONを代入する形にしています。
import * as cdk from '@aws-cdk/core'; import * as lambda from '@aws-cdk/aws-lambda'; import * as apig from '@aws-cdk/aws-apigateway'; import { Swagger } from './interfaces/swagger'; import { CfnRestApi } from '@aws-cdk/aws-apigateway'; export class ApiGatewaySwaggerStack extends cdk.Stack { private myRegion: string = cdk.Stack.of(this).region; constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // Lambdaの作成 this.testFunction = new lambda.Function(this, 'testFunction', { code: lambda.Code.fromAsset('src/lambda/handlers/api-gw'), runtime: lambda.Runtime.NODEJS_10_X, handler: 'test-function.handler', description: 'Api GatewayからトリガされるFunction', functionName: 'test-function', memorySize: 256, }); // API Gatewayの作成(RestApiを使う) const api = new apig.RestApi(this, 'RestApi', { restApiName: "test", }); // nodeの子要素からAWS::ApiGateway::RestApiを取得し、bodyプロパティにswaggerのJSONを追記 const apiNode = api.node.findChild('Resource') as CfnRestApi; apiNode.body = this.createSwaggerJson(); // 現状、公式でSwaggerが対応されていなく、「The REST API doesn't contain any methods」エラーになってしまうため、ダミーでメソッドを追加する api.root.addMethod('ANY', new apig.MockIntegration()); } private createSwaggerJson(): Swagger { return { openapi: '3.0.0', info: { description: 'test', title: 'test', version: "0.1" }, paths: { '/invoke':{ post: { summary: 'LambdaをInvokeする', parameters: [ { in: 'path', name: 'hoge', required: true, schema: { type: 'string' } } ], requestBody: { content: { 'application/json': { schema: { properties: { foo: { type: 'string', }, bar: { type: 'integer' } } } } } }, responses: { 200: { description: 'OK', content: { 'application/json': { schema: { properties: { id: { type: 'integer', }, name: { type: 'string' } } } } } } }, 'x-amazon-apigateway-integration': { uri: `arn:aws:apigateway:${this.myRegion}:lambda:path/2015-03-31/functions/${this.testFunction.functionArn}/invocations`, responses: { default: { statusCode: "200" } }, passthroughBehavior: "when_no_match", httpMethod: "POST", contentHandling: "CONVERT_TO_TEXT", type: "aws_proxy" } } } } } } }
cdk synth
でCloudFormationテンプレートを出力すると、以下のようにBody
プロパティに、Swaggerの定義が出力されています。
RestApi0C43BF4B: Type: AWS::ApiGateway::RestApi Properties: Body: openapi: 3.0.0 info: description: test title: test version: "0.1" paths: /invoke: post: summary: LambdaをInvokeする parameters: - in: path name: hoge required: true schema: type: string requestBody: content: application/json: schema: properties: foo: type: string bar: type: integer responses: "200": description: OK content: application/json: schema: properties: id: type: integer name: type: string x-amazon-apigateway-integration: uri: Fn::Join: - "" - - "arn:aws:apigateway:" - Ref: AWS::Region - :lambda:path/2015-03-31/functions/ - Fn::GetAtt: - TestFunctionC517E46D - Arn - /invocations responses: default: statusCode: "200" passthroughBehavior: when_no_match httpMethod: POST contentHandling: CONVERT_TO_TEXT type: aws_proxy Name: test
まとめ
このように、現状高レベルで対応されていないリソースに関しても、ConstructNodeを使うことで、直接CloudFormationテンプレートに対して値を追記、設定をオーバーライドすることができるため、とても便利な機能でした。