LambdaでS3上に保管したJSON形式の設定ファイルを一括更新する手順の覚え書きです。
要件
- ロボットの設定ファイルを一括更新する
- 設定ファイルの要素は全ファイル同じ
- 要素の増減について、バージョン管理するものとする
- 処理は手動実行のため、コンソールでログが見られれば問題ない
イメージ
イメージは以下の通り
Version 1.0.0のCountry要素の後ろにRegion要素を空白で追加し、Version 1.1.0に更新する場合
手順
1. S3バケットとフォルダの作成
まず、S3設定ファイルを保管するバケットとフォルダ(厳密にはオブジェクト)を作成します。
S3バケットの構成は以下の通りで、settingsフォルダ下に設定ファイルを配置する構成。同階層にbackupフォルダも作成し、更新前にバックアップをとることとします。
bucket
|
|--- settings
|--- backup
|--- xxx.json
|--- yyy.json
|--- zzz.json
|--- :
|--- :
(1) S3にアクセスして、「バケットを作成」をクリック
(2) バケット名を入力し(①)、「バケットを作成」をクリック(②)
(3) 次にsettingsフォルダを作成します。バケット一覧にて、作成したバケットをクリック
(4) バケット配下に移動したら、「フォルダの作成」をクリック
(5) フォルダの作成ページに移動したら、フォルダ名(settings)を入力し(①)、「フォルダの作成」をクリック(②)
(6) 続いて、backアップフォルダを作成します。settingsフォルダ直下まで移動した後、「フォルダの作成」をクリック
(7) フォルダの作成ページに移動したら、フォルダ名(backup)を入力し(①)、「フォルダの作成」をクリック(②)
(8) 次のステップで使用するので、バケット名を控えておいてください。
※なお、本記事で作成したバケットは既に削除済です。
2. IAMポリシーとIAMロールの作成
続いて、LambdaがS3を操作するために必要なIAMポリシーとIAMロールの作成を行います。
(1) IAMに移動し、「ポリシー」をクリック(①)、画面遷移したら、「ポリシーの作成」をクリック(②)
(2) 「アクセス許可を指定」に遷移したら、サービス(①)、アクセスレベル(②)、リソース(③)に以下の通り入力 or 選択し(※1)、「次へ」をクリック
※1: サービス、アクセスレベル、リソースの入力
No | 項目名 | 入力 or 選択内容 | 意味 |
---|---|---|---|
① | サービス | S3 | |
② | アクセスレベル(リスト) | ListBucket | オブジェクト一覧の取得 |
② | アクセスレベル(読み取り) | GetObject | オブジェクトの内容を取得 |
② | アクセスレベル(書き込み) | PutObject | オブジェクトの作成/更新 |
③ | リソース(特定/bucket) | 対象バケットのARN | 指定バケットを対象とする |
③ | リソース(特定/object) | 対象バケットのARN/settings | 指定バケットのsettingsフォルダを対象とする |
③ | リソース(特定/object) | 対象バケットのARN/settings/*(※2) | 対象バケットのsettingsフォルダ下のオブジェクトを対象とする |
→ 最低限必要な権限にしています。
※2: リソース(特定/object)に関しては、少し入力に癖があったので、編集画面のキャプチャも載せておきます
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) ポリシー名に名称を入力し(①)、説明欄に説明を入力(②: 任意)したら(②)、「ポリシーの作成」をクリック(①)
(4) 続いて、IAMロールを作成します。IAMにて、「ロール」(①) > 「ロールを作成」をクリック(②)
(5) 「信頼されたエンティティ」タイプにて、「AWSのサービス」を選択し(①)、「サービスまたはユースケース」の「Lambda」を選択し(②)、「次へ」をクリック(③)
(6) 「許可を追加」画面にて、(3) で入力したポリシー名を検索(①)、選択し(②)、「次へ」(③)をクリック
(7) 「ロール名」を入力し(①)、「ロールを作成」をクリック(②)
3. Lambda関数の作成
続いて、Lambda関数を作成します。今回、言語はPython 3.11でいきます。
(1) Lambdaにアクセスし、「関数の作成」をクリック(①)
(2) 「関数の作成」画面に遷移したら、以下の通り入力・選択し、「関数の作成」(④)をクリック。
No | 項目名 | 入力 or 選択内容 |
---|---|---|
① | 関数名 | お好みの関数名を入力 |
② | ランタイム | (今回は)Python3.11 |
③ | 既存のロール | 手順2で作成したIAMロールを選択 |
(4) 「lambda_function」にソースコードを入力し(①※)、「Deploy」をクリック(②)
※ソースコード
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) テストを作成します。
「テスト」タブを選択し(①)、イベント名に入力(②)、「テスト」をクリック(③)して成功したら、「保存」をクリック(④)
(6) 続いて、バージョンも発行しておきましょう。
「バージョン」タブを選択し、「新しいバージョンを発行」をクリック(②)
(7) 「バージョンの説明」に簡単な説明を入力し(①)、「発行」をクリック(②)
(8) バージョンが作成されました。
これによって、この時点のコードやテストの内容が保持されます。
※最初のバージョン発行時に一部内容を誤ってそれを削除したため、2になっております。
4. 動作確認
最後に簡単ではありますが、動作確認をしておきます。
(1) S3バケットのsettings配下に以下の3ファイル(①※)をアップロード(②)。
なお、設定ファイル(JSONファイルの)文字コードはUTF-8であるものとします。
※以下、設定ファイルの内容です。
設定ファイルその1: 正常に更新される
{
"Version": "1.0.0",
"Language": "Japanese",
"Country": "Japan",
"Type": "Cat"
}
設定ファイルその2: Versionが1.0.0でないため更新対象外
{
"Version": "1.1.0",
"Language": "Chinese",
"Country": "China",
"Region": "Beijing",
"Type": "Cat"
}
設定ファイルその3: Countryが存在しない(綴り間違い)ため、エラーが発生する
{
"Version": "1.0.0",
"Language": "English",
"Countory": "UK",
"Type": "Cat"
}
(2) 以下ファイルアップロード後(更新前)のsettings直下とその下のbackup直下の様子です。
▼settings直下
▼settings/backup直下
(3) Lambdaの対象関数に移動し、「Test」をクリック
(4) 以下、実行後のログの内容です。
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フォルダの様子は以下の通り。
ログに記載の内容通り、jp-001.jsonのみ更新されて、ch-001.json、uk-001.jsonは更新されていません。
バックアップについては、Versionが1.0.0のjp-001.jsonとuk-001.jsonが取得されています。
(6) 更新後の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自体のバージョンも更新すると、いい感じにバージョン管理できるはず。