in aws/solutions/StackSetsResource/FunctionCode/lambda_function.py [0:0]
def update(event, context):
"""
Handle StackSetResource UPDATE events.
Update StackSet resource and/or any stack instances specified in the template.
"""
# Collect everything we need to update the stack set
set_id = event['PhysicalResourceId']
# Process the Operational Preferences (if any)
if 'OperationPreferences' in event['ResourceProperties']:
set_ops_prefs = convert_ops_prefs(event['ResourceProperties']['OperationPreferences'])
else:
set_ops_prefs = {}
logger.debug("OperationPreferences: {}".format(set_ops_prefs))
# Circumstances under which we update the StackSet itself
stack_set_attributes = [
'TemplateURL',
'Parameters',
'Tags',
'Capabilities',
'StackSetDescription'
]
stack_set_needs_update = change_requires_update(stack_set_attributes,
event['OldResourceProperties'],
event['ResourceProperties'])
if stack_set_needs_update:
logger.info("Changes impacting StackSet detected")
# Optional properties
logger.info("Evaluating optional properties")
if 'StackSetDescription' in event['ResourceProperties']:
set_description = event['ResourceProperties']['StackSetDescription']
elif 'StackSetDescription' in event['OldResourceProperties']:
set_description = event['OldResourceProperties']['StackSetDescription']
else:
set_description = "This StackSet belongs to the CloudFormation stack {}.".format(
get_stack_from_arn(event['StackId']))
logger.debug("StackSetDescription: {}".format(set_description))
if 'Capabilities' in event['ResourceProperties']:
set_capabilities = event['ResourceProperties']['Capabilities']
elif 'Capabilities' in event['OldResourceProperties']:
set_capabilities = event['OldResourceProperties']['Capabilities']
else:
set_capabilities = []
logger.debug("Capabilities: {}".format(set_capabilities))
if 'Tags' in event['ResourceProperties']:
set_tags = expand_tags(event['ResourceProperties']['Tags'])
elif 'Tags' in event['OldResourceProperties']:
set_tags = expand_tags(event['OldResourceProperties']['Tags'])
else:
set_tags = []
logger.debug("Tags: {}".format(set_tags))
if 'Parameters' in event['ResourceProperties']:
set_parameters = expand_parameters(event['ResourceProperties']['Parameters'])
elif 'Parameters' in event['OldResourceProperties']:
set_parameters = expand_parameters(event['OldResourceProperties']['Parameters'])
else:
set_parameters = []
logger.debug("Parameters: {}".format(set_parameters))
# Required properties
logger.info("Evaluating required properties")
if 'TemplateURL' in event['ResourceProperties']:
set_template = event['ResourceProperties']['TemplateURL']
elif 'TemplateURL' in event['OldResourceProperties']:
set_template = event['OldResourceProperties']['TemplateURL']
else:
raise Exception('Template URL not found during update event')
logger.debug("TemplateURL: {}".format(set_template))
# Update the StackSet
logger.info("Updating StackSet resource {}".format(set_id))
update_stack_set(os.environ['AWS_REGION'], set_id, set_description,
set_template, set_parameters, set_capabilities,
set_tags, set_ops_prefs)
# Now, look for changes to stack instances
logger.info("Evaluating stack instances")
# Flatten all the account/region tuples to compare differences
if 'StackInstances' in event['ResourceProperties']:
new_stacks = flatten_stacks(event['ResourceProperties']['StackInstances'])
else:
new_stacks = []
if 'StackInstances' in event['OldResourceProperties']:
old_stacks = flatten_stacks(event['OldResourceProperties']['StackInstances'])
else:
old_stacks = []
# Evaluate all differences we need to handle
to_add = list(set(new_stacks) - set(old_stacks))
to_delete = list(set(old_stacks) - set(new_stacks))
to_compare = list(set(old_stacks).intersection(new_stacks))
# Launch all new stack instances
if to_add:
logger.info("Adding stack instances: {}".format(to_add))
# Aggregate accounts with similar regions to reduce number of API calls
add_instances = aggregate_instances(to_add, new_stacks)
# Add stack instances
for instance in add_instances:
logger.debug("Add aggregated accounts: {} and regions: {} and overrides: {}".format(
instance['accounts'], instance['regions'], instance['overrides']))
if 'overrides' in instance:
param_overrides = expand_parameters(instance['overrides'])
else:
param_overrides = []
response = create_stacks(
os.environ['AWS_REGION'],
set_id,
instance['accounts'],
instance['regions'],
param_overrides,
set_ops_prefs
)
logger.debug(response)
# Delete all old stack instances
if to_delete:
logger.info("Deleting stack instances: {}".format(to_delete))
# Aggregate accounts with similar regions to reduce number of API calls
delete_instances = aggregate_instances(to_delete, old_stacks)
# Add stack instances
for instance in delete_instances:
logger.debug("Delete aggregated accounts: {} and regions: {}".format(
instance['accounts'], instance['regions']))
response = delete_stacks(
os.environ['AWS_REGION'],
set_id,
instance['accounts'],
instance['regions'],
set_ops_prefs
)
logger.debug(response)
# Determine if any existing instances need to be updated
if to_compare:
logger.info("Examining stack instances: {}".format(to_compare))
# Update any stacks in both lists, but with new overrides
to_update = []
for instance in to_compare:
if old_stacks[instance] == new_stacks[instance]:
logger.debug("{}: SAME!".format(instance))
else:
logger.debug("{}: DIFFERENT!".format(instance))
to_update.append(instance)
# Aggregate accounts with similar regions to reduce number of API calls
update_instances = aggregate_instances(to_update, new_stacks)
for instance in update_instances:
logger.debug("Update aggregated accounts: {} and regions: {} with overrides {}".format(
instance['accounts'], instance['regions'], instance['overrides']))
if 'overrides' in instance:
param_overrides = expand_parameters(instance['overrides'])
else:
param_overrides = []
response = update_stacks(
os.environ['AWS_REGION'],
set_id,
instance['accounts'],
instance['regions'],
param_overrides,
set_ops_prefs
)
logger.debug(response)
physical_resource_id = set_id
response_data = {}
return physical_resource_id, response_data