LoL 〜Lambda operate Lambda〜

## 前書き

**Lambda関数群を更新するLambda関数** を作って継続的Lambda更新にチャレンジしようという企画です

* タイトルはネタです
* 後述の追加要素を丁寧に整えていけば、実用性はある程度あると思います

## AWS Lambdaおさらい

AWS Lambdaで動かすコードは3種類の方法で配布できます。

* インラインコード: 直接コードを書いて実行させる
* zipアップロード：複数のコードをzip圧縮したコードセットをアップロードする
* S3ダウンロード：指定したS3上にあるzip圧縮したコードセットを取ってきてもらう

### やってみたいこと

実は一番最初に選択肢を見たときに、S3ダウンロード形式はLambda起動ごとにS3のファイルを参照するものと思ってました。これで、「S3に配布すればその時点でコードデプロイできるじゃないですかヤッター」的な。

パッと挙動を見る限りはそんなことはなさそうなのでちょっとだけガックリしながらローカルからデプロイする仕組みを作ってたのですが、「AWS SDKならLambda自体も触れるんじゃね？」という発想をもとに、

0. S3にコードセットのzipファイルがPOSTされたことをトリガーに
0. zipに含まれているLambda設定リストを取り出し
0. AWS SDKを使って、Lamdaを上書き更新する

みたいな仕組みが作れないかなと思い、ちょこっとコードを作って試してみました。

なお、個人的都合でPythonで書いてます。

## 用意するもの

* 環境系
    * AWSアカウント
* コード類
    * メインコード + Lambdaリスト
    * Lambda更新Lambda

## 進める

### Lamda更新Lambda関数を用意する

[このGistにあるupsert_lambda.py](https://gist.github.com/attakei/cb6c14ac471f6b8df4e6#file-upsert_lambda-py) をインラインコードにたLambda関数を定義します。

メモリ自体はそんなに要求しないと思います。が、中身次第では結構なAPIコールするのでタイムアウトの秒数は長めにとっておいたほうがいいです。（自分は一応30秒にしています）

（中身をはしょりながら、コメントでざっくり説明）

```python:upsert_lambda.py
# -*- coding:utf8 -*-
import boto3
from botocore.exceptions import ClientError
import zipfile
import json


class S3Object(object):
    # 受け渡し簡素化用のクラス
    # <initごと略>

def lambda_handler(event, context):
    # AWS Lambdaから呼ばれる関数
    records = event.get('Records', [])
    for record in records:
        s3_object = S3Object(record['awsRegion'], record['s3']['bucket']['name'], record['s3']['object']['key'])
        _update_functions('sharequiz', 'test', s3_object)


def _update_functions(project, env, s3_object):
    # アップロードされたzipから関数リストのjsonを取得
    with zipfile.ZipFile(project_zip_path) as zfp:
        handlers_json = zfp.read('functions.json')
    functions = json.loads(handlers_json)

    _lambda = boto3.client('lambda')
    for function in functions:
        try:
            # とりあえずLambda関数を作成しようとしてみる
            # 少なくともこのコードを書いてた時点では、get_functionも例外吐いてた記憶が
            _lambda.create_function(
                # <引数略>
            )
        except ClientError as err:
            # すでに存在するなら、設定とコードを上書き更新する
            _lambda.update_function_configuration(**function)
            _lambda.update_function_code(
                # <引数略>
            )
```
### S3バケットを準備する

S3では、オブジェクト作成などのイベントをLambdaに通知して特定のLambda関数を呼ぶことができます。[^1] この仕組みを用いることで、**指定したバケット・フォルダにファイルがアップロードされると上記のLambda関数をコールできる**ようになります。


### Lambda関数のコードセットをまとめたzipを作る

この辺は基本的にLambdaのドキュメントに従って作れば問題ないです。
ただし、boto3で`lambda.create_function`や`lambda.update_function_code`をさせるためのjsonを含めてあげてください。(名前はfunctions.jsonで)

↓みたいな感じで

```functions.json
[{
	"Handler": "path/to/module.some_function",
	"Role": "arn:aws:iam::your-role",
	"FunctionName": "some_funtion",
	"Timeout": 3,
	"MemorySize": 128,
	"Description": "簡単な処理"
}, {
	"Handler": "path/to/module.other_function",
	"Role": "arn:aws:iam::your-role",
	"FunctionName": "else_funtion",
	"Timeout": 30,
	"MemorySize": 1280,
	"Description": "重めの処理"
}]
```

### アップロードする

Lambda側に通知が行く場所に、作成したzipをアップロードしてください。

## やってみた

(投稿後に時間の余裕があれば、動画orGIFを貼ります)

## 発展編（？）

* lambda更新lambdaの中でSlack Webhookを使えば、更新したことを周知できます
* CI環境でzip作成とS3アップロードできる仕組みを用意すれば、masterへのpushなどで自動で更新できます
* 上2個を組み合わせれば、継続的デリバリーとアナウンスもできて素敵です

以上、lambda-uploaderの存在を知る前に作ってみてたコードの紹介でした。

[^1]: http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/NotificationHowTo.html