API Gatewayを一発で作成するLambda関数を作成してみた(Python版)

API Gatewayを一発で作成するLambda関数を作成してみた(Python版):


はじめに

AWS Lambdaを使ったシステムを構築するときはLambda関数とセットでAPIGatewayを作成して外部連携のためにAPI化することがほとんどなのですが、作成するAPIGatewayはネーミング以外は共通の設定で十分なことが多いです。そこで、指定のLambda関数に対するリクエスト送信/レスポンス取得を行うAPIGatewayを一発で作成できるスクリプトを作りました。

このスクリプトで作成されるAPIGatewayは、APIキー認証により、クライアントから受けたPOSTリクエストのペイロードをそのままLambda関数へ渡し、Lambda関数からのreturnをペイロードとしたレスポンスをそのままクライアントへ返す、最低限の機能のAPIエンドポイント/HTTPプロキシとなりますが、大抵の場合はそのまま利用できると思います。


スクリプト(Lambda関数)の作成

ランタイムにPython(バージョン不問)を指定したLambda関数を作成します。

Lambda関数の関数コードでは以下のコードを記載したlambda_function.pyを作成します。

lambda_function.py
import boto3 
import uuid 
 
agw = boto3.client('apigateway') 
lam = boto3.client('lambda') 
sts = boto3.client('sts') 
 
def lambda_handler(event, context): 
 
    #Lambda関数存在チェック 
    try: 
        #Lambda関数ARN取得 
        response=lam.get_function(FunctionName=event['functionName']) 
        lambdaArn=response['Configuration']['FunctionArn'] 
    except: 
        return('存在しないLambda関数が指定されています。') 
 
    #初期化 
    apiName = event['functionName'] + '_api' 
    regionName = 'ap-northeast-1' 
    accountId = (sts.get_caller_identity())['Account'] 
    stageName = 'dev' 
 
    #API作成 
    agw.create_rest_api(name=apiName) 
 
    #API ID取得 
    apis = {} 
    for item in (agw.get_rest_apis())['items']: 
        apis[item['name']]=item 
    apiId = apis[apiName]['id'] 
 
    #リソース作成 
    agw.create_resource( 
        restApiId=apiId, 
        parentId=(agw.get_resources(restApiId=apiId))['items'][0]['id'], 
        pathPart=apiName+'_rsc' 
    ) 
 
    #リソースID取得 
    resourcePath = '/' + apiName + '_rsc' 
    resources = {} 
    for item in (agw.get_resources(restApiId=apiId))['items']: 
        resources[item['path']]=item 
    resourceId = resources[resourcePath]['id'] 
 
    #メソッドリクエスト作成 
    agw.put_method( 
        restApiId=apiId, 
        resourceId=resourceId, 
        httpMethod='POST', 
        authorizationType='None', 
        apiKeyRequired=True 
    ) 
 
    #Lambda側での関数実行許可 
    lam.add_permission( 
        FunctionName=event['functionName'], 
        StatementId=str(uuid.uuid4()), 
        Action='lambda:InvokeFunction', 
        Principal='apigateway.amazonaws.com', 
        SourceArn='arn:aws:execute-api:' + regionName + ':' + accountId + ':' + apiId + '/*/POST/' + apiName + '_rsc' 
    ) 
 
    #統合リクエスト作成 
    agw.put_integration( 
        restApiId=apiId, 
        resourceId=resourceId, 
        httpMethod='POST', 
        type='AWS', 
        integrationHttpMethod='POST', 
        uri='arn:aws:apigateway:' + regionName + ':lambda:path/2015-03-31/functions/' + lambdaArn + '/invocations' 
    ) 
 
    #統合レスポンス作成 
    agw.put_method_response( 
        restApiId=apiId, 
        resourceId=resourceId, 
        httpMethod='POST', 
        statusCode='200' 
    ) 
 
    #メソッドレスポンス作成 
    agw.put_integration_response( 
        restApiId=apiId, 
        resourceId=resourceId, 
        httpMethod='POST', 
        statusCode='200', 
        responseTemplates={ 
            'application/json': '' 
        } 
    ) 
 
    #APIデプロイ 
    agw.create_deployment( 
        restApiId=apiId, 
        stageName=stageName 
    ) 
 
    #APIキー作成、キー値取得 
    api_key_value = (agw.create_api_key( 
        name=apiName+'_key', 
        enabled=True, 
        stageKeys=[ 
            { 
                'restApiId': apiId, 
                'stageName': stageName 
            }, 
        ] 
        ) 
    )['value'] 
 
    #Usageプラン作成 
    agw.create_usage_plan( 
        name=apiName+'_plan', 
        apiStages=[ 
            { 
                'apiId': apiId, 
                'stage': stageName 
            }, 
        ], 
        throttle={ 
            'burstLimit': 10, 
            'rateLimit': 5 
        }, 
        quota={ 
            'limit': 200, 
            'offset': 0, 
            'period': 'MONTH' 
        } 
    ) 
 
    #UsageプランID取得 
    plans = {} 
    for item in (agw.get_usage_plans())['items']: 
        plans[item['name']]=item 
    usegePlanId = plans[apiName+'_plan']['id'] 
 
    #APIキーID取得 
    keys = {} 
    for item in (agw.get_api_keys())['items']: 
        keys[item['name']]=item 
    keyId = keys[apiName+'_key']['id'] 
 
    #Usageプランキー作成 
    agw.create_usage_plan_key( 
        usagePlanId=usegePlanId, 
        keyId=keyId, 
        keyType='API_KEY' 
    ) 
 
    return('https://' + apiId + '.execute-api.' + regionName + '.amazonaws.com/' + stageName + resourcePath,api_key_value) 
