Guest User

Untitled

a guest
Feb 20th, 2018
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.50 KB | None | 0 0
  1. import copy
  2. import json
  3. import re
  4. import time
  5.  
  6. import boto3
  7. from botocore.vendored import requests
  8.  
  9. SUCCESS = "SUCCESS"
  10. FAILED = "FAILED"
  11. FAILED_PHYSICAL_RESOURCE_ID = "FAILED_PHYSICAL_RESOURCE_ID"
  12.  
  13.  
  14. class AddOrUpdateTargetArnsError(Exception):
  15. def __init__(self):
  16. self.message = 'Target arns are not allowed to be changed/added.'
  17. super().__init__(self.message)
  18.  
  19.  
  20. class FailedVpcLinkError(Exception):
  21. def __init__(self, status_message):
  22. self.message = f'statusMessages: {status_message}'
  23. super().__init__(self.message)
  24.  
  25.  
  26. def lambda_handler(event, context):
  27. try:
  28. _lambda_handler(event, context)
  29. except Exception as e:
  30. send(
  31. event,
  32. context,
  33. response_status=FAILED,
  34. # Do not fail on delete to avoid rollback failure
  35. response_data=None,
  36. physical_resource_id=event.get('PhysicalResourceId', FAILED_PHYSICAL_RESOURCE_ID),
  37. reason=e
  38. )
  39. # Must raise, otherwise the Lambda will be marked as successful, and the exception
  40. # will not be logged to CloudWatch logs.
  41. raise
  42.  
  43.  
  44. def _lambda_handler(event, context):
  45. print("Received event: ")
  46. print(event)
  47.  
  48. resource_type = event['ResourceType']
  49. if resource_type != "Custom::ApiGatewayVpcLink":
  50. raise ValueError(f'Unexpected resource_type: {resource_type}')
  51.  
  52. request_type = event['RequestType']
  53. wait_for = event.get('WaitFor', None)
  54. resource_properties = event['ResourceProperties']
  55. physical_resource_id = event.get('PhysicalResourceId', None)
  56.  
  57. apigateway = boto3.client('apigateway')
  58.  
  59. if wait_for:
  60. handle_self_invocation(
  61. wait_for=wait_for,
  62. physical_resource_id=physical_resource_id,
  63. event=event,
  64. context=context,
  65. )
  66. else:
  67. if request_type == 'Create':
  68. kwargs = dict(
  69. name=resource_properties['Name'],
  70. targetArns=resource_properties['TargetArns'],
  71. description=resource_properties.get('Description', None)
  72. )
  73. response = apigateway.create_vpc_link(**kwargs)
  74. event_copy = copy.deepcopy(event)
  75. event_copy['WaitFor'] = 'CreateComplete'
  76. event_copy['PhysicalResourceId'] = response['id']
  77.  
  78. print('Reinvoking function because VPC link creation is asynchronous')
  79. relaunch_lambda(event=event_copy, context=context)
  80. return
  81.  
  82. elif request_type == 'Update':
  83. old_resource_properties = event['OldResourceProperties']
  84.  
  85. current_target_arns = apigateway.get_vpc_link(
  86. vpcLinkId=physical_resource_id,
  87. )['targetArns']
  88.  
  89. # must compare current_target_arns to resource_properties['TargetArns'], to protect against
  90. # UPDATE created by UPDATE_FAILED. In that particular case, current_target_arns will be the same as
  91. # resource_properties['TargetArns'] but different than old_resource_properties['TargetArns']
  92. if set(current_target_arns) != set(resource_properties['TargetArns']) and
  93. set(resource_properties['TargetArns']) != set(old_resource_properties['TargetArns']):
  94. raise AddOrUpdateTargetArnsError()
  95.  
  96. patch_operations = []
  97.  
  98. if resource_properties['Name'] != old_resource_properties['Name']:
  99. patch_operations.append(dict(
  100. op='replace',
  101. path='/name',
  102. value=resource_properties['Name'],
  103. ))
  104.  
  105. if 'Description' in resource_properties and 'Description' in old_resource_properties:
  106. if resource_properties['Description'] != old_resource_properties['Description']:
  107. patch_operations.append(dict(
  108. op='replace',
  109. path='/description',
  110. value=resource_properties['Description'],
  111. ))
  112. elif 'Description' in resource_properties and 'Description' not in old_resource_properties:
  113. patch_operations.append(dict(
  114. op='replace',
  115. path='/description',
  116. value=resource_properties['Description'],
  117. ))
  118. elif 'Description' not in resource_properties and 'Description' in old_resource_properties:
  119. patch_operations.append(dict(
  120. op='replace',
  121. path='/description',
  122. value=None,
  123. ))
  124.  
  125. apigateway.update_vpc_link(
  126. vpcLinkId=physical_resource_id,
  127. patchOperations=patch_operations,
  128. )
  129.  
  130. elif request_type == 'Delete':
  131. delete = True
  132.  
  133. if physical_resource_id == FAILED_PHYSICAL_RESOURCE_ID:
  134. delete = False
  135. print('Custom resource was never properly created, skipping deletion.')
  136.  
  137. stack_name = re.match("arn:aws:cloudformation:.+:stack/(?P<stack_name>.+)/.+", event['StackId']).group('stack_name')
  138. if stack_name in physical_resource_id:
  139. delete = False
  140. print(f'Skipping deletion, because VPC link was not created properly. Heuristic: stack name ({stack_name}) found in physical resource ID ({physical_resource_id})')
  141.  
  142. logical_resource_id = event['LogicalResourceId']
  143. if logical_resource_id in physical_resource_id:
  144. delete = False
  145. print(f'Skipping deletion, because VPC link was not created properly. Heuristic: logical resource ID ({logical_resource_id}) found in physical resource ID ({physical_resource_id})')
  146.  
  147. if delete:
  148. apigateway.delete_vpc_link(
  149. vpcLinkId=physical_resource_id
  150. )
  151. event_copy = copy.deepcopy(event)
  152. event_copy['WaitFor'] = 'DeleteComplete'
  153. print('Reinvoking function because VPC link deletion is asynchronous')
  154. relaunch_lambda(event=event_copy, context=context)
  155. return
  156.  
  157. else:
  158. print(f'Request type is {request_type}, doing nothing.')
  159.  
  160. send(
  161. event,
  162. context,
  163. response_status=SUCCESS,
  164. response_data=None,
  165. physical_resource_id=physical_resource_id,
  166. )
  167.  
  168.  
  169. def handle_self_invocation(wait_for, physical_resource_id, event, context):
  170. apigateway = boto3.client('apigateway')
  171. if wait_for == 'CreateComplete':
  172. print('Waiting for creation of VPC link: {vpc_link_id}'.format(vpc_link_id=physical_resource_id))
  173. response = apigateway.get_vpc_link(
  174. vpcLinkId=physical_resource_id,
  175. )
  176. status = response['status']
  177. print('Status of VPC link {vpc_link_id} is {status}'.format(vpc_link_id=physical_resource_id, status=status))
  178.  
  179. if status == 'AVAILABLE':
  180. send(
  181. event,
  182. context,
  183. response_status=SUCCESS,
  184. response_data=None,
  185. physical_resource_id=physical_resource_id,
  186. )
  187. elif status == 'FAILED':
  188. raise FailedVpcLinkError(status_message=response['statusMessage'])
  189. elif status == 'PENDING':
  190. # Sleeping here to avoid polluting CloudWatch Logs by reinvoking the Lambda too quickly
  191. time.sleep(30)
  192. relaunch_lambda(event, context)
  193. else:
  194. print('Unexpected status, doing nothing')
  195.  
  196. elif wait_for == 'DeleteComplete':
  197. print('Waiting for deletion of VPC link: {vpc_link_id}'.format(vpc_link_id=physical_resource_id))
  198. try:
  199. response = apigateway.get_vpc_link(
  200. vpcLinkId=physical_resource_id,
  201. )
  202. except apigateway.exceptions.NotFoundException:
  203. print('VPC link {vpc_link_id} deleted successfully'.format(vpc_link_id=physical_resource_id))
  204. send(
  205. event,
  206. context,
  207. response_status=SUCCESS,
  208. response_data=None,
  209. physical_resource_id=physical_resource_id,
  210. )
  211. else:
  212. status = response['status']
  213. assert status == 'DELETING', f'status is {status}'
  214. # Sleeping here to avoid polluting CloudWatch Logs by reinvoking the Lambda too quickly
  215. time.sleep(10)
  216. relaunch_lambda(event, context)
  217. else:
  218. raise ValueError(f'Unexpected WaitFor: {wait_for}')
  219.  
  220.  
  221. def relaunch_lambda(event, context):
  222. boto3.client("lambda").invoke(
  223. FunctionName=context.function_name,
  224. InvocationType='Event',
  225. Payload=json.dumps(event),
  226. )
  227.  
  228.  
  229. def send(event, context, response_status, response_data, physical_resource_id, reason=None):
  230. response_url = event['ResponseURL']
  231.  
  232. response_body = {
  233. 'Status': response_status,
  234. 'Reason': str(reason) if reason else 'See the details in CloudWatch Log Stream: ' + context.log_stream_name,
  235. 'PhysicalResourceId': physical_resource_id,
  236. 'StackId': event['StackId'],
  237. 'RequestId': event['RequestId'],
  238. 'LogicalResourceId': event['LogicalResourceId'],
  239. 'Data': response_data,
  240. }
  241.  
  242. json_response_body = json.dumps(response_body)
  243.  
  244. headers = {
  245. 'content-type': '',
  246. 'content-length': str(len(json_response_body))
  247. }
  248.  
  249. try:
  250. requests.put(
  251. response_url,
  252. data=json_response_body,
  253. headers=headers
  254. )
  255. except Exception as e:
  256. print("send(..) failed executing requests.put(..): " + str(e))
Add Comment
Please, Sign In to add comment