この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
おはようございます。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テンプレートに対して値を追記、設定をオーバーライドすることができるため、とても便利な機能でした。