Slack + Lambda (Python) でAWS API Gatewayモックを作ってみよう

#前回までのあらすじ
[前々回の記事](https://qiita.com/takeh/items/4fd78774eef1a36d8b3a)でAPI Gatewayを使ってAPIのモックを作ってみました。
API Gatewayの統合リクエストの統合タイプ：Mockを使えば固定値を返すAPIモックは簡単に作れることが分かったので、今後も業務でちょこちょこ作っていこうと思います。
が、いちいちAWSコンソールから作るのが面倒なので、Slackからサクッと作れるようにしたいと思います。

#API Gatewayを作るLambda(Python)の作成
PythonからAWSをコントロールするために`Boto3`を使います。
参考にした[Boto3のドキュメント](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda.html#Lambda.Client.create_function)。

##Slackでのキーワード
Slackからは以下のキーワードでAPI Gatewayを作ることにします。
`api_gateway [API名] [リソース名] [メソッド] [ステータスコード] [レスポンスボディ] [ステージ名]`
Slackから受け取った上記のキーワードをLambdaでは以下のように取り出して変数に格納することにします。

```python
# クエリ文字列解析
query = parse_qs(event.get('body') or '')

SETTINGS = query.get('text', [''])[0].split(' ')
API_GATEWAY_NAME = SETTINGS[1]          # API名
PATH_PART = SETTINGS[2]                 # リソース名
HTTP_METHOD = SETTINGS[3]               # メソッド
STATUS_CODE = SETTINGS[4]               # ステータスコード
RESPONSE_TEMPLATES = SETTINGS[5]        # レスポンスボディ
STAGE_NAME = SETTINGS[6]                # ステージ名
```

##API Gatewayの作成

```python
REST_API_ID = agw.create_rest_api(name = API_GATEWAY_NAME)
```

##ルートのリソースIDの取得とリソースの作成

```python
# ルートのリソースIDの取得
PARENT_ID = agw.get_resources(restApiId = REST_API_ID['id'])

# リソースの作成
RESOURCE_ID = agw.create_resource(
    restApiId = REST_API_ID['id'],
    parentId = PARENT_ID['items'][0]['id'],
    pathPart = PATH_PART
    )
```

##メソッドリクエストの作成

```python
agw.put_method(
    restApiId = REST_API_ID['id'],
    resourceId = RESOURCE_ID['id'],
    httpMethod = HTTP_METHOD,
    authorizationType = 'None'
    )
```

##統合リクエストの作成

```python
agw.put_integration(
    restApiId = REST_API_ID['id'],
    resourceId = RESOURCE_ID['id'],
    httpMethod = HTTP_METHOD,
    type = 'MOCK',
    requestTemplates = {'application/json':'{"statusCode":' + STATUS_CODE + '}'}
    )
```

##メソッドレスポンスの作成

```python
agw.put_method_response(
    restApiId = REST_API_ID['id'],
    resourceId = RESOURCE_ID['id'],
    httpMethod = HTTP_METHOD,
    statusCode = STATUS_CODE,
    responseModels = {'application/json': 'Empty'}
    )
```

##統合レスポンスの作成

```python
agw.put_integration_response(
    restApiId = REST_API_ID['id'],
    resourceId = RESOURCE_ID['id'],
    httpMethod = HTTP_METHOD,
    statusCode = STATUS_CODE,
    responseTemplates = {'application/json': RESPONSE_TEMPLATES }
    )
```

##作成したAPIのデプロイ

```python
agw.create_deployment(
    restApiId = REST_API_ID['id'],
    stageName = STAGE_NAME
    )
```

##呼び出しURLをSlackに返す
手を抜いてリージョンはコードに埋め込んでいるので、可変にしたい場合は変数で逃しておいてください。

```python
RESOURCE_NAME = agw.get_resources(restApiId = REST_API_ID['id'])
URL = 'https://' + REST_API_ID['id'] + '.execute-api.ap-northeast-1.amazonaws.com/' + STAGE_NAME + '/' + PATH_PART
return {
    'statusCode': 200,
    'body': json.dumps({
        'text': URL
        })
     }
```

##コードのまとめ
上記のコードをまとめると以下のようになります。
余計なリクエストを受けないようにSlackトークンをチェックしています。
SlackトークンはLambdaの環境変数にセットしそれを`os.environ.get('SLACK_TOKEN')`で呼び出しています。
Slackのキーワードが不足している時の処理などは入れていないので、必要に応じて追加してください。

```python
import boto3
import json
import os
from urllib.parse import parse_qs
 
agw = boto3.client('apigateway')
token = os.environ.get('SLACK_TOKEN')
 
def lambda_handler(event, context):
     
    # クエリ文字列解析
    query = parse_qs(event.get('body') or '')
     
    # 設定値
    SETTINGS = query.get('text', [''])[0].split(' ')
    API_GATEWAY_NAME = SETTINGS[1]          # API名
    PATH_PART = SETTINGS[2]                 # リソース名
    HTTP_METHOD = SETTINGS[3]               # メソッド
    STATUS_CODE = SETTINGS[4]               # ステータスコード
    RESPONSE_TEMPLATES = SETTINGS[5]        # レスポンスボディ
    STAGE_NAME = SETTINGS[6]                # ステージ名
     
    # slackトークンの確認
    if query.get('token', [''])[0] != token:
        return { 'statusCode': 400 }
     
    # API Gatewayの作成
    REST_API_ID = agw.create_rest_api(name = API_GATEWAY_NAME)
 
    # ルートのリソースIDの取得
    PARENT_ID = agw.get_resources(restApiId = REST_API_ID['id'])
    # リソースの作成
    RESOURCE_ID = agw.create_resource(
        restApiId = REST_API_ID['id'],
        parentId = PARENT_ID['items'][0]['id'],
        pathPart = PATH_PART
        )
     
    # メソッドリクエストの作成
    agw.put_method(
        restApiId = REST_API_ID['id'],
        resourceId = RESOURCE_ID['id'],
        httpMethod = HTTP_METHOD,
        authorizationType = 'None'
        )
     
    # 統合リクエストの作成
    agw.put_integration(
        restApiId = REST_API_ID['id'],
        resourceId = RESOURCE_ID['id'],
        httpMethod = HTTP_METHOD,
        type = 'MOCK',
        requestTemplates = {'application/json':'{"statusCode":' + STATUS_CODE + '}'}
        )
     
    # メソッドレスポンスの作成
    agw.put_method_response(
        restApiId = REST_API_ID['id'],
        resourceId = RESOURCE_ID['id'],
        httpMethod = HTTP_METHOD,
        statusCode = STATUS_CODE,
        responseModels = {'application/json': 'Empty'}
        )
     
    # 統合レスポンスの作成
    agw.put_integration_response(
        restApiId = REST_API_ID['id'],
        resourceId = RESOURCE_ID['id'],
        httpMethod = HTTP_METHOD,
        statusCode = STATUS_CODE,
        responseTemplates = {'application/json': RESPONSE_TEMPLATES }
        )
 
    # 作成したAPIのデプロイ
    agw.create_deployment(
        restApiId = REST_API_ID['id'],
        stageName = STAGE_NAME
        )
         
    # 呼び出しURLをSlackに返す
    RESOURCE_NAME = agw.get_resources(restApiId = REST_API_ID['id'])
    URL = 'https://' + REST_API_ID['id'] + '.execute-api.ap-northeast-1.amazonaws.com/' + STAGE_NAME + '/' + PATH_PART
    return {
        'statusCode': 200,
        'body': json.dumps({
            'text': URL
            })
        }
```

#AWS Gatewayの作成
上記で作ったLambdaを呼び出すAPI Gatewayを以下の設定で作成します。
・ プロトコル：REST
・ メソッド：POST
・ 統合リクエスト
　・ 統合タイプ：Lambda関数
　・ Lambdaプロキシ統合の使用：チェックON
　・ Lambda関数：作成したLambda関数を指定

#Slackの発信Webhookの作成
・ Outgoing Webhookで
　・ チャンネル：botを参加させるSlackチャンネルを指定
　・ 引き金となる言葉：Lambda関数を起動させるトリガー（今回は「api_gateway」と指定）
　・ URL：上で作成したAPI Gatewayのステージの呼び出しURL
　・ トークン：表示されているトークンをコピーして、Lambda関数の環境変数に登録
　　・ キー：SLACK_TOKEN（今回の場合）、値：Slackに表示されているトークン
名前のカスタマイズやアイコンはお好みで。

#まとめ
SlackからAPI Gatewayを作れるようになりました。
API Gatewayの削除などまだまだ足りない機能はありますが、上記を参考に追加してみてください。

#プチ連載
[1回目　AWS API Gatewayでモックを作ってみよう](https://qiita.com/takeh/items/4fd78774eef1a36d8b3a)
[2回目　AWS API Gateway + Lambda (Python) でモックを作ってみよう](https://qiita.com/takeh/items/3915e47f58939a6fab88)
3回目　Slack + Lambda (Python) でAWS API Gatewayモックを作ってみよう（本記事）

#We're hiring!
AIチャットボットを開発しています。
ご興味ある方は [Wantedlyページ](https://www.wantedly.com/projects/266837) からお気軽にご連絡ください！

#参考記事
[Boto3 Docs Lambda](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda.html#Lambda.Client.create_function)
[Qiita AWS Lambda (Python3) + API Gateway で Slackのbotを作ろう](https://qiita.com/t--k/items/1377d1a57075fdbc238d)
[Qiita [JAWS-UG CLI] API Gateway: #1 RestAPIの作成](https://qiita.com/tcsh/items/e0d592a2b35fea32e9c5)
[Qiita AWS CLIのレスポンス処理は jq 使う前に --query オプションの利用を考えてみよう！](https://qiita.com/m-yamashita/items/74dbcbf029a9324b7952)
[Qiita 【AWS Lambda】API Gatewayを一発で作成するスクリプトを作成してみた（Python版）](https://qiita.com/r-wakatsuki/items/da9cf517124ca9a6351b)
[AWS CLI Command Reference apigateway](https://docs.aws.amazon.com/cli/latest/reference/apigateway/index.html)