2
0

LambdaでS3上のJSON形式の設定ファイルを一括更新する

Last updated at Posted at 2023-11-12

LambdaでS3上に保管したJSON形式の設定ファイルを一括更新する手順の覚え書きです。

要件

  • ロボットの設定ファイルを一括更新する
  • 設定ファイルの要素は全ファイル同じ
  • 要素の増減について、バージョン管理するものとする
  • 処理は手動実行のため、コンソールでログが見られれば問題ない

イメージ

イメージは以下の通り
Version 1.0.0のCountry要素の後ろにRegion要素を空白で追加し、Version 1.1.0に更新する場合

image.png

手順

1. S3バケットとフォルダの作成

まず、S3設定ファイルを保管するバケットとフォルダ(厳密にはオブジェクト)を作成します。

S3バケットの構成は以下の通りで、settingsフォルダ下に設定ファイルを配置する構成。同階層にbackupフォルダも作成し、更新前にバックアップをとることとします。

S3の構成
bucket
    |
    |--- settings
            |--- backup
            |--- xxx.json
            |--- yyy.json
            |--- zzz.json
            |--- :
            |--- :

(1) S3にアクセスして、「バケットを作成」をクリック

image.png

(2) バケット名を入力し(①)、「バケットを作成」をクリック(②)

image.png

(3) 次にsettingsフォルダを作成します。バケット一覧にて、作成したバケットをクリック
image.png

(4) バケット配下に移動したら、「フォルダの作成」をクリック
image.png

(5) フォルダの作成ページに移動したら、フォルダ名(settings)を入力し(①)、「フォルダの作成」をクリック(②)
image.png

(6) 続いて、backアップフォルダを作成します。settingsフォルダ直下まで移動した後、「フォルダの作成」をクリック
image.png

(7) フォルダの作成ページに移動したら、フォルダ名(backup)を入力し(①)、「フォルダの作成」をクリック(②)
image.png

(8) 次のステップで使用するので、バケット名を控えておいてください。
※なお、本記事で作成したバケットは既に削除済です。

2. IAMポリシーとIAMロールの作成

続いて、LambdaがS3を操作するために必要なIAMポリシーとIAMロールの作成を行います。

(1) IAMに移動し、「ポリシー」をクリック(①)、画面遷移したら、「ポリシーの作成」をクリック(②)
image.png

(2) 「アクセス許可を指定」に遷移したら、サービス(①)、アクセスレベル(②)、リソース(③)に以下の通り入力 or 選択し(※1)、「次へ」をクリック
image.png

※1: サービス、アクセスレベル、リソースの入力

