たそらぼ

日頃思ったこととかメモとか。

S3にPUTしたテキストから、LambdaでHTMLを生成する

AWSでは可視化に関するサービスが充実している。
例えば、センサーデータなどをモニタリングしたい場合はKibanaやQuickSightなどで可視化すれば良い。
一方で、もっと泥臭いこと、例えば収集したデータから算出したデータを使って、独自のレポートやHTMLなどをほぼリアルタイムで作成したい時がある。
これをなんとかAWSを使ってできないかと思い試行錯誤した。

今回は第一歩として、S3にデータをPUTしたことをトリガーにLambdaでHTMLを作成してみた。
イメージとしてはこんな感じ。
f:id:tasotasoso:20190210003951p:plain

コードはgithubに。
github.com


S3へのPUTの準備

まず、boto3でS3にPUTするデータを作成する。
とりあえずdata.txtとかにしとく。

#data.txt

HTML Generation Success!!!

続いて、S3にPUTするスクリプトを作成する。

import boto3

# 設定値
bucket_name = $バケットの名前
filepath = $data.txtのパス
Key = $S3に上げた時のファイル名
 
#実行内容
s3 = boto3.resource('s3')
bucket = s3.Bucket(bucket_name)
bucket.upload_file(filepath, Key)

この内容に合うように、S3にバケットを作成しておく。

Lambdaの設定

Lambdaの実行内容は以下のようにした。
ポイントとしては、
・ソースのS3とKeyはeventから取得する。
・HTMLのS3への格納はboto3で行う。
 html_storeみたいな名前であらかじめバケットを作っておく。
・読み込むS3上のファイルは、ローカルにtmpフォルダを作成して格納する。
・HTMLはハードコードしておく。
・トリガーのバケットと、出力のバケットは違うものにする。
 同じものにしておくと無限ループに入ってしまう可能性がある?
 → 無限ループに入って破産する可能性があるので注意してください。(はじめてのクラウド破産体験★LambdaのトリガーにS3を設定するときに気を付けること!!

import boto3
import os

def lambda_handler(event, context):

    #putトリガーの適用元
    input_bucket = event['Records'][0]['s3']['bucket']['name']
    input_key = event['Records'][0]['s3']['object']['key']
    tmp = '/tmp/' + os.path.basename(input_key)

    #出力先
    output_bucket = 'html_store'
    output_key = 'data.html'

    try:
        s3 = boto3.resource('s3')
        bucket = s3.Bucket(input_bucket)
        bucket.download_file(input_key, tmp)



        with open(tmp, 'r') as input_f:
            lines = [ line for line in input_f ]

            with open('./data.html', 'w') as output_f:
                output_f.write('<html><head></head><body>\n')
                [output_f.write(line + '\n') for line in lines]
                output_f.write('</body></html>')

                s3.Bucket(output_bucket).put_object(Key = output_key ,
                                                    Body = output_f)
    
    except Exception as e:
        raise e

トリガーはS3を選択する。
バケット:data.txtを格納するバケットを指定する。
イベントタイプ:今回はPUTを選択。
プレフィックスサフィックス:今回は未検証。とりあえず空にしておく。

IAMロールはS3の読み出しと書き込みができれば良いので、今回はとりあえずFullAccessにしておく。
バケットも作成しておくこと。


~~~
HTMLはハードコードしないバージョンも試してみた。
lambdaに入れるpythonスクリプトをzipで上げる際に、同じzipにHTMLの中身を入れておく。
これをスクリプト中で開いてファイルオブジェクトに書き出す。
今回はHTML中にlambdaで処理した内容を入れるイメージで、head.txtとtail.txtに前後のHTMLを書いておく。

import boto3
import os

def lambda_handler(event, context):

    #putトリガーの適用元
    input_bucket = event['Records'][0]['s3']['bucket']['name']
    input_key = event['Records'][0]['s3']['object']['key']
    tmp = '/tmp/' + os.path.basename(input_key)

    #出力先
    output_bucket = 'htmlstore'
    output_key = 'data.html'

    try:
        s3 = boto3.resource('s3')
        ibucket = s3.Bucket(input_bucket)
        ibucket.download_file(input_key, tmp)
        
        obucket = s3.Bucket(output_bucket)


        with open('./head.txt', 'r') as head_f:
            heads = [ line for line in head_f ]

        with open(tmp, 'r', encoding='utf-8') as input_f:
            lines = [ line for line in input_f ]

        with open('./tail.txt', 'r') as tail_f:
            tails = [ line for line in tail_f ]

            with open('/tmp/data.html', 'w', encoding='utf-8') as output_f:
                [output_f.write(line + '\n') for line in heads]
                [output_f.write(line + '\n') for line in lines]
                [output_f.write(line + '\n') for line in tails]
            obucket.upload_file('/tmp/data.html','data.html')
    
    except Exception as e:
        print(e)
        
        raise e

S3からHTMLをダウンロードする準備

ダウンロードの方法はいろいろある。
作成したHTMLにPublic権限をつけておけばブラウザからでも読めるが、意外と難しかったので、とりあえずboto3で取ってくることにする。

import boto3

#設定値
bucket_name = $バケットの名前
filepath = $data.txtのパス
Key = $S3に上げた時のファイル名

#実行内容
s3 = boto3.resource('s3') 
bucket = s3.Bucket(bucket_name)
bucket.download_file(Key, filepath)

実行結果

こんな感じ。
f:id:tasotasoso:20190210010845p:plain

感想

head.txtとtail.txtはS3に入れて置いて、boto3でtmpに取ってきても良さそう。
生成したHTMLにPublic権限をつけて、metaデータで再読み込みできるようにしておけば、リアルタイムでレポートを更新することもできる。
head.txtとtail.txtの書き方次第でかなり使えそう。