def update()

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