AWS SAMを使ってS3にファイルがアップロードされたらLambdaでファイル名とバケット名を取得Localテスト

AWS SAM（Serverless Application Model）を使用すると、AWS LambdaとS3を組み合わせて簡単にサーバーレスアプリケーションを構築できます。

この記事では、S3バケットにファイルがアップロードされたときに自動的に起動するLambda関数を作成し、ファイル名とそのバケット名を取得してみました。

実際に作成する中でエラーも出たので合わせて対応を書いていきます。

### 事前準備
#### AWS SAM をインストールしておきます
```ターミナル
> pip install aws-sam-cli 
```
#### SAM プロジェクトの作成
```
> sam init
Which template source would you like to use?
        1 - AWS Quick Start Templates

Choose an AWS Quick Start application template
        1 - Hello World Example

Use the most popular runtime and package type? (Python and zip) [y/N]: y

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: N

Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: N  

Would you like to set Structured Logging in JSON format on your Lambda functions?  [y/N]: N

Project name [sam-app]: 好きなプロジェクト名
```

#### Lambda 関数を修正
```Python:hello_world/app.py
import json
# import requests

def lambda_handler(event, context):
    for rec in event["Records"]:
        print("アップロードされたファイルは: " + rec["s3"]["object"]["key"])
        print("アップロードされたバケットは: " + rec["s3"]["bucket"]["name"])
```

### ローカルテスト
#### イベントの作成
##### sam コマンドでS3 サンプルイベントが作成できるか確認
```ターミナル
> sam local generate-event
Commands:
    alexa-skills-kit
    alexa-smart-home
    ...
    s3
```

#### S3サービスに対してput サンプルイベントを生成できるか確認
```ターミナル
> sam local generate-event s3
Commands:
  batch-invocation  Generates an Amazon S3 Batch Operations Invocation Event
  delete            Generates an Amazon S3 Delete Event
  put               Generates an Amazon S3 Put Event
```

#### S3 put サンプルイベントを確認
```ターミナル
> sam local generate-event s3 put
{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "us-east-1",
      ...
```

#### このままでは使えないので json ファイルとして作成
```ターミナル
> sam local generate-event s3 put | tee events/s3-local-event.json
```
コマンドの説明
- sam local generate-event s3 put: S3バケットへのputイベントにトリガーされるイベント作成
- tee events/s3-local-event.json: イベントを標準出力と event/s3-local-event.jsonに出力

#### 作成された event/s3-local-event.json
```json:s3-local-event.json
{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "us-east-1",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "requestParameters": {
        "sourceIPAddress": "127.0.0.1"
      },
      "responseElements": {
        "x-amz-request-id": "EXAMPLE123456789",
        "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "example-bucket",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::example-bucket"
        },
        "object": {
          "key": "test/key",
          "size": 1024,
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901"
        }
      }
    }
  ]
}
```
※サンプルイベントを変更したい場合は以下のhelpコマンドに沿って調整してください(今回はデフォルトのまま)
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-local-generate-event.html
```ターミナル
> sam local generate-event s3 put --help
Usage: sam local generate-event s3 put [OPTIONS]

Options:
  --region TEXT       Specify the region name you'd like, otherwise the default = us-east-1
  --partition TEXT    Specify the partition name you'd like, otherwise the default = aws
  --bucket TEXT       Specify the bucket name you'd like, otherwise the default = example-bucket
  --key TEXT          Specify the key name you'd like, otherwise the default = test/key
  --debug             Turn on debug logging to print debug message generated by AWS SAM CLI and display timestamps.
  --config-file TEXT  Configuration file containing default parameter values.  [default: samconfig.toml]
  --config-env TEXT   Environment name specifying default parameter values in the configuration file.  [default:
                      default]
  -h, --help          Show this message and exit.
```

### 構築
#### Build の実行
```ターミナル
> sam build --use-container
: python3.9 metadata: {}
architecture: x86_64 functions: HelloWorldFunction

Build Failed
Error: Docker is unreachable. Docker needs to be running to build inside a container.
```
エラーが出てしまいました。
Docker が 起動していないよとの事。

#### Docker Desktop を起動して再実行
```ターミナル
> sam build --use-container
Build Succeeded
```

### ローカル環境で Lambda を実行
#### s3-local-event.json に対して HelloWorldFuncion を実行
```
> sam local invoke -e events/s3-local-event.json HelloWorldFunction
Error: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
```
またまたエラー。
utf-8 コードを使用していないからダメっぽい。
作成した s3-locla-evnet.json が utf-16 となっていたため utf-8 に変更。

#### 再度実行
```ターミナル
> sam local invoke -e events/s3-local-event.json HelloWorldFunction
Invoking app.lambda_handler (python3.11)
Local image was not found.
Removing rapid images for repo public.ecr.aws/sam/emulation-python3.11
Building image.........................................................................................................................................................................................
Using local image: public.ecr.aws/lambda/python:3.11-rapid-x86_64.

Mounting C:\~\Project\.aws-sam\build\HelloWorldFunction as /var/task:ro,delegated,     
inside runtime container
START RequestId: 9b97556a-10c2-4840-aac8-f50b5182fe4d Version: $LATEST
アップロードされたファイルは: test/key
アップロードされたバケットは: example-bucket
END RequestId: cb70f32b-6d80-41de-9492-6515f3f4d843
REPORT RequestId: cb70f32b-6d80-41de-9492-6515f3f4d843  Init Duration: 0.03 ms  Duration: 39.65 ms      Billed Duration: 40 ms  Memory Size: 128 MB  Max Memory Used: 128 MB
```
出力されました。

#### 出力されるファイル名とバケット名を変更してみる
```json:s3-local-event.json
{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "us-east-1",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "requestParameters": {
        "sourceIPAddress": "127.0.0.1"
      },
      "responseElements": {
        "x-amz-request-id": "EXAMPLE123456789",
        "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "example-bucket-oreore",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::example-bucket"
        },
        "object": {
          "key": "test/key/oreore",
          "size": 1024,
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901"
        }
      }
    }
  ]
}
```
s3 buket name に oreore を追加
s3 object key に oreore を追加

#### 再度HelloWorldFunctionを実行
```ターミナル
> sam local invoke -e events/s3-local-event.json HelloWorldFunction
Invoking app.lambda_handler (python3.11)
Local image is up-to-date
Using local image: public.ecr.aws/lambda/python:3.11-rapid-x86_64.

Mounting C:\Users\kutin\Desktop\Program\python\AWS_SAM_Hands-On\AWS_SAM_CheckProject\.aws-sam\build\HelloWorldFunction as
/var/task:ro,delegated, inside runtime container
START RequestId: b399de95-79e5-4d1f-97ea-23888467134b Version: $LATEST
アップロードされたファイルは: test/key/oreore
アップロードされたバケットは: example-bucket-oreore
END RequestId: 52e205a7-a3e7-45de-affe-3b7734859552
REPORT RequestId: 52e205a7-a3e7-45de-affe-3b7734859552  Init Duration: 0.03 ms  Duration: 32.08 ms      Billed Duration: 33 ms  Memory Size: 128 MB      Max Memory Used: 128 MB
```
オレオレが追加されているのが確認できました。
これでS3 put イベントに対して Lambda がLocalで実行されるのが確認できました。

次回は実際にAWS上で実行するように設定を変更して動作確認を行う予定です。