Merge pull request #127 develop into master
This commit is contained in:
commit
65b820dd07
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,5 +3,7 @@ package-lock.json
|
||||
node_modules/
|
||||
# ローカル確認用環境変数ファイル
|
||||
.env
|
||||
# Pythonの仮想環境ファイル
|
||||
.venv
|
||||
# pythonのキャッシュファイル
|
||||
__pycache__/
|
||||
__pycache__/
|
||||
|
||||
48
ecs/dataimport/README.md
Normal file
48
ecs/dataimport/README.md
Normal file
@ -0,0 +1,48 @@
|
||||
# データ取り込み処理
|
||||
|
||||
## 概要
|
||||
|
||||
データ取り込みバケット(`mbj-newdwh2021-<環境名>-data`)に配置されたデータファイルを、設定に基づいてデータベースに登録する処理を行う
|
||||
処理順序等の詳細は設計書を参照のこと
|
||||
|
||||
## 環境構築
|
||||
|
||||
### 事前準備
|
||||
|
||||
- [Wiki - Python環境構築](https://nds-tyo.backlog.com/alias/wiki/1874930)の「pipenvの導入」まで完了していること
|
||||
|
||||
### Python仮想環境にパッケージをインストール
|
||||
|
||||
- このドキュメントと同じ階層でコマンドラインを開き、以下のコマンドを実行する
|
||||
|
||||
```sh
|
||||
pipenv install -r requirements.txt
|
||||
```
|
||||
|
||||
- 以降、依存モジュールの追加が発生した場合に、`requirements.txt`に追記した上で、上記のコマンドを実行すること
|
||||
|
||||
## ローカルでの実行手順
|
||||
|
||||
- 当ディレクトリ内に`.vscode/launch.json`を作成し、以下のコードを貼り付ける
|
||||
- 既にある場合は作成不要
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: データ取り込み処理",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "ecs/dataimport/controller.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- 当ディレクトリ内に`.env`ファイルを、作成し、環境変数を設定する
|
||||
- 設定する環境変数は設計書を参照のこと
|
||||
- F5キーを押し、処理を実行する
|
||||
@ -1,11 +1,13 @@
|
||||
from datetime import datetime
|
||||
import boto3
|
||||
import io
|
||||
import csv
|
||||
import io
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
import boto3
|
||||
|
||||
from common import convert_quotechar, debug_log
|
||||
from end import end
|
||||
from error import error
|
||||
from common import debug_log
|
||||
|
||||
# 定数
|
||||
DIRECTORY_WORK = '/work/'
|
||||
@ -23,6 +25,14 @@ SETTINGS_ITEM = {
|
||||
'storageSchemaName': 9,
|
||||
'loadSchemaName': 10,
|
||||
'exSqlFileName': 11,
|
||||
'commaReplaceColumns': 12,
|
||||
'importManner': 13,
|
||||
'reserved1': 14,
|
||||
'reserved2': 15,
|
||||
'reserved3': 16,
|
||||
'reserved4': 17,
|
||||
'reserved5': 18,
|
||||
'reserved6': 19
|
||||
}
|
||||
LINE_FEED_CODE = {
|
||||
'CR': '\r',
|
||||
@ -74,14 +84,18 @@ def check(bucket_name, target_data_source, target_file_name, settings_key, log_i
|
||||
work_obj = s3_resource.Object(bucket_name, work_key)
|
||||
work_response = work_obj.get()
|
||||
work_data = io.TextIOWrapper(io.BytesIO(work_response["Body"].read()), encoding=settings_list[SETTINGS_ITEM["charCode"]], newline=LINE_FEED_CODE[settings_list[SETTINGS_ITEM["lineFeedCode"]]])
|
||||
work_header_list = []
|
||||
for line in csv.reader(work_data, quotechar=settings_list[SETTINGS_ITEM["quotechar"]], delimiter=settings_list[SETTINGS_ITEM["delimiter"]]):
|
||||
work_header_list = line
|
||||
work_csv_row = []
|
||||
for i, line in enumerate(csv.reader(work_data, quotechar=convert_quotechar(settings_list[SETTINGS_ITEM["quotechar"]]), delimiter=settings_list[SETTINGS_ITEM["delimiter"]])):
|
||||
# ヘッダあり、かつ、1行目の場合
|
||||
if int(settings_list[SETTINGS_ITEM["headerFlag"]]) == 1 and i == 0:
|
||||
work_csv_row.append(line)
|
||||
continue
|
||||
work_csv_row.append(line)
|
||||
break
|
||||
|
||||
# ② C-0のデータ件数チェックを開始する
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["i"]} I-CHK-02 - C-0のチェックを開始します')
|
||||
if not len(work_header_list):
|
||||
if is_empty_file(work_csv_row, settings_list):
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["i"]} I-CHK-03 - 投入ファイルが0バイトのため処理を終了します')
|
||||
end(bucket_name, target_data_source, target_file_name, '', log_info, mode)
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["i"]} I-CHK-04 - 終了処理完了')
|
||||
@ -90,16 +104,17 @@ def check(bucket_name, target_data_source, target_file_name, settings_key, log_i
|
||||
|
||||
# ③ C-1の項目数チェックを開始する
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["i"]} I-CHK-06 - C-1のチェックを開始します')
|
||||
work_header_list_len = len(work_header_list)
|
||||
if work_header_list_len == int(settings_list[SETTINGS_ITEM["csvNumItems"]]):
|
||||
work_csv_row_item_len = len(work_csv_row[0])
|
||||
if work_csv_row_item_len == int(settings_list[SETTINGS_ITEM["csvNumItems"]]):
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["i"]} I-CHK-07 - C-1:正常終了')
|
||||
else:
|
||||
raise CheckError(f'E-CHK-01 - 項目数が一致しません 個別設定ファイル項目数:{settings_list[SETTINGS_ITEM["csvNumItems"]]} 投入データ項目数:{work_header_list_len}')
|
||||
raise CheckError(f'E-CHK-01 - 項目数が一致しません 個別設定ファイル項目数:{settings_list[SETTINGS_ITEM["csvNumItems"]]} 投入データ項目数:{work_csv_row_item_len}')
|
||||
|
||||
# ④ C-2の項目並び順チェック開始する
|
||||
if int(settings_list[SETTINGS_ITEM["headerFlag"]]) == True:
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["i"]} I-CHK-08 - C-2のチェックを開始します')
|
||||
settings_header_list = settings_list[SETTINGS_ITEM["csvNameItems"]].rstrip().split(',')
|
||||
work_header_list = work_csv_row[0]
|
||||
for i in range(len(settings_header_list)):
|
||||
if not settings_header_list[i] == work_header_list[i]:
|
||||
raise CheckError(f'E-CHK-02 - 項目順序が一致しません {i + 1}番目の項目 個別設定ファイル項目:{settings_header_list[i]} 投入データ項目:{work_header_list[i]}')
|
||||
@ -114,3 +129,22 @@ def check(bucket_name, target_data_source, target_file_name, settings_key, log_i
|
||||
except Exception as e:
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["e"]} E-CHK-99 - エラー内容:{e}')
|
||||
error(bucket_name, target_data_source, target_file_name, log_info)
|
||||
|
||||
|
||||
def is_empty_file(work_csv_row: list, settings_list: list):
|
||||
"""② C-0のデータ件数チェック
|
||||
ヘッダ行がある場合は、1行目を読み飛ばして判定する
|
||||
|
||||
Args:
|
||||
work_csv_row (list): CSVファイルの1行目(ヘッダを含む場合は2行目まで)
|
||||
settings_list (list): 個別設定ファイルのリスト
|
||||
|
||||
Returns:
|
||||
bool: CSVファイルの1行目が0件だった場合はTrue
|
||||
"""
|
||||
has_header = int(settings_list[SETTINGS_ITEM["headerFlag"]]) == 1
|
||||
# ヘッダのみのファイルも0バイトファイルをみなす
|
||||
if has_header:
|
||||
return len(work_csv_row[1:]) == 0
|
||||
|
||||
return len(work_csv_row) == 0
|
||||
|
||||
@ -11,3 +11,18 @@ MODE_TYPE = {
|
||||
def debug_log(log, log_info, mode):
|
||||
if MODE_TYPE['d'] == mode:
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["d"]} {log}')
|
||||
|
||||
def convert_quotechar(quotechar):
|
||||
"""csvモジュールの囲い文字を変換する
|
||||
|
||||
Args:
|
||||
quotechar : 項目囲い文字の設定値
|
||||
|
||||
Returns:
|
||||
空文字、空白文字の場合→None
|
||||
それ以外→設定値をそのまま帰す
|
||||
"""
|
||||
if (quotechar.strip(' ') == ''):
|
||||
return None
|
||||
|
||||
return quotechar
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import csv
|
||||
import io
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
import boto3
|
||||
import pymysql
|
||||
from pymysql.constants import CLIENT
|
||||
import io
|
||||
import csv
|
||||
|
||||
from common import convert_quotechar, debug_log
|
||||
from error import error
|
||||
from common import debug_log
|
||||
|
||||
# 定数
|
||||
DIRECTORY_WORK = '/work/'
|
||||
@ -23,6 +26,14 @@ SETTINGS_ITEM = {
|
||||
'storageSchemaName': 9,
|
||||
'loadSchemaName': 10,
|
||||
'exSqlFileName': 11,
|
||||
'commaReplaceColumns': 12,
|
||||
'importManner': 13,
|
||||
'reserved1': 14,
|
||||
'reserved2': 15,
|
||||
'reserved3': 16,
|
||||
'reserved4': 17,
|
||||
'reserved5': 18,
|
||||
'reserved6': 19
|
||||
}
|
||||
LINE_FEED_CODE = {
|
||||
'CR': '\r',
|
||||
@ -30,6 +41,9 @@ LINE_FEED_CODE = {
|
||||
'CRLF': '\r\n',
|
||||
}
|
||||
DIRECTORY_SETTINGS = '/settings/'
|
||||
TRUNCATE_SRC_TABLE_SYMBOL = 'truncate_src_table:'
|
||||
TRUNCATE_SRC_TABLE_IDENTIFY_SYMBOL_FORMAT = f'{TRUNCATE_SRC_TABLE_SYMBOL}[蓄積スキーマのテーブル名]'
|
||||
INVALID_CONFIG_EXCEPTION_MESSAGE = f'個別設定ファイルのインポート方法に不備がありました。 インポート方法は "{TRUNCATE_SRC_TABLE_IDENTIFY_SYMBOL_FORMAT}" のように設定してください'
|
||||
|
||||
# クラス変数
|
||||
s3_client = boto3.client('s3')
|
||||
@ -76,12 +90,18 @@ def main(bucket_name, target_data_source, target_file_name, settings_key, db_inf
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["i"]} I-MAIN-03 - タイムゾーンを変更しました')
|
||||
|
||||
# ④ 個別設定ファイルのロードスキーマのテーブル名に記載されているテーブルをTRUNCATEする
|
||||
# 個別設定ファイルの読み込み
|
||||
settings_obj = s3_resource.Object(bucket_name, settings_key)
|
||||
settings_response = settings_obj.get()
|
||||
settings_list = []
|
||||
for line in io.TextIOWrapper(io.BytesIO(settings_response["Body"].read()), encoding='utf-8'):
|
||||
settings_list.append(line.rstrip('\n'))
|
||||
|
||||
# 設定ファイルに記載のない行を空文字として扱い、予約行とする
|
||||
for _ in range(len(SETTINGS_ITEM) - len(settings_list)):
|
||||
settings_list.append('')
|
||||
|
||||
# ロードスキーマのTRUNCATE
|
||||
with conn.cursor() as cur:
|
||||
sql_truncate = f'TRUNCATE table {settings_list[SETTINGS_ITEM["loadSchemaName"]]}'
|
||||
cur.execute(sql_truncate)
|
||||
@ -100,8 +120,9 @@ def main(bucket_name, target_data_source, target_file_name, settings_key, db_inf
|
||||
warning_info = '' # ワーニング情報
|
||||
index = 0 # ループインデックス
|
||||
settings_db_columu_list = settings_list[SETTINGS_ITEM["dbColumuName"]].rstrip().split(',')
|
||||
settings_replace_comma_list = settings_list[SETTINGS_ITEM["commaReplaceColumns"]].rstrip().split(',')
|
||||
|
||||
for line in csv.reader(work_data, quotechar=settings_list[SETTINGS_ITEM["quotechar"]], delimiter=settings_list[SETTINGS_ITEM["delimiter"]]):
|
||||
for line in csv.reader(work_data, quotechar=convert_quotechar(settings_list[SETTINGS_ITEM["quotechar"]]), delimiter=settings_list[SETTINGS_ITEM["delimiter"]]):
|
||||
try:
|
||||
if int(settings_list[SETTINGS_ITEM["headerFlag"]]) == True and index == 0:
|
||||
index += 1
|
||||
@ -112,9 +133,10 @@ def main(bucket_name, target_data_source, target_file_name, settings_key, db_inf
|
||||
process_count += 1
|
||||
|
||||
# SQL文生成
|
||||
query_parameter_list = []
|
||||
sql = f'INSERT INTO {settings_list[SETTINGS_ITEM["loadSchemaName"]]} ('
|
||||
for i in range(len(settings_db_columu_list)):
|
||||
sql = f'{sql} {settings_db_columu_list[i]},'
|
||||
for db_column in settings_db_columu_list:
|
||||
sql = f'{sql} {db_column},'
|
||||
sql = f'{sql} file_name,' # システム項目:取込ファイル名
|
||||
sql = f'{sql} file_row_cnt,' # システム項目:取込ファイル行番号
|
||||
sql = f'{sql} delete_flg,' # システム項目:論理削除フラグ
|
||||
@ -125,13 +147,18 @@ def main(bucket_name, target_data_source, target_file_name, settings_key, db_inf
|
||||
sql = f'{sql} VALUES ('
|
||||
for i in range(len(line)):
|
||||
# データ項目値が0桁より大きいかチェックする
|
||||
if len(line[i]) > 0:
|
||||
# 0桁より大きい場合
|
||||
replace_line = line[i].replace('\\', '\\\\')
|
||||
sql = f'{sql} "{replace_line}",'
|
||||
else:
|
||||
# 上記以外の場合
|
||||
if len(line[i]) == 0:
|
||||
# 0桁の場合
|
||||
sql = f'{sql} NULL,'
|
||||
continue
|
||||
|
||||
# データ項目値の変換処理(カンマ除去)
|
||||
org_column_value = line[i]
|
||||
current_settings_db_column_name = settings_db_columu_list[i]
|
||||
column_value = convert_column_value(org_column_value, current_settings_db_column_name, settings_replace_comma_list)
|
||||
# INSERT文のパラメータとそれに対応するプレースホルダーを設定する
|
||||
query_parameter_list.append(column_value)
|
||||
sql = f'{sql} %s,'
|
||||
sql = f'{sql} "{target_file_name}",' # システム項目:取込ファイル名
|
||||
sql = f'{sql} "{index + 1}",' # システム項目:取込ファイル行番号
|
||||
sql = f'{sql} "0",' # システム項目:論理削除フラグ
|
||||
@ -146,7 +173,7 @@ def main(bucket_name, target_data_source, target_file_name, settings_key, db_inf
|
||||
|
||||
# ロードスキーマのトランザクション開始
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql)
|
||||
cur.execute(sql, query_parameter_list)
|
||||
conn.commit()
|
||||
normal_count += 1
|
||||
except Exception as e:
|
||||
@ -161,6 +188,18 @@ def main(bucket_name, target_data_source, target_file_name, settings_key, db_inf
|
||||
|
||||
# ⑦ ロードスキーマのデータを蓄積スキーマにUPSERTする
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["i"]} I-MAIN-08 - ロードスキーマ({settings_list[SETTINGS_ITEM["loadSchemaName"]]})のデータを蓄積スキーマ({settings_list[SETTINGS_ITEM["storageSchemaName"]]})に登録します')
|
||||
|
||||
# インポート方法判断
|
||||
try:
|
||||
if truncate_judge(settings_list):
|
||||
with conn.cursor() as cur:
|
||||
sql_truncate = f'TRUNCATE table {settings_list[SETTINGS_ITEM["storageSchemaName"]]}'
|
||||
cur.execute(sql_truncate)
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["i"]} I-MAIN-20 - {settings_list[SETTINGS_ITEM["storageSchemaName"]]} をTRUNCATEしました')
|
||||
|
||||
except InvalidConfigException as e:
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["e"]} E-MAIN-01 - エラー内容:{e}')
|
||||
error(bucket_name, target_data_source, target_file_name, log_info)
|
||||
|
||||
# SQL文生成
|
||||
sql = f'INSERT INTO {settings_list[SETTINGS_ITEM["storageSchemaName"]]} ('
|
||||
@ -190,10 +229,10 @@ def main(bucket_name, target_data_source, target_file_name, settings_key, db_inf
|
||||
sql = f'{sql} file_name=t.file_name,' # システム項目:取込ファイル名
|
||||
sql = f'{sql} file_row_cnt=t.file_row_cnt,' # システム項目:取込ファイル行番号
|
||||
sql = f'{sql} delete_flg={settings_list[SETTINGS_ITEM["storageSchemaName"]]}.delete_flg,' # システム項目:論理削除フラグ
|
||||
sql = f'{sql} ins_user=t.ins_user,' # システム項目:登録者
|
||||
sql = f'{sql} ins_date=t.ins_date,' # システム項目:登録日時
|
||||
sql = f'{sql} upd_user={settings_list[SETTINGS_ITEM["storageSchemaName"]]}.upd_user,' # システム項目:更新者
|
||||
sql = f'{sql} upd_date={settings_list[SETTINGS_ITEM["storageSchemaName"]]}.upd_date' # システム項目:更新日時
|
||||
sql = f'{sql} ins_user={settings_list[SETTINGS_ITEM["storageSchemaName"]]}.ins_user,' # システム項目:登録者
|
||||
sql = f'{sql} ins_date={settings_list[SETTINGS_ITEM["storageSchemaName"]]}.ins_date,' # システム項目:登録日時
|
||||
sql = f'{sql} upd_user=t.ins_user,' # システム項目:更新者
|
||||
sql = f'{sql} upd_date=t.ins_date' # システム項目:更新日時
|
||||
|
||||
debug_log(sql, log_info, mode)
|
||||
|
||||
@ -272,3 +311,50 @@ def connection_close(conn, bucket_name, target_data_source, target_file_name, lo
|
||||
except Exception as e:
|
||||
print(f'{datetime.now():%Y-%m-%d %H:%M:%S} {log_info} {LOG_LEVEL["e"]} E-MAIN-99 - エラー内容:{e}')
|
||||
error(bucket_name, target_data_source, target_file_name, log_info)
|
||||
|
||||
def convert_column_value(org_column_value, current_settings_db_column_name, settings_replace_comma_list):
|
||||
"""データ項目値変換処理
|
||||
- 数値内のカンマ除去処理
|
||||
Args:
|
||||
org_column_value : 投入データの値
|
||||
current_settings_db_column_name : 投入データのDBカラム物理名
|
||||
settings_replace_comma_list : 投入データの数値型のDBカラム物理名のリスト
|
||||
|
||||
Returns:
|
||||
converted_column_value:変換処理を行った投入データの値
|
||||
"""
|
||||
# 投入データのDB物理カラム名が設定ファイルの数値型のDBカラム物理名に含まれている場合、データ項目値の「,」を取り除く
|
||||
converted_column_value = org_column_value
|
||||
if current_settings_db_column_name in settings_replace_comma_list:
|
||||
converted_column_value = converted_column_value.replace(',', '')
|
||||
|
||||
return converted_column_value
|
||||
|
||||
|
||||
def truncate_judge(settings_list):
|
||||
"""TRUNCATE処理対応判定
|
||||
Args:
|
||||
settings_list (list): 個別設定ファイル
|
||||
Raises:
|
||||
InvalidConfigException: 個別設定ファイルのインポート方法の設定ミス
|
||||
Returns:
|
||||
Bool: Truncate対象の場合True。Truncate対象でない場合False
|
||||
"""
|
||||
|
||||
# upsert判定
|
||||
if not settings_list[SETTINGS_ITEM["importManner"]]:
|
||||
return False
|
||||
|
||||
# インポート方法設定チェック
|
||||
if not settings_list[SETTINGS_ITEM["importManner"]].startswith(TRUNCATE_SRC_TABLE_SYMBOL):
|
||||
raise InvalidConfigException(INVALID_CONFIG_EXCEPTION_MESSAGE)
|
||||
import_manner_splitted_list = settings_list[SETTINGS_ITEM["importManner"]].split(':')
|
||||
if len(import_manner_splitted_list) != 2:
|
||||
raise InvalidConfigException(INVALID_CONFIG_EXCEPTION_MESSAGE)
|
||||
if import_manner_splitted_list[1] != settings_list[SETTINGS_ITEM["storageSchemaName"]]:
|
||||
raise InvalidConfigException(INVALID_CONFIG_EXCEPTION_MESSAGE)
|
||||
return True
|
||||
|
||||
|
||||
class InvalidConfigException(Exception):
|
||||
pass
|
||||
|
||||
19
lambda/check-view-security-option/Dockerfile
Normal file
19
lambda/check-view-security-option/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
FROM python:3.9
|
||||
|
||||
ENV WORKDIR /function/
|
||||
ENV TZ="Asia/Tokyo"
|
||||
WORKDIR ${WORKDIR}
|
||||
|
||||
COPY Pipfile Pipfile.lock ${WORKDIR}
|
||||
RUN \
|
||||
apt update -y && \
|
||||
# パッケージのセキュリティアップデートのみを適用するコマンド
|
||||
apt install -y unattended-upgrades && \
|
||||
unattended-upgrades && \
|
||||
pip install pipenv --no-cache-dir && \
|
||||
pipenv install --system --deploy && \
|
||||
pip uninstall -y pipenv virtualenv-clone virtualenv
|
||||
COPY check-view-option ./
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/python", "-m", "awslambdaric" ]
|
||||
CMD [ "main.handler" ]
|
||||
17
lambda/check-view-security-option/Pipfile
Normal file
17
lambda/check-view-security-option/Pipfile
Normal file
@ -0,0 +1,17 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
awslambdaric = "*"
|
||||
boto3 = "*"
|
||||
pymysql = "*"
|
||||
cryptography = "*"
|
||||
|
||||
[dev-packages]
|
||||
autopep8 = "*"
|
||||
flake8 = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.9"
|
||||
313
lambda/check-view-security-option/Pipfile.lock
generated
Normal file
313
lambda/check-view-security-option/Pipfile.lock
generated
Normal file
@ -0,0 +1,313 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "0bf055eba7a510de27e990db23f5203946ebbc02a6c678b89051dc0d1437444f"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.9"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"awslambdaric": {
|
||||
"hashes": [
|
||||
"sha256:059c7a66d4470169e01620d93f07424b80d302e3736cd11e68373f293a41e396",
|
||||
"sha256:0e90053614f0e5e5d6d6ae6d164412ce95b5d549c6fb0f6ff4290d77c5e9d3e5",
|
||||
"sha256:11a365164efec105aa670259dfe473d9609da8f6f2e468790b2dfc24969bfff1",
|
||||
"sha256:19da28e8c892b1c52a9db4d2b986af303932e3a4c4632eb0c5d5eb6a673c6022",
|
||||
"sha256:2eb2fdb1ae0f84669d37f193f247fa115a282a7777e051ced3a33620d6280646",
|
||||
"sha256:2efff2292fc8f8484eb094ffd77808a67815353be898a7f0b33ce51b841af691",
|
||||
"sha256:387b94cb0358662ae2b203f0aa2af25e80c6a2019a6b569f733ecd993a4f53d2",
|
||||
"sha256:38f8ae67ecb5b4e9f7fc42746ee39765dd7ddab359cb7e8ebfda1de0f0c0b059",
|
||||
"sha256:3fd0e1b3891987fa7ebb0c08d24c76af5fc17466f6efdfa9a59848dfb23930ec",
|
||||
"sha256:63a82d21d66146b3fde7eb6086abd058b75bdcab4a02b02afe0e8e4a45edfb5b",
|
||||
"sha256:676a741ad8f3aa27d651bcf3a2b83d5cee815f99c8b2b9abef3cb22ca7b29698",
|
||||
"sha256:9b0781bd41c20a2f2a0b018464a1daa376f663bd5eb7b0b6ba78f483681b1519",
|
||||
"sha256:bad98f2f94cecc90b89ac4e1d4feed96eb664e13c29b7ce232444cc9358e0d36",
|
||||
"sha256:d64dcba8da9dbea62644133a48c75376a37bfe0f84096ad73bf7fc5b2eb31fc7",
|
||||
"sha256:d8f280b25d8a7ae6b6ff92a9bbc6567b984264be8ef3e0fcb0402a1247f6c75d",
|
||||
"sha256:dad646f566aa7ec9b7179f16ca6741a2bea148abec6ed5947f86d00607e0a9a2",
|
||||
"sha256:dc7072f642fdd215387d4921bbd5ac91b96a4a705bce5e7853622d09fe59f57d",
|
||||
"sha256:fbbd24446ce2f876335b178f04aa4ec7ec480afc0f9621ebfdd5f55ad4b7c06e",
|
||||
"sha256:fe76893a1b42bcee4c91c6456092d2a42455818756e8f62d50e8c5adb22fa9e7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.4"
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:626bbec91ca2423e427636db207a03c854b52d22715c9b34a953ee8260817f6f",
|
||||
"sha256:7e0a5c86059866d7f9e27d6574da9bfb4f8a03c4caf055724145f3cd44785b81"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.24.27"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:524da451350c41e3136353183e7424c95952124163ea8ec03f57f29597bbcb4b",
|
||||
"sha256:583b85f8a799fb89d1a762db041163b5848b08e79cee06b609bcaaeb69ea1fa6"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.27.27"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5",
|
||||
"sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef",
|
||||
"sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104",
|
||||
"sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426",
|
||||
"sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405",
|
||||
"sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375",
|
||||
"sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a",
|
||||
"sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e",
|
||||
"sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc",
|
||||
"sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf",
|
||||
"sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185",
|
||||
"sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497",
|
||||
"sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3",
|
||||
"sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35",
|
||||
"sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c",
|
||||
"sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83",
|
||||
"sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21",
|
||||
"sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca",
|
||||
"sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984",
|
||||
"sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac",
|
||||
"sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd",
|
||||
"sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee",
|
||||
"sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a",
|
||||
"sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2",
|
||||
"sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192",
|
||||
"sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7",
|
||||
"sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585",
|
||||
"sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f",
|
||||
"sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e",
|
||||
"sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27",
|
||||
"sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b",
|
||||
"sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e",
|
||||
"sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e",
|
||||
"sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d",
|
||||
"sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c",
|
||||
"sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415",
|
||||
"sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82",
|
||||
"sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02",
|
||||
"sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314",
|
||||
"sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325",
|
||||
"sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c",
|
||||
"sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3",
|
||||
"sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914",
|
||||
"sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045",
|
||||
"sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d",
|
||||
"sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9",
|
||||
"sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5",
|
||||
"sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2",
|
||||
"sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c",
|
||||
"sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3",
|
||||
"sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2",
|
||||
"sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8",
|
||||
"sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d",
|
||||
"sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d",
|
||||
"sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9",
|
||||
"sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162",
|
||||
"sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76",
|
||||
"sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4",
|
||||
"sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e",
|
||||
"sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9",
|
||||
"sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6",
|
||||
"sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b",
|
||||
"sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01",
|
||||
"sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"
|
||||
],
|
||||
"version": "==1.15.1"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59",
|
||||
"sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596",
|
||||
"sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3",
|
||||
"sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5",
|
||||
"sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab",
|
||||
"sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884",
|
||||
"sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82",
|
||||
"sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b",
|
||||
"sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441",
|
||||
"sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa",
|
||||
"sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d",
|
||||
"sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b",
|
||||
"sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a",
|
||||
"sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6",
|
||||
"sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157",
|
||||
"sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280",
|
||||
"sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282",
|
||||
"sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67",
|
||||
"sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8",
|
||||
"sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046",
|
||||
"sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327",
|
||||
"sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==37.0.4"
|
||||
},
|
||||
"jmespath": {
|
||||
"hashes": [
|
||||
"sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980",
|
||||
"sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
|
||||
"sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
|
||||
],
|
||||
"version": "==2.21"
|
||||
},
|
||||
"pymysql": {
|
||||
"hashes": [
|
||||
"sha256:41fc3a0c5013d5f039639442321185532e3e2c8924687abe6537de157d403641",
|
||||
"sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
||||
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.8.2"
|
||||
},
|
||||
"s3transfer": {
|
||||
"hashes": [
|
||||
"sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd",
|
||||
"sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"simplejson": {
|
||||
"hashes": [
|
||||
"sha256:034550078a11664d77bc1a8364c90bb7eef0e44c2dbb1fd0a4d92e3997088667",
|
||||
"sha256:05b43d568300c1cd43f95ff4bfcff984bc658aa001be91efb3bb21df9d6288d3",
|
||||
"sha256:0dd9d9c738cb008bfc0862c9b8fa6743495c03a0ed543884bf92fb7d30f8d043",
|
||||
"sha256:10fc250c3edea4abc15d930d77274ddb8df4803453dde7ad50c2f5565a18a4bb",
|
||||
"sha256:2862beabfb9097a745a961426fe7daf66e1714151da8bb9a0c430dde3d59c7c0",
|
||||
"sha256:292c2e3f53be314cc59853bd20a35bf1f965f3bc121e007ab6fd526ed412a85d",
|
||||
"sha256:2d3eab2c3fe52007d703a26f71cf649a8c771fcdd949a3ae73041ba6797cfcf8",
|
||||
"sha256:2e7b57c2c146f8e4dadf84977a83f7ee50da17c8861fd7faf694d55e3274784f",
|
||||
"sha256:311f5dc2af07361725033b13cc3d0351de3da8bede3397d45650784c3f21fbcf",
|
||||
"sha256:344e2d920a7f27b4023c087ab539877a1e39ce8e3e90b867e0bfa97829824748",
|
||||
"sha256:3fabde09af43e0cbdee407555383063f8b45bfb52c361bc5da83fcffdb4fd278",
|
||||
"sha256:42b8b8dd0799f78e067e2aaae97e60d58a8f63582939af60abce4c48631a0aa4",
|
||||
"sha256:4b3442249d5e3893b90cb9f72c7d6ce4d2ea144d2c0d9f75b9ae1e5460f3121a",
|
||||
"sha256:55d65f9cc1b733d85ef95ab11f559cce55c7649a2160da2ac7a078534da676c8",
|
||||
"sha256:5c659a0efc80aaaba57fcd878855c8534ecb655a28ac8508885c50648e6e659d",
|
||||
"sha256:72d8a3ffca19a901002d6b068cf746be85747571c6a7ba12cbcf427bfb4ed971",
|
||||
"sha256:75ecc79f26d99222a084fbdd1ce5aad3ac3a8bd535cd9059528452da38b68841",
|
||||
"sha256:76ac9605bf2f6d9b56abf6f9da9047a8782574ad3531c82eae774947ae99cc3f",
|
||||
"sha256:7d276f69bfc8c7ba6c717ba8deaf28f9d3c8450ff0aa8713f5a3280e232be16b",
|
||||
"sha256:7f10f8ba9c1b1430addc7dd385fc322e221559d3ae49b812aebf57470ce8de45",
|
||||
"sha256:8042040af86a494a23c189b5aa0ea9433769cc029707833f261a79c98e3375f9",
|
||||
"sha256:813846738277729d7db71b82176204abc7fdae2f566e2d9fcf874f9b6472e3e6",
|
||||
"sha256:845a14f6deb124a3bcb98a62def067a67462a000e0508f256f9c18eff5847efc",
|
||||
"sha256:869a183c8e44bc03be1b2bbcc9ec4338e37fa8557fc506bf6115887c1d3bb956",
|
||||
"sha256:8acf76443cfb5c949b6e781c154278c059b09ac717d2757a830c869ba000cf8d",
|
||||
"sha256:8f713ea65958ef40049b6c45c40c206ab363db9591ff5a49d89b448933fa5746",
|
||||
"sha256:934115642c8ba9659b402c8bdbdedb48651fb94b576e3b3efd1ccb079609b04a",
|
||||
"sha256:9551f23e09300a9a528f7af20e35c9f79686d46d646152a0c8fc41d2d074d9b0",
|
||||
"sha256:9a2b7543559f8a1c9ed72724b549d8cc3515da7daf3e79813a15bdc4a769de25",
|
||||
"sha256:a55c76254d7cf8d4494bc508e7abb993a82a192d0db4552421e5139235604625",
|
||||
"sha256:ad8f41c2357b73bc9e8606d2fa226233bf4d55d85a8982ecdfd55823a6959995",
|
||||
"sha256:af4868da7dd53296cd7630687161d53a7ebe2e63814234631445697bd7c29f46",
|
||||
"sha256:afebfc3dd3520d37056f641969ce320b071bc7a0800639c71877b90d053e087f",
|
||||
"sha256:b59aa298137ca74a744c1e6e22cfc0bf9dca3a2f41f51bc92eb05695155d905a",
|
||||
"sha256:bc00d1210567a4cdd215ac6e17dc00cb9893ee521cee701adfd0fa43f7c73139",
|
||||
"sha256:c1cb29b1fced01f97e6d5631c3edc2dadb424d1f4421dad079cb13fc97acb42f",
|
||||
"sha256:c94dc64b1a389a416fc4218cd4799aa3756f25940cae33530a4f7f2f54f166da",
|
||||
"sha256:ceaa28a5bce8a46a130cd223e895080e258a88d51bf6e8de2fc54a6ef7e38c34",
|
||||
"sha256:cff6453e25204d3369c47b97dd34783ca820611bd334779d22192da23784194b",
|
||||
"sha256:d0b64409df09edb4c365d95004775c988259efe9be39697d7315c42b7a5e7e94",
|
||||
"sha256:d4813b30cb62d3b63ccc60dd12f2121780c7a3068db692daeb90f989877aaf04",
|
||||
"sha256:da3c55cdc66cfc3fffb607db49a42448785ea2732f055ac1549b69dcb392663b",
|
||||
"sha256:e058c7656c44fb494a11443191e381355388443d543f6fc1a245d5d238544396",
|
||||
"sha256:fed0f22bf1313ff79c7fc318f7199d6c2f96d4de3234b2f12a1eab350e597c06",
|
||||
"sha256:ffd4e4877a78c84d693e491b223385e0271278f5f4e1476a4962dca6824ecfeb"
|
||||
],
|
||||
"markers": "python_version >= '2.5' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.17.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:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec",
|
||||
"sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
|
||||
"version": "==1.26.10"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"autopep8": {
|
||||
"hashes": [
|
||||
"sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979",
|
||||
"sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d",
|
||||
"sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.0.1"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20",
|
||||
"sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.8.0"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c",
|
||||
"sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
import boto3
|
||||
import environments
|
||||
from constants import AWS_RESOURCE_S3, S3_RESPONSE_BODY, UTF8
|
||||
|
||||
|
||||
class S3Resource:
|
||||
|
||||
def __init__(self, bucket_name: str) -> None:
|
||||
self.__s3_resource = boto3.resource(AWS_RESOURCE_S3)
|
||||
self.__s3_bucket = self.__s3_resource.Bucket(bucket_name)
|
||||
|
||||
def get_object(self, object_key: str):
|
||||
s3_object = self.__s3_bucket.Object(object_key)
|
||||
response = s3_object.get()
|
||||
return response[S3_RESPONSE_BODY].read().decode(UTF8)
|
||||
|
||||
|
||||
class ConfigBucket:
|
||||
__s3_resource: S3Resource = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__s3_resource = S3Resource(environments.CONFIG_BUCKET_NAME)
|
||||
|
||||
@property
|
||||
def check_target_schema_names(self):
|
||||
return self.__s3_resource.get_object(environments.CHECK_TARGET_SCHEMA_NAMES_PATH)
|
||||
|
||||
@property
|
||||
def notice_mail_title_template(self):
|
||||
return self.__s3_resource.get_object(environments.NOTICE_MAIL_TITLE_TEMPLATE_PATH)
|
||||
|
||||
@property
|
||||
def notice_mail_body_template(self):
|
||||
return self.__s3_resource.get_object(environments.NOTICE_MAIL_BODY_TEMPLATE_PATH)
|
||||
@ -0,0 +1,31 @@
|
||||
import boto3
|
||||
import environments
|
||||
from constants import AWS_RESOURCE_SNS
|
||||
|
||||
|
||||
class SNSClient:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__sns_client = boto3.client(AWS_RESOURCE_SNS)
|
||||
|
||||
def publish(self, sns_topic_arn: str, subject: str, message: str) -> None:
|
||||
publish_params = {
|
||||
'TopicArn': sns_topic_arn,
|
||||
'Subject': subject.rstrip('\n'),
|
||||
'Message': message
|
||||
}
|
||||
self.__sns_client.publish(**publish_params)
|
||||
|
||||
|
||||
class SNSNotifier:
|
||||
__sns_client: SNSClient = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__sns_client = SNSClient()
|
||||
|
||||
def publish_to_mbj(self, subject: str, message: str):
|
||||
self.__sns_client.publish(environments.MBJ_NOTICE_TOPIC, subject, message)
|
||||
|
||||
def publish_to_nds(self, error_id: str, exception: Exception):
|
||||
error_message = f'{error_id} のエラーが発生しました。ご確認ください\n詳細:{exception}'
|
||||
self.__sns_client.publish(environments.NDS_NOTICE_TOPIC, environments.NDS_NOTICE_TITLE, error_message)
|
||||
@ -0,0 +1,34 @@
|
||||
import boto3
|
||||
import environments
|
||||
from constants import (AWS_RESOURCE_SSM, SSM_PARAMETER_RESPONSE,
|
||||
SSM_PARAMETER_VALUE)
|
||||
|
||||
|
||||
class SSMClient:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__ssm_client = boto3.client(AWS_RESOURCE_SSM)
|
||||
|
||||
def get_ssm_params(self, parameter_key: str, with_decryption: bool):
|
||||
response = self.__ssm_client.get_parameter(Name=parameter_key, WithDecryption=with_decryption)
|
||||
parameter_value = response[SSM_PARAMETER_RESPONSE][SSM_PARAMETER_VALUE]
|
||||
return parameter_value
|
||||
|
||||
|
||||
class SSMParameterStore:
|
||||
__ssm_client: SSMClient = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__ssm_client = SSMClient()
|
||||
|
||||
@property
|
||||
def db_host(self):
|
||||
return self.__ssm_client.get_ssm_params(environments.PARAM_NAME_DB_HOST, True)
|
||||
|
||||
@property
|
||||
def db_user_name(self):
|
||||
return self.__ssm_client.get_ssm_params(environments.PARAM_NAME_DB_USER_NAME, True)
|
||||
|
||||
@property
|
||||
def db_user_password(self):
|
||||
return self.__ssm_client.get_ssm_params(environments.PARAM_NAME_DB_USER_PASSWORD, True)
|
||||
@ -0,0 +1,47 @@
|
||||
# logger
|
||||
LOG_FORMAT = '[%(levelname)s]\t%(asctime)s\t%(message)s\n'
|
||||
LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||
LOG_LEVEL_INFO = 'INFO'
|
||||
|
||||
# environments
|
||||
CHECK_TARGET_SCHEMA_NAMES_PATH = 'CHECK_TARGET_SCHEMA_NAMES_PATH'
|
||||
CONFIG_BUCKET_NAME = 'CONFIG_BUCKET_NAME'
|
||||
LOG_LEVEL = 'LOG_LEVEL'
|
||||
MBJ_NOTICE_TOPIC = 'MBJ_NOTICE_TOPIC'
|
||||
NDS_NOTICE_TOPIC = 'NDS_NOTICE_TOPIC'
|
||||
NDS_NOTICE_TITLE = 'NDS_NOTICE_TITLE'
|
||||
NOTICE_MAIL_BODY_TEMPLATE_PATH = 'NOTICE_MAIL_BODY_TEMPLATE_PATH'
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH = 'NOTICE_MAIL_TITLE_TEMPLATE_PATH'
|
||||
PARAM_NAME_DB_HOST = 'PARAM_NAME_DB_HOST'
|
||||
PARAM_NAME_DB_USER_NAME = 'PARAM_NAME_DB_USER_NAME'
|
||||
PARAM_NAME_DB_USER_PASSWORD = 'PARAM_NAME_DB_USER_PASSWORD'
|
||||
TZ = 'TZ'
|
||||
|
||||
# aws
|
||||
AWS_RESOURCE_S3 = 's3'
|
||||
AWS_RESOURCE_SSM = 'ssm'
|
||||
AWS_RESOURCE_SNS = 'sns'
|
||||
S3_RESPONSE_BODY = 'Body'
|
||||
SSM_PARAMETER_RESPONSE = 'Parameter'
|
||||
SSM_PARAMETER_NAME = 'Name'
|
||||
SSM_PARAMETER_VALUE = 'Value'
|
||||
RESPONSE_ERROR = 'Error'
|
||||
RESPONSE_ERROR_CODE = 'Code'
|
||||
RESPONSE_CODE_NO_SUCH_KEY = 'NoSuchKey'
|
||||
RESPONSE_CODE_PARAMETER_NOT_FOUND = 'ParameterNotFound'
|
||||
|
||||
# sql
|
||||
DEFAULT_SCHEMA = 'INFORMATION_SCHEMA'
|
||||
INFORMATION_SCHEMA_SECURITY_TYPE_INVOKER = 'INVOKER'
|
||||
CONNECTION_TIMEOUT = 5
|
||||
|
||||
# system var
|
||||
UTF8 = 'utf-8'
|
||||
LAUNCH_ON_LOCAL = 'local'
|
||||
CHECK_TARGET_SCHEMAS = 'check_target_schemas'
|
||||
# メール本文に出力する不足ファイル名一覧のインデント
|
||||
MAIL_INDENT = '\n '
|
||||
# JSONファイル上のコメント業を表すシンボル
|
||||
JSON_COMMENT_SYMBOL = '#'
|
||||
# JSON内のコメントを置き換える正規表現
|
||||
REPLACE_COMMENT_REGEX = rf'\s(?!\"){JSON_COMMENT_SYMBOL}[\s\S]*?.*'
|
||||
@ -0,0 +1,40 @@
|
||||
import contextlib
|
||||
|
||||
import pymysql
|
||||
from pymysql.constants import CLIENT
|
||||
|
||||
from constants import CONNECTION_TIMEOUT, DEFAULT_SCHEMA
|
||||
|
||||
|
||||
class Database:
|
||||
|
||||
__connection: pymysql.Connection = None
|
||||
__host: str = None
|
||||
__user: str = None
|
||||
__password: str = None
|
||||
__database: str = None
|
||||
|
||||
def __init__(self, host: str, user: str, password: str) -> None:
|
||||
self.__host = host
|
||||
self.__user = user
|
||||
self.__password = password
|
||||
self.__database = DEFAULT_SCHEMA
|
||||
|
||||
def connect(self):
|
||||
connection = pymysql.connect(host=self.__host, user=self.__user, passwd=self.__password,
|
||||
database=self.__database, connect_timeout=CONNECTION_TIMEOUT,
|
||||
client_flag=CLIENT.MULTI_STATEMENTS)
|
||||
self.__connection = connection
|
||||
|
||||
@contextlib.contextmanager
|
||||
def query(self, query: str):
|
||||
if self.__connection is None:
|
||||
raise Exception('データベースに接続されていません')
|
||||
|
||||
with self.__connection.cursor() as cursor:
|
||||
cursor.execute(query)
|
||||
yield cursor
|
||||
|
||||
def close(self):
|
||||
self.__connection.close()
|
||||
self.__connection = None
|
||||
@ -0,0 +1,8 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class NoSecurityOptionView:
|
||||
schema_name: str
|
||||
table_name: str
|
||||
definer: str
|
||||
@ -0,0 +1,24 @@
|
||||
import os
|
||||
|
||||
from constants import (CHECK_TARGET_SCHEMA_NAMES_PATH, CONFIG_BUCKET_NAME,
|
||||
LOG_LEVEL, LOG_LEVEL_INFO, MBJ_NOTICE_TOPIC,
|
||||
NDS_NOTICE_TITLE, NDS_NOTICE_TOPIC,
|
||||
NOTICE_MAIL_BODY_TEMPLATE_PATH,
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH, PARAM_NAME_DB_HOST,
|
||||
PARAM_NAME_DB_USER_NAME, PARAM_NAME_DB_USER_PASSWORD,
|
||||
TZ)
|
||||
|
||||
LOG_LEVEL = os.environ.get(LOG_LEVEL, LOG_LEVEL_INFO)
|
||||
CHECK_TARGET_SCHEMA_NAMES_PATH = os.environ[CHECK_TARGET_SCHEMA_NAMES_PATH]
|
||||
CONFIG_BUCKET_NAME = os.environ[CONFIG_BUCKET_NAME]
|
||||
MBJ_NOTICE_TOPIC = os.environ[MBJ_NOTICE_TOPIC]
|
||||
NDS_NOTICE_TOPIC = os.environ[NDS_NOTICE_TOPIC]
|
||||
NDS_NOTICE_TITLE = os.environ[NDS_NOTICE_TITLE]
|
||||
NOTICE_MAIL_BODY_TEMPLATE_PATH = os.environ[NOTICE_MAIL_BODY_TEMPLATE_PATH]
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH = os.environ[NOTICE_MAIL_TITLE_TEMPLATE_PATH]
|
||||
|
||||
PARAM_NAME_DB_HOST = os.environ[PARAM_NAME_DB_HOST]
|
||||
PARAM_NAME_DB_USER_NAME = os.environ[PARAM_NAME_DB_USER_NAME]
|
||||
PARAM_NAME_DB_USER_PASSWORD = os.environ[PARAM_NAME_DB_USER_PASSWORD]
|
||||
|
||||
TZ = os.environ[TZ]
|
||||
@ -0,0 +1,39 @@
|
||||
from abc import ABCMeta
|
||||
|
||||
|
||||
class MeDaCaException(Exception, metaclass=ABCMeta):
|
||||
"""MeDaCaシステム固有のカスタムエラークラス"""
|
||||
|
||||
def __init__(self, error_id: str, message) -> None:
|
||||
super().__init__(message)
|
||||
self.error_id = error_id
|
||||
|
||||
|
||||
class FileNotFoundException(MeDaCaException):
|
||||
"""S3のファイルが見つからない場合の例外"""
|
||||
pass
|
||||
|
||||
|
||||
class ParameterNotFoundException(MeDaCaException):
|
||||
"""パラメータストアのキーが見つからない場合の例外"""
|
||||
pass
|
||||
|
||||
|
||||
class DatabaseConnectionException(MeDaCaException):
|
||||
"""データベース接続に失敗した場合の例外"""
|
||||
pass
|
||||
|
||||
|
||||
class QueryExecutionException(MeDaCaException):
|
||||
"""クエリ実行に失敗した場合の例外"""
|
||||
pass
|
||||
|
||||
|
||||
class SNSPublishException(MeDaCaException):
|
||||
"""AmazonSNSへの通知に失敗した場合の例外"""
|
||||
pass
|
||||
|
||||
|
||||
class JSONParseException(MeDaCaException):
|
||||
"""JSONのパースに失敗した場合の例外"""
|
||||
pass
|
||||
@ -0,0 +1,17 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from constants import REPLACE_COMMENT_REGEX
|
||||
|
||||
|
||||
class JSONParser:
|
||||
|
||||
__json_str: str = None
|
||||
|
||||
def __init__(self, json_str: str) -> None:
|
||||
self.__json_str = json_str
|
||||
|
||||
def parse(self):
|
||||
# コメントを除去して辞書に変換する
|
||||
without_comment = re.sub(REPLACE_COMMENT_REGEX, '', self.__json_str)
|
||||
return json.loads(without_comment)
|
||||
296
lambda/check-view-security-option/check-view-option/main.py
Normal file
296
lambda/check-view-security-option/check-view-option/main.py
Normal file
@ -0,0 +1,296 @@
|
||||
"""
|
||||
Viewセキュリティオプション付与チェック用Lambda関数のエントリーポイント
|
||||
"""
|
||||
|
||||
import botocore
|
||||
|
||||
from aws.s3 import ConfigBucket
|
||||
from aws.sns import SNSNotifier
|
||||
from aws.ssm import SSMParameterStore
|
||||
from constants import (CHECK_TARGET_SCHEMAS,
|
||||
INFORMATION_SCHEMA_SECURITY_TYPE_INVOKER, MAIL_INDENT,
|
||||
RESPONSE_CODE_NO_SUCH_KEY,
|
||||
RESPONSE_CODE_PARAMETER_NOT_FOUND, RESPONSE_ERROR,
|
||||
RESPONSE_ERROR_CODE)
|
||||
from database import Database
|
||||
from dto.no_security_option_view import NoSecurityOptionView
|
||||
from environments import (CONFIG_BUCKET_NAME, MBJ_NOTICE_TOPIC,
|
||||
NDS_NOTICE_TOPIC, NOTICE_MAIL_BODY_TEMPLATE_PATH,
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH)
|
||||
from exceptions import (DatabaseConnectionException, FileNotFoundException,
|
||||
JSONParseException, MeDaCaException,
|
||||
ParameterNotFoundException, QueryExecutionException,
|
||||
SNSPublishException)
|
||||
from json_perser import JSONParser
|
||||
from medaca_logger import MeDaCaLogger
|
||||
|
||||
logger = MeDaCaLogger.get_logger()
|
||||
|
||||
|
||||
def handler(event, context):
|
||||
try:
|
||||
# ① 処理開始ログを出力する
|
||||
logger.info('I-01-01', '処理開始 Viewセキュリティオプション付与チェック')
|
||||
# ② 設定ファイル[チェック対象スキーマ名ファイル]を読み込む
|
||||
logger.info('I-02-01', 'チェック対象スキーマ名ファイルを読み込み 開始')
|
||||
check_target_schemas = read_check_target_schemas()
|
||||
logger.info('I-02-02', f'チェック対象スキーマ名ファイルを読み込み 終了 チェック対象スキーマ名:{check_target_schemas}')
|
||||
# ③ データベースに接続する
|
||||
logger.info('I-03-01', 'データベースへの接続開始 開始')
|
||||
# DB接続のためのパラメータ取得
|
||||
db_host, db_user_name, db_user_password = read_db_param_from_parameter_store()
|
||||
connection = connection_database(db_host, db_user_name, db_user_password)
|
||||
logger.info('I-03-02', 'データベースへの接続開始 成功')
|
||||
# ④ Viewのオプションを確認するため、データを取得する
|
||||
logger.info('I-04-01', 'Viewセキュリティオプション チェック開始')
|
||||
check_result = fetch_view_security_options(connection, check_target_schemas)
|
||||
logger.debug('D-04-01', f'取得データ:{check_result}')
|
||||
if len(check_result) == 0:
|
||||
logger.info('I-04-02', 'Viewセキュリティオプション 未設定のViewはありません。処理を終了します。')
|
||||
return
|
||||
logger.info('I-05-01', 'Viewセキュリティオプション 未設定のViewがあるため、メール送信処理を開始します。')
|
||||
# ⑤ 取得できたデータをもとに、メール通知する文言を作成する
|
||||
no_security_option_views = [NoSecurityOptionView(*row) for row in check_result]
|
||||
logger.info(
|
||||
'I-05-02', f'通知メール(タイトル)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_TITLE_TEMPLATE_PATH}')
|
||||
mail_title = read_mail_title()
|
||||
logger.info(
|
||||
'I-05-03', '通知メール(タイトル)テンプレートファイルを読み込みました')
|
||||
logger.info(
|
||||
'I-05-04', f'通知メール(本文)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_BODY_TEMPLATE_PATH}')
|
||||
mail_body_template = read_mail_body_template()
|
||||
logger.info(
|
||||
'I-05-05', '通知メール(本文)テンプレートファイルを読み込みました')
|
||||
mail_body = make_notice_mail_body(no_security_option_views, mail_body_template)
|
||||
|
||||
logger.info('I-05-06', f'メール送信指示をします 送信先トピック:{MBJ_NOTICE_TOPIC}')
|
||||
notice_to_mbj(mail_title, mail_body)
|
||||
logger.info('I-05-07', 'メール送信指示をしました')
|
||||
|
||||
except MeDaCaException as e:
|
||||
logger.exception(e.error_id, e)
|
||||
logger.error('E-ERR-01', f'処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
|
||||
notice_to_nds(e.error_id, e)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.exception('E-99', f'想定外のエラーが発生しました エラー内容:{e}')
|
||||
logger.error('E-ERR-01', f'処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
|
||||
notice_to_nds('E-99', e)
|
||||
raise e
|
||||
finally:
|
||||
# ⑥ 処理終了ログを出力する
|
||||
logger.info('I-06-01', '処理終了 Viewセキュリティオプション付与チェック')
|
||||
|
||||
|
||||
def read_check_target_schemas() -> list:
|
||||
"""設定ファイル[チェック対象スキーマ名ファイル]を読み込む
|
||||
|
||||
Raises:
|
||||
FileNotFoundException: ファイルが読み込めなかったエラー
|
||||
JSONParseException: JSONを辞書オブジェクトに変換できなかったエラー
|
||||
Exception: 想定外のエラー
|
||||
|
||||
Returns:
|
||||
list: チェック対象のスキーマ名のリスト
|
||||
"""
|
||||
try:
|
||||
config_bucket = ConfigBucket()
|
||||
check_target_schema_names = config_bucket.check_target_schema_names
|
||||
except botocore.exceptions.ClientError as e:
|
||||
if e.response[RESPONSE_ERROR][RESPONSE_ERROR_CODE] == RESPONSE_CODE_NO_SUCH_KEY:
|
||||
raise FileNotFoundException('E-02-01', f'チェック対象スキーマ名ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
else:
|
||||
raise Exception(e)
|
||||
try:
|
||||
json_parser = JSONParser(check_target_schema_names)
|
||||
check_target_schemas = json_parser.parse()[CHECK_TARGET_SCHEMAS]
|
||||
except Exception as e:
|
||||
raise JSONParseException('E-02-01', f'チェック対象スキーマ名ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
|
||||
return check_target_schemas
|
||||
|
||||
|
||||
def read_db_param_from_parameter_store() -> tuple:
|
||||
"""パラメータストアからDB接続情報を取得する
|
||||
|
||||
Raises:
|
||||
ParameterNotFoundException: 指定されたパラメータが存在しないエラー
|
||||
Exception: 想定外のエラー
|
||||
|
||||
Returns:
|
||||
tuple: DB接続情報
|
||||
"""
|
||||
try:
|
||||
parameter_store = SSMParameterStore()
|
||||
db_host = parameter_store.db_host
|
||||
db_user_name = parameter_store.db_user_name
|
||||
db_user_password = parameter_store.db_user_password
|
||||
return db_host, db_user_name, db_user_password
|
||||
except botocore.exceptions.ClientError as e:
|
||||
if e.response[RESPONSE_ERROR][RESPONSE_ERROR_CODE] == RESPONSE_CODE_PARAMETER_NOT_FOUND:
|
||||
raise ParameterNotFoundException('E-03-01', f'パラメータストアの取得に失敗しました エラー内容:{e}')
|
||||
else:
|
||||
raise Exception(e)
|
||||
|
||||
|
||||
def connection_database(host: str, user_name: str, password: str) -> Database:
|
||||
"""データベース接続
|
||||
|
||||
Args:
|
||||
host (str): DBホスト
|
||||
user_name (str): DBユーザー名
|
||||
password (str): DBパスワード
|
||||
|
||||
Raises:
|
||||
DatabaseConnectionException: データベースへの接続に失敗したエラー
|
||||
|
||||
Returns:
|
||||
Database: データベース操作クラス
|
||||
"""
|
||||
try:
|
||||
database = Database(host, user_name, password)
|
||||
database.connect()
|
||||
return database
|
||||
except Exception as e:
|
||||
raise DatabaseConnectionException('E-03-02', f'データベースへの接続に失敗しました エラー内容:{e}')
|
||||
|
||||
|
||||
def fetch_view_security_options(connection: Database, check_target_schemas: list) -> tuple:
|
||||
"""SECURITY INVOKERのついていないViewの一覧を取得する
|
||||
|
||||
Args:
|
||||
connection (Database): 接続済みDB操作クラス
|
||||
check_target_schemas (str): チェック対象のスキーマ一覧
|
||||
|
||||
Raises:
|
||||
QueryExecutionException: クエリ実行エラー
|
||||
|
||||
Returns:
|
||||
tuple: クエリ実行結果
|
||||
"""
|
||||
|
||||
select_view_security_option_sql = f"""
|
||||
SELECT
|
||||
TABLE_SCHEMA,
|
||||
TABLE_NAME,
|
||||
DEFINER
|
||||
FROM
|
||||
INFORMATION_SCHEMA.VIEWS
|
||||
WHERE
|
||||
TABLE_SCHEMA IN (
|
||||
{','.join([f"'{schema_name}'" for schema_name in check_target_schemas])}
|
||||
)
|
||||
AND SECURITY_TYPE <> '{INFORMATION_SCHEMA_SECURITY_TYPE_INVOKER}'
|
||||
"""
|
||||
try:
|
||||
with connection.query(select_view_security_option_sql) as cursor:
|
||||
result = cursor.fetchall()
|
||||
connection.close()
|
||||
return result
|
||||
except Exception as e:
|
||||
raise QueryExecutionException('E-04-01', f'Viewセキュリティオプションチェックに失敗しました エラー内容:{e}')
|
||||
|
||||
|
||||
def make_notice_mail_body(no_security_option_views: list[NoSecurityOptionView], mail_body_template: str) -> tuple[str]:
|
||||
"""メール本文を生成します
|
||||
|
||||
Args:
|
||||
view_security_options (list[NoSecurityOptionView]): チェック対象のビュー一覧
|
||||
mail_body_template (str): メール本文のテンプレート
|
||||
|
||||
Returns:
|
||||
tuple[str]: メール本文
|
||||
"""
|
||||
# メール本文に埋め込むView名と作成ユーザー(DEFINER)をリストに格納
|
||||
schema_view_definers = []
|
||||
for option in no_security_option_views:
|
||||
view_fullname = f'{option.schema_name}.{option.table_name}'
|
||||
schema_view_definers.append(f'{view_fullname} View作成ユーザー:{option.definer}')
|
||||
|
||||
# インデントした上でメール本文に打ち出し
|
||||
mail_message = MAIL_INDENT.join(schema_view_definers)
|
||||
mail_body = mail_body_template.format(no_option_views=mail_message)
|
||||
return mail_body
|
||||
|
||||
|
||||
def read_mail_title() -> str:
|
||||
"""メールタイトルを読み込む
|
||||
|
||||
Raises:
|
||||
FileNotFoundException: ファイルが読み込めなかったエラー
|
||||
Exception: 想定外のエラー
|
||||
|
||||
Returns:
|
||||
str: メールタイトル
|
||||
"""
|
||||
|
||||
try:
|
||||
config_bucket = ConfigBucket()
|
||||
mail_title = config_bucket.notice_mail_title_template
|
||||
except botocore.exceptions.ClientError as e:
|
||||
if e.response[RESPONSE_ERROR][RESPONSE_ERROR_CODE] == RESPONSE_CODE_NO_SUCH_KEY:
|
||||
raise FileNotFoundException('E-05-01', f'通知メール(タイトル)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
else:
|
||||
raise Exception(e)
|
||||
|
||||
return mail_title
|
||||
|
||||
|
||||
def read_mail_body_template() -> str:
|
||||
"""メール本文を読み込む
|
||||
|
||||
Raises:
|
||||
FileNotFoundException: ファイルが読み込めなかったエラー
|
||||
Exception: 想定外のエラー
|
||||
|
||||
Returns:
|
||||
str: メール本文
|
||||
"""
|
||||
try:
|
||||
config_bucket = ConfigBucket()
|
||||
mail_body_template = config_bucket.notice_mail_body_template
|
||||
except botocore.exceptions.ClientError as e:
|
||||
if e.response[RESPONSE_ERROR][RESPONSE_ERROR_CODE] == RESPONSE_CODE_NO_SUCH_KEY:
|
||||
raise FileNotFoundException('E-05-02', f'通知メール(本文)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
else:
|
||||
raise Exception(e)
|
||||
|
||||
return mail_body_template
|
||||
|
||||
|
||||
def notice_to_mbj(mail_title: str, mail_body: str) -> None:
|
||||
"""MBJへ通知を行います
|
||||
|
||||
Args:
|
||||
mail_title (str): メールタイトル
|
||||
mail_body (str): メール本文
|
||||
|
||||
Raises:
|
||||
SNSPublishException: SNSでの通知失敗した場合のエラー
|
||||
"""
|
||||
try:
|
||||
notifier = SNSNotifier()
|
||||
notifier.publish_to_mbj(mail_title, mail_body)
|
||||
except Exception as e:
|
||||
raise SNSPublishException('E-98', f'通知の送信指示に失敗しました エラー内容:{e}')
|
||||
|
||||
|
||||
def notice_to_nds(error_id: str, error_message: str) -> None:
|
||||
"""NDSに処理以上通知を行う
|
||||
|
||||
Args:
|
||||
error_id (str): エラーID
|
||||
error_message (str): エラーメッセージ
|
||||
Raises:
|
||||
SNSPublishException: SNSでの通知失敗した場合のエラー
|
||||
"""
|
||||
try:
|
||||
notifier = SNSNotifier()
|
||||
notifier.publish_to_nds(error_id, error_message)
|
||||
except Exception as e:
|
||||
raise SNSPublishException('E-98', f'通知の送信指示に失敗しました エラー内容:{e}')
|
||||
|
||||
|
||||
# ローカル実行用
|
||||
if __name__ == '__main__':
|
||||
handler({}, {})
|
||||
@ -0,0 +1,60 @@
|
||||
import datetime
|
||||
import logging
|
||||
import sys
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from constants import LAUNCH_ON_LOCAL, LOG_DATE_FORMAT, LOG_FORMAT
|
||||
from environments import LOG_LEVEL, TZ
|
||||
|
||||
|
||||
class SingletonLogger:
|
||||
__logger: logging.Logger = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
# logger設定
|
||||
logger = logging.getLogger()
|
||||
formatter = logging.Formatter(
|
||||
LOG_FORMAT,
|
||||
LOG_DATE_FORMAT
|
||||
)
|
||||
formatter.converter = lambda *arg: datetime.datetime.now(ZoneInfo(TZ)).timetuple()
|
||||
# ローカル環境で動かす場合、標準出力ハンドラーを追加する
|
||||
# AWS Lambda上では`LambdaLoggerHandler`がデフォルトでセットされている
|
||||
if len(sys.argv) == 2 and sys.argv[1] == LAUNCH_ON_LOCAL:
|
||||
localHandler = logging.StreamHandler()
|
||||
logger.addHandler(localHandler)
|
||||
for handler in logger.handlers:
|
||||
handler.setFormatter(formatter)
|
||||
level = logging.getLevelName(LOG_LEVEL)
|
||||
logger.setLevel(level)
|
||||
|
||||
self.__logger = logger
|
||||
|
||||
def debug(self, log_id: str, msg: str):
|
||||
self._log(logging.DEBUG, log_id, msg)
|
||||
|
||||
def info(self, log_id: str, msg: str):
|
||||
self._log(logging.INFO, log_id, msg)
|
||||
|
||||
def warning(self, log_id: str, msg: str):
|
||||
self._log(logging.WARNING, log_id, msg)
|
||||
|
||||
def error(self, log_id: str, msg: str):
|
||||
self._log(logging.ERROR, log_id, msg)
|
||||
|
||||
def exception(self, log_id: str, msg: str):
|
||||
self._log(logging.ERROR, log_id, msg, exc_info=True)
|
||||
|
||||
def _log(self, log_level: int, log_id: str, msg: str, exc_info=False):
|
||||
self.__logger.log(log_level, f'{log_id} {msg}', exc_info=exc_info)
|
||||
|
||||
|
||||
class MeDaCaLogger:
|
||||
__unique_instance: logging.Logger = None
|
||||
|
||||
@staticmethod
|
||||
def get_logger() -> SingletonLogger:
|
||||
# インスタンス未生成の場合、唯一のインスタンスを生成する
|
||||
if not MeDaCaLogger.__unique_instance:
|
||||
MeDaCaLogger.__unique_instance = SingletonLogger()
|
||||
return MeDaCaLogger.__unique_instance
|
||||
@ -1,12 +1,11 @@
|
||||
import logging
|
||||
import os
|
||||
import boto3
|
||||
import gnupg
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
from abc import *
|
||||
from zoneinfo import ZoneInfo
|
||||
import traceback
|
||||
|
||||
import boto3
|
||||
import gnupg
|
||||
|
||||
# 環境変数
|
||||
SECRET_KEY_FILE_BUCKET_NAME = os.environ["SECRET_KEY_FILE_BUCKET_NAME"]
|
||||
@ -23,6 +22,7 @@ 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
|
||||
@ -42,8 +42,12 @@ 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'
|
||||
@ -78,7 +82,7 @@ def handler(event, context):
|
||||
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.error(f'E-03-01 PGP暗号化ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
logger.exception(f'E-03-01 PGP暗号化ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise EncryptFileReadException('E-03-01', EXTENSION_ERROR, e)
|
||||
|
||||
# ④ S3から秘密鍵ファイルを読み込む
|
||||
@ -87,7 +91,7 @@ def handler(event, context):
|
||||
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.error(f'E-04-01 秘密鍵ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
logger.exception(f'E-04-01 秘密鍵ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-04-01', EXTENSION_ERROR, e)
|
||||
|
||||
# ⑤ 「③」で読み込んだ秘密鍵ファイルをPGPライブラリにインポートを行う
|
||||
@ -98,7 +102,7 @@ def handler(event, context):
|
||||
gpg.import_keys(key_file.read())
|
||||
logger.info('I-05-02 秘密鍵ファイルをインポートしました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-01 秘密鍵ファイルのインポートに失敗しました エラー内容:{e}')
|
||||
logger.exception(f'E-05-01 秘密鍵ファイルのインポートに失敗しました エラー内容:{e}')
|
||||
raise PrivateKeyImportException('E-05-01', EXTENSION_ERROR, e)
|
||||
|
||||
# ⑥ 「④」で読み込んだPGP暗号化ファイルを「⑤」でインポートした秘密鍵ファイルで復号する
|
||||
@ -109,7 +113,7 @@ def handler(event, context):
|
||||
decrypt_file = open(PATH_TEMP_DECRYPT_FILE, 'rb')
|
||||
logger.info('I-06-02 PGP暗号化ファイルを復号しました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-06-01 PGP暗号化ファイルの復号化に失敗しました エラー内容:{e}')
|
||||
logger.exception(f'E-06-01 PGP暗号化ファイルの復号化に失敗しました エラー内容:{e}')
|
||||
raise DecryptException('E-06-01', EXTENSION_DECRYPT_ERROR, e)
|
||||
|
||||
# ⑦ 各ファイルをS3に出力する
|
||||
@ -126,7 +130,7 @@ def handler(event, context):
|
||||
decrypt_file.close
|
||||
logger.info('I-07-03 復号化ファイルをS3に出力しました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-07-01 復号化ファイルのS3出力に失敗しました エラー内容:{e}')
|
||||
logger.exception(f'E-07-01 復号化ファイルのS3出力に失敗しました エラー内容:{e}')
|
||||
raise FileOutputException('E-07-01', EXTENSION_ERROR, e)
|
||||
|
||||
# 「④」で読み込んだPGP暗号化ファイルを以下に移動(コピー削除)する
|
||||
@ -136,29 +140,41 @@ def handler(event, context):
|
||||
'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}')
|
||||
logger.info(
|
||||
f'I-07-04 PGP暗号化ファイル移動 移動元:{s3_event.bucket_name}/{s3_event.file_path} 移動先:{SAP_DATA_BACKUP_BUCKET_NAME}/{backup_file_key}')
|
||||
backup_file_obj = s3_resource.Object(SAP_DATA_BACKUP_BUCKET_NAME, backup_file_key)
|
||||
backup_file_obj.copy(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.error(f'E-07-02 PGP暗号化ファイルのS3出力に失敗しました エラー内容:{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}')
|
||||
import_file_obj = s3_resource.Object(s3_event.bucket_name, import_file_key)
|
||||
import_file_obj.copy(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:
|
||||
traceback.print_exc()
|
||||
create_status_file(s3_event, e.extension)
|
||||
error_notice(e.id, e.arg)
|
||||
except CustomException as e:
|
||||
traceback.print_exc()
|
||||
create_status_file(s3_event, e.extension)
|
||||
move_encrypt_file(s3_event)
|
||||
error_notice(e.id, e.arg)
|
||||
except Exception as e:
|
||||
logger.error(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
logger.exception(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
|
||||
create_status_file(s3_event, EXTENSION_ERROR)
|
||||
move_encrypt_file(s3_event)
|
||||
error_notice('E-99', e)
|
||||
@ -172,10 +188,10 @@ def create_status_file(s3_event, extension) -> None:
|
||||
result_error_key = s3_event.data_source_name + DIRECTORY_RECV + result_error_file_name
|
||||
result_error_obj = s3_resource.Object(s3_event.bucket_name, result_error_key)
|
||||
result_error_obj.put(Body='')
|
||||
logger.error(f'E-ERR-01 recvディレクトリにエラーファイルを作成しました ファイル名:{result_error_file_name} 出力先:{s3_event.bucket_name}/{result_error_key}')
|
||||
logger.error(
|
||||
f'E-ERR-01 recvディレクトリにエラーファイルを作成しました ファイル名:{result_error_file_name} 出力先:{s3_event.bucket_name}/{result_error_key}')
|
||||
except Exception as e:
|
||||
logger.error(f'E-96 エラーステータスファイルの作成に失敗しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
logger.exception(f'E-96 エラーステータスファイルの作成に失敗しました エラー内容:{e}')
|
||||
return
|
||||
|
||||
|
||||
@ -191,10 +207,10 @@ def move_encrypt_file(s3_event) -> None:
|
||||
error_obj = s3_resource.Object(s3_event.bucket_name, error_key)
|
||||
error_obj.copy(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}')
|
||||
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.error(f'E-97 PGP暗号化ファイルの移動に失敗しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
logger.exception(f'E-97 PGP暗号化ファイルの移動に失敗しました エラー内容:{e}')
|
||||
return
|
||||
|
||||
|
||||
@ -210,8 +226,7 @@ def error_notice(error_log_id, exception) -> None:
|
||||
sns_client.publish(**params)
|
||||
logger.error(f'E-ERR-03 処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
|
||||
except Exception as e:
|
||||
logger.error(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
logger.exception(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
|
||||
return
|
||||
|
||||
|
||||
|
||||
@ -1,186 +1,187 @@
|
||||
import os
|
||||
import datetime
|
||||
import boto3
|
||||
import io
|
||||
import re
|
||||
import csv
|
||||
import logging
|
||||
from abc import *
|
||||
from zoneinfo import ZoneInfo
|
||||
import traceback
|
||||
|
||||
# 環境変数
|
||||
CONFIG_BUCKET_NAME = os.environ["CONFIG_BUCKET_NAME"]
|
||||
RECEIVE_MONTHLY_FILE_NAME_LIST_PATH = os.environ["RECEIVE_MONTHLY_FILE_NAME_LIST_PATH"]
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH = os.environ["NOTICE_MAIL_TITLE_TEMPLATE_PATH"]
|
||||
NOTICE_MAIL_BODY_TEMPLATE_PATH = os.environ["NOTICE_MAIL_BODY_TEMPLATE_PATH"]
|
||||
MBJ_SAP_NOTICE_TOPIC = os.environ["MBJ_SAP_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TOPIC = os.environ["NDS_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TITLE = os.environ["NDS_NOTICE_TITLE"]
|
||||
LOG_LEVEL = os.environ["LOG_LEVEL"]
|
||||
|
||||
# 定数
|
||||
INDEX_REGEX = 0
|
||||
INDEX_DATA_NAME = 1
|
||||
|
||||
# メール本文に出力する不足ファイル名一覧のインデント
|
||||
MAIL_INDENT = ' '
|
||||
|
||||
# 変数
|
||||
s3_client = boto3.client('s3')
|
||||
s3_resource = boto3.resource('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 lambda_handler(event, context):
|
||||
try:
|
||||
# ① 処理開始ログを出力する
|
||||
logger.info('I-01-01 処理開始 SAP_finデータ月次I/F受領通知処理')
|
||||
execute_date = datetime.date.today().strftime('%Y/%m/%d')
|
||||
logger.info(f'I-01-02 処理稼働日:{execute_date}')
|
||||
mail_msg = ''
|
||||
|
||||
# ② 処理開始時に受け取ったイベント情報の以下内容をログに出力しメモリに保持する
|
||||
logger.info('I-02-01 イベント情報出力処理')
|
||||
s3_event = event["Records"][0]["s3"]
|
||||
event_bucket_name = s3_event["bucket"]["name"]
|
||||
event_file_path = s3_event["object"]["key"]
|
||||
event_file_name = os.path.basename(event_file_path)
|
||||
logger.info(f'I-02-02 バケット名:{event_bucket_name}')
|
||||
logger.info(f'I-02-03 ファイルパス:{event_file_path}')
|
||||
|
||||
# ③ 設定ファイル[SAP_finI/Fファイルネーム設定ファイル(月次)]を読み込む
|
||||
try:
|
||||
logger.info(f'I-03-01 月次I/Fファイルネーム設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{RECEIVE_MONTHLY_FILE_NAME_LIST_PATH}')
|
||||
receive_monthly_file_name_obj = s3_resource.Object(CONFIG_BUCKET_NAME, RECEIVE_MONTHLY_FILE_NAME_LIST_PATH)
|
||||
receive_monthly_file_name_response = receive_monthly_file_name_obj.get()
|
||||
logger.info('I-03-02 月次I/Fファイルネーム設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-03-01 月次I/Fファイルネーム設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-03-01', e)
|
||||
|
||||
# ④ 月次I/Fファイル受領通知処理を行う
|
||||
logger.info('I-04-01 月次I/Fファイル受領通知処理開始')
|
||||
logger.info(f'I-04-02 受領したファイル名:{event_file_name}')
|
||||
logger.info('I-04-03 受領したファイル名と月次I/Fファイルネーム設定ファイルの突き合わせを開始します')
|
||||
file_exists = False
|
||||
receive_monthly_file_name_body = io.TextIOWrapper(io.BytesIO(receive_monthly_file_name_response["Body"].read()), encoding='utf-8')
|
||||
for row in csv.reader(receive_monthly_file_name_body, delimiter='\t'):
|
||||
match_result = re.fullmatch(row[INDEX_REGEX], event_file_name)
|
||||
if match_result is not None:
|
||||
file_exists = True
|
||||
break
|
||||
if file_exists == True:
|
||||
logger.info(f'I-04-04 月次I/Fを受領しました ファイル名:{row[INDEX_DATA_NAME]} {event_file_name}')
|
||||
mail_msg += f'{MAIL_INDENT}{row[INDEX_DATA_NAME]} {event_file_name}\n'
|
||||
else:
|
||||
logger.info('I-04-05 受領したファイルは月次I/Fではありませんでした')
|
||||
|
||||
# ⑤ 「①」でメモリ保持しているメール挿入用文言に出力内容が存在するか確認する
|
||||
logger.info('I-05-01 メール送信処理開始')
|
||||
if len(mail_msg) > 0:
|
||||
# 1.存在した場合
|
||||
logger.info('I-05-02 月次I/Fファイルを受領したため、メール送信処理を開始します')
|
||||
try:
|
||||
logger.info(f'I-05-03 通知メール(タイトル)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_TITLE_TEMPLATE_PATH}')
|
||||
mail_title_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_TITLE_TEMPLATE_PATH)
|
||||
mail_title = mail_title_obj['Body'].read().decode('utf-8')
|
||||
logger.info('I-05-04 通知メール(タイトル)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-01 通知メール(タイトル)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-01', e)
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-05 通知メール(本文)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_BODY_TEMPLATE_PATH}')
|
||||
mail_body_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_BODY_TEMPLATE_PATH)
|
||||
mail_body_response = mail_body_obj['Body'].read().decode('utf-8')
|
||||
# メール本文内のプレースホルダーを置き換える
|
||||
mail_body = substitute_mail_body(mail_body_response, mail_msg)
|
||||
logger.info('I-05-06 通知メール(本文)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-02 通知メール(本文)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-02', e)
|
||||
|
||||
logger.info(f'I-05-07 メール送信指示をします 送信先トピック:{MBJ_SAP_NOTICE_TOPIC}')
|
||||
params = {
|
||||
'TopicArn': MBJ_SAP_NOTICE_TOPIC,
|
||||
'Subject': mail_title.rstrip('\n'),
|
||||
'Message': mail_body
|
||||
}
|
||||
sns_client.publish(**params)
|
||||
logger.info('I-05-08 メール送信指示をしました')
|
||||
else:
|
||||
# 2.存在しない場合
|
||||
logger.info('I-05-09 受領したファイルは月次I/Fファイルではないため、メール送信処理をスキップします')
|
||||
|
||||
# ⑥ 処理終了ログを出力する
|
||||
logger.info('I-06-01 処理終了 SAP_finデータ月次I/F受領通知処理')
|
||||
except CustomException as e:
|
||||
traceback.print_exc()
|
||||
error_notice(e.id, e.arg)
|
||||
except Exception as e:
|
||||
logger.error(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
error_notice('E-99', 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-01 処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
|
||||
except Exception as e:
|
||||
logger.error(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
|
||||
def substitute_mail_body(before_mail_body:str, mail_msg: str) -> str:
|
||||
"""メール本文のプレースホルダーを置き換えます
|
||||
|
||||
Args:
|
||||
before_mail_body (str): 置き換え前のメール本文
|
||||
mail_msg (str): メール本文のプレースホルダーを置き換える文言
|
||||
|
||||
Returns:
|
||||
str: 置き換え後のメール本文
|
||||
"""
|
||||
substitute_dict = {
|
||||
"notice_file_names": mail_msg
|
||||
}
|
||||
mail_body = before_mail_body.format_map(substitute_dict)
|
||||
return mail_body
|
||||
|
||||
# カスタムExceptionクラス
|
||||
class CustomException(Exception, metaclass=ABCMeta):
|
||||
def __init__(self, id, arg):
|
||||
self.arg = arg
|
||||
self.id = id
|
||||
|
||||
|
||||
class FileReadException(CustomException):
|
||||
pass
|
||||
import os
|
||||
import datetime
|
||||
import boto3
|
||||
import io
|
||||
import re
|
||||
import csv
|
||||
import logging
|
||||
from abc import *
|
||||
from zoneinfo import ZoneInfo
|
||||
import traceback
|
||||
|
||||
# 環境変数
|
||||
CONFIG_BUCKET_NAME = os.environ["CONFIG_BUCKET_NAME"]
|
||||
RECEIVE_MONTHLY_FILE_NAME_LIST_PATH = os.environ["RECEIVE_MONTHLY_FILE_NAME_LIST_PATH"]
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH = os.environ["NOTICE_MAIL_TITLE_TEMPLATE_PATH"]
|
||||
NOTICE_MAIL_BODY_TEMPLATE_PATH = os.environ["NOTICE_MAIL_BODY_TEMPLATE_PATH"]
|
||||
MBJ_SAP_NOTICE_TOPIC = os.environ["MBJ_SAP_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TOPIC = os.environ["NDS_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TITLE = os.environ["NDS_NOTICE_TITLE"]
|
||||
LOG_LEVEL = os.environ["LOG_LEVEL"]
|
||||
|
||||
# 定数
|
||||
INDEX_REGEX = 0
|
||||
INDEX_DATA_NAME = 1
|
||||
|
||||
# メール本文に出力する不足ファイル名一覧のインデント
|
||||
MAIL_INDENT = ' '
|
||||
|
||||
# 変数
|
||||
s3_client = boto3.client('s3')
|
||||
s3_resource = boto3.resource('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 lambda_handler(event, context):
|
||||
try:
|
||||
# ① 処理開始ログを出力する
|
||||
logger.info('I-01-01 処理開始 SAP_finデータ月次I/F受領通知処理')
|
||||
execute_date = datetime.date.today().strftime('%Y/%m/%d')
|
||||
logger.info(f'I-01-02 処理稼働日:{execute_date}')
|
||||
mail_msg = ''
|
||||
|
||||
# ② 処理開始時に受け取ったイベント情報の以下内容をログに出力しメモリに保持する
|
||||
logger.info('I-02-01 イベント情報出力処理')
|
||||
s3_event = event["Records"][0]["s3"]
|
||||
event_bucket_name = s3_event["bucket"]["name"]
|
||||
event_file_path = s3_event["object"]["key"]
|
||||
event_file_name = os.path.basename(event_file_path)
|
||||
logger.info(f'I-02-02 バケット名:{event_bucket_name}')
|
||||
logger.info(f'I-02-03 ファイルパス:{event_file_path}')
|
||||
|
||||
# ③ 設定ファイル[SAP_finI/Fファイルネーム設定ファイル(月次)]を読み込む
|
||||
try:
|
||||
logger.info(f'I-03-01 月次I/Fファイルネーム設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{RECEIVE_MONTHLY_FILE_NAME_LIST_PATH}')
|
||||
receive_monthly_file_name_obj = s3_resource.Object(CONFIG_BUCKET_NAME, RECEIVE_MONTHLY_FILE_NAME_LIST_PATH)
|
||||
receive_monthly_file_name_response = receive_monthly_file_name_obj.get()
|
||||
logger.info('I-03-02 月次I/Fファイルネーム設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-03-01 月次I/Fファイルネーム設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-03-01', e)
|
||||
|
||||
# ④ 月次I/Fファイル受領通知処理を行う
|
||||
logger.info('I-04-01 月次I/Fファイル受領通知処理開始')
|
||||
logger.info(f'I-04-02 受領したファイル名:{event_file_name}')
|
||||
logger.info('I-04-03 受領したファイル名と月次I/Fファイルネーム設定ファイルの突き合わせを開始します')
|
||||
file_exists = False
|
||||
receive_monthly_file_name_body = io.TextIOWrapper(io.BytesIO(receive_monthly_file_name_response["Body"].read()), encoding='utf-8')
|
||||
for row in csv.reader(receive_monthly_file_name_body, delimiter='\t'):
|
||||
match_result = re.fullmatch(row[INDEX_REGEX], event_file_name)
|
||||
if match_result is not None:
|
||||
file_exists = True
|
||||
break
|
||||
if file_exists == True:
|
||||
logger.info(f'I-04-04 月次I/Fを受領しました ファイル名:{row[INDEX_DATA_NAME]} {event_file_name}')
|
||||
mail_msg += f'{MAIL_INDENT}{row[INDEX_DATA_NAME]} {event_file_name}\n'
|
||||
else:
|
||||
logger.info('I-04-05 受領したファイルは月次I/Fではありませんでした')
|
||||
|
||||
# ⑤ 「①」でメモリ保持しているメール挿入用文言に出力内容が存在するか確認する
|
||||
logger.info('I-05-01 メール送信処理開始')
|
||||
if len(mail_msg) > 0:
|
||||
# 1.存在した場合
|
||||
logger.info('I-05-02 月次I/Fファイルを受領したため、メール送信処理を開始します')
|
||||
try:
|
||||
logger.info(f'I-05-03 通知メール(タイトル)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_TITLE_TEMPLATE_PATH}')
|
||||
mail_title_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_TITLE_TEMPLATE_PATH)
|
||||
mail_title = mail_title_obj['Body'].read().decode('utf-8')
|
||||
logger.info('I-05-04 通知メール(タイトル)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-01 通知メール(タイトル)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-01', e)
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-05 通知メール(本文)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_BODY_TEMPLATE_PATH}')
|
||||
mail_body_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_BODY_TEMPLATE_PATH)
|
||||
mail_body_response = mail_body_obj['Body'].read().decode('utf-8')
|
||||
# メール本文内のプレースホルダーを置き換える
|
||||
mail_body = substitute_mail_body(mail_body_response, mail_msg)
|
||||
logger.info('I-05-06 通知メール(本文)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-02 通知メール(本文)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-02', e)
|
||||
|
||||
logger.info(f'I-05-07 メール送信指示をします 送信先トピック:{MBJ_SAP_NOTICE_TOPIC}')
|
||||
mail_title_without_line_break = mail_title.splitlines()[0]
|
||||
params = {
|
||||
'TopicArn': MBJ_SAP_NOTICE_TOPIC,
|
||||
'Subject': mail_title_without_line_break,
|
||||
'Message': mail_body
|
||||
}
|
||||
sns_client.publish(**params)
|
||||
logger.info('I-05-08 メール送信指示をしました')
|
||||
else:
|
||||
# 2.存在しない場合
|
||||
logger.info('I-05-09 受領したファイルは月次I/Fファイルではないため、メール送信処理をスキップします')
|
||||
|
||||
# ⑥ 処理終了ログを出力する
|
||||
logger.info('I-06-01 処理終了 SAP_finデータ月次I/F受領通知処理')
|
||||
except CustomException as e:
|
||||
traceback.print_exc()
|
||||
error_notice(e.id, e.arg)
|
||||
except Exception as e:
|
||||
logger.error(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
error_notice('E-99', 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-01 処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
|
||||
except Exception as e:
|
||||
logger.error(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
|
||||
def substitute_mail_body(before_mail_body:str, mail_msg: str) -> str:
|
||||
"""メール本文のプレースホルダーを置き換えます
|
||||
|
||||
Args:
|
||||
before_mail_body (str): 置き換え前のメール本文
|
||||
mail_msg (str): メール本文のプレースホルダーを置き換える文言
|
||||
|
||||
Returns:
|
||||
str: 置き換え後のメール本文
|
||||
"""
|
||||
substitute_dict = {
|
||||
"notice_file_names": mail_msg
|
||||
}
|
||||
mail_body = before_mail_body.format_map(substitute_dict)
|
||||
return mail_body
|
||||
|
||||
# カスタムExceptionクラス
|
||||
class CustomException(Exception, metaclass=ABCMeta):
|
||||
def __init__(self, id, arg):
|
||||
self.arg = arg
|
||||
self.id = id
|
||||
|
||||
|
||||
class FileReadException(CustomException):
|
||||
pass
|
||||
|
||||
@ -1,242 +1,243 @@
|
||||
import os
|
||||
import datetime
|
||||
import boto3
|
||||
import io
|
||||
import re
|
||||
import csv
|
||||
import logging
|
||||
from abc import *
|
||||
from zoneinfo import ZoneInfo
|
||||
import traceback
|
||||
|
||||
# 環境変数
|
||||
CHECK_BUCKET_NAME = os.environ["CHECK_BUCKET_NAME"]
|
||||
CONFIG_BUCKET_NAME = os.environ["CONFIG_BUCKET_NAME"]
|
||||
RECEIVE_DAILY_FILE_NAME_LIST_PATH = os.environ["RECEIVE_DAILY_FILE_NAME_LIST_PATH"]
|
||||
NON_BUSINESS_DAY_LIST_PATH = os.environ["NON_BUSINESS_DAY_LIST_PATH"]
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH = os.environ["NOTICE_MAIL_TITLE_TEMPLATE_PATH"]
|
||||
NOTICE_MAIL_BODY_TEMPLATE_PATH = os.environ["NOTICE_MAIL_BODY_TEMPLATE_PATH"]
|
||||
MBJ_SAP_NOTICE_TOPIC = os.environ["MBJ_SAP_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TOPIC = os.environ["NDS_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TITLE = os.environ["NDS_NOTICE_TITLE"]
|
||||
LOG_LEVEL = os.environ["LOG_LEVEL"]
|
||||
|
||||
# 定数
|
||||
ROW_COMMENT_SYMBOL = '#'
|
||||
INDEX_REGEX = 0
|
||||
INDEX_DATA_NAME = 1
|
||||
INDEX_ROW_COMMENT_SYMBOL = 0
|
||||
INDEX_SPLIT_NUM = 1
|
||||
INDEX_LAST = -1
|
||||
|
||||
# メール本文に出力する不足ファイル名一覧のインデント
|
||||
MAIL_INDENT = ' '
|
||||
|
||||
# 変数
|
||||
s3_client = boto3.client('s3')
|
||||
s3_resource = boto3.resource('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 lambda_handler(event, context):
|
||||
try:
|
||||
# ① 処理開始ログを出力する
|
||||
logger.info('I-01-01 処理開始 SAP_finデータ受領チェック処理(日次)')
|
||||
execute_date = datetime.date.today().strftime('%Y/%m/%d')
|
||||
logger.info(f'I-01-02 処理稼働日:{execute_date}')
|
||||
mail_msg = ''
|
||||
|
||||
# ② 営業日チェック処理を行う
|
||||
logger.info('I-02-01 営業日チェック処理開始')
|
||||
|
||||
# 1.設定ファイル[メルク社非営業日設定ファイル]を読み込む
|
||||
try:
|
||||
logger.info(f'I-02-02 非営業日設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NON_BUSINESS_DAY_LIST_PATH}')
|
||||
non_business_day_obj = s3_resource.Object(CONFIG_BUCKET_NAME, NON_BUSINESS_DAY_LIST_PATH)
|
||||
non_business_day_response = non_business_day_obj.get()
|
||||
logger.info('I-02-03 非営業日設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-02-01 非営業日設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-02-01', e)
|
||||
|
||||
# 2.処理稼働日が「②1.」で読み込んだ「メルク社非営業日設定ファイル」に存在するか確認する
|
||||
try:
|
||||
logger.info(f'I-02-04 本日が非営業日かチェックします チェック日:{execute_date}')
|
||||
none_business_day_list = []
|
||||
for row in io.TextIOWrapper(io.BytesIO(non_business_day_response["Body"].read()), encoding='utf-8'):
|
||||
if row[INDEX_ROW_COMMENT_SYMBOL] == ROW_COMMENT_SYMBOL:
|
||||
continue
|
||||
non_date = row.rstrip('\n')
|
||||
# 日付妥当性判定
|
||||
try:
|
||||
datetime.datetime.strptime(non_date, "%Y/%m/%d")
|
||||
except Exception as e:
|
||||
raise e
|
||||
none_business_day_list.append(non_date)
|
||||
if execute_date in none_business_day_list:
|
||||
logger.info('I-02-05 本日は非営業日のため、チェック処理をスキップします')
|
||||
return
|
||||
else:
|
||||
logger.info('I-02-06 本日は営業日のため、チェック処理を実施します')
|
||||
except Exception as e:
|
||||
logger.error(f'E-02-02 メルク社非営業日設定ファイルに不備があります エラー内容:{e}')
|
||||
raise NoneBusinessDayException('E-02-02', e)
|
||||
|
||||
# ③ 設定ファイル[SAP_finI/Fファイルネーム設定ファイル(日次)]を読み込む
|
||||
try:
|
||||
logger.info(f'I-03-01 日次I/Fファイルネーム設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{RECEIVE_DAILY_FILE_NAME_LIST_PATH}')
|
||||
receive_daily_file_name_obj = s3_resource.Object(CONFIG_BUCKET_NAME, RECEIVE_DAILY_FILE_NAME_LIST_PATH)
|
||||
receive_daily_file_name_response = receive_daily_file_name_obj.get()
|
||||
logger.info('I-03-02 日次I/Fファイルネーム設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-03-01 日次I/Fファイルネーム設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-03-01', e)
|
||||
|
||||
# ④ 日次チェック処理を行う
|
||||
logger.info('I-04-01 日次チェック処理開始')
|
||||
|
||||
# 1.SAP保管用バケットの処理稼働日に該当するサブフォルダにあるファイル一覧を取得する
|
||||
logger.info(f'I-04-02 オブジェクトリストの取得 取得先:{CHECK_BUCKET_NAME}/{execute_date}/')
|
||||
object_prefix = f'{execute_date}/'
|
||||
object_list = s3_resource.Bucket(CHECK_BUCKET_NAME).objects.filter(Prefix=object_prefix)
|
||||
file_list = []
|
||||
for obj in object_list:
|
||||
obj_key = obj.key.rsplit('/', INDEX_SPLIT_NUM)
|
||||
file_list.append(obj_key[INDEX_LAST])
|
||||
|
||||
# 2.日次I/Fファイルチェック処理
|
||||
logger.info('I-04-03 日次I/Fファイルチェック処理開始')
|
||||
logger.info('I-04-04 取得したオブジェクトリストと日次I/Fファイルネーム設定ファイルの突き合わせを開始します')
|
||||
receive_daily_file_name_body = io.TextIOWrapper(io.BytesIO(receive_daily_file_name_response["Body"].read()), encoding='utf-8')
|
||||
match_count = 0
|
||||
row_count = sum(1 for line in io.BytesIO(receive_daily_file_name_obj.get()["Body"].read()))
|
||||
for row in csv.reader(receive_daily_file_name_body, delimiter='\t'):
|
||||
file_exists = False
|
||||
for file_name in file_list:
|
||||
match_result = re.fullmatch(row[INDEX_REGEX], file_name)
|
||||
if match_result is not None:
|
||||
file_exists = True
|
||||
break
|
||||
if file_exists == True:
|
||||
match_count += 1
|
||||
logger.info(f'I-04-05 日次I/Fファイルの受領を確認しました ファイル名:{file_name}')
|
||||
else:
|
||||
logger.error(f'E-04-01 日次I/Fファイルに不足があります ファイル名:{row[INDEX_DATA_NAME]}')
|
||||
mail_msg += f'{MAIL_INDENT}{row[INDEX_DATA_NAME]}\n'
|
||||
if row_count == match_count:
|
||||
logger.info('I-04-06 日次I/Fファイルは全て受領していることを確認しました')
|
||||
|
||||
# ⑤ 「①」でメモリ保持しているメール挿入用文言に出力内容が存在するか確認する
|
||||
logger.info('I-05-01 メール送信処理開始')
|
||||
|
||||
if len(mail_msg) > 0:
|
||||
# 1.存在した場合
|
||||
logger.info(f'I-05-02 {execute_date} 日次I/Fファイルに不足があるため、メール送信処理を開始します')
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-03 通知メール(タイトル)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_TITLE_TEMPLATE_PATH}')
|
||||
mail_title_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_TITLE_TEMPLATE_PATH)
|
||||
mail_title = mail_title_obj['Body'].read().decode('utf-8')
|
||||
logger.info('I-05-04 通知メール(タイトル)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-01 通知メール(タイトル)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-01', e)
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-05 通知メール(本文)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_BODY_TEMPLATE_PATH}')
|
||||
mail_body_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_BODY_TEMPLATE_PATH)
|
||||
mail_body_response = mail_body_obj['Body'].read().decode('utf-8')
|
||||
# メール本文内のプレースホルダーを置き換える
|
||||
mail_body = substitute_mail_body(mail_body_response, mail_msg)
|
||||
logger.info('I-05-06 通知メール(本文)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-02 通知メール(本文)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-02', e)
|
||||
|
||||
logger.info(f'I-05-07 メール送信指示をします 送信先トピック:{MBJ_SAP_NOTICE_TOPIC}')
|
||||
params = {
|
||||
'TopicArn': MBJ_SAP_NOTICE_TOPIC,
|
||||
'Subject': mail_title.rstrip('\n'),
|
||||
'Message': mail_body
|
||||
}
|
||||
sns_client.publish(**params)
|
||||
logger.info('I-05-08 メール送信指示をしました')
|
||||
else:
|
||||
# 2.存在しない場合
|
||||
logger.info(f'I-05-09 {execute_date} 日次I/Fファイルに不足がなかったため、メール送信処理をスキップします')
|
||||
|
||||
# ⑥ 処理終了ログを出力する
|
||||
logger.info('I-06-01 処理終了 SAP_finデータ受領チェック処理(日次)')
|
||||
except CustomException as e:
|
||||
traceback.print_exc()
|
||||
error_notice(e.id, e.arg)
|
||||
except Exception as e:
|
||||
logger.error(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
error_notice('E-99', 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-01 処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
|
||||
except Exception as e:
|
||||
logger.error(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
def substitute_mail_body(before_mail_body:str, mail_msg: str) -> str:
|
||||
"""メール本文のプレースホルダーを置き換えます
|
||||
|
||||
Args:
|
||||
before_mail_body (str): 置き換え前のメール本文
|
||||
mail_msg (str): メール本文のプレースホルダーを置き換える文言
|
||||
|
||||
Returns:
|
||||
str: 置き換え後のメール本文
|
||||
"""
|
||||
substitute_dict = {
|
||||
"notice_file_names": mail_msg
|
||||
}
|
||||
mail_body = before_mail_body.format_map(substitute_dict)
|
||||
return mail_body
|
||||
|
||||
|
||||
# カスタムExceptionクラス
|
||||
class CustomException(Exception, metaclass=ABCMeta):
|
||||
def __init__(self, id, arg):
|
||||
self.arg = arg
|
||||
self.id = id
|
||||
|
||||
|
||||
class FileReadException(CustomException):
|
||||
pass
|
||||
|
||||
|
||||
class NoneBusinessDayException(CustomException):
|
||||
pass
|
||||
import os
|
||||
import datetime
|
||||
import boto3
|
||||
import io
|
||||
import re
|
||||
import csv
|
||||
import logging
|
||||
from abc import *
|
||||
from zoneinfo import ZoneInfo
|
||||
import traceback
|
||||
|
||||
# 環境変数
|
||||
CHECK_BUCKET_NAME = os.environ["CHECK_BUCKET_NAME"]
|
||||
CONFIG_BUCKET_NAME = os.environ["CONFIG_BUCKET_NAME"]
|
||||
RECEIVE_DAILY_FILE_NAME_LIST_PATH = os.environ["RECEIVE_DAILY_FILE_NAME_LIST_PATH"]
|
||||
NON_BUSINESS_DAY_LIST_PATH = os.environ["NON_BUSINESS_DAY_LIST_PATH"]
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH = os.environ["NOTICE_MAIL_TITLE_TEMPLATE_PATH"]
|
||||
NOTICE_MAIL_BODY_TEMPLATE_PATH = os.environ["NOTICE_MAIL_BODY_TEMPLATE_PATH"]
|
||||
MBJ_SAP_NOTICE_TOPIC = os.environ["MBJ_SAP_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TOPIC = os.environ["NDS_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TITLE = os.environ["NDS_NOTICE_TITLE"]
|
||||
LOG_LEVEL = os.environ["LOG_LEVEL"]
|
||||
|
||||
# 定数
|
||||
ROW_COMMENT_SYMBOL = '#'
|
||||
INDEX_REGEX = 0
|
||||
INDEX_DATA_NAME = 1
|
||||
INDEX_ROW_COMMENT_SYMBOL = 0
|
||||
INDEX_SPLIT_NUM = 1
|
||||
INDEX_LAST = -1
|
||||
|
||||
# メール本文に出力する不足ファイル名一覧のインデント
|
||||
MAIL_INDENT = ' '
|
||||
|
||||
# 変数
|
||||
s3_client = boto3.client('s3')
|
||||
s3_resource = boto3.resource('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 lambda_handler(event, context):
|
||||
try:
|
||||
# ① 処理開始ログを出力する
|
||||
logger.info('I-01-01 処理開始 SAP_finデータ受領チェック処理(日次)')
|
||||
execute_date = datetime.date.today().strftime('%Y/%m/%d')
|
||||
logger.info(f'I-01-02 処理稼働日:{execute_date}')
|
||||
mail_msg = ''
|
||||
|
||||
# ② 営業日チェック処理を行う
|
||||
logger.info('I-02-01 営業日チェック処理開始')
|
||||
|
||||
# 1.設定ファイル[メルク社非営業日設定ファイル]を読み込む
|
||||
try:
|
||||
logger.info(f'I-02-02 非営業日設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NON_BUSINESS_DAY_LIST_PATH}')
|
||||
non_business_day_obj = s3_resource.Object(CONFIG_BUCKET_NAME, NON_BUSINESS_DAY_LIST_PATH)
|
||||
non_business_day_response = non_business_day_obj.get()
|
||||
logger.info('I-02-03 非営業日設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-02-01 非営業日設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-02-01', e)
|
||||
|
||||
# 2.処理稼働日が「②1.」で読み込んだ「メルク社非営業日設定ファイル」に存在するか確認する
|
||||
try:
|
||||
logger.info(f'I-02-04 本日が非営業日かチェックします チェック日:{execute_date}')
|
||||
none_business_day_list = []
|
||||
for row in io.TextIOWrapper(io.BytesIO(non_business_day_response["Body"].read()), encoding='utf-8'):
|
||||
if row[INDEX_ROW_COMMENT_SYMBOL] == ROW_COMMENT_SYMBOL:
|
||||
continue
|
||||
non_date = row.rstrip('\n')
|
||||
# 日付妥当性判定
|
||||
try:
|
||||
datetime.datetime.strptime(non_date, "%Y/%m/%d")
|
||||
except Exception as e:
|
||||
raise e
|
||||
none_business_day_list.append(non_date)
|
||||
if execute_date in none_business_day_list:
|
||||
logger.info('I-02-05 本日は非営業日のため、チェック処理をスキップします')
|
||||
return
|
||||
else:
|
||||
logger.info('I-02-06 本日は営業日のため、チェック処理を実施します')
|
||||
except Exception as e:
|
||||
logger.error(f'E-02-02 メルク社非営業日設定ファイルに不備があります エラー内容:{e}')
|
||||
raise NoneBusinessDayException('E-02-02', e)
|
||||
|
||||
# ③ 設定ファイル[SAP_finI/Fファイルネーム設定ファイル(日次)]を読み込む
|
||||
try:
|
||||
logger.info(f'I-03-01 日次I/Fファイルネーム設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{RECEIVE_DAILY_FILE_NAME_LIST_PATH}')
|
||||
receive_daily_file_name_obj = s3_resource.Object(CONFIG_BUCKET_NAME, RECEIVE_DAILY_FILE_NAME_LIST_PATH)
|
||||
receive_daily_file_name_response = receive_daily_file_name_obj.get()
|
||||
logger.info('I-03-02 日次I/Fファイルネーム設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-03-01 日次I/Fファイルネーム設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-03-01', e)
|
||||
|
||||
# ④ 日次チェック処理を行う
|
||||
logger.info('I-04-01 日次チェック処理開始')
|
||||
|
||||
# 1.SAP保管用バケットの処理稼働日に該当するサブフォルダにあるファイル一覧を取得する
|
||||
logger.info(f'I-04-02 オブジェクトリストの取得 取得先:{CHECK_BUCKET_NAME}/{execute_date}/')
|
||||
object_prefix = f'{execute_date}/'
|
||||
object_list = s3_resource.Bucket(CHECK_BUCKET_NAME).objects.filter(Prefix=object_prefix)
|
||||
file_list = []
|
||||
for obj in object_list:
|
||||
obj_key = obj.key.rsplit('/', INDEX_SPLIT_NUM)
|
||||
file_list.append(obj_key[INDEX_LAST])
|
||||
|
||||
# 2.日次I/Fファイルチェック処理
|
||||
logger.info('I-04-03 日次I/Fファイルチェック処理開始')
|
||||
logger.info('I-04-04 取得したオブジェクトリストと日次I/Fファイルネーム設定ファイルの突き合わせを開始します')
|
||||
receive_daily_file_name_body = io.TextIOWrapper(io.BytesIO(receive_daily_file_name_response["Body"].read()), encoding='utf-8')
|
||||
match_count = 0
|
||||
row_count = sum(1 for line in io.BytesIO(receive_daily_file_name_obj.get()["Body"].read()))
|
||||
for row in csv.reader(receive_daily_file_name_body, delimiter='\t'):
|
||||
file_exists = False
|
||||
for file_name in file_list:
|
||||
match_result = re.fullmatch(row[INDEX_REGEX], file_name)
|
||||
if match_result is not None:
|
||||
file_exists = True
|
||||
break
|
||||
if file_exists == True:
|
||||
match_count += 1
|
||||
logger.info(f'I-04-05 日次I/Fファイルの受領を確認しました ファイル名:{file_name}')
|
||||
else:
|
||||
logger.error(f'E-04-01 日次I/Fファイルに不足があります ファイル名:{row[INDEX_DATA_NAME]}')
|
||||
mail_msg += f'{MAIL_INDENT}{row[INDEX_DATA_NAME]}\n'
|
||||
if row_count == match_count:
|
||||
logger.info('I-04-06 日次I/Fファイルは全て受領していることを確認しました')
|
||||
|
||||
# ⑤ 「①」でメモリ保持しているメール挿入用文言に出力内容が存在するか確認する
|
||||
logger.info('I-05-01 メール送信処理開始')
|
||||
|
||||
if len(mail_msg) > 0:
|
||||
# 1.存在した場合
|
||||
logger.info(f'I-05-02 {execute_date} 日次I/Fファイルに不足があるため、メール送信処理を開始します')
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-03 通知メール(タイトル)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_TITLE_TEMPLATE_PATH}')
|
||||
mail_title_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_TITLE_TEMPLATE_PATH)
|
||||
mail_title = mail_title_obj['Body'].read().decode('utf-8')
|
||||
logger.info('I-05-04 通知メール(タイトル)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-01 通知メール(タイトル)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-01', e)
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-05 通知メール(本文)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_BODY_TEMPLATE_PATH}')
|
||||
mail_body_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_BODY_TEMPLATE_PATH)
|
||||
mail_body_response = mail_body_obj['Body'].read().decode('utf-8')
|
||||
# メール本文内のプレースホルダーを置き換える
|
||||
mail_body = substitute_mail_body(mail_body_response, mail_msg)
|
||||
logger.info('I-05-06 通知メール(本文)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-02 通知メール(本文)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-02', e)
|
||||
|
||||
logger.info(f'I-05-07 メール送信指示をします 送信先トピック:{MBJ_SAP_NOTICE_TOPIC}')
|
||||
mail_title_without_line_break = mail_title.splitlines()[0]
|
||||
params = {
|
||||
'TopicArn': MBJ_SAP_NOTICE_TOPIC,
|
||||
'Subject': mail_title_without_line_break,
|
||||
'Message': mail_body
|
||||
}
|
||||
sns_client.publish(**params)
|
||||
logger.info('I-05-08 メール送信指示をしました')
|
||||
else:
|
||||
# 2.存在しない場合
|
||||
logger.info(f'I-05-09 {execute_date} 日次I/Fファイルに不足がなかったため、メール送信処理をスキップします')
|
||||
|
||||
# ⑥ 処理終了ログを出力する
|
||||
logger.info('I-06-01 処理終了 SAP_finデータ受領チェック処理(日次)')
|
||||
except CustomException as e:
|
||||
traceback.print_exc()
|
||||
error_notice(e.id, e.arg)
|
||||
except Exception as e:
|
||||
logger.error(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
error_notice('E-99', 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-01 処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
|
||||
except Exception as e:
|
||||
logger.error(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
def substitute_mail_body(before_mail_body:str, mail_msg: str) -> str:
|
||||
"""メール本文のプレースホルダーを置き換えます
|
||||
|
||||
Args:
|
||||
before_mail_body (str): 置き換え前のメール本文
|
||||
mail_msg (str): メール本文のプレースホルダーを置き換える文言
|
||||
|
||||
Returns:
|
||||
str: 置き換え後のメール本文
|
||||
"""
|
||||
substitute_dict = {
|
||||
"notice_file_names": mail_msg
|
||||
}
|
||||
mail_body = before_mail_body.format_map(substitute_dict)
|
||||
return mail_body
|
||||
|
||||
|
||||
# カスタムExceptionクラス
|
||||
class CustomException(Exception, metaclass=ABCMeta):
|
||||
def __init__(self, id, arg):
|
||||
self.arg = arg
|
||||
self.id = id
|
||||
|
||||
|
||||
class FileReadException(CustomException):
|
||||
pass
|
||||
|
||||
|
||||
class NoneBusinessDayException(CustomException):
|
||||
pass
|
||||
|
||||
@ -173,9 +173,10 @@ def lambda_handler(event, context):
|
||||
raise FileReadException('E-05-02', e)
|
||||
|
||||
logger.info(f'I-05-07 メール送信指示をします 送信先トピック:{MBJ_SAP_NOTICE_TOPIC}')
|
||||
mail_title_without_line_break = mail_title.splitlines()[0]
|
||||
params = {
|
||||
'TopicArn': MBJ_SAP_NOTICE_TOPIC,
|
||||
'Subject': mail_title.rstrip('\n'),
|
||||
'Subject': mail_title_without_line_break,
|
||||
'Message': mail_body
|
||||
}
|
||||
sns_client.publish(**params)
|
||||
|
||||
@ -1,186 +1,187 @@
|
||||
import os
|
||||
import datetime
|
||||
import boto3
|
||||
import io
|
||||
import re
|
||||
import csv
|
||||
import logging
|
||||
from abc import *
|
||||
from zoneinfo import ZoneInfo
|
||||
import traceback
|
||||
|
||||
# 環境変数
|
||||
CONFIG_BUCKET_NAME = os.environ["CONFIG_BUCKET_NAME"]
|
||||
RECEIVE_MONTHLY_FILE_NAME_LIST_PATH = os.environ["RECEIVE_MONTHLY_FILE_NAME_LIST_PATH"]
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH = os.environ["NOTICE_MAIL_TITLE_TEMPLATE_PATH"]
|
||||
NOTICE_MAIL_BODY_TEMPLATE_PATH = os.environ["NOTICE_MAIL_BODY_TEMPLATE_PATH"]
|
||||
MBJ_SAP_NOTICE_TOPIC = os.environ["MBJ_SAP_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TOPIC = os.environ["NDS_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TITLE = os.environ["NDS_NOTICE_TITLE"]
|
||||
LOG_LEVEL = os.environ["LOG_LEVEL"]
|
||||
|
||||
# 定数
|
||||
INDEX_REGEX = 0
|
||||
INDEX_DATA_NAME = 1
|
||||
|
||||
# メール本文に出力する不足ファイル名一覧のインデント
|
||||
MAIL_INDENT = ' '
|
||||
|
||||
# 変数
|
||||
s3_client = boto3.client('s3')
|
||||
s3_resource = boto3.resource('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 lambda_handler(event, context):
|
||||
try:
|
||||
# ① 処理開始ログを出力する
|
||||
logger.info('I-01-01 処理開始 SAP_supデータ月次I/F受領通知処理')
|
||||
execute_date = datetime.date.today().strftime('%Y/%m/%d')
|
||||
logger.info(f'I-01-02 処理稼働日:{execute_date}')
|
||||
mail_msg = ''
|
||||
|
||||
# ② 処理開始時に受け取ったイベント情報の以下内容をログに出力しメモリに保持する
|
||||
logger.info('I-02-01 イベント情報出力処理')
|
||||
s3_event = event["Records"][0]["s3"]
|
||||
event_bucket_name = s3_event["bucket"]["name"]
|
||||
event_file_path = s3_event["object"]["key"]
|
||||
event_file_name = os.path.basename(event_file_path)
|
||||
logger.info(f'I-02-02 バケット名:{event_bucket_name}')
|
||||
logger.info(f'I-02-03 ファイルパス:{event_file_path}')
|
||||
|
||||
# ③ 設定ファイル[SAP_supI/Fファイルネーム設定ファイル(月次)]を読み込む
|
||||
try:
|
||||
logger.info(f'I-03-01 月次I/Fファイルネーム設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{RECEIVE_MONTHLY_FILE_NAME_LIST_PATH}')
|
||||
receive_monthly_file_name_obj = s3_resource.Object(CONFIG_BUCKET_NAME, RECEIVE_MONTHLY_FILE_NAME_LIST_PATH)
|
||||
receive_monthly_file_name_response = receive_monthly_file_name_obj.get()
|
||||
logger.info('I-03-02 月次I/Fファイルネーム設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-03-01 月次I/Fファイルネーム設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-03-01', e)
|
||||
|
||||
# ④ 月次I/Fファイル受領通知処理を行う
|
||||
logger.info('I-04-01 月次I/Fファイル受領通知処理開始')
|
||||
logger.info(f'I-04-02 受領したファイル名:{event_file_name}')
|
||||
logger.info('I-04-03 受領したファイル名と月次I/Fファイルネーム設定ファイルの突き合わせを開始します')
|
||||
file_exists = False
|
||||
receive_monthly_file_name_body = io.TextIOWrapper(io.BytesIO(receive_monthly_file_name_response["Body"].read()), encoding='utf-8')
|
||||
for row in csv.reader(receive_monthly_file_name_body, delimiter='\t'):
|
||||
match_result = re.fullmatch(row[INDEX_REGEX], event_file_name)
|
||||
if match_result is not None:
|
||||
file_exists = True
|
||||
break
|
||||
if file_exists == True:
|
||||
logger.info(f'I-04-04 月次I/Fを受領しました ファイル名:{row[INDEX_DATA_NAME]} {event_file_name}')
|
||||
mail_msg += f'{MAIL_INDENT}{row[INDEX_DATA_NAME]} {event_file_name}\n'
|
||||
else:
|
||||
logger.info('I-04-05 受領したファイルは月次I/Fではありませんでした')
|
||||
|
||||
# ⑤ 「①」でメモリ保持しているメール挿入用文言に出力内容が存在するか確認する
|
||||
logger.info('I-05-01 メール送信処理開始')
|
||||
if len(mail_msg) > 0:
|
||||
# 1.存在した場合
|
||||
logger.info('I-05-02 月次I/Fファイルを受領したため、メール送信処理を開始します')
|
||||
try:
|
||||
logger.info(f'I-05-03 通知メール(タイトル)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_TITLE_TEMPLATE_PATH}')
|
||||
mail_title_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_TITLE_TEMPLATE_PATH)
|
||||
mail_title = mail_title_obj['Body'].read().decode('utf-8')
|
||||
logger.info('I-05-04 通知メール(タイトル)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-01 通知メール(タイトル)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-01', e)
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-05 通知メール(本文)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_BODY_TEMPLATE_PATH}')
|
||||
mail_body_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_BODY_TEMPLATE_PATH)
|
||||
mail_body_response = mail_body_obj['Body'].read().decode('utf-8')
|
||||
# メール本文内のプレースホルダーを置き換える
|
||||
mail_body = substitute_mail_body(mail_body_response, mail_msg)
|
||||
logger.info('I-05-06 通知メール(本文)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-02 通知メール(本文)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-02', e)
|
||||
|
||||
logger.info(f'I-05-07 メール送信指示をします 送信先トピック:{MBJ_SAP_NOTICE_TOPIC}')
|
||||
params = {
|
||||
'TopicArn': MBJ_SAP_NOTICE_TOPIC,
|
||||
'Subject': mail_title.rstrip('\n'),
|
||||
'Message': mail_body
|
||||
}
|
||||
sns_client.publish(**params)
|
||||
logger.info('I-05-08 メール送信指示をしました')
|
||||
else:
|
||||
# 2.存在しない場合
|
||||
logger.info('I-05-09 受領したファイルは月次I/Fファイルではないため、メール送信処理をスキップします')
|
||||
|
||||
# ⑥ 処理終了ログを出力する
|
||||
logger.info('I-06-01 処理終了 SAP_supデータ月次I/F受領通知処理')
|
||||
except CustomException as e:
|
||||
traceback.print_exc()
|
||||
error_notice(e.id, e.arg)
|
||||
except Exception as e:
|
||||
logger.error(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
error_notice('E-99', 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-01 処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
|
||||
except Exception as e:
|
||||
logger.error(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
|
||||
def substitute_mail_body(before_mail_body:str, mail_msg: str) -> str:
|
||||
"""メール本文のプレースホルダーを置き換えます
|
||||
|
||||
Args:
|
||||
before_mail_body (str): 置き換え前のメール本文
|
||||
mail_msg (str): メール本文のプレースホルダーを置き換える文言
|
||||
|
||||
Returns:
|
||||
str: 置き換え後のメール本文
|
||||
"""
|
||||
substitute_dict = {
|
||||
"notice_file_names": mail_msg
|
||||
}
|
||||
mail_body = before_mail_body.format_map(substitute_dict)
|
||||
return mail_body
|
||||
|
||||
# カスタムExceptionクラス
|
||||
class CustomException(Exception, metaclass=ABCMeta):
|
||||
def __init__(self, id, arg):
|
||||
self.arg = arg
|
||||
self.id = id
|
||||
|
||||
|
||||
class FileReadException(CustomException):
|
||||
pass
|
||||
import os
|
||||
import datetime
|
||||
import boto3
|
||||
import io
|
||||
import re
|
||||
import csv
|
||||
import logging
|
||||
from abc import *
|
||||
from zoneinfo import ZoneInfo
|
||||
import traceback
|
||||
|
||||
# 環境変数
|
||||
CONFIG_BUCKET_NAME = os.environ["CONFIG_BUCKET_NAME"]
|
||||
RECEIVE_MONTHLY_FILE_NAME_LIST_PATH = os.environ["RECEIVE_MONTHLY_FILE_NAME_LIST_PATH"]
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH = os.environ["NOTICE_MAIL_TITLE_TEMPLATE_PATH"]
|
||||
NOTICE_MAIL_BODY_TEMPLATE_PATH = os.environ["NOTICE_MAIL_BODY_TEMPLATE_PATH"]
|
||||
MBJ_SAP_NOTICE_TOPIC = os.environ["MBJ_SAP_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TOPIC = os.environ["NDS_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TITLE = os.environ["NDS_NOTICE_TITLE"]
|
||||
LOG_LEVEL = os.environ["LOG_LEVEL"]
|
||||
|
||||
# 定数
|
||||
INDEX_REGEX = 0
|
||||
INDEX_DATA_NAME = 1
|
||||
|
||||
# メール本文に出力する不足ファイル名一覧のインデント
|
||||
MAIL_INDENT = ' '
|
||||
|
||||
# 変数
|
||||
s3_client = boto3.client('s3')
|
||||
s3_resource = boto3.resource('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 lambda_handler(event, context):
|
||||
try:
|
||||
# ① 処理開始ログを出力する
|
||||
logger.info('I-01-01 処理開始 SAP_supデータ月次I/F受領通知処理')
|
||||
execute_date = datetime.date.today().strftime('%Y/%m/%d')
|
||||
logger.info(f'I-01-02 処理稼働日:{execute_date}')
|
||||
mail_msg = ''
|
||||
|
||||
# ② 処理開始時に受け取ったイベント情報の以下内容をログに出力しメモリに保持する
|
||||
logger.info('I-02-01 イベント情報出力処理')
|
||||
s3_event = event["Records"][0]["s3"]
|
||||
event_bucket_name = s3_event["bucket"]["name"]
|
||||
event_file_path = s3_event["object"]["key"]
|
||||
event_file_name = os.path.basename(event_file_path)
|
||||
logger.info(f'I-02-02 バケット名:{event_bucket_name}')
|
||||
logger.info(f'I-02-03 ファイルパス:{event_file_path}')
|
||||
|
||||
# ③ 設定ファイル[SAP_supI/Fファイルネーム設定ファイル(月次)]を読み込む
|
||||
try:
|
||||
logger.info(f'I-03-01 月次I/Fファイルネーム設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{RECEIVE_MONTHLY_FILE_NAME_LIST_PATH}')
|
||||
receive_monthly_file_name_obj = s3_resource.Object(CONFIG_BUCKET_NAME, RECEIVE_MONTHLY_FILE_NAME_LIST_PATH)
|
||||
receive_monthly_file_name_response = receive_monthly_file_name_obj.get()
|
||||
logger.info('I-03-02 月次I/Fファイルネーム設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-03-01 月次I/Fファイルネーム設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-03-01', e)
|
||||
|
||||
# ④ 月次I/Fファイル受領通知処理を行う
|
||||
logger.info('I-04-01 月次I/Fファイル受領通知処理開始')
|
||||
logger.info(f'I-04-02 受領したファイル名:{event_file_name}')
|
||||
logger.info('I-04-03 受領したファイル名と月次I/Fファイルネーム設定ファイルの突き合わせを開始します')
|
||||
file_exists = False
|
||||
receive_monthly_file_name_body = io.TextIOWrapper(io.BytesIO(receive_monthly_file_name_response["Body"].read()), encoding='utf-8')
|
||||
for row in csv.reader(receive_monthly_file_name_body, delimiter='\t'):
|
||||
match_result = re.fullmatch(row[INDEX_REGEX], event_file_name)
|
||||
if match_result is not None:
|
||||
file_exists = True
|
||||
break
|
||||
if file_exists == True:
|
||||
logger.info(f'I-04-04 月次I/Fを受領しました ファイル名:{row[INDEX_DATA_NAME]} {event_file_name}')
|
||||
mail_msg += f'{MAIL_INDENT}{row[INDEX_DATA_NAME]} {event_file_name}\n'
|
||||
else:
|
||||
logger.info('I-04-05 受領したファイルは月次I/Fではありませんでした')
|
||||
|
||||
# ⑤ 「①」でメモリ保持しているメール挿入用文言に出力内容が存在するか確認する
|
||||
logger.info('I-05-01 メール送信処理開始')
|
||||
if len(mail_msg) > 0:
|
||||
# 1.存在した場合
|
||||
logger.info('I-05-02 月次I/Fファイルを受領したため、メール送信処理を開始します')
|
||||
try:
|
||||
logger.info(f'I-05-03 通知メール(タイトル)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_TITLE_TEMPLATE_PATH}')
|
||||
mail_title_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_TITLE_TEMPLATE_PATH)
|
||||
mail_title = mail_title_obj['Body'].read().decode('utf-8')
|
||||
logger.info('I-05-04 通知メール(タイトル)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-01 通知メール(タイトル)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-01', e)
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-05 通知メール(本文)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_BODY_TEMPLATE_PATH}')
|
||||
mail_body_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_BODY_TEMPLATE_PATH)
|
||||
mail_body_response = mail_body_obj['Body'].read().decode('utf-8')
|
||||
# メール本文内のプレースホルダーを置き換える
|
||||
mail_body = substitute_mail_body(mail_body_response, mail_msg)
|
||||
logger.info('I-05-06 通知メール(本文)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-02 通知メール(本文)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-02', e)
|
||||
|
||||
logger.info(f'I-05-07 メール送信指示をします 送信先トピック:{MBJ_SAP_NOTICE_TOPIC}')
|
||||
mail_title_without_line_break = mail_title.splitlines()[0]
|
||||
params = {
|
||||
'TopicArn': MBJ_SAP_NOTICE_TOPIC,
|
||||
'Subject': mail_title_without_line_break,
|
||||
'Message': mail_body
|
||||
}
|
||||
sns_client.publish(**params)
|
||||
logger.info('I-05-08 メール送信指示をしました')
|
||||
else:
|
||||
# 2.存在しない場合
|
||||
logger.info('I-05-09 受領したファイルは月次I/Fファイルではないため、メール送信処理をスキップします')
|
||||
|
||||
# ⑥ 処理終了ログを出力する
|
||||
logger.info('I-06-01 処理終了 SAP_supデータ月次I/F受領通知処理')
|
||||
except CustomException as e:
|
||||
traceback.print_exc()
|
||||
error_notice(e.id, e.arg)
|
||||
except Exception as e:
|
||||
logger.error(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
error_notice('E-99', 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-01 処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
|
||||
except Exception as e:
|
||||
logger.error(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
|
||||
def substitute_mail_body(before_mail_body:str, mail_msg: str) -> str:
|
||||
"""メール本文のプレースホルダーを置き換えます
|
||||
|
||||
Args:
|
||||
before_mail_body (str): 置き換え前のメール本文
|
||||
mail_msg (str): メール本文のプレースホルダーを置き換える文言
|
||||
|
||||
Returns:
|
||||
str: 置き換え後のメール本文
|
||||
"""
|
||||
substitute_dict = {
|
||||
"notice_file_names": mail_msg
|
||||
}
|
||||
mail_body = before_mail_body.format_map(substitute_dict)
|
||||
return mail_body
|
||||
|
||||
# カスタムExceptionクラス
|
||||
class CustomException(Exception, metaclass=ABCMeta):
|
||||
def __init__(self, id, arg):
|
||||
self.arg = arg
|
||||
self.id = id
|
||||
|
||||
|
||||
class FileReadException(CustomException):
|
||||
pass
|
||||
|
||||
@ -1,242 +1,243 @@
|
||||
import os
|
||||
import datetime
|
||||
import boto3
|
||||
import io
|
||||
import re
|
||||
import csv
|
||||
import logging
|
||||
from abc import *
|
||||
from zoneinfo import ZoneInfo
|
||||
import traceback
|
||||
|
||||
# 環境変数
|
||||
CHECK_BUCKET_NAME = os.environ["CHECK_BUCKET_NAME"]
|
||||
CONFIG_BUCKET_NAME = os.environ["CONFIG_BUCKET_NAME"]
|
||||
RECEIVE_DAILY_FILE_NAME_LIST_PATH = os.environ["RECEIVE_DAILY_FILE_NAME_LIST_PATH"]
|
||||
NON_BUSINESS_DAY_LIST_PATH = os.environ["NON_BUSINESS_DAY_LIST_PATH"]
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH = os.environ["NOTICE_MAIL_TITLE_TEMPLATE_PATH"]
|
||||
NOTICE_MAIL_BODY_TEMPLATE_PATH = os.environ["NOTICE_MAIL_BODY_TEMPLATE_PATH"]
|
||||
MBJ_SAP_NOTICE_TOPIC = os.environ["MBJ_SAP_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TOPIC = os.environ["NDS_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TITLE = os.environ["NDS_NOTICE_TITLE"]
|
||||
LOG_LEVEL = os.environ["LOG_LEVEL"]
|
||||
|
||||
# 定数
|
||||
ROW_COMMENT_SYMBOL = '#'
|
||||
INDEX_REGEX = 0
|
||||
INDEX_DATA_NAME = 1
|
||||
INDEX_ROW_COMMENT_SYMBOL = 0
|
||||
INDEX_SPLIT_NUM = 1
|
||||
INDEX_LAST = -1
|
||||
|
||||
# メール本文に出力する不足ファイル名一覧のインデント
|
||||
MAIL_INDENT = ' '
|
||||
|
||||
# 変数
|
||||
s3_client = boto3.client('s3')
|
||||
s3_resource = boto3.resource('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 lambda_handler(event, context):
|
||||
try:
|
||||
# ① 処理開始ログを出力する
|
||||
logger.info('I-01-01 処理開始 SAP_supデータ受領チェック処理(日次)')
|
||||
execute_date = datetime.date.today().strftime('%Y/%m/%d')
|
||||
logger.info(f'I-01-02 処理稼働日:{execute_date}')
|
||||
mail_msg = ''
|
||||
|
||||
# ② 営業日チェック処理を行う
|
||||
logger.info('I-02-01 営業日チェック処理開始')
|
||||
|
||||
# 1.設定ファイル[メルク社非営業日設定ファイル]を読み込む
|
||||
try:
|
||||
logger.info(f'I-02-02 非営業日設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NON_BUSINESS_DAY_LIST_PATH}')
|
||||
non_business_day_obj = s3_resource.Object(CONFIG_BUCKET_NAME, NON_BUSINESS_DAY_LIST_PATH)
|
||||
non_business_day_response = non_business_day_obj.get()
|
||||
logger.info('I-02-03 非営業日設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-02-01 非営業日設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-02-01', e)
|
||||
|
||||
# 2.処理稼働日が「②1.」で読み込んだ「メルク社非営業日設定ファイル」に存在するか確認する
|
||||
try:
|
||||
logger.info(f'I-02-04 本日が非営業日かチェックします チェック日:{execute_date}')
|
||||
none_business_day_list = []
|
||||
for row in io.TextIOWrapper(io.BytesIO(non_business_day_response["Body"].read()), encoding='utf-8'):
|
||||
if row[INDEX_ROW_COMMENT_SYMBOL] == ROW_COMMENT_SYMBOL:
|
||||
continue
|
||||
non_date = row.rstrip('\n')
|
||||
# 日付妥当性判定
|
||||
try:
|
||||
datetime.datetime.strptime(non_date, "%Y/%m/%d")
|
||||
except Exception as e:
|
||||
raise e
|
||||
none_business_day_list.append(non_date)
|
||||
if execute_date in none_business_day_list:
|
||||
logger.info('I-02-05 本日は非営業日のため、チェック処理をスキップします')
|
||||
return
|
||||
else:
|
||||
logger.info('I-02-06 本日は営業日のため、チェック処理を実施します')
|
||||
except Exception as e:
|
||||
logger.error(f'E-02-02 メルク社非営業日設定ファイルに不備があります エラー内容:{e}')
|
||||
raise NoneBusinessDayException('E-02-02', e)
|
||||
|
||||
# ③ 設定ファイル[SAP_supI/Fファイルネーム設定ファイル(日次)]を読み込む
|
||||
try:
|
||||
logger.info(f'I-03-01 日次I/Fファイルネーム設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{RECEIVE_DAILY_FILE_NAME_LIST_PATH}')
|
||||
receive_daily_file_name_obj = s3_resource.Object(CONFIG_BUCKET_NAME, RECEIVE_DAILY_FILE_NAME_LIST_PATH)
|
||||
receive_daily_file_name_response = receive_daily_file_name_obj.get()
|
||||
logger.info('I-03-02 日次I/Fファイルネーム設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-03-01 日次I/Fファイルネーム設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-03-01', e)
|
||||
|
||||
# ④ 日次チェック処理を行う
|
||||
logger.info('I-04-01 日次チェック処理開始')
|
||||
|
||||
# 1.SAP保管用バケットの処理稼働日に該当するサブフォルダにあるファイル一覧を取得する
|
||||
logger.info(f'I-04-02 オブジェクトリストの取得 取得先:{CHECK_BUCKET_NAME}/{execute_date}/')
|
||||
object_prefix = f'{execute_date}/'
|
||||
object_list = s3_resource.Bucket(CHECK_BUCKET_NAME).objects.filter(Prefix=object_prefix)
|
||||
file_list = []
|
||||
for obj in object_list:
|
||||
obj_key = obj.key.rsplit('/', INDEX_SPLIT_NUM)
|
||||
file_list.append(obj_key[INDEX_LAST])
|
||||
|
||||
# 2.日次I/Fファイルチェック処理
|
||||
logger.info('I-04-03 日次I/Fファイルチェック処理開始')
|
||||
logger.info('I-04-04 取得したオブジェクトリストと日次I/Fファイルネーム設定ファイルの突き合わせを開始します')
|
||||
receive_daily_file_name_body = io.TextIOWrapper(io.BytesIO(receive_daily_file_name_response["Body"].read()), encoding='utf-8')
|
||||
match_count = 0
|
||||
row_count = sum(1 for line in io.BytesIO(receive_daily_file_name_obj.get()["Body"].read()))
|
||||
for row in csv.reader(receive_daily_file_name_body, delimiter='\t'):
|
||||
file_exists = False
|
||||
for file_name in file_list:
|
||||
match_result = re.fullmatch(row[INDEX_REGEX], file_name)
|
||||
if match_result is not None:
|
||||
file_exists = True
|
||||
break
|
||||
if file_exists == True:
|
||||
match_count += 1
|
||||
logger.info(f'I-04-05 日次I/Fファイルの受領を確認しました ファイル名:{file_name}')
|
||||
else:
|
||||
logger.error(f'E-04-01 日次I/Fファイルに不足があります ファイル名:{row[INDEX_DATA_NAME]}')
|
||||
mail_msg += f'{MAIL_INDENT}{row[INDEX_DATA_NAME]}\n'
|
||||
if row_count == match_count:
|
||||
logger.info('I-04-06 日次I/Fファイルは全て受領していることを確認しました')
|
||||
|
||||
# ⑤ 「①」でメモリ保持しているメール挿入用文言に出力内容が存在するか確認する
|
||||
logger.info('I-05-01 メール送信処理開始')
|
||||
|
||||
if len(mail_msg) > 0:
|
||||
# 1.存在した場合
|
||||
logger.info(f'I-05-02 {execute_date} 日次I/Fファイルに不足があるため、メール送信処理を開始します')
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-03 通知メール(タイトル)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_TITLE_TEMPLATE_PATH}')
|
||||
mail_title_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_TITLE_TEMPLATE_PATH)
|
||||
mail_title = mail_title_obj['Body'].read().decode('utf-8')
|
||||
logger.info('I-05-04 通知メール(タイトル)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-01 通知メール(タイトル)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-01', e)
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-05 通知メール(本文)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_BODY_TEMPLATE_PATH}')
|
||||
mail_body_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_BODY_TEMPLATE_PATH)
|
||||
mail_body_response = mail_body_obj['Body'].read().decode('utf-8')
|
||||
# メール本文内のプレースホルダーを置き換える
|
||||
mail_body = substitute_mail_body(mail_body_response, mail_msg)
|
||||
logger.info('I-05-06 通知メール(本文)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-02 通知メール(本文)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-02', e)
|
||||
|
||||
logger.info(f'I-05-07 メール送信指示をします 送信先トピック:{MBJ_SAP_NOTICE_TOPIC}')
|
||||
params = {
|
||||
'TopicArn': MBJ_SAP_NOTICE_TOPIC,
|
||||
'Subject': mail_title.rstrip('\n'),
|
||||
'Message': mail_body
|
||||
}
|
||||
sns_client.publish(**params)
|
||||
logger.info('I-05-08 メール送信指示をしました')
|
||||
else:
|
||||
# 2.存在しない場合
|
||||
logger.info(f'I-05-09 {execute_date} 日次I/Fファイルに不足がなかったため、メール送信処理をスキップします')
|
||||
|
||||
# ⑥ 処理終了ログを出力する
|
||||
logger.info('I-06-01 処理終了 SAP_supデータ受領チェック処理(日次)')
|
||||
except CustomException as e:
|
||||
traceback.print_exc()
|
||||
error_notice(e.id, e.arg)
|
||||
except Exception as e:
|
||||
logger.error(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
error_notice('E-99', 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-01 処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
|
||||
except Exception as e:
|
||||
logger.error(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
def substitute_mail_body(before_mail_body:str, mail_msg: str) -> str:
|
||||
"""メール本文のプレースホルダーを置き換えます
|
||||
|
||||
Args:
|
||||
before_mail_body (str): 置き換え前のメール本文
|
||||
mail_msg (str): メール本文のプレースホルダーを置き換える文言
|
||||
|
||||
Returns:
|
||||
str: 置き換え後のメール本文
|
||||
"""
|
||||
substitute_dict = {
|
||||
"notice_file_names": mail_msg
|
||||
}
|
||||
mail_body = before_mail_body.format_map(substitute_dict)
|
||||
return mail_body
|
||||
|
||||
|
||||
# カスタムExceptionクラス
|
||||
class CustomException(Exception, metaclass=ABCMeta):
|
||||
def __init__(self, id, arg):
|
||||
self.arg = arg
|
||||
self.id = id
|
||||
|
||||
|
||||
class FileReadException(CustomException):
|
||||
pass
|
||||
|
||||
|
||||
class NoneBusinessDayException(CustomException):
|
||||
pass
|
||||
import os
|
||||
import datetime
|
||||
import boto3
|
||||
import io
|
||||
import re
|
||||
import csv
|
||||
import logging
|
||||
from abc import *
|
||||
from zoneinfo import ZoneInfo
|
||||
import traceback
|
||||
|
||||
# 環境変数
|
||||
CHECK_BUCKET_NAME = os.environ["CHECK_BUCKET_NAME"]
|
||||
CONFIG_BUCKET_NAME = os.environ["CONFIG_BUCKET_NAME"]
|
||||
RECEIVE_DAILY_FILE_NAME_LIST_PATH = os.environ["RECEIVE_DAILY_FILE_NAME_LIST_PATH"]
|
||||
NON_BUSINESS_DAY_LIST_PATH = os.environ["NON_BUSINESS_DAY_LIST_PATH"]
|
||||
NOTICE_MAIL_TITLE_TEMPLATE_PATH = os.environ["NOTICE_MAIL_TITLE_TEMPLATE_PATH"]
|
||||
NOTICE_MAIL_BODY_TEMPLATE_PATH = os.environ["NOTICE_MAIL_BODY_TEMPLATE_PATH"]
|
||||
MBJ_SAP_NOTICE_TOPIC = os.environ["MBJ_SAP_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TOPIC = os.environ["NDS_NOTICE_TOPIC"]
|
||||
NDS_NOTICE_TITLE = os.environ["NDS_NOTICE_TITLE"]
|
||||
LOG_LEVEL = os.environ["LOG_LEVEL"]
|
||||
|
||||
# 定数
|
||||
ROW_COMMENT_SYMBOL = '#'
|
||||
INDEX_REGEX = 0
|
||||
INDEX_DATA_NAME = 1
|
||||
INDEX_ROW_COMMENT_SYMBOL = 0
|
||||
INDEX_SPLIT_NUM = 1
|
||||
INDEX_LAST = -1
|
||||
|
||||
# メール本文に出力する不足ファイル名一覧のインデント
|
||||
MAIL_INDENT = ' '
|
||||
|
||||
# 変数
|
||||
s3_client = boto3.client('s3')
|
||||
s3_resource = boto3.resource('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 lambda_handler(event, context):
|
||||
try:
|
||||
# ① 処理開始ログを出力する
|
||||
logger.info('I-01-01 処理開始 SAP_supデータ受領チェック処理(日次)')
|
||||
execute_date = datetime.date.today().strftime('%Y/%m/%d')
|
||||
logger.info(f'I-01-02 処理稼働日:{execute_date}')
|
||||
mail_msg = ''
|
||||
|
||||
# ② 営業日チェック処理を行う
|
||||
logger.info('I-02-01 営業日チェック処理開始')
|
||||
|
||||
# 1.設定ファイル[メルク社非営業日設定ファイル]を読み込む
|
||||
try:
|
||||
logger.info(f'I-02-02 非営業日設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NON_BUSINESS_DAY_LIST_PATH}')
|
||||
non_business_day_obj = s3_resource.Object(CONFIG_BUCKET_NAME, NON_BUSINESS_DAY_LIST_PATH)
|
||||
non_business_day_response = non_business_day_obj.get()
|
||||
logger.info('I-02-03 非営業日設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-02-01 非営業日設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-02-01', e)
|
||||
|
||||
# 2.処理稼働日が「②1.」で読み込んだ「メルク社非営業日設定ファイル」に存在するか確認する
|
||||
try:
|
||||
logger.info(f'I-02-04 本日が非営業日かチェックします チェック日:{execute_date}')
|
||||
none_business_day_list = []
|
||||
for row in io.TextIOWrapper(io.BytesIO(non_business_day_response["Body"].read()), encoding='utf-8'):
|
||||
if row[INDEX_ROW_COMMENT_SYMBOL] == ROW_COMMENT_SYMBOL:
|
||||
continue
|
||||
non_date = row.rstrip('\n')
|
||||
# 日付妥当性判定
|
||||
try:
|
||||
datetime.datetime.strptime(non_date, "%Y/%m/%d")
|
||||
except Exception as e:
|
||||
raise e
|
||||
none_business_day_list.append(non_date)
|
||||
if execute_date in none_business_day_list:
|
||||
logger.info('I-02-05 本日は非営業日のため、チェック処理をスキップします')
|
||||
return
|
||||
else:
|
||||
logger.info('I-02-06 本日は営業日のため、チェック処理を実施します')
|
||||
except Exception as e:
|
||||
logger.error(f'E-02-02 メルク社非営業日設定ファイルに不備があります エラー内容:{e}')
|
||||
raise NoneBusinessDayException('E-02-02', e)
|
||||
|
||||
# ③ 設定ファイル[SAP_supI/Fファイルネーム設定ファイル(日次)]を読み込む
|
||||
try:
|
||||
logger.info(f'I-03-01 日次I/Fファイルネーム設定ファイル読込 読込元:{CONFIG_BUCKET_NAME}/{RECEIVE_DAILY_FILE_NAME_LIST_PATH}')
|
||||
receive_daily_file_name_obj = s3_resource.Object(CONFIG_BUCKET_NAME, RECEIVE_DAILY_FILE_NAME_LIST_PATH)
|
||||
receive_daily_file_name_response = receive_daily_file_name_obj.get()
|
||||
logger.info('I-03-02 日次I/Fファイルネーム設定ファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-03-01 日次I/Fファイルネーム設定ファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-03-01', e)
|
||||
|
||||
# ④ 日次チェック処理を行う
|
||||
logger.info('I-04-01 日次チェック処理開始')
|
||||
|
||||
# 1.SAP保管用バケットの処理稼働日に該当するサブフォルダにあるファイル一覧を取得する
|
||||
logger.info(f'I-04-02 オブジェクトリストの取得 取得先:{CHECK_BUCKET_NAME}/{execute_date}/')
|
||||
object_prefix = f'{execute_date}/'
|
||||
object_list = s3_resource.Bucket(CHECK_BUCKET_NAME).objects.filter(Prefix=object_prefix)
|
||||
file_list = []
|
||||
for obj in object_list:
|
||||
obj_key = obj.key.rsplit('/', INDEX_SPLIT_NUM)
|
||||
file_list.append(obj_key[INDEX_LAST])
|
||||
|
||||
# 2.日次I/Fファイルチェック処理
|
||||
logger.info('I-04-03 日次I/Fファイルチェック処理開始')
|
||||
logger.info('I-04-04 取得したオブジェクトリストと日次I/Fファイルネーム設定ファイルの突き合わせを開始します')
|
||||
receive_daily_file_name_body = io.TextIOWrapper(io.BytesIO(receive_daily_file_name_response["Body"].read()), encoding='utf-8')
|
||||
match_count = 0
|
||||
row_count = sum(1 for line in io.BytesIO(receive_daily_file_name_obj.get()["Body"].read()))
|
||||
for row in csv.reader(receive_daily_file_name_body, delimiter='\t'):
|
||||
file_exists = False
|
||||
for file_name in file_list:
|
||||
match_result = re.fullmatch(row[INDEX_REGEX], file_name)
|
||||
if match_result is not None:
|
||||
file_exists = True
|
||||
break
|
||||
if file_exists == True:
|
||||
match_count += 1
|
||||
logger.info(f'I-04-05 日次I/Fファイルの受領を確認しました ファイル名:{file_name}')
|
||||
else:
|
||||
logger.error(f'E-04-01 日次I/Fファイルに不足があります ファイル名:{row[INDEX_DATA_NAME]}')
|
||||
mail_msg += f'{MAIL_INDENT}{row[INDEX_DATA_NAME]}\n'
|
||||
if row_count == match_count:
|
||||
logger.info('I-04-06 日次I/Fファイルは全て受領していることを確認しました')
|
||||
|
||||
# ⑤ 「①」でメモリ保持しているメール挿入用文言に出力内容が存在するか確認する
|
||||
logger.info('I-05-01 メール送信処理開始')
|
||||
|
||||
if len(mail_msg) > 0:
|
||||
# 1.存在した場合
|
||||
logger.info(f'I-05-02 {execute_date} 日次I/Fファイルに不足があるため、メール送信処理を開始します')
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-03 通知メール(タイトル)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_TITLE_TEMPLATE_PATH}')
|
||||
mail_title_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_TITLE_TEMPLATE_PATH)
|
||||
mail_title = mail_title_obj['Body'].read().decode('utf-8')
|
||||
logger.info('I-05-04 通知メール(タイトル)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-01 通知メール(タイトル)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-01', e)
|
||||
|
||||
try:
|
||||
logger.info(f'I-05-05 通知メール(本文)テンプレートファイル読込 読込元:{CONFIG_BUCKET_NAME}/{NOTICE_MAIL_BODY_TEMPLATE_PATH}')
|
||||
mail_body_obj = s3_client.get_object(Bucket=CONFIG_BUCKET_NAME, Key=NOTICE_MAIL_BODY_TEMPLATE_PATH)
|
||||
mail_body_response = mail_body_obj['Body'].read().decode('utf-8')
|
||||
# メール本文内のプレースホルダーを置き換える
|
||||
mail_body = substitute_mail_body(mail_body_response, mail_msg)
|
||||
logger.info('I-05-06 通知メール(本文)テンプレートファイルを読み込みました')
|
||||
except Exception as e:
|
||||
logger.error(f'E-05-02 通知メール(本文)テンプレートファイルの読み込みに失敗しました エラー内容:{e}')
|
||||
raise FileReadException('E-05-02', e)
|
||||
|
||||
logger.info(f'I-05-07 メール送信指示をします 送信先トピック:{MBJ_SAP_NOTICE_TOPIC}')
|
||||
mail_title_without_line_break = mail_title.splitlines()[0]
|
||||
params = {
|
||||
'TopicArn': MBJ_SAP_NOTICE_TOPIC,
|
||||
'Subject': mail_title_without_line_break,
|
||||
'Message': mail_body
|
||||
}
|
||||
sns_client.publish(**params)
|
||||
logger.info('I-05-08 メール送信指示をしました')
|
||||
else:
|
||||
# 2.存在しない場合
|
||||
logger.info(f'I-05-09 {execute_date} 日次I/Fファイルに不足がなかったため、メール送信処理をスキップします')
|
||||
|
||||
# ⑥ 処理終了ログを出力する
|
||||
logger.info('I-06-01 処理終了 SAP_supデータ受領チェック処理(日次)')
|
||||
except CustomException as e:
|
||||
traceback.print_exc()
|
||||
error_notice(e.id, e.arg)
|
||||
except Exception as e:
|
||||
logger.error(f'E-99 想定外のエラーが発生しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
error_notice('E-99', 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-01 処理異常通知の送信指示をしました 通知先トピック:{NDS_NOTICE_TOPIC}')
|
||||
except Exception as e:
|
||||
logger.error(f'E-98 処理異常通知の送信指示に失敗しました エラー内容:{e}')
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
def substitute_mail_body(before_mail_body:str, mail_msg: str) -> str:
|
||||
"""メール本文のプレースホルダーを置き換えます
|
||||
|
||||
Args:
|
||||
before_mail_body (str): 置き換え前のメール本文
|
||||
mail_msg (str): メール本文のプレースホルダーを置き換える文言
|
||||
|
||||
Returns:
|
||||
str: 置き換え後のメール本文
|
||||
"""
|
||||
substitute_dict = {
|
||||
"notice_file_names": mail_msg
|
||||
}
|
||||
mail_body = before_mail_body.format_map(substitute_dict)
|
||||
return mail_body
|
||||
|
||||
|
||||
# カスタムExceptionクラス
|
||||
class CustomException(Exception, metaclass=ABCMeta):
|
||||
def __init__(self, id, arg):
|
||||
self.arg = arg
|
||||
self.id = id
|
||||
|
||||
|
||||
class FileReadException(CustomException):
|
||||
pass
|
||||
|
||||
|
||||
class NoneBusinessDayException(CustomException):
|
||||
pass
|
||||
|
||||
@ -173,9 +173,10 @@ def lambda_handler(event, context):
|
||||
raise FileReadException('E-05-02', e)
|
||||
|
||||
logger.info(f'I-05-07 メール送信指示をします 送信先トピック:{MBJ_SAP_NOTICE_TOPIC}')
|
||||
mail_title_without_line_break = mail_title.splitlines()[0]
|
||||
params = {
|
||||
'TopicArn': MBJ_SAP_NOTICE_TOPIC,
|
||||
'Subject': mail_title.rstrip('\n'),
|
||||
'Subject': mail_title_without_line_break,
|
||||
'Message': mail_body
|
||||
}
|
||||
sns_client.publish(**params)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
2022/06/15
|
||||
2022/07/15
|
||||
2022/08/15
|
||||
2022/09/15
|
||||
2022/10/15
|
||||
2022/11/15
|
||||
2022/12/15
|
||||
2022/06/03
|
||||
2022/07/03
|
||||
2022/08/03
|
||||
2022/09/03
|
||||
2022/10/03
|
||||
2022/11/07
|
||||
2022/12/03
|
||||
@ -1,73 +0,0 @@
|
||||
2022/06/04
|
||||
2022/06/05
|
||||
2022/06/11
|
||||
2022/06/12
|
||||
2022/06/18
|
||||
2022/06/19
|
||||
2022/06/25
|
||||
2022/06/26
|
||||
2022/07/02
|
||||
2022/07/03
|
||||
2022/07/09
|
||||
2022/07/10
|
||||
2022/07/16
|
||||
2022/07/17
|
||||
2022/07/18
|
||||
2022/07/23
|
||||
2022/07/24
|
||||
2022/07/30
|
||||
2022/07/31
|
||||
2022/08/06
|
||||
2022/08/07
|
||||
2022/08/11
|
||||
2022/08/12
|
||||
2022/08/13
|
||||
2022/08/14
|
||||
2022/08/15
|
||||
2022/08/16
|
||||
2022/08/20
|
||||
2022/08/21
|
||||
2022/08/27
|
||||
2022/08/28
|
||||
2022/09/03
|
||||
2022/09/04
|
||||
2022/09/10
|
||||
2022/09/11
|
||||
2022/09/17
|
||||
2022/09/18
|
||||
2022/09/19
|
||||
2022/09/23
|
||||
2022/09/24
|
||||
2022/09/25
|
||||
2022/10/01
|
||||
2022/10/02
|
||||
2022/10/08
|
||||
2022/10/09
|
||||
2022/10/10
|
||||
2022/10/15
|
||||
2022/10/16
|
||||
2022/10/22
|
||||
2022/10/23
|
||||
2022/10/29
|
||||
2022/10/30
|
||||
2022/11/03
|
||||
2022/11/05
|
||||
2022/11/06
|
||||
2022/11/12
|
||||
2022/11/13
|
||||
2022/11/19
|
||||
2022/11/20
|
||||
2022/11/23
|
||||
2022/11/26
|
||||
2022/11/27
|
||||
2022/12/03
|
||||
2022/12/04
|
||||
2022/12/10
|
||||
2022/12/11
|
||||
2022/12/17
|
||||
2022/12/18
|
||||
2022/12/24
|
||||
2022/12/25
|
||||
2022/12/29
|
||||
2022/12/30
|
||||
2022/12/31
|
||||
3
s3/config/view_check/check_target_schemas.json
Normal file
3
s3/config/view_check/check_target_schemas.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"check_target_schemas": ["custom01", "custom02", "custom03"]
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
宛先各位
|
||||
customスキーマの以下のviewに「SQL SECURITY INVOKER」オプションが指定されておりません。viewを再作成しオプションを指定してください。
|
||||
{no_option_views}
|
||||
|
||||
尚、本メールはシステム自動送信ですので、返信できません。
|
||||
本件に関する問い合わせは、IT部門ゴザリ様にお願いいたします。
|
||||
@ -0,0 +1 @@
|
||||
【MeDaCaシステム通知】view参照制限オプション指定漏れを検出しました
|
||||
13
s3/data/SAP_fin/settings/CostReport.txt
Normal file
13
s3/data/SAP_fin/settings/CostReport.txt
Normal file
@ -0,0 +1,13 @@
|
||||
SAP_fin
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
18
|
||||
Value Type,Fiscal Yr,Period,Cost Center,Cost Elem.,Cost Elem.text,Value.Crcy,Offsetting acct.,Off.acc.nam,Name,Doc.Text,Posting Date,Doc.Date,Purch.Doc,DocumentNo,Post.row,RefDocNo,Reversed
|
||||
value_type,fiscal_yr,period,cost_center,cost_elem,cost_elem_text,value_crcy,offsetting_acct,off_acc_nam_name,name,doc_text,posting_date,doc_date,purch_doc,document_no,post_row,ref_doc_no,reversed
|
||||
src03a.sapf_costreport
|
||||
org03a.sapf_costreport
|
||||
|
||||
value_crcy
|
||||
13
s3/data/SAP_fin/settings/IOReport.txt
Normal file
13
s3/data/SAP_fin/settings/IOReport.txt
Normal file
@ -0,0 +1,13 @@
|
||||
SAP_fin
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
18
|
||||
Fiscal Yr,Period,OrderNo.,Cost Elem.,Cost Elem.text,Value.Crcy,Offsetting acct,Offsetting Account Name,AuxAcctAstmt_1,Name,Document Header Text,Posting Date,Doc.Date,Purchasing Doc.,Document No,Post.Row,RefDocNo.,Reversed
|
||||
fiscal_yr,period,order_no,cost_elem,cost_elem_text,value_crcy,offsetting_acct,offsetting_account_name,aux_acct_astmt_1,name,document_header_text,posting_date,doc_date,purchasing_doc,document_no,post_row,ref_doc_no,reversed
|
||||
src03a.sapf_ioreport
|
||||
org03a.sapf_ioreport
|
||||
|
||||
value_crcy
|
||||
13
s3/data/SAP_fin/settings/Invoice.txt
Normal file
13
s3/data/SAP_fin/settings/Invoice.txt
Normal file
@ -0,0 +1,13 @@
|
||||
SAP_fin
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
25
|
||||
Billing Type,ConditionType,Distribution Channel,Invoiced Number,Item Number,Invoice Date,Account Number (Sold To),Customer Name,Ship-To,Ship-to (Name),Quantity Invoiced,Extended amount (Invoice Amount),Accural Value (Rebate 1),Accural % (Rebate 1),Accural Value (Rebate 2),Accural % (Rebate 2),Unit Selling Price,Item Code,Item Name,Tax,Order Reason,Reference doc,Sales Order No.,Rejected RSN,Batch Number
|
||||
billing_type,condition_type,distribution_channel,invoiced_number,item_number,invoice_date,account_number_sold_to,customer_name,ship_to,ship_to_name,quantity_invoiced,extended_amount_invoice_amount,accural_value_rebate_1,accural_percent_rebate_1,accural_value_rebate_2,accural_percent_rebate_2,unit_selling_price,item_code,item_name,tax,order_reason,reference_doc,sales_order_no,rejected_rsn,batch_number
|
||||
src03b.sapf_invoice
|
||||
org03b.sapf_invoice
|
||||
|
||||
quantity_invoiced,extended_amount_invoice_amount,accural_value_rebate_1,accural_percent_rebate_1,accural_value_rebate_2,accural_percent_rebate_2,unit_selling_price,tax
|
||||
11
s3/data/SAP_fin/settings/WBSList.txt
Normal file
11
s3/data/SAP_fin/settings/WBSList.txt
Normal file
@ -0,0 +1,11 @@
|
||||
SAP_fin
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
10
|
||||
WBS Element,Level,Obj.no.,Profit Ctr,Project Definition,Description,COAr,Respons.,Basic start,Basic finish
|
||||
wbs_element,level,obj_no,profit_ctr,project_definition,description,coar,respons,basic_start,basic_finish
|
||||
src03a.sapf_wbslist
|
||||
org03a.sapf_wbslist
|
||||
13
s3/data/SAP_fin/settings/WBSReport.txt
Normal file
13
s3/data/SAP_fin/settings/WBSReport.txt
Normal file
@ -0,0 +1,13 @@
|
||||
SAP_fin
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
18
|
||||
Fiscal Yr,Period,WBS Element,Cost Elem.,Cost Elem.text,Value.Crcy,Offsetting acct,Off.acc.name,AuxAcctAsmnt_1,Name,Doc.Text,Posting Date,Doc.Date,Purchasing Doc.,DocumentNo,Post.Row,RefDocNo,Reversed
|
||||
fiscal_yr,period,wbs_element,cost_elem,cost_elem_text,value_crcy,offsetting_acct,off_acc_name,aux_acct_asmnt_1,name,doc_text,posting_date,doc_date,purchasing_doc,document_no,post_row,ref_doc_no,reversed
|
||||
src03a.sapf_wbsreport
|
||||
org03a.sapf_wbsreport
|
||||
|
||||
value_crcy
|
||||
7
s3/data/SAP_fin/settings/configmap.config
Normal file
7
s3/data/SAP_fin/settings/configmap.config
Normal file
@ -0,0 +1,7 @@
|
||||
/* SAPデータ取込:Finance */
|
||||
CostReport_[0-9]{8}_[0-9]{6}\.(TSV|tsv) CostReport.txt
|
||||
IOReport_[0-9]{8}_[0-9]{6}\.(TSV|tsv) IOReport.txt
|
||||
WBSReport_[0-9]{8}_[0-9]{6}\.(TSV|tsv) WBSReport.txt
|
||||
WBSList_[0-9]{8}_[0-9]{6}\.(TSV|tsv) WBSList.txt
|
||||
Invoice_[0-9]{8}_[0-9]{6}\.(TSV|tsv) Invoice.txt
|
||||
|
||||
13
s3/data/SAP_sup/settings/ConfReport.txt
Normal file
13
s3/data/SAP_sup/settings/ConfReport.txt
Normal file
@ -0,0 +1,13 @@
|
||||
SAP_sup
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
11
|
||||
Process Order,Operation / Activity,Yield,UoM,Posting date,Finish execution date,Material,Material description,Confirmation,Conf. Counter,Cancelled Conf.
|
||||
process_order,operation_activity,yield,uo_m,posting_date,finish_execution_date,material,material_description,confirmation,conf_counter,cancelled_conf
|
||||
src04.saps_confreport
|
||||
org04.saps_confreport
|
||||
|
||||
yield
|
||||
13
s3/data/SAP_sup/settings/GMReport.txt
Normal file
13
s3/data/SAP_sup/settings/GMReport.txt
Normal file
@ -0,0 +1,13 @@
|
||||
SAP_sup
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
13
|
||||
Process Order,Material document,Material document item,Material,Material description,Goods movement,Posting date,Movement type,D/C indicator,Storage location,Batch,Qty,Base Unit of Measure
|
||||
process_order,material_document,material_document_item,material,material_description,goods_movement,posting_date,movement_type,d_c_indicator,storage_location,batch,qty,base_unit_of_measure
|
||||
src04.saps_gmreport
|
||||
org04.saps_gmreport
|
||||
|
||||
qty
|
||||
13
s3/data/SAP_sup/settings/GRReport.txt
Normal file
13
s3/data/SAP_sup/settings/GRReport.txt
Normal file
@ -0,0 +1,13 @@
|
||||
SAP_sup
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
24
|
||||
Plant,Material,Material Description,Batch,Posting Date,Qty in Unit of Entry,Movement Type,Unit of Entry,Entry on,Material Document Year,Document Date,Total valuated stock,Base Unit of Measure,Quantity,Reference,Purchase Order,Customer,Amount,Amount in LC,Vendor,Item,Material Document,Storage Location,Movement Type Text
|
||||
plant,material,material_description,batch,posting_date,qty_in_unit_of_entry,movement_type,unit_of_entry,entry_on,material_document_year,document_date,total_valuated_stock,base_unit_of_measure,quantity,reference,purchase_order,customer,amount,amount_in_lc,vendor,item,material_document,storage_location,movement_type_text
|
||||
src04.saps_grreport
|
||||
org04.saps_grreport
|
||||
|
||||
qty_in_unit_of_entry,total_valuated_stock,quantity,amount
|
||||
13
s3/data/SAP_sup/settings/MLCReport.txt
Normal file
13
s3/data/SAP_sup/settings/MLCReport.txt
Normal file
@ -0,0 +1,13 @@
|
||||
SAP_sup
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
24
|
||||
Plant,Material,Material Description,Batch,Posting Date,Qty in Unit of Entry,Movement Type,Unit of Entry,Entry on,Material Document Year,Document Date,Total valuated stock,Base Unit of Measure,Quantity,Reference,Purchase Order,Customer,Amount,Amount in LC,Vendor,Item,Material Document,Storage Location,Movement Type Text
|
||||
plant,material,material_description,batch,posting_date,qty_in_unit_of_entry,movement_type,unit_of_entry,entry_on,material_document_year,document_date,total_valuated_stock,base_unit_of_measure,quantity,reference,purchase_order,customer,amount,amount_in_lc,vendor,item,material_document,storage_location,movement_type_text
|
||||
src04.saps_mlcreport
|
||||
org04.saps_mlcreport
|
||||
|
||||
qty_in_unit_of_entry,total_valuated_stock,quantity,amount
|
||||
13
s3/data/SAP_sup/settings/POReport.txt
Normal file
13
s3/data/SAP_sup/settings/POReport.txt
Normal file
@ -0,0 +1,13 @@
|
||||
SAP_sup
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
12
|
||||
Purchase Doc,Vendor,Item,Material,Short Text,Document Date,Order Quantity,Order Unit,Net Order Value,Currency,Price Unit,Del Indicator
|
||||
purchase_doc,vendor,item,material,short_text,document_date,order_quantity,order_unit,net_order_value,currency,price_unit,del_indicator
|
||||
src04.saps_poreport
|
||||
org04.saps_poreport
|
||||
|
||||
order_quantity,net_order_value
|
||||
13
s3/data/SAP_sup/settings/QAReport.txt
Normal file
13
s3/data/SAP_sup/settings/QAReport.txt
Normal file
@ -0,0 +1,13 @@
|
||||
SAP_sup
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
24
|
||||
Plant,Material,Material Description,Batch,Posting Date,Qty in Unit of Entry,Movement Type,Unit of Entry,Entry on,Material Document Year,Document Date,Total valuated stock,Base Unit of Measure,Quantity,Reference,Purchase Order,Customer,Amount,Amount in LC,Vendor,Item,Material Document,Storage Location,Movement Type Text
|
||||
plant,material,material_description,batch,posting_date,qty_in_unit_of_entry,movement_type,unit_of_entry,entry_on,material_document_year,document_date,total_valuated_stock,base_unit_of_measure,quantity,reference,purchase_order,customer,amount,amount_in_lc,vendor,item,material_document,storage_location,movement_type_text
|
||||
src04.saps_qareport
|
||||
org04.saps_qareport
|
||||
|
||||
qty_in_unit_of_entry,total_valuated_stock,quantity,amount
|
||||
13
s3/data/SAP_sup/settings/StockList.txt
Normal file
13
s3/data/SAP_sup/settings/StockList.txt
Normal file
@ -0,0 +1,13 @@
|
||||
SAP_sup
|
||||
|
||||
utf-8
|
||||
|
||||
LF
|
||||
1
|
||||
13
|
||||
SPL.stock Indic,Material Num.,Material Desc.,Storage Location,Batch Num.,Expired Date,Unrestricted Stock,In Quality Stock,Blocked Stock,Consign Stock,Total Stock Quantity,Sold to,Name
|
||||
spl_stock_indic,material_num,material_desc,storage_location,batch_num,expired_date,unrestricted_stock,in_quality_stock,blocked_stock,consign_stock,total_stock_quantity,sold_to,name
|
||||
src04.saps_stocklist
|
||||
org04.saps_stocklist
|
||||
StockList_ex.sql
|
||||
unrestricted_stock,in_quality_stock,blocked_stock,consign_stock,total_stock_quantity
|
||||
7
s3/data/SAP_sup/settings/StockList_ex.sql
Normal file
7
s3/data/SAP_sup/settings/StockList_ex.sql
Normal file
@ -0,0 +1,7 @@
|
||||
/* 蓄積スキーマ */
|
||||
/* execute_dateがnullのレコードを抽出し、取込ファイル名のyyyymmdd部分を切り出しセットする */
|
||||
update src04.saps_stocklist
|
||||
set
|
||||
execute_date = STR_TO_DATE(SUBSTRING(file_name,11,8),'%Y%m%d')
|
||||
where
|
||||
execute_date is null
|
||||
9
s3/data/SAP_sup/settings/configmap.config
Normal file
9
s3/data/SAP_sup/settings/configmap.config
Normal file
@ -0,0 +1,9 @@
|
||||
/* SAPデータ取込:SupplyChain */
|
||||
GRReport_[0-9]{8}_[0-9]{6}\.(TSV|tsv) GRReport.txt
|
||||
QAReport_[0-9]{8}_[0-9]{6}\.(TSV|tsv) QAReport.txt
|
||||
MLCReport_[0-9]{8}_[0-9]{6}\.(TSV|tsv) MLCReport.txt
|
||||
MLCReport_[0-9]{8}_[0-9]{6}\.(TSV|tsv) MLCReport.txt
|
||||
POReport_[0-9]{8}_[0-9]{6}\.(TSV|tsv) POReport.txt
|
||||
StockList_[0-9]{8}_[0-9]{6}\.(TSV|tsv) StockList.txt
|
||||
ConfReport_[0-9]{8}_[0-9]{6}\.(TSV|tsv) ConfReport.txt
|
||||
GMReport_[0-9]{8}_[0-9]{6}\.(TSV|tsv) GMReport.txt
|
||||
Loading…
x
Reference in New Issue
Block a user