2025-04-04 10:19:22 +09:00

278 lines
12 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 abc import *
from zoneinfo import ZoneInfo
import boto3
import gnupg
# 環境変数
SECRET_KEY_FILE_BUCKET_NAME = os.environ["SECRET_KEY_FILE_BUCKET_NAME"]
SECRET_KEY_FILE_PATH = os.environ["SECRET_KEY_FILE_PATH"]
SAP_SUP_BUCKET_NAME = os.environ["SAP_SUP_BUCKET_NAME"]
SAP_FIN_BUCKET_NAME = os.environ["SAP_FIN_BUCKET_NAME"]
SAP_DATA_BACKUP_BUCKET_NAME = os.environ["SAP_DATA_BACKUP_BUCKET_NAME"]
DATA_SOURCE_SAP_SUP = os.environ["DATA_SOURCE_SAP_SUP"]
DATA_SOURCE_SAP_FIN = os.environ["DATA_SOURCE_SAP_FIN"]
NDS_NOTICE_TOPIC = os.environ["NDS_NOTICE_TOPIC"]
NDS_NOTICE_TITLE = os.environ["NDS_NOTICE_TITLE"]
LOG_LEVEL = os.environ["LOG_LEVEL"]
# 定数
DIRECTORY_RECV = '/recv/'
DIRECTORY_RECV_ERROR = '/recv_error/'
DIRECTORY_TARGET = '/target/'
EXTENSION_ERROR = '.error'
EXTENSION_DECRYPT_ERROR = '.decrypt_error'
INDEX_EXTENSION_DELETE_NUM = -4
PATH_TEMP = '/tmp'
PATH_TEMP_PRIVATE_KEY = '/tmp/private.key'
PATH_TEMP_ENCRYPT_FILE = '/tmp/encrypt_file'
PATH_TEMP_DECRYPT_FILE = '/tmp/decrypt_file'
TARGET_BUCKET_BY_DATA_SOURCE = {
DATA_SOURCE_SAP_SUP: SAP_SUP_BUCKET_NAME,
DATA_SOURCE_SAP_FIN: SAP_FIN_BUCKET_NAME
}
# 変数
s3_client = boto3.client('s3')
sns_client = boto3.client('sns')
# logger設定
logger = logging.getLogger()
def custome_time(*arg):
return datetime.datetime.now(ZoneInfo("Asia/Tokyo")).timetuple()
formatter = logging.Formatter(
'[%(levelname)s]\t%(asctime)s\t%(message)s\n',
'%Y-%m-%d %H:%M:%S'
)
formatter.converter = custome_time
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 handler(event, context):
try:
# ① 処理開始ログを出力する
logger.info('I-01-01 処理開始 SAPデータ復号処理')
execute_date = datetime.date.today().strftime('%Y/%m/%d')
logger.info(f'I-01-02 処理稼働日:{execute_date}')
# ② 処理開始時に受け取ったイベント情報の以下内容をログに出力しメモリに保持する
logger.info('I-02-01 イベント情報出力処理')
s3_event = S3EventInfo(event["Records"][0]["s3"])
logger.info(f'I-02-02 バケット名:{s3_event.bucket_name}')
logger.info(f'I-02-03 ファイルパス:{s3_event.file_path}')
logger.info(f'I-02-04 データソース名:{s3_event.data_source_name}')
# ③ S3からPGP暗号化ファイルを読み込む
try:
logger.info(f'I-03-01 PGP暗号化ファイル読込 読込元{s3_event.bucket_name}/{s3_event.file_path}')
s3_client.download_file(s3_event.bucket_name, s3_event.file_path, PATH_TEMP_ENCRYPT_FILE)
logger.info('I-03-02 PGP暗号化ファイルを読み込みました')
except Exception as e:
logger.exception(f'E-03-01 PGP暗号化ファイルの読み込みに失敗しました エラー内容{e}')
raise EncryptFileReadException('E-03-01', EXTENSION_ERROR, e)
# ④ S3から秘密鍵ファイルを読み込む
try:
logger.info(f'I-04-01 秘密鍵ファイル読込 読込元:{SECRET_KEY_FILE_BUCKET_NAME}/{SECRET_KEY_FILE_PATH}')
s3_client.download_file(SECRET_KEY_FILE_BUCKET_NAME, SECRET_KEY_FILE_PATH, PATH_TEMP_PRIVATE_KEY)
logger.info('I-04-02 秘密鍵ファイルを読み込みました')
except Exception as e:
logger.exception(f'E-04-01 秘密鍵ファイルの読み込みに失敗しました エラー内容:{e}')
raise FileReadException('E-04-01', EXTENSION_ERROR, e)
# ⑤ 「③」で読み込んだ秘密鍵ファイルをPGPライブラリにインポートを行う
try:
logger.info('I-05-01 秘密鍵ファイルインポート')
gpg = gnupg.GPG(gnupghome=PATH_TEMP)
with open(PATH_TEMP_PRIVATE_KEY) as key_file:
gpg.import_keys(key_file.read())
logger.info('I-05-02 秘密鍵ファイルをインポートしました')
except Exception as e:
logger.exception(f'E-05-01 秘密鍵ファイルのインポートに失敗しました エラー内容:{e}')
raise PrivateKeyImportException('E-05-01', EXTENSION_ERROR, e)
# ⑥ 「④」で読み込んだPGP暗号化ファイルを「⑤」でインポートした秘密鍵ファイルで復号する
try:
logger.info('I-06-01 PGP暗号化ファイルの復号化')
with open(PATH_TEMP_ENCRYPT_FILE, 'rb') as temp_encrypr_file:
gpg.decrypt_file(temp_encrypr_file, output=PATH_TEMP_DECRYPT_FILE)
decrypt_file = open(PATH_TEMP_DECRYPT_FILE, 'rb')
logger.info('I-06-02 PGP暗号化ファイルを復号しました')
except Exception as e:
logger.exception(f'E-06-01 PGP暗号化ファイルの復号化に失敗しました エラー内容{e}')
raise DecryptException('E-06-01', EXTENSION_DECRYPT_ERROR, e)
# ⑦ 各ファイルをS3に出力する
try:
logger.info('I-07-01 各ファイルをS3に出力する')
# 「⑥」で復号化したファイルをSAPデータ保管用バケットに出力する
decrypt_file_name = s3_event.file_name[:INDEX_EXTENSION_DELETE_NUM]
decrypt_file_key = f'{execute_date}/{decrypt_file_name}'
decrypt_bucket_name = TARGET_BUCKET_BY_DATA_SOURCE[s3_event.data_source_name]
logger.info(f'I-07-02 復号化ファイル出力 ファイル名:{decrypt_file_name} 出力先:{decrypt_bucket_name}/{decrypt_file_key}')
s3_client.put_object(Bucket=decrypt_bucket_name, Key=decrypt_file_key, Body=decrypt_file)
decrypt_file.close()
logger.info('I-07-03 復号化ファイルをS3に出力しました')
except Exception as e:
logger.exception(f'E-07-01 復号化ファイルのS3出力に失敗しました エラー内容{e}')
raise FileOutputException('E-07-01', EXTENSION_ERROR, e)
# 「④」で読み込んだPGP暗号化ファイルを以下に移動コピー削除する
try:
copy_source = {
'Bucket': s3_event.bucket_name,
'Key': s3_event.file_path
}
backup_file_key = f'{s3_event.data_source_name}/{execute_date}/{s3_event.file_name}'
logger.info(
f'I-07-04 PGP暗号化ファイル移動 移動元{s3_event.bucket_name}/{s3_event.file_path} 移動先:{SAP_DATA_BACKUP_BUCKET_NAME}/{backup_file_key}')
s3_client.copy_object(Bucket=SAP_DATA_BACKUP_BUCKET_NAME, Key=backup_file_key, CopySource=copy_source)
s3_client.delete_object(Bucket=s3_event.bucket_name, Key=s3_event.file_path)
logger.info('I-07-05 PGP暗号化ファイルをバックアップ用バケットに移動しました')
except Exception as e:
logger.exception(f'E-07-02 PGP暗号化ファイルのS3出力に失敗しました エラー内容{e}')
raise FileOutputException('E-07-02', EXTENSION_ERROR, e)
# 「⑥」で復号化したファイルをデータ取込用バケットに出力する
try:
copy_source = {
'Bucket': decrypt_bucket_name,
'Key': decrypt_file_key
}
import_file_folder = f'{s3_event.data_source_name}{DIRECTORY_TARGET}'
import_file_key = f'{import_file_folder}{decrypt_file_name}'
logger.info(f'I-07-06 復号化ファイル出力 ファイル名:{decrypt_file_name} 出力先:{s3_event.bucket_name}/{import_file_folder}')
s3_client.copy_object(Bucket=s3_event.bucket_name, Key=import_file_key, CopySource=copy_source)
logger.info(f'I-07-07 復号化ファイルをS3に出力しました')
except Exception as e:
logger.exception(f'E-07-03 復号化ファイルのS3出力に失敗しました エラー内容{e}')
raise FileOutputException('E-07-02', EXTENSION_ERROR, e)
# ⑧ 処理終了ログを出力する
logger.info('I-08-01 処理終了 SAPデータ復号処理')
except EncryptFileReadException as e:
create_status_file(s3_event, e.extension)
error_notice(e.id, e.arg)
except CustomException as e:
create_status_file(s3_event, e.extension)
move_encrypt_file(s3_event)
error_notice(e.id, e.arg)
except Exception as e:
logger.exception(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
create_status_file(s3_event, EXTENSION_ERROR)
move_encrypt_file(s3_event)
error_notice('E-99', e)
return
# エラーステータスファイル作成
def create_status_file(s3_event, extension) -> None:
try:
result_error_file_name = s3_event.file_name + extension
result_error_key = s3_event.data_source_name + DIRECTORY_RECV + result_error_file_name
s3_client.put_object(Bucket=s3_event.bucket_name, Key=result_error_key, Body='')
logger.error(
f'E-ERR-01 recvディレクトリにエラーファイルを作成しました ファイル名{result_error_file_name} 出力先:{s3_event.bucket_name}/{result_error_key}')
except Exception as e:
logger.exception(f'E-96 エラーステータスファイルの作成に失敗しました エラー内容:{e}')
return
# PGP暗号化ファイル移動
def move_encrypt_file(s3_event) -> None:
try:
copy_source = {
'Bucket': s3_event.bucket_name,
'Key': s3_event.file_path
}
error_file_name = f'{datetime.datetime.now():%Y%m%d%H%M%S}_{s3_event.file_name}'
error_key = s3_event.data_source_name + DIRECTORY_RECV_ERROR + error_file_name
s3_client.copy_object(Bucket=s3_event.bucket_name, Key=error_key, CopySource=copy_source)
s3_client.delete_object(Bucket=s3_event.bucket_name, Key=s3_event.file_path)
logger.error(
f'E-ERR-02 recv_errorディレクトリにファイルを移動しました 移動元{s3_event.bucket_name}/{s3_event.file_path} 移動先:{s3_event.bucket_name}/{error_key}')
except Exception as e:
logger.exception(f'E-97 PGP暗号化ファイルの移動に失敗しました エラー内容{e}')
return
# 保守要員チーム通知
def error_notice(error_log_id, exception) -> None:
try:
error_msg = f'{error_log_id} のエラーが発生しました。ご確認ください\n詳細:{exception}'
params = {
'TopicArn': NDS_NOTICE_TOPIC,
'Subject': NDS_NOTICE_TITLE,
'Message': error_msg
}
sns_client.publish(**params)
logger.error(f'E-ERR-03 処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
except Exception as e:
logger.exception(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
return
# S3イベント情報を保持するクラス
class S3EventInfo:
def __init__(self, s3_event) -> None:
self._bucket_name = s3_event["bucket"]["name"]
self._file_path = s3_event["object"]["key"]
self._file_name = os.path.basename(s3_event["object"]["key"])
self._data_source_name = os.path.dirname(s3_event["object"]["key"]).split('/')[0]
@property
def bucket_name(self):
return self._bucket_name
@property
def file_path(self):
return self._file_path
@property
def file_name(self):
return self._file_name
@property
def data_source_name(self):
return self._data_source_name
# カスタムExceptionクラス
class CustomException(Exception, metaclass=ABCMeta):
def __init__(self, id, extension, arg):
self.id = id
self.extension = extension
self.arg = arg
class FileReadException(CustomException):
pass
class EncryptFileReadException(CustomException):
pass
class FileOutputException(CustomException):
pass
class PrivateKeyImportException(CustomException):
pass
class DecryptException(CustomException):
pass