LoginSignup
5
5

More than 5 years have passed since last update.

【AWS Lambda】API Gatewayを一発で作成するスクリプトを作成してみた(Python版)

Last updated at Posted at 2019-01-14

はじめに

  • 任意のAWS Lambda関数に対するAPI Gatewayを一発の実行で作成できるPythonスクリプトの紹介です。
  • スクリプト自体はAWS Lambdaに作成しました。
  • クライアントからのリクエスト、Lambdaからのレスポンスのボディを無変換で対向に渡します。
  • API Gateway作成と同時にAPIキー認証、スロットリング、クォータの設定をしています。

スクリプトの作成

ランタイムにPythonを指定したAWS Lambda関数(今回は関数名agdep)を作成します。

以下のコードを記載した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)

実行ロールは以下のポリシーと同等以上の権限が付与されたロールを使用してください。

{
  "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"
    }
  ]
}

動作確認

APIGatewayを作成したいAWS Lambda関数(今回は関数名test-func)を指定して、先程作成したスクリプトをAWSCLIより実行します。
実行結果には作成されたAPIGatewayのエンドポイントURIとAPIキーが出力されるので控えておきます。

$ function_name=test-func
$ aws lambda invoke --function-name agdep --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****************************"
]

先程控えたエンドポイント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 のレスポンス ###

おわりに

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

わたしはAWS Lambdaを使ったシステムを構築するときはLambda関数とセットでAPIGatewayを作成して外部連携のためにAPI化することがほとんどなのですが、作成するAPIGatewayはネーミング以外は共通の設定で十分なことが多いため、我ながらとても便利に使っているスクリプトとなります。

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

余裕があればPowerShell版とシェルスクリプト版もそのうち投稿したいと思います。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5