-
Notifications
You must be signed in to change notification settings - Fork 0
/
crhelper.py
160 lines (134 loc) · 6.2 KB
/
crhelper.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
###################################################################################################
#### Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
####
#### Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file
#### except in compliance with the License. A copy of the License is located at
####
#### http://aws.amazon.com/apache2.0/
####
#### or in the "license" file accompanying this file. This file is distributed on an "AS IS"
#### BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#### License for the specific language governing permissions and limitations under the License.
###################################################################################################
from __future__ import print_function
import traceback
import logging
import threading
from time import sleep
from datetime import datetime
from botocore.vendored import requests
import json
def log_config(event, loglevel=None, botolevel=None):
if 'ResourceProperties' in event.keys():
if 'loglevel' in event['ResourceProperties'] and not loglevel:
loglevel = event['ResourceProperties']['loglevel']
if 'botolevel' in event['ResourceProperties'] and not botolevel:
botolevel = event['ResourceProperties']['botolevel']
if not loglevel:
loglevel = 'warning'
if not botolevel:
botolevel = 'error'
# Set log verbosity levels
loglevel = getattr(logging, loglevel.upper(), 20)
botolevel = getattr(logging, botolevel.upper(), 40)
mainlogger = logging.getLogger()
mainlogger.setLevel(loglevel)
logging.getLogger('boto3').setLevel(botolevel)
logging.getLogger('botocore').setLevel(botolevel)
# Set log message format
logfmt = '[%(requestid)s][%(asctime)s][%(levelname)s] %(message)s \n'
mainlogger.handlers[0].setFormatter(logging.Formatter(logfmt))
try:
return logging.LoggerAdapter(mainlogger, {'requestid': event['RequestId']})
except KeyError:
return logging.LoggerAdapter(mainlogger, {'requestid': event['id']})
def send(event, context, responseStatus, responseData, physicalResourceId,
logger, reason=None):
responseUrl = event['ResponseURL']
logger.debug("CFN response URL: " + responseUrl)
responseBody = {}
responseBody['Status'] = responseStatus
msg = 'See details in CloudWatch Log Stream: ' + context.log_stream_name
if not reason:
responseBody['Reason'] = msg
else:
responseBody['Reason'] = str(reason)[0:255] + '... ' + msg
if physicalResourceId:
responseBody['PhysicalResourceId'] = physicalResourceId
elif 'PhysicalResourceId' in event:
responseBody['PhysicalResourceId'] = event['PhysicalResourceId']
else:
responseBody['PhysicalResourceId'] = context.log_stream_name
responseBody['StackId'] = event['StackId']
try:
responseBody['RequestId'] = event['RequestId']
except KeyError:
responseBody['RequestId'] = event['id']
responseBody['LogicalResourceId'] = event['LogicalResourceId']
if responseData and responseData != {} and responseData != [] and isinstance(responseData, dict):
responseBody['Data'] = responseData
json_responseBody = json.dumps(responseBody)
logger.debug("Response body:\n" + json_responseBody)
headers = {
'content-type': '',
'content-length': str(len(json_responseBody))
}
try:
response = requests.put(responseUrl,
data=json_responseBody,
headers=headers)
logger.info("CloudFormation returned status code: " + response.reason)
except Exception as e:
logger.error("send(..) failed executing requests.put(..): " + str(e))
raise
# Function that executes just before lambda excecution times out
def timeout(event, context, logger):
logger.error("Execution is about to time out, sending failure message")
send(event, context, "FAILED", None, None, reason="Execution timed out",
logger=logger)
# Handler function
def cfn_handler(event, context, create, update, delete, logger, init_failed):
try:
logger.info("Lambda RequestId: %s CloudFormation RequestId: %s" %
(context.aws_request_id, event['RequestId']))
except KeyError:
logger.info("Lambda RequestId: %s CloudFormation RequestId: %s" %
(context.aws_request_id, event['id']))
# Define an object to place any response information you would like to send
# back to CloudFormation (these keys can then be used by Fn::GetAttr)
responseData = {}
# Define a physicalId for the resource, if the event is an update and the
# returned phyiscalid changes, cloudformation will then issue a delete
# against the old id
physicalResourceId = None
logger.debug("EVENT: " + str(event))
# handle init failures
if init_failed:
send(event, context, "FAILED", responseData, physicalResourceId,
init_failed, logger=logger)
raise
# Setup timer to catch timeouts
t = threading.Timer((context.get_remaining_time_in_millis()/1000.00)-0.5,
timeout, args=[event, context, logger])
t.start()
try:
# Execute custom resource handlers
logger.info("Received a %s Request" % event['RequestType'])
if event['RequestType'] == 'Create':
physicalResourceId, responseData = create(event, context)
elif event['RequestType'] == 'Update':
physicalResourceId, responseData = update(event, context)
elif event['RequestType'] == 'Delete':
delete(event, context)
# Send response back to CloudFormation
logger.info("Completed successfully, sending response to cfn")
send(event, context, "SUCCESS", responseData, physicalResourceId,
logger=logger)
# Catch any exceptions, log the stacktrace, send a failure back to
# CloudFormation and then raise an exception
except Exception as e:
logger.error(e, exc_info=True)
send(event, context, "FAILED", responseData, physicalResourceId,
reason=e, logger=logger)
finally:
t.cancel()