S3にアップロードされたZIPファイルをLambdaで自動解凍する

# 参考

>
[S3にアップロードされたファイルがzipならlambdaで自動解凍する - Qiita](http://qiita.com/bump_of_kiharu/items/644d8614b99e86c4b188)
>>
S3にアップしたファイルがzipかどうかを判別して、自動的にunzipする処理をlambdaで実行します。
圧縮のサンプルはよく見かけるけど、解凍のサンプルがあまり無い気がしたので、利用シーンは正直まったく浮かばないけど作ってみました。


>
[S3とLambdaとCircleCIでほぼ無料の社内向けデザインテンプレートサイトを構築する - Qiita](http://qiita.com/saku/items/52c6a8ddd1dc510e6bc7)
>>
今回のやることを簡単に表現すると「S3に成果物をzipで放り投げて、それを解凍する」ということなので以下の３ステップで実現できそうです。

参考というか、コピペでそのまま動きました。
素晴らしい！ありがたい！お世話になりました！
利用シーンあります！ドンピシャです！ありがとうございます！

ただ、いくつか自分の用途に合わせて変えたところがあったのでメモです。

# やりたいこと

S3の特定の場所にZIPファイルがアップロードされたら、自動的に解凍したい。
具体的には、UnityCloudBuildでビルドされたWebGLのアーカイブを自前のS3に展開したい。

# 変更点

## ContentTypeの判断
* `ContentType`を見てZIPかどうか判断する処理が入っていましたが、イベントそのものをZIPのときしか起動しない設定にできたので、スクリプトでは判断しないように変更しました。
![image](https://qiita-image-store.s3.amazonaws.com/0/64098/0c217f97-d775-864c-c11e-443bd742c8bb.png)
* また、これにより、解凍後のファイルを再配置しても(ZIPでない限り)再帰的に起動することはないので、以下の問題も発生しないと思います。（最近の新機能なのかな？）

>
・保存先は別バケットにしました。同一バケットだと、またこのラムダ君が実行されてしまうから、というだけの理由です。


>
・S3 bucketは１つでもできますが、S3のevent通知はS3のフォルダ単位にかけられないため、アップロード先と解凍先を同じにすると解凍したときにもLambdaへのイベント通知がきて起動してしまいます。
このあたりテストしてたときに大量にLambda functionが起動してビビりました（^^;

## async→Promise

* 単に好みの問題です

## 再配置場所

* 元のZIPファイルと同フォルダ
* 例）`/aaa/bbb/ccc.zip` → `/aaa/bbb/ccc/dd.txt、ee.txt` といった感じ

## アクセスコントロール(ACL)

* 配置したJSやCSSを、別開発しているアプリから参照したかったので `public-read（公開）`になるようにしています。
* ACLを変更したい場合は、ポリシー？Role？に`s3:PutObjectAcl` が必要ぽいです。

# サンプルソース

[ダウンロードはこちらから](https://gist.github.com/goosys/5472fec24eb824e4925230697bc6c048)

```js:index.js
'use strict';

const aws = require('aws-sdk');
const s3 = new aws.S3({apiVersion: '2006-03-01'});
const node_zip = require('node-zip');
const mime = require('mime-types');

function lookup_mime( name ){
    console.log(name);
    const mimetype = mime.lookup(name) || 'application/octet-stream';
    console.log(mimetype);
    return mimetype;
}

exports.handler = function(event, context) {
    const bucket = event.Records[0].s3.bucket.name;
    const key    = event.Records[0].s3.object.key;
    
    Promise.resolve()
        .then(function(){
            return new Promise(function(resolve, reject){
                s3.getObject(
                    { Bucket: bucket, Key:key }, 
                    function(error, data) {
                        if (error) { reject(error); }
                        else { resolve(data); }
                    }
                );
            });
        })
        .then(function(data){
            const zip = new node_zip(data.Body, {base64: false, checkCRC32: true});
            const dir = key.replace(/\.zip/,'/');
            
            return Promise.all(Object.keys(zip.files).map(function(i) {
                return new Promise(function(resolve, reject){
                    const f = zip.files[i];
                    s3.putObject(
                        {
                            Bucket: bucket,
                            Key   : dir + f.name,
                            Body  : new Buffer(f.asBinary(), 'binary'),
                            ContentType: lookup_mime( f.name ),
                            ACL   : 'public-read'
                        },
                        function (err) {
                            if (err) { reject(err); }
                            else{ resolve(); }
                        });
                    });
            }));
        })
        .then(function(){
            context.succeed();
        })
        .catch(function(mes){
            context.fail(mes);
        })
};
```

```json:package.json
{
  "name": "unzip_hook_from_s3",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT",
  "description": "",
  "dependencies": {
    "mime-types": "^2.1.11",
    "node-zip": "^1.1.1",
    "zip": "^1.2.0"
  }
}
```

# おわり

これで、イベントを設定したバケットにZIPがアップロードされると自動解凍してくれるようになりました。めでたし。

UnityCloudBuildからZIPファイルを持ってくるLambdaもまた後でまとめます。