AWS SAM LocalのCodeUri設定に罠があった

この記事は、自分のブログからの転載です。
http://selmertsx.hatenablog.com/

## やりたいこと

- TypeScript で書かれている AWS Lambda のコードを AWS SAM Local で動かしたい
- TypeScriptのコードとbuild後のコードは別ディレクトリで保持したい
- AWS SAM LocalのREADMEではそこの説明が無いので、自分でなんとか動く方法を模索する必要がある

## 結論

**CodeUri プロパティはcloudformation.ymlからの相対パスを指定する必要がある**


## AWS SAM Localとは

https://github.com/awslabs/aws-sam-local#advanced

AWS Lambda関数をローカルで実行しテストができるようになるツール。
S3, DynamoDB, Kinesis 等のリソースからのイベントをエミュレートすることもできる。
API Gatewayをlocalで動かし、Lambdaコードの変更をhot reloadすることもできる。


## AWS SAM のinstall

```shell-session
npm install -g aws-sam-local
sam --version # installできているか確認する
```

上記コマンドだけで installが可能。Dockerも必要なので気をつけること。

## 設定内容

AWS SAMの説明とcloudformation.yml の書き方についてはここに書いてあるので、ここを見ながら設定を行う。
https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction

### yamlの設定内容

```yaml

# cloudformation.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Simple Lambda functions

Resources:
  HelloWorld:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: build/src
      Handler: index.post
      Runtime: nodejs6.10
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /chat-bot
            Method: post
```

上記の設定ファイルが SAM Localを使う上で、自分が設定した CloudFormationのymlである。CloudFormationに `Transform: AWS::Serverless-2016-10-31` という値を追加することで、SAMの定義をCloudFormationで行うことが可能となる。

> In order to include objects defined by AWS SAM within a CloudFormation template, the template must include a Transform section in the document root with a value of AWS::Serverless-2016-10-31.

ここで重要なのは HelloWorldリソースの`CodeUri` プロパティで、こいつはjsファイルのディレクトリを指す。気をつけなければならないのは、指定するディレクトリパスは絶対パスか、**cloudformation.ymlからの相対パスを指定しなければならないということ。**よって、このときのディレクトリ構成は下記のようになる。


```shell-session

➜  speee_pf_checker git:(aws_saml) ✗ tree -I node_modules
.
├── build
│   └── src
│       ├── index.js
 |          ...
│       └── index.js.map
├── cloudformation.yml
├── package.json
├── src
│   ├── application.ts
│   ├── index.ts
 |      ...
│   └── pc_environment.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
```

ちなみにドキュメントでは下記のように記載されている。

> For example, if your AWS Lambda function source code is in the /home/user/code/lambdafunction/ folder, specify `` CodeUri: /home/user/code/lambdafunction`` for the AWS::Serverless::Function resource. The command returns a template and replaces the local path with the S3 location: CodeUri: s3://mybucket/lambdafunction.zip.


ということで、絶対パスじゃないといけないかと思ったら、絶対パス指定して実行したら下記のエラーとなった。

```shell-session

➜  speee_pf_checker git:(aws_saml) ✗ echo '{"body": "chiba"}' |  sam local invoke HelloWorld --template cloudformation.yml
2017/11/27 19:22:28 Successfully parsed cloudformation.yml
2017/11/27 19:22:28 Connected to Docker 1.34
2017/11/27 19:22:28 Fetching lambci/lambda:nodejs6.10 image for nodejs6.10 runtime...
nodejs6.10: Pulling from lambci/lambda
Digest: sha256:0721cee9614fe0c995c2eb0f52b9803a23d1c2da3007d46cb54b745d970850a0
Status: Image is up to date for lambci/lambda:nodejs6.10
2017/11/27 19:22:31 Reading invoke payload from stdin (you can also pass it from file with --event)
2017/11/27 19:22:31 Invoking index.post (nodejs6.10)
START RequestId: 08f01507-747c-1789-1312-b3368c5a26ad Version: $LATEST
Unable to import module 'index': Error
    at Function.Module._resolveFilename (module.js:469:15)
    at Function.Module._load (module.js:417:25)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
END RequestId: 08f01507-747c-1789-1312-b3368c5a26ad
REPORT RequestId: 08f01507-747c-1789-1312-b3368c5a26ad	Duration: 13.31 ms	Billed Duration: 0 ms	Memory Size: 0 MB	Max Memory Used: 29 MB

{"errorMessage":"Cannot find module '/var/task/index'","errorType":"Error","stackTrace":["Function.Module._load (module.js:417:25)","Module.require (module.js:497:17)","require (internal/module.js:20:19)"]}
```

### JavaScriptのコード


```ts

// build/src/index.js
'use strict';
console.log('Loading function');

exports.post = (event, context, callback) => {
    callback(null, {
        statusCode: 200,
        headers: { "x-custom-header" : "my custom header value" },
        body: "Hello " + event.body
    });
};
```

確認用のシンプルなJSコード。

## 動作確認


```shell-session
➜  speee_pf_checker git:(aws_saml) yarn build
yarn run v1.3.2
$ tsc
✨  Done in 3.25s.
➜  lambda git:(aws_saml) ✗ echo '{"body": "chiba"}' |  sam local invoke HelloWorld --template cloudformation.yml
2017/11/27 18:04:54 Successfully parsed cloudformation.yml
...
{"statusCode":200,"headers":{"x-custom-header":"my custom header value"},"body":"Hello chiba"}
```

上記コマンドで確認した結果、aws sam localが動いていることが確認できた。

## 後程確認すること


- node_modulesを AWS SAM Localで読み込む
- API GatewayをLocalで使う