S3 イベント通知と Lambda 関数でオブジェクトのアップロード日時をユーザー定義メタデータとして自動で追加してみた
はじめに
テクニカルサポートの 片方 です。
特定の S3 バケットにアップロードされたオブジェクトに対して自動でユーザー定義のメタデータ (アップロード日時) を追加する Lambda 関数を実装してみました。
こちらは、私の所属するテクニカルサポートチームへ「〇〇〇 を実現するサービスや機能はないか」と実際にお客様より頂いたお問い合わせを参考 (ヒント) にしてカスタムソリューションを作成してみました。
システム定義のメタデータは仕様上により、オブジェクト作成日または最終更新日のいずれか遅い方が記録されるため、ユーザー側でタイムスタンプの変更や保持は叶いません。
そのため、初めてオブジェクトをアップロードした日時 (例: 2024/06/17 01:36:56 PM JST ) を保持する目的で、ユーザー定義のメタデータにタイムスタンプ値として自動で書き込みたいと思います。
構成と説明
今回は、S3 イベント通知 の PUT アクションをトリガーに、オブジェクトをアップロードした日時のタイムスタンプ値をユーザー定義のメタデータへ自動で書き込む Lamabda 関数を呼び出し処理させます。
S3 バケットへオブジェクト追加 ⇒ S3 イベント通知 (PUT) ⇒ Lamabda 関数 ⇒ PUT したオブジェクトへユーザー定義のメタデータにタイムスタンプ値を付与
※ 以下はイメージです。
やってみた
以下の手順で作成しました。
- Lamabda 実行ロール
- Lambda 関数
- S3 イベント通知
Lamabda 実行ロール
ロール名: LambdaS3MetaDataUpdateRole
Lambda 関数にアタッチするロールを作成します。信頼関係は以下です。
{ "Version": "2008-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
アタッチするポリシー例
リソースを絞るなど適宜ご対応ください { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:PutObjectTagging", "s3:GetObject" ], "Resource": "*" }, { "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*" }, { "Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": [ "*" ] } ] }
Lambda 関数
Lambda 関数を作成します。ランタイムは Python 3.12 を使用しました。
関数名: LambdaS3MetaDataUpdate
アタッチする実行ロールは、先ほど作成した "LambdaS3MetaDataUpdateRole"
を選択します。
サンプルコード
import json import boto3 import datetime import urllib.parse s3 = boto3.client('s3') def lambda_handler(event, context): try: # Extract bucket name and object key from the event bucket = event['Records'][0]['s3']['bucket']['name'] key = event['Records'][0]['s3']['object']['key'] # Decode the URL-encoded key decoded_key = urllib.parse.unquote_plus(key, encoding='utf-8') # Get current timestamp formatted as desired (assuming JST) now = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9))) creation_datetime = now.strftime('%Y/%m/%d %I:%M:%S %p JST') # Retrieve existing metadata head_response = s3.head_object(Bucket=bucket, Key=decoded_key) existing_metadata = head_response.get('Metadata', {}) # Check if the metadata already exists before updating if 'creation-datetime' not in existing_metadata: # Add new metadata without x-amz-meta- prefix existing_metadata['creation-datetime'] = creation_datetime # Copy object with new metadata s3.copy_object( Bucket=bucket, Key=decoded_key, CopySource={'Bucket': bucket, 'Key': decoded_key}, Metadata=existing_metadata, MetadataDirective='REPLACE' ) return { 'statusCode': 200, 'body': json.dumps('Metadata updated successfully.') } except Exception as e: return { 'statusCode': 500, 'body': json.dumps(f"Error: {e}") }
S3 イベント通知
対象 S3 バケットを選択 ⇒ プロパティを選択 ⇒ 下部へスクロール
イベント通知を作成をクリック
イベント名の記載と、対象のアクションを選択。 プレフィックスやサフィックス指定があれば適宜修正して下部へスクロール
送信先のセクションで Lamabda 関数を選択 ⇒ 先ほど作成した Lambda 関数 (LambdaS3MetaDataUpdate) を指定
以上で実装は終了です。お疲れさまでした。
検証してみた
対象のバケットへ適当にファイル (オブジェクト) をアップロードします。
以下の通り付与されたので成功です。フォルダー配下のファイル (オブジェクト) についても纏めて記載されます。
日本語表記のオブジェクトでも問題なく動作するのでご安心ください。
注意点
本 Lambada 関数ではシステム定義のメタデータと、ユーザー定義のメタデータのタイムスタンプ値で 1 秒~ の誤差が発生する可能性があることに注意してください。 こちらの誤差が許容できない場合は、システム定義のオブジェクト作成日をユーザー定義のメタデータに反映(コピー)させるなどといった処理をご検討ください。
まとめ
ご自身の環境に合わせ適宜修正の上ご利用ください。本ブログが誰かの参考となれば幸いです。
参考資料
- オブジェクトメタデータの使用 - Amazon Simple Storage Service
- Amazon S3 コンソールでのオブジェクトメタデータの編集 - Amazon Simple Storage Service
- Amazon S3 イベント通知 - Amazon Simple Storage Service
- チュートリアル: Amazon S3 トリガーを使用して Lambda 関数を呼び出す - AWS Lambda
アノテーション株式会社について
アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社WEBサイトをご覧ください。