[AWS] Lambdaをローカルで開発するためにAWS SAMに入門してみた (S3のputイベント検知編)

## Pickup
- aws-sam-cliを利用した `ローカルで擬似的にS3のputイベントを検知させるテスト with golang` の備忘録です
- 
- 事前にhello world編を見ておいて頂けると環境が整います

## Requirement
- golang:1.11.4
- aws-cli
- aws-sam-cli
- docker (local lambda test)

## Document

### ■ どうやったらlocalでS3のputイベント作れる？
- そもそもどうやってsamコマンドってどんなことできるぞや？
- イベントとかはどうやってつくる？

の疑問を解消させていくために、samコマンドのヘルプを参照してみる。　
（※　以下、ざっくり和訳なのであしからず）

#### sam
- aws-sam-cliのalias
- cloudformationのサーバーレス拡張版
- オプションでやることが大分違うが、「生成 / ビルド / localでのテスト / バリデーションチェック / deploy」など一通りこの子で完結できる

→　sam自体が色々なことができる模様。
寄り道して他のものも見てみたくなるが、今回は `local` にのみピックアップ。

```
$ sam --help
Usage: sam [OPTIONS] COMMAND [ARGS]...

  AWS Serverless Application Model (SAM) CLI

  The AWS Serverless Application Model extends AWS CloudFormation to provide
  a simplified way of defining the Amazon API Gateway APIs, AWS Lambda
  functions, and Amazon DynamoDB tables needed by your serverless
  application. You can find more in-depth guide about the SAM specification
  here: https://github.com/awslabs/serverless-application-model.

  Commands:
  logs      Fetch logs for a function
  deploy    Deploy an AWS SAM application. This is an alias for 'aws
            cloudformation deploy'.
  package   Package an AWS SAM application. This is an alias for 'aws
            cloudformation package'.
  publish   Publish a packaged AWS SAM template to the AWS Serverless
            Application Repository.
  init      Initialize a serverless application with a...
  build     Build your Lambda function code
  validate  Validate an AWS SAM template.
  local     Run your Serverless application locally for...
```

#### sam local
- `generate-event`:
    - 疑似イベントつくる
- `invoke`: 
    - Lambda functionの実行
- `start-api`:
    - 開発するAPIのテストする時にローカルエンドポイントをセットする。
    - なお、 hot-reloadingしているためfunctionに変更を加えた場合でもRestartする必要はない
- `start-lambda`: 
    - ローカルエンドポイントに設定したLambda funcを使えるように起動するコマンド

→　今回のお目当ては `generate-event` 

```
$ sam local --help
Usage: sam local [OPTIONS] COMMAND [ARGS]...

  Run your Serverless application locally for quick development & testing

Options:
  --help  Show this message and exit.

Commands:
  generate-event  You can use this command to generate sample...
  invoke          Invokes a local Lambda function once.
  start-api       Sets up a local endpoint you can use to test your API.
                  Supports hot-reloading so you don't need to restart this
                  service when you make changes to your function.
  start-lambda    Starts a local endpoint you can use to invoke your local
                  Lambda functions.
```

#### sam local generate-event

helpにS3イベントの作成コマンドがまとまっているのでこれを活用する。

```
$ sam local generate-event --help
Usage: sam local generate-event [OPTIONS] COMMAND [ARGS]...

~ (中略) ~
  Generate the event that S3 sends to your Lambda function when a new object is uploaded
  $ sam local generate-event s3 [put/delete]

  You can even customize the event by adding parameter flags. To find which flags apply to your command,
  run:

  $ sam local generate-event s3 [put/delete] --help

  Then you can add in those flags that you wish to customize using

  $ sam local generate-event s3 [put/delete] --bucket <bucket> --key <key>

  After you generate a sample event, you can use it to test your Lambda function locally
  $ sam local generate-event s3 [put/delete] --bucket <bucket> --key <key> | sam local invoke <function logical id>

~ (後略) ~
```

### ■ S3のputイベントを作る

```
$ sam local generate-event s3 put --bucket test.secondly --key app/test.json
```

※ bucketは指定しないと `example-bucket` が指定されるのでbucket指定操作を行う場合は注意
※ 今回はkeyは別に入れなくても問題ないが、見栄えのためいれた

### ■ S3の擬似putイベントをgolangに検知させる

どんなイベントをキャッチしたかわかりやすいようにmain.goを修正。

```
$ cat hello-world/main.go 
package main

import (
    "fmt"
    "context"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
)

func handler(ctx context.Context, s3Event events.S3Event) {
    for _, record := range s3Event.Records {
        if record.EventName == "ObjectCreated:Put" {
            s3 := record.S3
            fmt.Printf("[SUCCESS] event doing!! - %s \n",record.EventName)
            fmt.Printf("[%s - %s] Bucket = %s, Key = %s \n", record.EventSource, record.EventTime, s3.Bucket.Name, s3.Object.Key) 
        } else {
            fmt.Printf("[Skip] event doing!! - %s \n",record.EventName)
        }
    }
}

func main(){
    lambda.Start(handler)
}
```

※ どんなjsonが取得できるかは↓のawsが提供しているリポジトリを参照
https://github.com/aws/aws-lambda-go/blob/master/events/s3.go#L13-L23

buildし直して、実行してみる。

```
$ make build
GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world

$ sam local invoke "HelloWorldFunction" -e event.json 
2019-01-07 13:50:13 Found credentials in shared credentials file: ~/.aws/credentials
2019-01-07 13:50:13 Invoking hello-world (go1.x)

~ (中略) ~

[SUCCESS] event doing!! - ObjectCreated:Put 
[aws:s3 - 1970-01-01 00:00:00 +0000 UTC] Bucket = test.secondly, Key = app/test.json 
null

~ (後略) ~
```

→　`ObjectCreated:Put` イベントをキャッチし、どんなbucketに対してputイベントがあったかを無事検知できた。