【小ネタ】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テンプレートに対して値を追記、設定をオーバーライドすることができるため、とても便利な機能でした。