2024-07-01 16:30:24 +09:00

237 lines
10 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import datetime
import logging
import os
from zoneinfo import ZoneInfo
import boto3
import pyzipper
from pyzipper.zipfile import BadZipFile
# 環境変数
DATA_IMPORT_BUCKET = os.environ["DATA_IMPORT_BUCKET"]
HCP_WEB_TARGET_FOLDER = os.environ["HCP_WEB_TARGET_FOLDER"]
HCP_WEB_BACKUP_BUCKET = os.environ["HCP_WEB_BACKUP_BUCKET"]
BACKUP_ZIPFILE_FOLDER = os.environ["BACKUP_ZIPFILE_FOLDER"]
BACKUP_DATA_IMPORT_FOLDER = os.environ["BACKUP_DATA_IMPORT_FOLDER"]
DATA_IMPORT_FILENAME = os.environ["DATA_IMPORT_FILENAME"]
MEDPASS_ZIP_PASSWORD_PARAMETER_STORE_KEY = os.environ["MEDPASS_ZIP_PASSWORD_PARAMETER_STORE_KEY"]
LOG_LEVEL = os.environ["LOG_LEVEL"]
TZ = os.environ["TZ"]
# 定数
# 多重起動抑制用のコントロールファイルの拡張子
EXCLUSIVE_CONTROL_FILE_EXT = '.doing'
# tmpフォルダパス
PATH_TMP = '/tmp'
# 拡張子
ZIP_FILE_EXT = 'zip'
CSV_FILE_EXT = 'csv'
# S3クライアント
s3_client = boto3.client('s3')
# SystemsManagerクライアント
ssm_client = boto3.client('ssm')
# logger設定
logger = logging.getLogger()
def log_datetime_convert_tz(*arg):
"""ログに出力するタイムスタンプのロケールを変更するJST指定"""
return datetime.datetime.now(ZoneInfo(TZ)).timetuple()
formatter = logging.Formatter(
'[%(levelname)s]\t%(asctime)s\t%(message)s\n',
'%Y-%m-%d %H:%M:%S'
)
formatter.converter = log_datetime_convert_tz
for handler in logger.handlers:
handler.setFormatter(formatter)
level = logging.getLevelName(LOG_LEVEL)
if not isinstance(level, int):
level = logging.INFO
logger.setLevel(level)
def extract_zip_with_password(zip_filepath: str, extract_to_folder: str, password: str) -> os.path:
"""
暗号化ZIPを解凍する。
:param zip_filepath: ZIPファイルが保管されているフォルダパス
:param extract_to_folder: ZIPファイルの解凍先フォルダ
:param password: ZIPパスワード
:return 解凍されたファイルパス
"""
# ZIPを解凍
try:
with pyzipper.AESZipFile(zip_filepath) as z:
# ZIP内のファイルは1つのみ
inner_filename = z.filelist[0].filename
z.extractall(path=extract_to_folder, pwd=password.encode())
except Exception as e:
raise e
return os.path.join(extract_to_folder, inner_filename)
def get_s3_event_parameter(event: dict) -> tuple[str, str, str, str]:
s3_event = event["Records"][0]["s3"]
event_bucket_name: str = s3_event["bucket"]["name"]
event_object_key: str = s3_event["object"]["key"]
event_file_name: str = os.path.basename(event_object_key)
event_folder_name: str = os.path.dirname(event_object_key).split('/')[0]
return event_bucket_name, event_object_key, event_file_name, event_folder_name
def get_ssm_params(parameter_key: str, with_decryption: bool = True) -> str:
"""SSMパラメータストアから指定されたパラメータ名の値を取得する"""
response = ssm_client.get_parameter(
Name=parameter_key, WithDecryption=with_decryption)
parameter_value: str = response['Parameter']['Value']
return parameter_value
def delete_doing_file(event: dict) -> None:
""".doingファイルをバケット上から削除する"""
# イベント情報を取得
(
event_bucket_name,
event_object_key,
_,
_
) = get_s3_event_parameter(event)
# ⑨ メモリに保持したバケット名/フォルダ名内の「受信データファイル名.doing」ファイルを削除する
s3_client.delete_object(
Bucket=event_bucket_name, Key=f'{event_object_key}{EXCLUSIVE_CONTROL_FILE_EXT}')
def handler(event, context) -> None:
try:
# ① 処理開始ログを出力する
logger.info('I-01-01 処理開始 medパスデータ解凍・復号化・転送処理')
# ② 処理開始時に受け取ったイベント情報をログに出力する
# バケット名・フォルダ名・受信データファイル名をメモリに保持
(
event_bucket_name,
event_object_key,
event_file_name,
event_folder_name
) = get_s3_event_parameter(event)
logger.info(f'I-02-01 受信バケット:{event_bucket_name}')
logger.info(f'I-02-01 フォルダ名:{event_folder_name}')
logger.info(f'I-02-01 ファイル名:{event_file_name}')
# ③ S3イベントによるLambdaの重複発火防止の為、メモリに保持したバケット名/フォルダ名内に、「受信データファイル名.doing」ファイルが存在するかチェックする
try:
s3_client.head_object(
Bucket=event_bucket_name, Key=f'{event_object_key}{EXCLUSIVE_CONTROL_FILE_EXT}')
logger.error(
f'E-01-01 {event_bucket_name}/{event_object_key}は現在処理中です。処理を終了します。')
return
except Exception:
# .doingファイルが見つからなかった場合は、処理を続行する
# メモリに保持したバケット名/フォルダ名内に、「受信データファイル名.doing」ファイルを作成する
logger.info('I-03-01 medパスデータの解凍・復号化・転送を開始します')
s3_client.put_object(
Bucket=event_bucket_name, Key=f'{event_object_key}{EXCLUSIVE_CONTROL_FILE_EXT}', Body=b'')
# ④ S3から暗号化ZIPファイルを読み込む
try:
logger.info(
f'I-04-01 暗号化ZIPファイル読込 読込元{event_bucket_name}/{event_object_key}')
s3_client.download_file(
event_bucket_name, event_object_key, os.path.join(PATH_TMP, event_file_name))
logger.info('I-04-02 暗号化ZIPファイルをダウンロードしました')
except Exception as e:
logger.exception(f'E-04-01 暗号化ZIPファイルのダウンロードに失敗しました エラー内容{e}')
delete_doing_file(event)
return
# ⑤ ZIP解凍パスワードをSSM パラメータストアから取得する
try:
logger.info('I-05-01 ZIP解凍パスワードを読込')
zip_password = get_ssm_params(
MEDPASS_ZIP_PASSWORD_PARAMETER_STORE_KEY)
except Exception as e:
logger.exception(f'E-05-01 ZIP解凍パスワードの読み込みに失敗しました エラー内容{e}')
delete_doing_file(event)
return
# ⑥ ZIPファイルを解凍してローカルに保存
try:
logger.info(f'I-05-02 ZIP解凍開始')
extracted_zip_file_path = extract_zip_with_password(
os.path.join(PATH_TMP, event_file_name), PATH_TMP, zip_password)
except RuntimeError as e:
if 'password' in str(e).lower():
# パスワードが間違っている場合のエラー
logger.exception(
f'E-05-02 ZIPのパスワードが不正のため、解凍に失敗しました エラー内容{e}')
delete_doing_file(event)
return
else:
# 想定外のエラー
raise e
# ZIPファイルが壊れている場合のエラー
except BadZipFile as e:
logger.exception(f'E-05-03 ZIPの形式が不正のため、解凍に失敗しました エラー内容{e}')
delete_doing_file(event)
return
# データ登録用にファイルをリネーム
# ZIPファイル名がyyyymmdd.zipのため、年月日部分をデータ登録用ファイル名の末尾につけ、拡張子をCSVに変更
data_import_file_name = f'{DATA_IMPORT_FILENAME}_{event_file_name.lower().replace(ZIP_FILE_EXT, CSV_FILE_EXT)}'
logger.info(f'I-05-03 ZIP解凍成功')
# ⑥ 受信した暗号化ZIPファイルと解凍後のファイルをバックアップする
backup_copy_source = {
'Bucket': event_bucket_name, 'Key': event_object_key}
execute_date_yyyymmdd = datetime.date.today().strftime('%Y/%m/%d')
# ZIPファイルのバックアップ
s3_client.copy_object(
Bucket=HCP_WEB_BACKUP_BUCKET,
Key=f'{BACKUP_ZIPFILE_FOLDER}/{execute_date_yyyymmdd}/{event_file_name}',
CopySource=backup_copy_source
)
logger.info(
f'I-06-01 medパス受信データのバックアップ完了{HCP_WEB_BACKUP_BUCKET}/{BACKUP_ZIPFILE_FOLDER}/{execute_date_yyyymmdd}/{event_file_name}')
# 解凍後ファイルのバックアップ
s3_client.upload_file(
extracted_zip_file_path,
Bucket=HCP_WEB_BACKUP_BUCKET,
Key=f'{BACKUP_DATA_IMPORT_FOLDER}/{execute_date_yyyymmdd}/{data_import_file_name}'
)
logger.info(
f'I-06-02 medパス解凍後データのバックアップ完了{HCP_WEB_BACKUP_BUCKET}/{BACKUP_DATA_IMPORT_FOLDER}/{execute_date_yyyymmdd}/{data_import_file_name}')
# ⑦ 解凍後のファイルをデータ登録バケットに転送する
data_import_copy_source = {'Bucket': HCP_WEB_BACKUP_BUCKET,
'Key': f'{BACKUP_DATA_IMPORT_FOLDER}/{execute_date_yyyymmdd}/{data_import_file_name}'}
s3_client.copy_object(
Bucket=DATA_IMPORT_BUCKET,
Key=f'{HCP_WEB_TARGET_FOLDER}/{data_import_file_name}',
CopySource=data_import_copy_source
)
# アップロード後、元のバケットからは削除する
s3_client.delete_object(Bucket=event_bucket_name, Key=event_object_key)
logger.info(
f'I-07-01 medパス解凍後データの転送完了{DATA_IMPORT_BUCKET}/{HCP_WEB_TARGET_FOLDER}/{data_import_file_name}')
# ⑧ メモリに保持したバケット名/フォルダ名内の「受信データファイル名.doing」ファイルを削除する
delete_doing_file(event)
logger.info('I-08-01 処理終了 medパスデータ解凍・復号化・転送処理')
except Exception as e:
logger.exception(f'想定外のエラーが発生しました。処理を終了します。 例外内容:{e}')
delete_doing_file(event)
raise e