import logging
import ssl
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
import email.encoders
import smtplib
import datetime
from email.utils import formatdate
import boto3
import json
from botocore.exceptions import ClientError
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
smtp_user_name = 'AKIA3NMSGNFDFKX11111'
password = 'BOg9yyu+HxqjXOVlHtq0S7FYI0PKZEo97YX8eQ22222'
CRLF = "\r\n"
port = 587
attendees = ["att@example.com", ]
organizer = "devops@example.com"
alias = 'DevOps'
location = 'Online'
fro = f"{alias} <{organizer}>"
time_zone = 8
GMT_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
error_msg = {
'statusCode': 1,
'body': json.dumps('格式错误')
}
def build_calendar_invitation_msg(msg_organizer,
msg_attendees,
msg_subject,
msg_description,
msg_summary,
msg_body,
ddt_start,
duration):
dt_end = ddt_start + duration
dt_stamp = datetime.datetime.now().strftime("%Y%m%dT%H%M%SZ")
dt_start = ddt_start.strftime("%Y%m%dT%H%M%SZ")
dt_end = dt_end.strftime("%Y%m%dT%H%M%SZ")
attendee = ""
for att in msg_attendees:
attendee += "ATTENDEE;CUTYPE=INDIVIDUAL;" \
"ROLE=REQ-PARTICIPANT;" \
"PARTSTAT=ACCEPTED;" \
"RSVP=TRUE;" \
f"CN={att}:" \
f"MAILTO:{att}" + CRLF
ical = "BEGIN:VCALENDAR" + CRLF \
+ "PRODID:pyICSParser" + CRLF \
+ "VERSION:2.0" + CRLF \
+ "BEGIN:VTIMEZONE" + CRLF \
+ "TZID:China Standard Time" + CRLF \
+ "BEGIN:STANDARD" + CRLF \
+ "DTSTART:16010101T000000" + CRLF \
+ "TZOFFSETFROM:+0800" + CRLF \
+ "TZOFFSETTO:+0800" + CRLF \
+ "END:STANDARD" + CRLF \
+ "BEGIN:DAYLIGHT" + CRLF \
+ "DTSTART:16010101T000000" + CRLF \
+ "TZOFFSETFROM:+0800" + CRLF \
+ "TZOFFSETTO:+0800" + CRLF \
+ "END:DAYLIGHT" + CRLF \
+ "END:VTIMEZONE" + CRLF \
+ "CALSCALE:GREGORIAN" + CRLF \
+ "METHOD:REQUEST" + CRLF \
+ "BEGIN:VEVENT" + CRLF \
+ f"DTSTART;TZID=China Standard Time:{dt_start}{CRLF}" \
+ f"DTEND;TZID=China Standard Time:{dt_end}{CRLF}" \
+ f"DTSTAMP:{dt_stamp}{CRLF}" \
+ f'ORGANIZER;CN="{alias}":mailto:{msg_organizer}{CRLF}' \
+ f"UID:FIXMEUID{dt_stamp}{CRLF}" \
+ attendee + "CREATED:" + dt_stamp + CRLF \
+ "DESCRIPTION;LANGUAGE=zh-CN:" + msg_description + CRLF \
+ "LAST-MODIFIED:" + dt_stamp + CRLF \
+ "LOCATION;LANGUAGE=zh-CN:" + location + CRLF \
+ "SEQUENCE:0" + CRLF \
+ "STATUS:CONFIRMED" + CRLF \
+ "SUMMARY;LANGUAGE=zh-CN:" + msg_summary + CRLF \
+ "TRANSP:OPAQUE" + CRLF \
+ "BEGIN:VALARM" + CRLF \
+ "DESCRIPTION:REMINDER" + CRLF \
+ "TRIGGER;RELATED=START:-PT15M" + CRLF \
+ "ACTION:DISPLAY" + CRLF \
+ "END:VALARM" + CRLF \
+ "END:VEVENT" + CRLF \
+ "END:VCALENDAR" + CRLF
msg = MIMEMultipart('mixed')
msg['Reply-To'] = fro
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = msg_subject
msg['From'] = fro
msg['To'] = ",".join(msg_attendees)
part_email = MIMEText(msg_body, "html")
part_cal = MIMEText(ical, 'calendar;method="REQUEST"')
msg_alternative = MIMEMultipart('alternative')
msg.attach(msg_alternative)
ical_atch_file_name = "invite.ics"
ical_atch = MIMEBase('application/ics', ' ;name="%s"' % ical_atch_file_name)
ical_atch.set_payload(ical)
email.encoders.encode_base64(ical_atch)
ical_atch.add_header('Content-Disposition', 'attachment; filename="%s"' % ical_atch_file_name)
msg_alternative.attach(part_email)
msg_alternative.attach(part_cal)
return msg
def send_email(msg):
boto3_session = boto3.Session()
region = boto3_session.region_name
smtp_server = f'email-smtp.{region}.amazonaws.com'
context = ssl.create_default_context()
with smtplib.SMTP(host=smtp_server, port=port) as server:
server.starttls(context=context)
server.login(smtp_user_name, password)
server.sendmail(fro, attendees, msg.as_string())
logging.info("Mail sent. Check your inbox!")
def validate_dt_field(filed):
if filed is None:
return 'N/A'
dt_filed = gmt_to_datetime(filed)
dt_filed = dt_filed.replace(tzinfo=datetime.timezone(datetime.timedelta(hours=0)))
dt_filed = dt_filed.astimezone(datetime.timezone(datetime.timedelta(hours=8)))
return dt_filed.strftime("Beijing Time %Y-%m-%d %H:%M")
def gmt_to_datetime(gmt_dt):
return datetime.datetime.strptime(gmt_dt, GMT_FORMAT)
def lambda_handler(event, context):
if 'detail' not in event:
return error_msg
phd_event_detail = event['detail']
if 'eventTypeCategory' not in phd_event_detail:
return error_msg
event_type_category = phd_event_detail['eventTypeCategory']
if event_type_category != 'accountNotification' and event_type_category != 'scheduledChange':
return error_msg
# 获取AWS Health受影响的资源ARN,需要订阅Enterprise Support或者On-Ramp
# 未订阅的情况下,会输出一行错误日志,但不影响逻辑执行
health_client = boto3.client('health')
entity_arn_list = []
try:
response = health_client.describe_affected_entities(
filter={
'eventArns': [
phd_event_detail['eventArn']
]
}
)
if 'entities' in response:
entity_arn_list = map(lambda entity: entity.get('entityArn'), response['entities'], )
except ClientError as ex:
logging.error(ex)
subject = 'AWS Health事件通知 - ' + phd_event_detail['eventTypeCode']
description = '来自于AWS Health事件通知,请关注维护事件,提前规划维护窗口'
template = '''
<table>
<tr><td>Event Arn</td><td>{}</td></tr>
<tr><td>Service</td><td>{}</td></tr>
<tr><td>EventType Code</td><td>{}</td></tr>
<tr><td>EventType Category</td><td>{}</td></tr>
<tr><td>Start Time</td><td>{}</td></tr>
<tr><td>End Time</td><td>{}</td></tr>
<tr><td>Latest Description</td><td>{}</td></tr>
<tr><td>Affected Entities</td><td>{}</td></tr>
</table>
'''
affected_entities_temple = '<ul>'
for entity_arn in entity_arn_list:
affected_entities_temple += f'<li>{entity_arn}</li>'
affected_entities_temple += '</ul>'
eml_body = template.format(phd_event_detail['eventArn'], phd_event_detail['service'],
phd_event_detail['eventTypeCode'], phd_event_detail['eventTypeCategory'],
validate_dt_field(phd_event_detail.get('startTime')),
validate_dt_field(phd_event_detail.get('endTime')),
phd_event_detail['eventDescription'][0]['latestDescription'],
affected_entities_temple)
start_ddt = gmt_to_datetime(phd_event_detail['startTime'])
start_ddt = start_ddt - datetime.timedelta(days=2, hours=time_zone) # 提前48小时提醒
event_dur = datetime.timedelta(hours=1)
summary = "AWS Health事件通知 - " + phd_event_detail['eventTypeCode'] + start_ddt.strftime("%Y-%m-%d %H:%M")
phd_msg = build_calendar_invitation_msg(organizer, attendees, subject, description,
summary, eml_body, start_ddt, event_dur)
send_email(phd_msg)
return {
'statusCode': 200,
'body': json.dumps('AWS Health邮件发送成功')
}