【小ネタ】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を代入する形にしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | 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の定義が出力されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | 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テンプレートに対して値を追記、設定をオーバーライドすることができるため、とても便利な機能でした。