AWS SAMを使ってサクッとPythonとlambda、API GatewayでAPIを作る

プロジェクトで、Pythonを使った簡単なAPIを作ることになったので、サクッとAPIを作る環境を整えていきます。

# 前提
エディター…VSCode
OS…Windows10
Python3.8インストール済み
AWS CLI導入済み
> [Windows での AWS CLI バージョン 2 のインストール - AWS Command Line Interface](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2-windows.html)

# 必要な環境
- AWS SAM CLI
- AWS Toolkit for Visual Studio Code

# AWS SAMの利点
[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/what-is-sam.html)によると

> - 単一のデプロイ構成
> - AWS CloudFormation の拡張
> - 組み込みのベストプラクティス
> - ローカルのデバッグとテスト
> - 開発ツールとの緊密な統合

がメリット。
個人的にはlambdaをローカルでデバッグできるのは大きなメリットでした。
実態はCloudFormationなので、カスタマイズ性があるのもポイント高い。

# AWS SAM CLIの導入
[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-install-windows.html)に従って行います。要約すると

> 1. dockerをインストール
> 2. AWSが配布してるインストーラ(.msiファイル)からインストールするか `pip install aws-sam-cli`でインストール
> 3. `sam --version`でバージョンが出力されればOK

dockerは、ローカルマシンにlambdaの実行環境を作るために導入します。

# AWS Toolkit for Visual Studio Codeの導入
これは、VSCodeからAWSのSAMをGUIで操作するためのVSCodeの拡張機能です。
[AWS Toolkit - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.aws-toolkit-vscode)

# プロジェクトを新規作成する
[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/toolkit-for-vscode/latest/userguide/create-sam.html)に従って行います。

1. コマンドパレット(Ctrl + Shift + P)を開き、`Create new SAM Application`を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/a8718eea-1bf7-3338-05ab-37c44ef89abe.png)
2. 使用するランタイムを選びます。今回はPython3.8
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/668f2f9a-c6d4-6b67-6500-f1f6b90c9b55.png)
3. テンプレートを選択。今回は単体で使うのでHwllo World
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/6b162b37-81cf-4b04-4fb8-4d432d6c564a.png)
4. プロジェクトを作成するディレクトリを指定します。エクスプローラーが開くので任意のフォルダを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/bf1c1a02-3ea9-2e57-d2cd-4854bd9bb346.png)
5. プロジェクト名を指定します。
Enterを押すと、プロジェクトが作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/c51f5e1a-cbde-b592-084f-8465a34adbc6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/f6b64658-07d3-681d-9fab-e2453fa3caf3.png)

# ローカルデバッグ実行する
1. `hello_world/app.py`を開き、`Debug Locally`を選択すると、dockerに仮想環境が作られ、関数が実行されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/ac9e6767-2677-88ca-1d10-c5293ce4ff8c.png)
2. デバッグコンソールに結果が出力されればOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/3286d44e-ea45-ff95-ac23-b63adc587842.png)

# eventを設定する
`.aws/templates.json`で設定できる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/91486180-651b-c21e-713c-fc24fe8c1b84.png)

# デプロイする
templete.yamlをもとにデプロイしていきます。
1. コマンドパレットから`Deploy SAM Application`を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/6b3d8696-a59d-1769-ba3b-550d5c1cc766.png)
2. テンプレートを選択する。今回は`templete.yaml`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/8e93f896-9095-7042-fa6c-5bc331cde8c3.png)
3. 任意のデプロイ先のリージョンを選択する。今回はTokyo
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/b6f68ccb-a573-731a-1a2a-c38892ccbc36.png)
4. S3のバケット名を指定します。ここでは既存のバケット名を指定しなければなりません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/47fa15bb-a7e2-043b-6625-a75513e35799.png)
5. Stack名を指定します。
Stackとは、CloudFormationで作成されるサービス群の名前です。プロジェクト名と同名にしてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/be0be408-592b-d1dc-c460-f039991bf57d.png)
6. デプロイが成功するとデバッグコンソールが表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/f096fc12-c3ea-fb93-0c10-19f7eddd0626.png)

## デプロイされたことを確認する
以下のように、lambdaが作られていることを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/79582bc3-0a6d-2cae-2214-117e42d9edb4.png)
API GatewayでもAPIが作られていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/c343fdda-ddc9-844f-08c4-c946ba354883.png)

# カスタマイズする
デフォルトで作られるAPIは
GET: `/hello`なので、これを変更するには`templete.yaml`を編集する必要があります。

下記はデフォルトの`templete.yaml`

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  trial-sam

  Sample SAM Template for trial-sam

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

```

この中を以下の通りに書き換えればパスとHTTPメソッドを変えられる

Before

```yaml
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get
```


After

```yaml
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /anypath # hello → anypath
            Method: post # get → post
```

POST: `anypath`に変更できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/20fd66b3-5bd5-12fc-95e1-1b6b100fa809.png)

# あとはロジックを書いていくだけ
あとは要件に合わせてロジックを組み立てていくだけです。

再度デプロイすれば、変更内容も反映されます。