우선 Lambda 를 생성하고 EvetnBridge 를 Trigger 로 설정하여 스케쥴링을 할 예정입니다.
Lambda 를 생성하다 보니 Lambda Function 자체적으로 URL 을 할당받을 수 있어서 API Gateway 를 사용안하기로 했습니다.
단순 데이터를 받으려고 했고 API Gateway 사용이 필요할만한 작업은 아닙니다. 또 IAM (권한) 에 대해선 따로 작성하지 않았습니다. 이번에 생성한 Lambda 는 S3의 템플릿과 SES 서비스를 호출해야해서 두 서비스에 대한 IAM 만 부여했으니 참고해주세요.
1. Lambda Function 생성
우선 Lambda Fuction 을 생성하고 Trigger 로 EventBridge 를 선택합니다. 위에 사진에서 Layers 가 (1) 인 이유는 문서 수정에 필요한 라이브러리가 Lambda Fuction 에서 제공하지 않아서 입니다. Lambda 에서 제공하지 않은 라이브러리는 Layers 에 등록한 후 사용가능합니다. 이번 포스팅에서는 문서 수정하는것을 다루지 않으니 참고해주세요
2. EventBridge 생성
다음으로는 EventBridge 를 설정해줍니다. 저는 Cron 방식으로 설정했는데 리눅스 cron 과 동일한 형태이긴 하지만 약간 차이가 있습니다. 우선 기본적인 순서는 [ 분, 시간, 일, 월, 요일, 년] 으로 설정하게 되어 있습니다. 리눅스와 다르게 연도도 설정가능합니다.
그 다음으로 입력할 수 있는 데이터 입니다. 리눅스와 동일하게 숫자 혹은 * (와일드카드) 로 설정가능합니다. 하지만 ? 가 추가되어 약간 혼란스러웠습니다. ? 는 언제일지 모를때(?) 사용합니다. 예를들어 그림의 내용은 3일 10시 10분 에 이벤트가 발생할 텐데 3일이 무슨요일인지는 매달 다를겁니다. 때문에 Day of week 항목에는 ? 를 넣어줘야합니다.
정상적으로 입력이 되었다면 하단에 언제수행되는지 보입니다. 혹시 보이지 않는다면 잘못 입력을 하였으니 다시 입력을 해봐야합니다.
3. Lambda Code
import json
import base64
import io
import os
from tempfile import NamedTemporaryFile
from botocore.exceptions import ClientError
import botocore
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
import boto3
from datetime import datetime, timedelta
from openpyxl import load_workbook
from openpyxl.styles import Alignment
s3 = boto3.resource('s3')
def download_file(bucket, key):
try:
file = NamedTemporaryFile(suffix = '.xlsx', delete=False)
bucket = s3.Bucket(bucket)
response = bucket.download_file(key, file.name)
return file.name
except ClientError as e:
if e.response['Error']['Code'] == "404":
return None
else:
raise
else:
raise
def upload_file(file_name, bucket, object_name=None):
if object_name is None:
object_name = os.path.basename(file_name)
s3_client = boto3.client('s3')
try:
response = s3_client.upload_file(file_name, bucket, object_name)
except ClientError as e:
logging.error(e)
return False
return True
def lambda_handler(event, context):
# MAIL Config
SENDER = "Sender Email Address"
RECIPIMENT = "Receiver Email Address"
AWS_REGION = "ap-northeast-2"
CHARSET = "UTF-8"
SUBJECT = "Subject"
client = boto3.client('ses', region_name=AWS_REGION)
# EventBridge Schedule 로 동작할 때
try:
if event["detail-type"] == "Scheduled Event":
BODY_HTML = """
<html>
<head></head>
<body>
<h3>주간 보고</h3>
<h5>업무시간이 52시간 인 경우 '메일 보내기', 시간을 직접 입력하실 경우 '다시 작성하기'를 눌러주세요</h5>
</br>
<p><a href="https://[Lambdafunction URL]/" style="text-decoration: none; color:#555">메일 보내기</a></p>
<p><a href="https://[S3 WebHosting URL]" style="text-decoration: none; color:#555">다시 작성하기</a></p>
</body>
</html>
"""
response = client.send_email(
Destination = {
'ToAddresses': [
RECIPIMENT,
],
},
Message = {
'Body' : {
'Html' : {
'Charset' : CHARSET,
'Data' : BODY_HTML,
}
},
'Subject':{
'Charset': CHARSET,
'Data' : SUBJECT,
},
},
Source=SENDER,
)
return '메일함을 확인해주세요'
else:
return event
# URL 호출을 받아 동작할때
except:
file = download_file([Bucket Name], [Object key])
today = datetime.today()
excel_document = load_workbook(file)
try:
# 입력값이 있을경우
if event["body"]:
decode_dt = base64.b64decode(event["body"])
str_dt = decode_dt.decode('ascii')
rate_data = str_dt.split('=')[1]
# 입력받은 데이터를 통해 엑셀 수정 후 저장
excel_document.save(file)
# 입력값이 없을 경우
except:
# 기본 데이터(52)를 통해 엑셀 수정 후 저장
rate_data = '52'
excel_document.save(file)
try:
# 헤더의 sec-fetch-mode 가 navigate 인 경우 수정한 엑셀 파일을 첨부하여 메일 송부
if event["headers"]["sec-fetch-mode"] == "navigate":
msg = MIMEMultipart('mixed')
msg['Subject'] = SUBJECT
msg['From'] = SENDER
msg['To'] = RECIPIMENT
msg_body = MIMEMultipart('alternative')
BODY_HTML = """
<html>
<head></head>
<body>
<h3>주간 업무시간 보고</h3>
</body>
</html>
"""
textpart = MIMEText(BODY_HTML.encode(CHARSET), 'plain', CHARSET)
htmlpart = MIMEText(BODY_HTML.encode(CHARSET), 'html', CHARSET)
msg_body.attach(textpart)
msg_body.attach(htmlpart)
att = MIMEApplication(open(file, 'rb').read())
att.add_header('Content-Disposition', 'attachment', filename=os.path.basename([첨부파일 명]))
msg.attach(msg_body)
msg.attach(att)
response = client.send_raw_email(
Source=SENDER,
Destinations=[RECIPIMENT],
RawMessage = {
'Data':msg.as_string()
}
)
upload_file(file, [Bucket Name], [Object key])
except ClientError as e:
return e.response['Error']['Message']
else:
with open('return.html', 'r', encoding='utf-8') as h:
content = h.read()
return {
"statusCode": 200,
"headers": {
'Content-Type': 'text/html'
},
"body": content
}
- Library
- base64, io : html form 에서 요청받은 데이터가 2진 데이터 이기 때문에 데이터를 인코딩 하기 위해 사용
- os, tempfile : S3 에서 파일을 읽을 때 임시파일로 저장하기 위해 사용
- botocore, boto3 : AWS 서비스들에 요청을 보내기 위해 사용
- email : Email 을 보낼 때 단순 메세지가 아니라 첨부파일이 포함된 메세지를 보내기 위해 사용
- datetime : 시간 데이터를 다루기 위해 사용
- openpyxl : 엑셀(첨부파일) 을 다루기 위해 사용, 기본적으로 제공되지 않아 Layer 에 등록해서 사용해야 함
- download_file 함수 : 버킷에서 기존 엑셀파일을 다운로드 받기 위한 함수
- upload_file 함수 : 다운로드 받은 함수를 수정 후 S3 에 업로드 하기 위한 함수
- Lambda_handler
- #EventBridge Schedule 로 동작할때 : 이벤트브릿지 스케쥴로 동작할때는 event 의 데이터 중 detail-type 에Scheduled Event 를 전달받는다 이 경우에는 첨부파일을 첨부하지 않고 정규적인 내용의 데이터를 보낸다. 정규적인 내용에는 주기적으로 받는 데이터인지 (52시간인지?) 혹은 데이터의 수정이 필요한지를 묻는 내용이 포함되어 있다.
- # URL 호출을 받아 동작할 때 : 이 경우 download_file 함수를 활용해 기존 엑셀파일을 다운로드를 받고 엑셀을수정하여 메일에 첨부한 후 보내게 된다. 이때 입력값이 있을 경우 (데이터 수정이 필요할때) 혹은 입력값이 없을경우 (데이터 수정이 필요없을때) 로 나누어 엑셀파일을 수정한 후 메일을 보내게 된다.
- 기타 : 마지막에 헤더부분의 sec-fetch-mode 가 navigate 인 경우에만 메일을 보내게 조건문을 달았다. 처음엔이 부분을 하지 않았는데 lambda 를 호출하는 html form 에서 post 메소드를 사용하여 호출하게 되면 보안상cors (교차 출처 리소스 공유) 를 통해 입력 데이터의 출처를 체크한다. 이때 1번의 post 와 1번의 get 을 받기 때문에 요청은 1번이였지만 메일은 2번 발송하게 된다. 따라서 1번의 post 와 get 을 식별하기 위해 저 부분을 작성하였다. request 데이터를 비교해 다른 부분을 작성하였으니 저 부분은 메소드 값으로 구분해도 무방하다.
- Return :
마지막 return 부분에서 headers 에 content-Type을 text/html 로 보내고 람다에 만들어뒀던 return.html 을 불러와서 body 에 넣어 보내면 return.html 로 응답을 받게 된다.
'AWS > Auto-Report' 카테고리의 다른 글
Auto Report (using AWS Lambda, SES) (0) | 2023.01.21 |
---|