のべラボ.blog

Tech Blog | AWS や サーバーレスやコンテナ などなど

AWS SAM で Open API の API 仕様を使って Amazon API Gateway の REST API を作成する

Amazon API Gateway では、 Open API 仕様(OpenAPI-Specification)をインポートすることで REST API を作成できます。

docs.aws.amazon.com

例えば、/order のパスで POST リクエストを受け付ける API を Open API 仕様に基づきファイルに記述しておくと、AWS CLI のコマンドにて API GatewayREST API としてインポートできます。

ただし、その API から AWS Lambda 関数を呼び出すための、いわゆる 統合 の設定については Amazon API Gateway 固有のものであり、Open API 仕様には含まれていません。

それを実現するためには、Open API の仕様に対する API Gateway 拡張という記述方法があるので、それを使用します。

docs.aws.amazon.com

一方、AWS Lambda 関数や Amazon API GatewayAPI をコードからデプロイするには、AWS SAM という強力なツールも存在します。

そこで今回は、Open API 仕様をベースに API をデザインして、それを AWS SAM を使用して Amazon API GatewayREST API としてデプロイしてみたいと思います。

この REST API から呼び出す AWS Lambda 関数も 同じ AWS SAM テンプレートで作成する前提です。

(この記事の内容は、2023年 1月に検証した内容を基に記載しています。)


使用する Open API 仕様

今回は、/hello のパスで GET リクエストを発行すると、Python で記述した AWS Lambda関数を呼び出す API を作ります。

使用する Open API 仕様は下記です。これを demo-openapi.yaml として保存します。

openapi: "3.0.1"
info:
  title: "demo-sam-openapi-api"
  version: "v1"
paths:
  /hello:
    get:
      responses:
        "200":
          description: "200 response"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Empty"
      x-amazon-apigateway-integration:
        httpMethod: "POST"
        uri: 
          Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DemoSamOpenApiFunction.Arn}/invocations"
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
components:
  schemas:
    Empty:
      title: "Empty Schema"
      type: "object"

上記中で、x-amazon-apigateway-integration: で指定している箇所が Amazon API Gateway の拡張部分で、AWS Lambda 関数とのプロキシ統合を指定しています。

プロキシ統合の対象、つまり呼び出す AWS Lambda 関数自体も 同じ AWS SAM テンプレートで作成するため、uri: で指定する AWS Lambda 関数の ARN は組込み関数 Sub を用いて設定しています。


使用する AWS SAM テンプレート

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-openapi

  Sample SAM Template for sam-openapi

Globals:
  Function:
    Timeout: 3

Resources:
  DemoSamOpenApi:
    Type: AWS::Serverless::Api
    Properties:
        Name: demo-sam-openapi-api
        StageName: Prod
        EndpointConfiguration:
          Type: REGIONAL
        DefinitionBody:
            Fn::Transform:
                Name: AWS::Include
                Parameters:
                    Location: demo-openapi.yaml
  DemoSamOpenApiFunction:
    Type: AWS::Serverless::Function 
    Properties:
      FunctionName: demo-sam-openapi-function
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        DemoSamOpenApiEvent:
          Type: Api 
          Properties:
            Path: /hello
            Method: get
            RestApiId: !Ref DemoSamOpenApi

Outputs:
 DemoSamOpenApi:
    Description: "API Gateway endpoint URL for Prod stage for DemoSamOpenApiFunction"
    Value: !Sub "https://${DemoSamOpenApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
 DemoSamOpenApiFunction:
    Description: "DemoSamOpenApiFunction ARN"
    Value: !GetAtt DemoSamOpenApiFunction.Arn
 DemoSamOpenApiFunctionIamRole:
    Description: "Implicit IAM Role created for DemoSamOpenApiFunction"
    Value: !GetAtt DemoSamOpenApiFunctionRole.Arn

上記テンプレートでは、Resources: にある DemoSamOpenApiAPI を定義しています。

その中で、Open API 仕様のファイルを指定します。

Open API 仕様のファイル ( demo-openapi.yaml ) は、この AWS SAM テンプレートのファイルと同じ場所にコピーしておきます。

なお今回は、AWS SAM テンプレート のファイルと Open API の仕様のファイルを別にしてローカルで保存していますが、Open API の仕様をそのまま AWS SAM テンプレートの中に記述することもできます。

その他の方法として、Open API の仕様ファイルを Amazon S3 バケットに保存して、AWS SAM テンプレートから その S3 の URI を指定することもできます。

AWS Lambda 関数は、今回の例では AWS SAM プロジェクトのフォルダに hello_world/ フォルダを作成し、そこに下記の PythonAWS Lambda 関数のコードを app.py として保存しています。

これは、hello world を返すだけのシンプルなコードです。

import json

def lambda_handler(event, context):

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world",
        }),
    }

AWS SAM テンプレートのデプロイと 結果確認

テンプレートの検証とビルドを行い、その後デプロイします。

sam validate
sam build
sam deploy --guided

上記では --guided オプションをつけて sam deploy を実行しているので、その後 対話式でデプロイが進みます。下記の時だけ y を入力します。それ以外はデフォルトのままで大丈夫です。

DemoSamOpenApiFunction may not have authorization defined, Is this okay? [y/N]:

デプロイが完了すると、出力された中で Key が DemoSamOpenApiValue で示されている URL を Web ブラウザでアクセスします。

下記は例です。

Key                 DemoSamOpenApi                                                              
Description         API Gateway endpoint URL for Prod stage for DemoSamOpenApiFunction          
Value               https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/  

その結果、下記のような hello world のメッセージが表示されれば成功です。

{"message": "hello world"}

やってみた所感

さて、今回やってみて成功したのですが、ひとつだけ気になる部分があります。

それは、AWS SAM テンプレートのリソースで AWS Lambda 関数の Events: の指定です。

この Events: で指定している内容は、Open API 仕様の中で指定している内容と同じなので、二重定義になってしまいます。

であれば、この Events: を記述しなければいい、という発想になるのですが、記述しない場合、Amazon API GatewayAPI からのアクセスを許可するという AWS Lambda 関数のリソースベースのポリシーが作成されません。

ではでは、そのポリシーを作成するように自分で明示的に記述すればいいではないかという発想になります。

確かに、下記のようなポリシー定義を AWS SAM テンプレートに追記することで Events: の記述は不要になります。

  DemoSamOpenApiFunctionPermission:
    Type: "AWS::Lambda::Permission"
    Properties: 
      Action: lambda:InvokeFunction
      FunctionName: !Ref DemoSamOpenApiFunction
      Principal: apigateway.amazonaws.com

ただし、上記のような記述でデプロイすると、AWS Lambda 関数のトリガーとして Amazon API GatewayAPI が認識されず、AWS マネジメントコンソールで AWS Lambda 関数のページを表示した場合、トリガーが表示されません。

それでも気にしない、運用上問題ない、ということであればよいのですが、どこか気持ち悪さを感じますよね。

よって、ポリシーも(自動)作成して、かつAWS Lambda 関数のトリガーとして Amazon API GatewayAPI を認識させたい場合は、 Events: を記載するのが良いかもしれません。

ただ、Open API 仕様で記述した内容と同じような内容を再び記述することになるので、混乱せず注意して記述するようにしましょう。

/* -----codeの行番号----- */