Lambda関数の実行ロールでは以下のポリシーと同等以上の権限が付与されたロールを使用します。

{ 
  "Version": "2012-10-17", 
  "Statement": [ 
    { 
      "Action": [ 
        "lambda:GetFunction", 
        "lambda:AddPermission", 
        "apigateway:PUT", 
        "apigateway:POST", 
        "apigateway:GET" 
      ], 
      "Resource": [ 
        "arn:aws:lambda:<RegionName>:<AccountID>:function:*", 
        "arn:aws:apigateway:<RegionName>::*" 
      ], 
      "Effect": "Allow" 
    } 
  ] 
} 


スクリプト(Lambda関数)を実行してAPIGatewayを作成

先程作成したスクリプト(Lambda関数)をAWSCLIより実行します。

作成されたAPIGateswayのエンドポイントURIとAPIキーが表示されるので控えます。

$ function_name=test-func #作成するAPIGatewayを使用するLambda関数名を指定 
$ aws lambda invoke --function-name <スクリプトのLambda関数名> --payload {\"functionName\":\"${function_name}\"} outfile;cat outfile | jq .;rm outfile 
{ 
    "ExecutedVersion": "$LATEST",  
    "StatusCode": 200 
} 
[ 
  "https://5wd******.execute-api.ap-northeast-1.amazonaws.com/dev/test-func_api_rsc", 
  "aW7OjaD8****************************" 
] 


作成したAPIGatewayを利用

先程控えたエンドポイントURIとAPIキーを指定してPOSTリクエストを実行し、想定通りのレスポンスが返ってくればAPIGatewayは利用可能になっています。

$ endpoint_uri=https://5wd******.execute-api.ap-northeast-1.amazonaws.com/dev/test-func_api_rsc 
$ api_key=aW7OjaD8**************************** 
$ data='{"key1":"value1","key2":"value2","key3":"value3"}' 
$ curl -X POST -H "x-api-key:$api_key" -d $data $endpoint_uri 
### test-func のレスポンス ### 


おわりに

クローズドな利用のLambda関数を量産しているため、我ながらとても便利に使っているスクリプトとなります。

作成されるAPIGatewayはID、キー値、実行するLambda関数以外は共通の設定のため、APIGatewayが実行するLambda関数をリクエスト時に動的に指定できるようになると、作成するAPIGatewayの数をうんと減らせるのですが、いまの仕様だと出来なさそうです。

コメント

このブログの人気の投稿

投稿時間:2021-06-17 22:08:45 RSSフィード2021-06-17 22:00 分まとめ(2089件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)