AWS Lambda+PythonでExcelファイルにPlantUMLを埋め込む

# はじめに

AWS Lambda+PythonでExcelファイルにPlantUMLを埋め込もうとして苦戦したのでメモ

手こずったところ
- plantwebを自前でレイヤー作成
- requestsのインポートでエラー
https://qiita.com/HLHHS11/items/c26f1632a141a1dcc7a7

2023/12/4以降AWS CloudShell環境がAmazon Linux 2からAmazon Linux 2023に更新されていく予定なので、この情報は使えなくなる可能性があります

2024/1/28追記
Amazon Linux 2023のCloudShellでpyenvを使ってPython3.10をインストールする方法
Amazon Linux 2023のCloudShellでは、Python3.9、OpenSSLは3.0.8がデフォルト

```sh:
# 準備
sudo dnf install gcc zlib-devel bzip2-devel readline-devel sqlite sqlite-devel openssl-devel tk-devel libffi-devel xz-devel

# pyenvインストール
curl https://pyenv.run | bash

# 環境変数設定
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc

# 環境変数を再読み込み
. .bashrc

# Python3.10.13インストール
pyenv install 3.10.13

# デフォルト指定
pyenv global 3.10.13
```


# 準備 - plantwebのレイヤー

### AWS CloudShellでPython3.10環境をビルド

AWS CloudShellでPython3.10環境をビルドします。AWS CloudShellのデフォルトでは3.7です。

OpenSSL 1.1.1をインストール

```sh:
sudo yum install openssl11 openssl11-devel
```

Python3.10をインストール

```sh:
sudo yum install gcc bzip2-devel libffi-devel
wget https://www.python.org/ftp/python/3.10.13/Python-3.10.13.tgz
tar xzf Python-3.10.13.tgz
cd Python-3.10.13
./configure --enable-optimizations
sudo make altinstall
```

（参考）
https://blog.serverworks.co.jp/install-python3-with-openssl11
https://medium.com/@teamcode20233/how-to-upgrade-python-version-in-aws-cloud-shell-bb248ee4c4b2

### plantwebのレイヤー作成

確認

```sh:
python3.10 --version
```

```sh:
cd
mkdir python
cd python
python3.10 -m pip install plantweb "urllib3<2" -t .
cd ..
zip -r python python
```

### Lambdaレイヤー

作成したzipファイルをs3に保存します

```sh:
aws s3 cp python.zip s3://<bucket名>
```

Lambdaの、レイヤーの作成で、レイヤーを作成します

（参考）
https://qiita.com/__DASHi__/items/268062f0dba0e93170f2

# 動かしてみる

### 関数作成

関数作成時にレイヤーを追加します。以下のレイヤーは公開されているものをARNを指定して使用します

- openpyxl
- Pillow

https://tech-deliberate-jiro.com/lambda-layer/

### サンプルソース

```python:
import requests
from plantweb.render import render
import json
import boto3
import openpyxl
from openpyxl.drawing.image import Image
import os
import tempfile
from io import BytesIO

s3_client = boto3.client('s3')

bucket_name = "<bucket名>"
template_name = "template.xlsx"

def lambda_handler(event, context):
    # TODO implement
    object_key = template_name
    s3_resp = s3_client.get_object(Bucket=bucket_name, Key=object_key)
    wb = openpyxl.load_workbook(BytesIO(s3_resp['Body'].read()),data_only=True)
#    main_ws = wb["Sheet1"]

    with tempfile.TemporaryDirectory() as tmpdir:
        file_name = 'output_20231007'
        excel_file_name = f'{file_name}.xlsx'
        tmp_file_path = os.path.join(tmpdir, excel_file_name)

        content = """
        :タイトル;
        """
        output = render(
            content,
            engine = 'plantuml',
            format = 'png',
            cacheopts = {'use_cache': False}
        )
        tmp_png_file_path = os.path.join(tmpdir, 'image.png')
        with open(tmp_png_file_path, 'wb') as f:
            f.write(output[0])
        sheet = wb["Sheet1"]
        img = Image(tmp_png_file_path)
        sheet.add_image(img, "A1")

        wb.save(tmp_file_path)
        wb.close()

        result = s3_client.upload_file(
            Bucket=bucket_name,
            Key=excel_file_name,
            Filename=os.path.join(tmpdir, excel_file_name),
            ExtraArgs={"ContentType": "application/vnd.ms-excel", 'ACL':'public-read'}
        )

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

```

関数URLを有効にして、ブラウザーでURLを表示すると「Hello from Lambda!」と表示されるとともに、S3に出力ファイルが作成されます

こんな感じ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555397/fcd65597-a3ca-c6f7-bb3b-a4ec295d4a4d.png)