Merge pull request #406 feature-NEWDWH2021-1618 into develop-v4.6.0

This commit is contained in:
下田雅人 2024-07-08 16:11:13 +09:00
commit e3d8769c17
4 changed files with 442 additions and 0 deletions

View File

@ -0,0 +1,19 @@
# AWS公式のDockerイメージを利用
FROM public.ecr.aws/lambda/python:3.12
# pythonの標準出力をバッファリングしないフラグ
ENV PYTHONUNBUFFERED=1
# pythonのバイトコードを生成しないフラグ
ENV PYTHONDONTWRITEBYTECODE=1
# 必要なファイルをイメージにコピー
COPY Pipfile Pipfile.lock main.py ./
# ライブラリインストール
RUN pip install --upgrade pip wheel setuptools && \
pip install pipenv --no-cache-dir && \
pipenv install --system --deploy && \
pip uninstall -y pipenv virtualenv-clone virtualenv
# lambdaハンドラを起動
CMD [ "main.handler" ]

View File

@ -0,0 +1,15 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
boto3 = "*"
pyzipper = "*"
[dev-packages]
autopep8 = "*"
flake8 = "*"
[requires]
python_version = "3.12"

172
lambda/transfer-medpass-data/Pipfile.lock generated Normal file
View File

@ -0,0 +1,172 @@
{
"_meta": {
"hash": {
"sha256": "d8b79fd5be60005b43448511c67536c114e5fd73722a17e77a5e60a9283aea25"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.12"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"boto3": {
"hashes": [
"sha256:0314e6598f59ee0f34eb4e6d1a0f69fa65c146d2b88a6e837a527a9956ec2731",
"sha256:d41037e2c680ab8d6c61a0a4ee6bf1fdd9e857f43996672830a95d62d6f6fa79"
],
"index": "pypi",
"version": "==1.34.136"
},
"botocore": {
"hashes": [
"sha256:7f7135178692b39143c8f152a618d2a3b71065a317569a7102d2306d4946f42f",
"sha256:c63fe9032091fb9e9477706a3ebfa4d0c109b807907051d892ed574f9b573e61"
],
"markers": "python_version >= '3.8'",
"version": "==1.34.136"
},
"jmespath": {
"hashes": [
"sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980",
"sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"
],
"markers": "python_version >= '3.7'",
"version": "==1.0.1"
},
"pycryptodomex": {
"hashes": [
"sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1",
"sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305",
"sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c",
"sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458",
"sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed",
"sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc",
"sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c",
"sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc",
"sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079",
"sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb",
"sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa",
"sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427",
"sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5",
"sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64",
"sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6",
"sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e",
"sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43",
"sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3",
"sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499",
"sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8",
"sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b",
"sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623",
"sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7",
"sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc",
"sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4",
"sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e",
"sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a",
"sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781",
"sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794",
"sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea",
"sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b",
"sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==3.20.0"
},
"python-dateutil": {
"hashes": [
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
"sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.9.0.post0"
},
"pyzipper": {
"hashes": [
"sha256:0adca90a00c36a93fbe49bfa8c5add452bfe4ef85a1b8e3638739dd1c7b26bfc",
"sha256:6d097f465bfa47796b1494e12ea65d1478107d38e13bc56f6e58eedc4f6c1a87"
],
"index": "pypi",
"version": "==0.3.6"
},
"s3transfer": {
"hashes": [
"sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6",
"sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69"
],
"markers": "python_version >= '3.8'",
"version": "==0.10.2"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"urllib3": {
"hashes": [
"sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3",
"sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"
],
"markers": "python_version < '3.10'",
"version": "==1.26.19"
}
},
"develop": {
"autopep8": {
"hashes": [
"sha256:8d6c87eba648fdcfc83e29b788910b8643171c395d9c4bcf115ece035b9c9dda",
"sha256:a203fe0fcad7939987422140ab17a930f684763bf7335bdb6709991dd7ef6c2d"
],
"index": "pypi",
"version": "==2.3.1"
},
"flake8": {
"hashes": [
"sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a",
"sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5"
],
"index": "pypi",
"version": "==7.1.0"
},
"mccabe": {
"hashes": [
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
],
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
"pycodestyle": {
"hashes": [
"sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c",
"sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"
],
"markers": "python_version >= '3.8'",
"version": "==2.12.0"
},
"pyflakes": {
"hashes": [
"sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f",
"sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"
],
"markers": "python_version >= '3.8'",
"version": "==3.2.0"
},
"tomli": {
"hashes": [
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_version < '3.11'",
"version": "==2.0.1"
}
}
}

View File

@ -0,0 +1,236 @@
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