forked from aws/aws-health-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Health-Event-Chat-Post-LambdaFn.py
200 lines (176 loc) · 7.62 KB
/
Health-Event-Chat-Post-LambdaFn.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# Health-Event-Chat-Post-LambdaFn
# Lambda Function to post open issues from an AWS event to a
# Amazon Chime or Slack room setup by the ENDPOINTARRAY environment variable
# Input: eventArn - Event ARN to post to the Amazon Chime room
# lastUpdatedTime - String with when the event was last updated
# If not set defaults to blank
# Environment variables for DEBUG, MESSAGEPREFIX,ENDPOINTARRAY,CHATCLIENT
# ENDPOINTARRAY is required
# Output: returns a string of the time the event was last updated.
# Imports
import json # essential to read json
import os # required to read in the os variable for the Webhook
import logging # handy to keep track of things
import boto3 # AWS CLI, required to poll AWS Health
from urllib.request import Request, urlopen # for chime API
from urllib.error import URLError, HTTPError
# Static vars
MAXCHIMEPOST = 4096 # Maximum number of characters a chime post will accept
CHIMEPOSTTRIM = 3800 # Truncated message size, must be < (4096-message encoding)
STATUSLINK = 'http://status.aws.amazon.com' # Status URL for messages truncated
# Read in the OS environment variables, default any missing vars
# read in the debugging flag, if 1 enable debug log level and some messages, defaults to 0
DEBUG = int(os.getenv('DEBUG', 0)) # set DEBUG environment variable to 1 to enable testing
# read in the BAIL_NOCHANGE flag. defaults to 0
BAIL = int(os.getenv('BAIL_NOCHANGE', 0)) # set to 1 to enable bailing on no update
# Setting up logging, default to INFO level
logger = logging.getLogger()
if (DEBUG):
logger.setLevel(logging.DEBUG)
logger.debug("DEBUGGING ON") # send debug status
else:
logger.setLevel(logging.INFO)
# read in the chime message tag to add to the post
MESSAGEPREFIX = str(os.getenv('MESSAGEPREFIX', '[AUTO Post] '))
logger.debug("MESSAGEPREFIX= %s" % MESSAGEPREFIX)
# read in the chat client type, default to chime
CHATCLIENT = str(os.getenv('CHATCLIENT', 'chime'))
logger.debug("CHATCLIENT= %s" % CHATCLIENT)
# Trims a message to fit inside the Chime character limit
# Input: message to shorten
# Expects CHIMEPOSTTRIM and STATUSLINK static vars
# Output: shortened message, with a decription of the trim
def chimeTrimMessage(message):
if (DEBUG): logger.debug(("Message Original Length: %i") % (len(message)))
message = message[:CHIMEPOSTTRIM] + "\n\n[MESSAGE TRUNCATED] For the full message refer to " + STATUSLINK
if (DEBUG): logger.debug(("Message Truncated Length: %i") % (len(message)))
return message
# Post the message to an sns topic
# Inputs: topic - arn of the topic to publish to
# message - message string
def snsMessage(topic, subject, message):
client = boto3.client("sns")
return client.publish(TopicArn=topic, Message=message, Subject=subject)
# Post the message to the chat webhook
# Inputs: message - supported message string
# webhook - webhook URL to post the message to
# type - 'chime'||'slack' as a string, defaults to chime
# Output 1 on success
def chatMessage(message, subject, webhook, type):
# detect if we are using slack, otherwise default to chime
if (type == 'slack'):
chat_message = {'text': message}
elif type == 'sns':
return snsMessage(webhook, subject, message)
else:
chat_message = {'Content': message}
# log the chat message
logger.info(str(chat_message))
# post the message, catch and log any errors
header = {'Content-type': 'application/json'}
req = Request(webhook, json.dumps(chat_message), header)
try:
response = urlopen(req)
response.read()
if (type == 'slack'):
logger.info("Message posted: %s", chat_message['text'])
else:
logger.info("Message posted: %s", chat_message['Content'])
except HTTPError as e:
logger.error(e)
eMessage = "WEBHOOK HTTP Request failed : %d %s" % (e.code, e.reason)
logger.error(eMessage)
raise Exception(eMessage)
except URLError as e:
logger.error(e)
eMessage = "WEBHOOK Server connection failed: %s" % (e.reason)
logger.error(eMessage)
raise Exception(eMessage)
# successful post
return 1
# Given an event return its latest description string
# Input: eventArn - string with the events AWS ARN
# Output: one detail object
def eventDetailedDesc(eventArn):
# Load the AWS Health API
health = boto3.client('health', region_name='us-east-1')
# get the event details
try:
details = health.describe_event_details(
eventArns=[eventArn]
)
except Exception as e:
logger.error(e)
eMessage = 'ERROR: Unable to retrieve events details'
logger.error(eMessage)
raise Exception(eMessage)
# we expect one success result, otherwise raise an exception
if (len(details['successfulSet']) is not 1):
eMessage = "Unable to retrieve details for event ARN: %s" % eventArn
logger.error(eMessage)
raise Exception(eMessage)
# since we know we only have one, return the 1 detail
return details['successfulSet'][0]
#
# Main lambda function
#
def lambda_handler(event, context):
# read in the webhook to post too, since it is required error out if it is missing
try:
ENDPOINTARRAY = str(os.environ['ENDPOINTARRAY'])
ENDPOINTARRAY = json.loads(ENDPOINTARRAY)
except Exception as e:
logger.error(e)
eMessage = 'ERROR: Missing ENDPOINTARRAY Environment Variable for the Lambda Function!'
logger.error(eMessage)
raise Exception(eMessage)
# read in the eventArn, error out if it is missing
try:
eventArn = event['eventArn']
except Exception as e:
logger.error(e)
eMessage = 'ERROR: Invalid input, Event ARN Invalid!'
logger.error(eMessage)
raise Exception(eMessage)
# read in the events name from the ARN
# Health ARN Pattern: arn:aws:health:[^:]*:[^:]*:event/[\w-]+
eventIDPos = eventArn.rfind('/')
if (eventIDPos > 1): eventIDPos = eventIDPos + 1
eventName = eventArn[eventIDPos:]
logger.info("Event name: %s" % (eventName))
# read in the lastUpdatedTime, if missing default to blank
try:
lastUpdatedTime = event['lastUpdatedTime']
except Exception as e:
eMessage = "WARN: Missing lastUpdatedTime defaulting to blank."
logger.debug(eMessage)
lastUpdatedTime = ''
logger.debug("lastUpdatedTime: %s" % (lastUpdatedTime))
# get the latest detailed description of the event
detail = eventDetailedDesc(eventArn)
# we want the events latest description
latestDesc = detail['eventDescription']['latestDescription']
# get this details last updated time
curLastUpdatedTime = str(detail['event']['lastUpdatedTime'])
logger.debug("curLastUpdatedTime: %s" % (curLastUpdatedTime))
if (curLastUpdatedTime == lastUpdatedTime):
if BAIL == 1:
return curLastUpdatedTime
latestDesc = "Event unchanged since the last update."
# create the latest description posting
message = ''
eventNameStr = "[%s] " % eventName
if (DEBUG):
message = '[TESTING] PLEASE IGNORE DEBUGGING:\n'
message = message + MESSAGEPREFIX
if CHATCLIENT == 'sns':
message = message + '\n\n'
message = message + eventNameStr + latestDesc
# trim messages larger than the maximum post size (4KB)
if (len(message) > MAXCHIMEPOST):
message = chimeTrimMessage(message)
# post the message to the array of webhooks
for webhook in ENDPOINTARRAY:
chatMessage(message, eventNameStr, webhook, CHATCLIENT)
# return the current updated time as the last updated time
return curLastUpdatedTime