Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 582 lines (543 sloc) 21.3 KB
#!/usr/bin/env python
import cfnresponse
import botocore
import boto3
import json
import os
import sys
from uuid import uuid4
from jsonpath import jsonpath
from time import sleep
from traceback import print_exc
from retrying import retry
class Provider:
def __init__(self):
self.default_wait_secs = 5
self.verbose = bool(int(os.getenv('VERBOSE', 0)))
self.profile = os.getenv('AWS_PROFILE')
try:
self.region = os.getenv('AWS_DEFAULT_REGION')
assert self.region
except:
try:
self.region = os.getenv('AWS_REGION')
assert self.region
except:
self.region = 'us-east-1'
self.session = boto3.session.Session()
boto3.setup_default_session()
if self.profile:
print('profile={} region={}'.format(self.profile, self.region))
self.session = boto3.session.Session(profile_name=self.profile)
boto3.setup_default_session(profile_name=self.profile)
def get_response(self, agent_attr, **agent_kwargs):
# TBC: better way to differentiate between resources and clients
if 'ResourceName' in agent_kwargs and 'ResourceId' in agent_kwargs:
return agent_attr(agent_kwargs['ResourceId'])
else:
return agent_attr(**agent_kwargs)
def get_resource(self, resource, expr):
return eval(expr)
def wait_event(self, agent, event, create=False, update=False, delete=False):
resource_key = 'ResourceProperties'
try:
no_echo = event[resource_key]['NoEcho'].lower()
except:
no_echo = 'false'
if update:
try:
agent_query_value = event[resource_key]['AgentWaitUpdateQueryValues']
except:
agent_query_value = None
try:
agent_exceptions = []
for ex in event[resource_key]['AgentWaitUpdateExceptions']:
agent_exceptions.append(eval(ex))
except:
agent_exceptions = None
if create:
try:
agent_query_value = event[resource_key]['AgentWaitCreateQueryValues']
except:
agent_query_value = None
try:
agent_exceptions = []
for ex in event[resource_key]['AgentWaitCreateExceptions']:
agent_exceptions.append(eval(ex))
except:
agent_exceptions = None
if delete:
try:
agent_query_value = event[resource_key]['AgentWaitDeleteQueryValues']
except:
agent_query_value = None
try:
agent_exceptions = []
for ex in event[resource_key]['AgentWaitDeleteExceptions']:
agent_exceptions.append(eval(ex))
except:
agent_exceptions = None
try:
agent_kwargs = json.loads(event[resource_key]['AgentWaitArgs'])
except:
try:
agent_kwargs = event[resource_key]['AgentWaitArgs']
except:
agent_kwargs = {}
try:
agent_resource_id = event[resource_key]['AgentWaitResourceId']
except:
agent_resource_id = None
if agent_resource_id:
try:
if type(agent_resource_id) == list:
agent_kwargs[agent_resource_id[0]] = [event['PhysicalResourceId']]
assert agent_kwargs[agent_resource_id[0]]
else:
agent_kwargs[agent_resource_id] = event['PhysicalResourceId']
assert agent_kwargs[agent_resource_id]
except:
try:
agent_kwargs[agent_resource_id] = event[resource_key]['AgentWaitArgs'][agent_resource_id]
except:
pass
try:
agent_method = event[resource_key]['AgentWaitMethod']
except:
agent_method = None
try:
agent_wait_delay = int(event[resource_key]['AgentWaitDelay'])
except:
agent_wait_delay = 0
try:
agent_query_expr = event[resource_key]['AgentWaitQueryExpr']
except:
agent_query_expr = None
try:
assert agent_method in getattr(agent, 'waiter_names')
waiter = getattr(agent, 'get_waiter')(agent_method)
agent_attr = None
except:
if self.verbose: print_exc()
waiter = None
try:
agent_attr = getattr(agent, agent_method)
except:
if self.verbose: print_exc()
agent_attr = None
if no_echo == 'false':
print('agent_method={}, agent_kwargs={}, agent_attr={} agent_resource_id={} agent_exceptions={} agent_wait_delay={}'.format(
agent_method, agent_kwargs, agent_attr, agent_resource_id, agent_exceptions, agent_wait_delay
))
if waiter:
if agent_exceptions:
try:
waiter.wait(**agent_kwargs)
except tuple(agent_exceptions) as e:
print('passing exception={}'.format(repr(e)))
if self.verbose: print_exc()
else:
waiter.wait(**agent_kwargs)
return
if agent_attr and agent_query_expr and agent_query_value is not None:
response = {}
match = None
sleep(agent_wait_delay)
while True:
if agent_exceptions:
try:
response = self.get_response(agent_attr, **agent_kwargs)
except tuple(agent_exceptions) as e:
print('passing exception={}'.format(repr(e)))
if self.verbose: print_exc()
else:
response = self.get_response(agent_attr, **agent_kwargs)
match = jsonpath(response, agent_query_expr)
if no_echo == 'false':
print('agent_query_expr={} agent_query_value={} match={} create={} update={} delete={}'.format(
agent_query_expr,
agent_query_value,
match,
create,
update,
delete
))
if match is not None and response and (match == agent_query_value or not match): break
sleep(self.default_wait_secs)
@retry(wait_exponential_multiplier=1000, wait_exponential_max=10000, stop_max_delay=30000)
def handle_client_event(self, agent, event, create=False, update=False, delete=False):
resource_key = 'ResourceProperties'
args_key = 'AgentCreateArgs'
method_key = 'AgentCreateMethod'
exceptions_key = 'AgentCreateExceptions'
if update:
args_key = 'AgentUpdateArgs'
method_key = 'AgentUpdateMethod'
exceptions_key = 'AgentUpdateExceptions'
if delete:
args_key = 'AgentDeleteArgs'
method_key = 'AgentDeleteMethod'
exceptions_key = 'AgentDeleteExceptions'
try:
no_echo = event[resource_key]['NoEcho'].lower()
except:
no_echo = 'false'
try:
agent_kwargs = json.loads(event[resource_key][args_key])
except:
try:
agent_kwargs = event[resource_key][args_key]
except:
agent_kwargs = {}
try:
agent_response_node = event[resource_key]['AgentResponseNode']
except:
agent_response_node = None
try:
agent_resource_id = event[resource_key]['AgentResourceId']
except:
agent_resource_id = None
if agent_resource_id:
try:
agent_kwargs[agent_resource_id] = event['PhysicalResourceId']
except:
try:
agent_kwargs[agent_resource_id] = event[resource_key][args_key][agent_resource_id]
except:
pass
try:
agent_query_expr = event[resource_key]['AgentWaitQueryExpr']
except:
agent_query_expr = None
try:
agent_exceptions = []
for ex in event[resource_key][exceptions_key]:
agent_exceptions.append(eval(ex))
except:
if self.verbose: print_exc()
agent_exceptions = None
try:
agent_method = event[resource_key][method_key]
except:
agent_method = None
try:
agent_attr = getattr(agent, agent_method)
except:
if self.verbose: print_exc()
agent_attr = None
if agent_attr:
response = {}
if no_echo == 'false':
print('agent_method={}, agent_kwargs={}, agent_attr={} agent_resource_id={} agent_exceptions={} agent_response_node={}'.format(
agent_method, agent_kwargs, agent_attr, agent_resource_id, agent_exceptions, agent_response_node
))
if agent_exceptions:
try:
response = self.get_response(agent_attr, **agent_kwargs)
except tuple(agent_exceptions) as e:
print('passing exception={}'.format(repr(e)))
if self.verbose: print_exc()
else:
response = self.get_response(agent_attr, **agent_kwargs)
if no_echo == 'false':
print('response={} create={} update={} delete={}'.format(
response,
create,
update,
delete
))
self.wait_event(agent, event, create=create, update=update, delete=delete)
try:
responseData = response[agent_response_node]
assert responseData, 'responseData from response[agent_response_node]'
except:
if self.verbose: print_exc()
try:
responseData = response
assert responseData
except:
if self.verbose: print_exc()
responseData = {}
try:
PhysicalResourceId = responseData[agent_resource_id]
assert PhysicalResourceId, 'PhysicalResourceId from responseData[agent_resource_id]'
except:
if self.verbose: print_exc()
try:
PhysicalResourceId = jsonpath(responseData, agent_query_expr)
assert PhysicalResourceId, 'PhysicalResourceId from jsonpath(response, agent_query_expr)'
PhysicalResourceId = ','.join(PhysicalResourceId)
except:
if self.verbose: print_exc()
try:
PhysicalResourceId = agent_kwargs[agent_resource_id]
assert PhysicalResourceId, 'PhysicalResourceId from event[resource_key][args_key][agent_resource_id]'
except:
if self.verbose: print_exc()
PhysicalResourceId = str(uuid4())
if create:
if no_echo == 'false':
print('PhysicalResourceId={} responseData={}'.format(
PhysicalResourceId,
responseData
))
return (PhysicalResourceId, responseData)
else:
print('PhysicalResourceId={} responseData={}'.format(
event['PhysicalResourceId'],
responseData
))
return responseData
return {}
@retry(wait_exponential_multiplier=1000, wait_exponential_max=10000, stop_max_delay=30000)
def handle_resource_event(self, agent, event):
PhysicalResourceId = str(uuid4())
responseData = {}
resource_key = 'ResourceProperties'
try:
no_echo = event[resource_key]['NoEcho'].lower()
except:
no_echo = 'false'
try:
agent_property = event[resource_key]['AgentCreateMethod']
except:
agent_property = None
try:
agent_resource_id = event[resource_key]['AgentResourceId']
except:
agent_resource_id = None
try:
agent_kwargs = json.loads(event[resource_key]['AgentCreateArgs'])
except:
try:
agent_kwargs = event[resource_key]['AgentCreateArgs']
except:
agent_kwargs = {}
try:
agent_query_expr = event[resource_key]['AgentWaitQueryExpr']
except:
agent_query_expr = None
try:
agent_attr = getattr(agent, agent_kwargs['ResourceName'])
except:
if self.verbose: print_exc()
agent_attr = None
if no_echo == 'false':
print('agent_kwargs={}, agent_query_expr={}, agent_attr={} agent_resource_id={} agent_property={}'.format(
agent_kwargs, agent_query_expr, agent_attr, agent_resource_id, agent_property
))
assert agent_attr and agent_resource_id and agent_query_expr and agent_property
resource = self.get_response(agent_attr, **agent_kwargs)
if agent_property in dir(resource):
response = self.get_resource(
resource,
'resource.{}'.format(agent_property)
)
match = jsonpath(response, agent_query_expr)
if no_echo == 'false': print('response={} match={}'.format(response, match))
try:
assert match
responseData[agent_resource_id] = ','.join(match)
except:
pass
return (PhysicalResourceId, responseData)
def handle_event(self, event=None, context=None):
try:
no_echo = event['ResourceProperties']['NoEcho'].lower()
except:
no_echo = 'false'
if no_echo == 'true':
no_echo = True
elif no_echo == 'false':
no_echo = False
else:
no_echo = False
try:
if not no_echo: print('event: {}, context: {}'.format(
json.dumps(event),
context
))
except:
pass
kwargs = {}
try:
kwargs['region_name'] = event['ResourceProperties']['AgentRegion']
except:
kwargs['region_name'] = self.region
try:
RoleArn = event['ResourceProperties']['RoleArn']
client = boto3.client('sts', region_name=self.region)
response = client.assume_role(
RoleArn=RoleArn,
RoleSessionName=str(uuid4())
)
if not no_echo: print('response={}'.format(response))
kwargs['aws_access_key_id'] = response['Credentials']['AccessKeyId']
kwargs['aws_secret_access_key'] = response['Credentials']['SecretAccessKey']
kwargs['aws_session_token'] = response['Credentials']['SessionToken']
if not no_echo: print('get_caller_identity={}'.format(
client.get_caller_identity()
))
except:
if not self.profile:
kwargs['aws_access_key_id'] = os.getenv('AWS_ACCESS_KEY_ID')
kwargs['aws_secret_access_key'] = os.getenv('AWS_SECRET_ACCESS_KEY')
kwargs['aws_session_token'] = os.getenv('AWS_SESSION_TOKEN')
if not no_echo: print('kwargs={}'.format(kwargs))
responseData = {}
try:
agent_service = event['ResourceProperties']['AgentService']
try:
agent_type = event['ResourceProperties']['AgentType']
except:
agent_type = 'client'
StackId = event['StackId']
ResponseURL = event['ResponseURL']
RequestType = event['RequestType']
ResourceType = event['ResourceType']
RequestId = event['RequestId']
LogicalResourceId = event['LogicalResourceId']
CreateFailedResourceId = '{}-CREATE_FAILED'.format(LogicalResourceId)
if agent_type == 'client':
agent = self.session.client(agent_service, **kwargs)
if agent_type == 'resource':
try:
agent = self.session.resource(agent_service, **kwargs)
(physicalResourceId, responseData) = self.handle_resource_event(
agent,
event
)
assert physicalResourceId and responseData
cfnresponse.send(
event,
context,
cfnresponse.SUCCESS,
responseData=responseData,
physicalResourceId=physicalResourceId,
noEcho=no_echo
)
return True
except Exception as e:
if self.verbose: print_exc()
cfnresponse.send(
event,
context,
cfnresponse.FAILED,
noEcho=no_echo,
reason=str(e)
)
return False
except Exception as e:
if self.verbose: print_exc()
cfnresponse.send(
event,
context,
cfnresponse.FAILED,
noEcho=no_echo,
reason=str(e)
)
return False
''' Update: runs only if AgentUpdateMethod is present otherwise the old
resource is deleted and a new one is created. No backups are taken,
possible loss of data.'''
if RequestType == 'Update':
try:
responseData = self.handle_client_event(
agent,
event,
update=True
)
if responseData:
cfnresponse.send(
event,
context,
cfnresponse.SUCCESS,
responseData=responseData,
physicalResourceId=event['PhysicalResourceId'],
noEcho=no_echo
)
return True
except Exception as e:
if self.verbose: print_exc()
cfnresponse.send(
event,
context,
cfnresponse.FAILED,
noEcho=no_echo,
reason=str(e)
)
return False
''' Delete: runs if AgentDeleteMethod is present. Returns immediatly after
completion if RequestType == 'Delete' or continues to (re)reate the
resource.'''
if RequestType in ['Update', 'Delete']:
try:
if event['PhysicalResourceId'] != CreateFailedResourceId:
responseData = self.handle_client_event(
agent,
event,
delete=True
)
else:
responseData = {}
if RequestType == 'Delete':
cfnresponse.send(
event,
context,
cfnresponse.SUCCESS,
responseData=responseData,
physicalResourceId=event['PhysicalResourceId'],
noEcho=no_echo
)
return True
event['ResourceProperties'].pop('AgentResourceId', None)
except Exception as e:
if self.verbose: print_exc()
cfnresponse.send(
event,
context,
cfnresponse.FAILED,
noEcho=no_echo,
reason=str(e)
)
return False
''' Create: (re)creates a resource and returns PhysicalResourceId based on
the specified AgentResourceId.'''
try:
(PhysicalResourceId, responseData) = self.handle_client_event(
agent,
event,
create=True
)
cfnresponse.send(
event,
context,
cfnresponse.SUCCESS,
responseData=responseData,
physicalResourceId=PhysicalResourceId,
noEcho=no_echo
)
return True
except Exception as e:
if self.verbose: print_exc()
cfnresponse.send(
event,
context,
cfnresponse.FAILED,
noEcho=no_echo,
physicalResourceId=CreateFailedResourceId,
reason=str(e)
)
return False
def lambda_handler(event=None, context=None):
provider = Provider()
return provider.handle_event(event=event, context=context)
if __name__ == '__main__':
try:
event = json.loads(sys.argv[1])
except:
try:
event = json.loads(sys.stdin.read())
except:
sys.exit(1)
lambda_handler(event=event)
You can’t perform that action at this time.