No 項目名 入力 or 選択内容 意味
サービス S3
アクセスレベル(リスト) ListBucket オブジェクト一覧の取得
アクセスレベル(読み取り) GetObject オブジェクトの内容を取得
アクセスレベル(書き込み) PutObject オブジェクトの作成/更新
リソース(特定/bucket) 対象バケットのARN 指定バケットを対象とする
リソース(特定/object) 対象バケットのARN/settings 指定バケットのsettingsフォルダを対象とする
リソース(特定/object) 対象バケットのARN/settings/*(※2) 対象バケットのsettingsフォルダ下のオブジェクトを対象とする

→ 最低限必要な権限にしています。

※2: リソース(特定/object)に関しては、少し入力に癖があったので、編集画面のキャプチャも載せておきます

image.png

No 項目名 入力内容 備考
Resource bucket name バケット名/settings 「バケット名」には手順1で作成したバケット名
Resource object name * 「任意のobject name」にチェックで自動入力される
リソースARN 対象バケットのARN/settings/* ①, ②入力で自動入力される

ちなみに、JSON形式にすると、以下の通り。

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "VisualEditor0",
			"Effect": "Allow",
			"Action": [
				"s3:PutObject",
				"s3:GetObject",
				"s3:ListBucket"
			],
			"Resource": [
				"arn:aws:s3:::my-test-bucket-20231110/settings/*",
				"arn:aws:s3:::my-test-bucket-20231110/settings",
				"arn:aws:s3:::my-test-bucket-20231110"
			]
		}
	]
}

(3) ポリシー名に名称を入力し(①)、説明欄に説明を入力(②: 任意)したら(②)、「ポリシーの作成」をクリック(①)
image.png

(4) 続いて、IAMロールを作成します。IAMにて、「ロール」(①) > 「ロールを作成」をクリック(②)
image.png

(5) 「信頼されたエンティティ」タイプにて、「AWSのサービス」を選択し(①)、「サービスまたはユースケース」の「Lambda」を選択し(②)、「次へ」をクリック(③)
image.png

(6) 「許可を追加」画面にて、(3) で入力したポリシー名を検索(①)、選択し(②)、「次へ」(③)をクリック
image.png

(7) 「ロール名」を入力し(①)、「ロールを作成」をクリック(②)
image.png

3. Lambda関数の作成

続いて、Lambda関数を作成します。今回、言語はPython 3.11でいきます。

(1) Lambdaにアクセスし、「関数の作成」をクリック(①)
image.png

(2) 「関数の作成」画面に遷移したら、以下の通り入力・選択し、「関数の作成」(④)をクリック。
image.png

No 項目名 入力 or 選択内容
関数名 お好みの関数名を入力
ランタイム (今回は)Python3.11
既存のロール 手順2で作成したIAMロールを選択

(3) 画面遷移したら、「コード」タブをクリック
image.png

(4) 「lambda_function」にソースコードを入力し(①※)、「Deploy」をクリック(②)
image.png

※ソースコード

update_json
import boto3
import json
import logging
from collections import OrderedDict

# ロガーの設定
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# 各種設定
bucket_name = 'my-test-bucket-20231110'
target_prefix = 'settings/'
backup_prefix = f'{target_prefix}backup/'
ver_prev = '1.0.0'  # 更新前のバージョン ☆ Version変更の内容に沿って書き換え
ver_live = '1.1.0'  # 更新後のバージョン ☆ Version変更の内容に沿って書き換え


# S3 クライアントの初期化
s3_client = boto3.client('s3')

def lambda_handler(event, context):
    # 関数内で使用する変数を宣言
    total_file_count = 0  # 総ファイル数
    updated_file_count = 0  # 更新されたファイル数
    error_messages = []  # エラーメッセージを記録するリスト
    not_updated_files = [] # 更新対象でないファイルリスト
    try:
        # バケット内の全ファイルリストを取得: list_objects_v2では最大1,000件しか取得できないためpaginatorで対処
        paginator = s3_client.get_paginator('list_objects_v2')
        pages = paginator.paginate(Bucket=bucket_name, Prefix=target_prefix)

        # バックアップフォルダの存在確認と作成
        response = s3_client.list_objects_v2(Bucket=bucket_name, Prefix=backup_prefix)
        if 'Contents' not in response:
            s3_client.put_object(Bucket=bucket_name, Key=backup_prefix)

        # 各ファイルに対して処理を実行
        for page in pages:
           for item in page.get('Contents', []):
                file_key = item['Key']
                if file_key.startswith(target_prefix) and file_key.endswith('.json') and not file_key.startswith(backup_prefix): 
                    # JSONファイル数をカウント
                    total_file_count += 1
                    try:
                        # ファイルの内容を取得
                        file = s3_client.get_object(Bucket=bucket_name, Key=file_key)
                        file_content = file['Body'].read().decode('utf-8')
                        json_content = json.loads(file_content, object_pairs_hook=OrderedDict)
                    
                        # Versionがver_prevの場合のみ処理
                        if json_content.get('Version') == ver_prev:
                            # バックアップ作成
                            backup_key = backup_prefix + file_key.split('/')[-1]
                            s3_client.copy_object(Bucket=bucket_name, CopySource={'Bucket': bucket_name, 'Key': file_key}, Key=backup_key)
                        
                            # --- ☆ Version変更の内容に沿って書き換え: ここから ☆ --- #
                        
                            # Country要素の後ろにRegion要素を追加
                            country_index = list(json_content.keys()).index('Country') + 1
                            json_content = OrderedDict(list(json_content.items())[:country_index] + [('Region', '')] + list(json_content.items())[country_index:])
                        
                            # --- ☆ Version変更の内容に沿って書き換え: ここまで ☆ --- #                        
                            
                            # Versionを更新
                            json_content['Version'] = ver_live
                        
                            # 更新された内容でファイルを再アップロード: ensure_ascii=FalseでUnicodeエスケープ対策を実施
                            s3_client.put_object(Bucket= bucket_name, Key=file_key, Body=json.dumps(json_content, indent=4, ensure_ascii=False))
                            logger.info(f'Updated and backed up file: {file_key}')
                        
                            # 更新されたJSONファイル数をカウント
                            updated_file_count += 1
                        else:
                            # 更新対象でない場合はファイル名を取得
                            not_updated_files.append(file_key)
                    
                    except Exception as e:
                        error_messages.append(f"Error processing file {file_key}: {str(e)}")
                        
    except Exception as e:
        logger.error(f"An error occurred: {e}")
    
    # 処理終了後のメッセージ: 総ファイル数、更新ファイル数、エラー発生ファイル数、エラーメッセージ
    logger.info('Finished processing S3 files.')
    logger.info('================== results ==================')
    logger.info(f'total file count directly below settings: {total_file_count}')
    logger.info(f'updated file count: {updated_file_count}')
    error_len = len(error_messages)
    logger.info(f'error count: {error_len}')
    logger.info('---------- error details ----------')
    if error_len == 0:
        logger.info('there are no errors')
    for error_message in error_messages:
        logger.error(error_message)
    logger.info('-----------------------------------')
    logger.info('-------- files not updated --------')
    if len(not_updated_files) == 0:
        logger.info('there are no files to update')
    for not_updated_file in not_updated_files:
        logger.error(not_updated_file)
    logger.info('-----------------------------------')
    logger.info('=============================================')

上記ソースコードの内容は以下の通り

  • 設定ファイルのVersionが1.0.0の場合に、1.1.0に更新
  • 更新前に、対象ファイル(Versionが1.0.0)のバックアップを取得
  • Country要素の後ろに、Region要素を空白で追加
  • ファイル更新処理時にエラーが発生しても処理を最後まで継続
  • 処理の最後に、
    • settings直下の総ファイル数
    • 更新ファイル数
    • エラー発生数
    • エラー発生ファイル名とその詳細
    • settings直下で更新対象にならなかったファイル名

を表示

(5) テストを作成します。
「テスト」タブを選択し(①)、イベント名に入力(②)、「テスト」をクリック(③)して成功したら、「保存」をクリック(④)
image.png

(6) 続いて、バージョンも発行しておきましょう。
「バージョン」タブを選択し、「新しいバージョンを発行」をクリック(②)
image.png

(7) 「バージョンの説明」に簡単な説明を入力し(①)、「発行」をクリック(②)
image.png

(8) バージョンが作成されました。
これによって、この時点のコードやテストの内容が保持されます。
image.png

※最初のバージョン発行時に一部内容を誤ってそれを削除したため、2になっております。

4. 動作確認

最後に簡単ではありますが、動作確認をしておきます。

(1) S3バケットのsettings配下に以下の3ファイル(①※)をアップロード(②)。
なお、設定ファイル(JSONファイルの)文字コードはUTF-8であるものとします。

image.png

※以下、設定ファイルの内容です。

設定ファイルその1: 正常に更新される

jp-001.json
{
	"Version": "1.0.0",
	"Language": "Japanese",
	"Country": "Japan",
	"Type": "Cat"
}

設定ファイルその2: Versionが1.0.0でないため更新対象外

ch-001.json
{
	"Version": "1.1.0",
	"Language": "Chinese",
	"Country": "China",
	"Region": "Beijing",
	"Type": "Cat"
}

設定ファイルその3: Countryが存在しない(綴り間違い)ため、エラーが発生する

uk-001.json
{
	"Version": "1.0.0",
	"Language": "English",
	"Countory": "UK",
	"Type": "Cat"
}

(2) 以下ファイルアップロード後(更新前)のsettings直下とその下のbackup直下の様子です。
▼settings直下
image.png
▼settings/backup直下
image.png

(3) Lambdaの対象関数に移動し、「Test」をクリック
image.png

(4) 以下、実行後のログの内容です。

Execution result
Test Event Name
test-s3-20231110

Response
null

Function Logs
[INFO]	2023-11-12T05:34:15.495Z		Found credentials in environment variables.
START RequestId: xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx Version: $LATEST
[INFO]	2023-11-12T05:34:16.283Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	Updated and backed up file: settings/jp-001.json
[INFO]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	Finished processing S3 files.
[INFO]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	================== results ==================
[INFO]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	total file count directly below settings: 3
[INFO]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	updated file count: 1
[INFO]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	error count: 1
[INFO]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	---------- error details ----------
[ERROR]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	Error processing file settings/uk-001.json: 'Country' is not in list
[INFO]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	-----------------------------------
[INFO]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	-------- files not updated --------
[ERROR]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	settings/ch-001.json
[INFO]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	-----------------------------------
[INFO]	2023-11-12T05:34:16.345Z	xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	=============================================
END RequestId: xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx
REPORT RequestId: xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx	Duration: 742.77 ms	Billed Duration: 743 ms	Memory Size: 128 MB	Max Memory Used: 82 MB	Init Duration: 414.04 ms

Request ID
xxxxx761-60b6-49f0-86c1-cdaf4caxxxxx

(5) 実行後(更新後)の、settingsフォルダとその直下のbackupフォルダの様子は以下の通り。

▼settings直下
image.png

ログに記載の内容通り、jp-001.jsonのみ更新されて、ch-001.json、uk-001.jsonは更新されていません。

▼settings/backup直下
image.png

バックアップについては、Versionが1.0.0のjp-001.jsonとuk-001.jsonが取得されています。

(6) 更新後のjp-001.jsonの内容は以下の通り。

jp-001.json
{
    "Version": "1.1.0",
    "Language": "Japanese",
    "Country": "Japan",
    "Region": "",
    "Type": "Cat"
}

Country要素の後ろに、Region要素が空文字で追加され、Versionが1.0.0から1.1.0に変更されています。

以上、LambdaでS3上のJSON形式の設定ファイルが更新できました。

終わりに

アクセス権回りで少々躓きましたが、無事やりたいことが実現できました。
※最初、S3のバケットポリシーの設定についても書いていたのですが、同一アカウント内ではIAMロールかどちらかが設定できていればよいため、削除しました。

設定ファイル(JSONファイル)のVersion更新毎に、ソースコードのver_prevとver_live、「Version変更の内容に沿って書き換え」の部分を書き換えつつ、lambda自体のバージョンも更新すると、いい感じにバージョン管理できるはず。

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0