【AWS lambda】lambdaで各種ライブラリを含めてデプロイする（パスワード付きzipを生成し、s3にアップロードする） @ Python

#やりたいこと
タイトルそのまんまですが、lbxxx.soなどのライブラリを含めてデプロイし、AWS lambda上でパスワード付きのzipを生成し、S3のバケットに格納します。

#立ちふさがる壁
そんな私のやりたいことの前に壁が立ちふさがります。
1. **[zipfile](https://docs.python.org/ja/3/library/zipfile.html)(Pythonで標準で用意されているzipファイルの作成などを行うモジュール)では、パスワード付きzipファイルの暗号化がサポートされていない！**
2. **ローカルのソースコードをzipファイルに固めて、lambdaにデプロイしただけでは、パスワード付きzipを作成することができない！**

いかにして、この壁を乗り越えたのかをここに残したいと思います。

#環境
####ローカル開発環境
+ Win10（ホストOS）
+ Docker
+ Linux 10（DockerコンテナのOS）
+ VS Code
+ Python 3.8.6
+ pip 20.2.3
+ aws-cli 2.0.56

VS Codeの「Remote-Containers」機能を使って、便利に開発しておりました。
Remote-Containers については[こちらの記事](https://qiita.com/Yuki_Oshima/items/d3b52c553387685460b0)を参考にしてくださいませ。

※上記のDockerや、VS Codeのインストールなどはあらかじめ完了していることとします。

####AWS環境
+ lambda pythonランタイム 3.8

特に書くことはないですね・・・

# 壁にぶつかる前まで
+ AWS マネジメントコンソール - Lambda - 「関数の作成」 
    +  「一から作成」
    +  関数名「zip-sample」
    +  ランタイム「Python3.8」
    
+  ローカルにて、VS Codeを立ち上げ、以下のディレクトリ構成を作る

```
zip-sample/
    └ .devcontainer/
        ├ Dockerfile
        └ devcontainer.json
    └ package/
        └  lambda_hundler.py
    └ requirements.txt
```

Dockerfileとdevcontainer.jsonについては、[こちらのソースコード](https://github.com/Microsoft/vscode-remote-try-python)からいただきました。
lambda_hundler.pyは以下になります。

```lambda_hundler.py
# lambdaの素のソースコードです
import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```

# 壁①の乗り越え方
準備ができたところで早速取り掛かりましょう。
なんと、zipfileモジュールではパスワード付きzipファイルを生成できないではありませんか！（唐突）
調べてみたところ、[**pyminizip**](https://pypi.org/project/pyminizip/)ではパスワード付きzipファイルが生成できるらしいです。
早速確かめてみましょう。

####準備
pyminizipは[zlib](https://zlib.net/)（データの圧縮および伸張を行うためのフリーのライブラリ）が必要なので、インストールします。

```
$ sudo apt install zlib
Reading package lists... Done
Building dependency tree       
Reading state information... Done
E: Unable to locate package zlib
```

**怒られました。**
代わりに、「zlib1g」を使用します。

```
$ sudo apt install zlib1g
```

requirements.txtに以下を記載し、`pip install`コマンドを打ちます。

```requirements.txt
pyminizip==0.2.4
```

```
$ cd /workspaces/zip-sample
$ pip install -r requirements.txt -t ./package
```

lambda_hundler.pyに以下を追記します。

```lambda_hundler.py
import json
import os
import pyminizip

def lambda_handler(event, context):
    # 後々lambdaに上げることを考慮して、tmpディレクトリを利用します。
    zip_path = "/tmp/zip/"

    # /tmp/zipディレクトリがなければディレクトリを作成します。
    if not os.path.isdir(zip_path):
        os.mkdir(zip_path)

    KEY = "/tmp/hello.txt"
    with open(KEY, mode='w') as f:
        f.write('this is test.')

    password = "password"
    compression_level = 9 # 圧縮レベル1-9、大きいほど圧縮が強い
    # 第一引数は、zipファイルに含めるファイルのパスの配列
    # 第二引数は、zipファイル内の階層
    # 第三引数は、zipファイルの配置先及びファイル名
    # 第四引数は、パスワード
    # 第五引数は、圧縮レベル
    pyminizip.compress_multiple([KEY], ["\\"], "/tmp/zip/sample.zip", password, compression_level)

    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

# 以下はデプロイ時に除きます。
lambda_hundler('a','a')
```

以下で起動してみましょう。

```
$ python lambda_hundler.py
```

```
$ ls /tmp/zip
sample.zip
```

無事できましたね！

# 壁②の乗り越え方
無事にローカル（dockerコンテナ）で、パスワード付きzipファイルが生成できたので、lambdaにデプロイしてみましょう。

+ MFA制限をしている場合は、事前に[aws-mfa](https://qiita.com/ogady/items/c17ffe8f7c8e15b15f77)などで、aws-cliを使えるようにしてください。
+ lambda_handler.pyの、`lambda_hundler('a','a')`はコメントアウトなり、削除するなりしておいてください。

```
$ cd /workspaces/zip-sample/package
$ zip -r ../function.zip .
$ aws lambda update-function-code --function-name sample-zip --zip-file fileb://../function.zip
```

デプロイが完了したら、AWSマネジメントコンソール - lambda - sample-zipより、「テスト」を実行してみましょう。

```
[ERROR] OSError: error in closing \tmp\zip\sample.zip (-102)
Traceback (most recent call last):
  File "/var/task/lambda_function.py", line 78, in lambda_handler
    pyminizip.compress_multiple([KEY], ["\\"], r"\tmp\zip\sample.zip", "password03", 1)
```

**怒られた**

色々と調べてみると、**zlibがデプロイパッケージに含まれていないために起きているらしい・・・**
※解決した後にカスタムランタイムを利用する解決方法もあるらしいですが、そちらは割愛します。

#### zlibをデプロイパッケージに含める戦いが始まる・・・！
以下のコマンドを打っていけばデプロイパッケージに含められました。

```
$ wget http://www.zlib.net/zlib-1.2.11.tar.gz
$ tar -xvzf zlib-1.2.11.tar.gz
$ cd zlib-1.2.11
$ ./configure --prefix=/workspaces/sample-zip
share lib includeが/workspaces/sample-zip/にできるはず
$ sudo make install

$ cd /workspaces/sample-zip/
zipファイルにshare lib includeを含める
$ zip -gr function.zip lambda_function.py share lib include
デプロイ
$ aws lambda update-function-code --function-name sample-zip --zip-file fileb://function.zip
```

linuxのライブラリについては一度上記をやれば変更しなくてよいので、以降はソースコードのzip化のみでよいです。

```
$ cd /workspaces/zip-sample/package
$ zip -r ../function.zip .
$ aws lambda update-function-code --function-name sample-zip --zip-file fileb://../function.zip
```

デプロイが完了したら、AWSマネジメントコンソール - lambda - sample-zipより、「テスト」を実行してみましょう。
**成功！**（するはず）

# 完成系
壁を乗り越えた後のソースコードは以下になります。

```lambda_hundler.py
def lambda_handler(event, context):
    zip_path = "/tmp/zip/"

    if not os.path.isdir(zip_path):
        os.mkdir(zip_path)

    KEY = '/tmp/hello.txt'
    with open(KEY, mode='w') as f:
        f.write("this is test.")

    password = "password"
    compression_level = 9 # 圧縮レベル1-9、大きいほど圧縮が強い
    pyminizip.compress_multiple([KEY], ["\\"], "/tmp/zip/sample.zip", password, compression_level)
    
    # 作成したzipファイルをs3にアップロードします
    s3 = boto3.resource('s3')
    s3.Bucket(BUCKET).upload_file(Filename="/tmp/zip/sample.zip", Key="sample.zip")

    return {
        'status': 200,
        'body': '処理が終了しました'
    }
```

# まとめ
- Pythonでパスワード付きzipファイルを作成したかったら、「pyminizip」！
- lambdaにデプロイするときは、「zlib」もデプロイパッケージに含める！

# 参考にさせていただいた記事など
- https://qiita.com/yaiwase/items/e64db2c0c06a88a52038
- https://www.systutorials.com/how-to-install-the-zlib-library-in-ubuntu/
- https://toolmania.info/post-12879/