apex で作成した Lambda Function を docker-lambda 上で動作させるシェルスクリプト

# 目的
apex で作成した Lambda function (python 2.7) を`apex deploy`
前に docker-lambda コンテナ上で動作確認するためのスクリプトを作る。

## 必要なもの(前提)

- [apex](http://apex.run/)
  - AWS Lambda のコードを apex で管理しているものとする
- [docker](https://www.docker.com/)
  - [docker-lambda](https://github.com/lambci/docker-lambda) イメージ
- [jq](https://stedolan.github.io/jq/)
- [AWS CLI](https://aws.amazon.com/jp/cli/) の実行環境設定
  - `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`
  - 環境変数にセット済み

## 動機

職場で運用中のAWSサービスの状態通知を行うため、AWS Lambda を活用しています。
Lambda function の作成/デプロイには apex を使用していますが、以下のように考えました。

1. `apex deploy` 前に, apex で作成した Lambda function の 動作テストを行いたい
1. AWS Lambda の動作環境を複製した `docker-lambda` イメージがある
1. `docker-lambda` コンテナ上で動作させ、通知先サービス(Slack等) に通知テストができそう
1. どの function 向けにも使えるよう, `docker-lambda` コンテナを実行するスクリプトを書けば良いのでは?

## 作成

`docker-lambda` の Project Page にある [Example](https://github.com/lambci/docker-lambda#example) を参考に, まず以下のように作成。

```bash:docker-lambda.sh(修正前)
#!/bin/bash

SAMPLE_MESSAGE=`cat sample/test.json` # json形式のテストメッセージ

docker run --rm \
    -e AWS_ACCESS_KEY_ID \
    -e AWS_SECRET_ACCESS_KEY \
    -e SLACK_OPS_CHANNEL="#channel-name" \
    -e SLACK_HOOK_URL="https://hooks.slack.com/services/xxx/xxx/xxx" \
    -v $(pwd):/var/task lambci/lambda:python2.7 main.handle $SAMPLE_MESSAGE

```

`-e` オプションで環境変数を `docker-lambda` コンテナへ渡していますが、 apex では
AWS Lambda へ渡す環境変数を `function.json` で既に持っています。

```json:function.json
{
  "description": "Notify the state of the instance to Slack",
  "timeout": 10,
  "memory": 128,
  "role": "arn:aws:iam::xxxxxxxxxxxx:role/LambdaEC2Readonly",
  "environment": {
      "SLACK_OPS_CHANNEL": "#channel-name",
      "SLACK_HOOK_URL": "https://hooks.slack.com/services/xxx/xxx/xxxxxxxx"
  }
}
```

これを読み込むため、今回は **jq を使って シェルスクリプトでdocker に渡す** ことにしました。

## jq の処理

参考にしたのはこちらの記事です。

- http://tkuchiki.hatenablog.com/entry/2016/04/01/173354

まずは実行結果から。

```shell-session:実行結果
% jq -r '.environment | to_entries | map("\(.key)=\(.value)") | join("\n")' function.json
SLACK_OPS_CHANNEL=#channel-name
SLACK_HOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxxxxxxx
```

### オプション解説

次に引数指定したオプションを、順を追って見ていきます。

- `.environment` ... json の `environment` キーの値を読み込み

```shell-session:部分実行その1
% jq -r '.environment' function.json
{
  "SLACK_OPS_CHANNEL": "#channel-name",
  "SLACK_HOOK_URL": "https://hooks.slack.com/services/xxx/xxx/xxxxxxxx"
}
```

- `to_entries` ... `key`, `value` の各値を持った ハッシュ配列に変換 

```shell-session:部分実行その2
% jq -r '.environment | to_entries' function.json
[
  {
    "key": "SLACK_OPS_CHANNEL",
    "value": "#channel-name"
  },
  {
    "key": "SLACK_HOOK_URL",
    "value": "https://hooks.slack.com/services/xxx/xxx/xxxxxxxx"
  }
]
``` 

- `map("\(.key)=\(.value)")` ... ハッシュ配列を `.key=.value` 形式の配列に変換

```shell-session:部分実行その3
% jq -r '.environment | to_entries | map("\(.key)=\(.value)")' function.json
[
  "SLACK_OPS_CHANNEL=#channel-name",
  "SLACK_HOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxxxxxxx"
]
``` 

- `join("\n")` ... 配列を改行コード区切りの**文字列**に変換

```shell-session:部分実行その４(実行結果に同じ)
% jq -r '.environment | to_entries | map("\(.key)=\(.value)") | join("\n")' function.json
SLACK_OPS_CHANNEL=#channel-name
SLACK_HOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxxxxxxx
```

変換後の文字列は、スクリプトでの変数宣言と同じ形にしました。
これをファイルにリダイレクトすると、 後述の方法で `docker` から読み込むことができます。

## `docker --env-file` オプション

先ほどの `jq` 実行結果をさらに、 `.tmp.list` にリダイレクトしておきます。

```shell-session
% jq -r '~(中略)~' function.json > .tmp.list
```

```text:.tmp.list
SLACK_OPS_CHANNEL=#channel-name
SLACK_HOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxxxxxxx
```

`docker` の `--env-file` オプションを使うことで、 `.tmp.list` 内の文字列を、 **dockerの環境変数として** 読み込むことができます。

```shell-session:オプション指定例
% MESSAGE=`cat sample/test.json` && docker run --rm \
    -e AWS_ACCESS_KEY_ID \
    -e AWS_SECRET_ACCESS_KEY \
    --env-file .tmp.list \
    -v $(pwd):/var/task lambci/lambda:python2.7 main.handle $MESSAGE
```
※便宜上、メッセージデータとなるjson を環境変数に入れる処理を行っています

詳しくは以下のドキュメントをご覧ください。

* https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e-env-env-file

## シェルスクリプト修正後

これらを踏まえて、最終的に以下のような形になりました。

```bash:docker-lambda.sh(修正後)

#!/bin/bash

set -e

cd $(dirname "$0")

create_env_file() {
    local env_json="function.json"

    jq -r '.environment | to_entries | map("\(.key)=\(.value)") | join("\n")' $env_json > .tmp.list
}

remove_env_file() {
    rm .tmp.list
}

create_env_file

docker run --rm \
  -e AWS_ACCESS_KEY_ID \
  -e AWS_SECRET_ACCESS_KEY \
  --env-file .tmp.list \
  -v $(pwd):/var/task lambci/lambda:python2.7 main.handle "$@"

remove_env_file
```

### 実行例

目的の apex ソースディレクトリ内に `docker-lambda.sh` を置いて、以下のように実行してください。

```bash
% MESSAGE=`cat ./sample_messages/test.json` && ./docker-lambda.sh $MESSAGE
```