def get_stack_info(self, stack_name): """ Get the template and parameters of the stack currently in AWS Returns [ template, parameters ] """ try: stacks = retry_on_throttling( self.cloudformation.describe_stacks, kwargs=dict(StackName=stack_name)) except botocore.exceptions.ClientError as e: if "does not exist" not in e.message: raise raise exceptions.StackDoesNotExist(stack_name) try: template = retry_on_throttling( self.cloudformation.get_template, kwargs=dict(StackName=stack_name))['TemplateBody'] except botocore.exceptions.ClientError as e: if "does not exist" not in e.message: raise raise exceptions.StackDoesNotExist(stack_name) stack = stacks['Stacks'][0] parameters = dict() if 'Parameters' in stack: for p in stack['Parameters']: parameters[p['ParameterKey']] = p['ParameterValue'] return [json.dumps(template), parameters]
def get_stack(self, stack_name, **kwargs): try: return self.cloudformation.describe_stacks( StackName=stack_name)['Stacks'][0] except botocore.exceptions.ClientError as e: if "does not exist" not in e.message: raise raise exceptions.StackDoesNotExist(stack_name)
def get_stack_info(self, stack): """ Get the template and parameters of the stack currently in AWS Returns [ template, parameters ] """ stack_name = stack['StackId'] try: template = self.cloudformation.get_template( StackName=stack_name)['TemplateBody'] except botocore.exceptions.ClientError as e: if "does not exist" not in e.message: raise raise exceptions.StackDoesNotExist(stack_name) parameters = self.params_as_dict(stack.get('Parameters', [])) return [json.dumps(template), parameters]
def get_stack_changes(self, stack, template, parameters, tags, **kwargs): """Get the changes from a ChangeSet. Args: stack (:class:`stacker.stack.Stack`): the stack to get changes template (:class:`stacker.providers.base.Template`): A Template object to compaired to. parameters (list): A list of dictionaries that defines the parameter list to be applied to the Cloudformation stack. tags (list): A list of dictionaries that defines the tags that should be applied to the Cloudformation stack. Returns: dict: Stack outputs with inferred changes. """ try: stack_details = self.get_stack(stack.fqn) # handling for orphaned changeset temp stacks if self.get_stack_status( stack_details) == self.REVIEW_STATUS: raise exceptions.StackDoesNotExist(stack.fqn) _old_template, old_params = self.get_stack_info( stack_details ) old_template = parse_cloudformation_template(_old_template) change_type = 'UPDATE' except exceptions.StackDoesNotExist: old_params = {} old_template = {} change_type = 'CREATE' changes, change_set_id = create_change_set( self.cloudformation, stack.fqn, template, parameters, tags, change_type, service_role=self.service_role, **kwargs ) new_parameters_as_dict = self.params_as_dict( [x if 'ParameterValue' in x else {'ParameterKey': x['ParameterKey'], 'ParameterValue': old_params[x['ParameterKey']]} for x in parameters] ) params_diff = diff_parameters(old_params, new_parameters_as_dict) if changes or params_diff: ui.lock() try: if self.interactive: output_summary(stack.fqn, 'changes', changes, params_diff, replacements_only=self.replacements_only) output_full_changeset(full_changeset=changes, params_diff=params_diff, fqn=stack.fqn) else: output_full_changeset(full_changeset=changes, params_diff=params_diff, answer='y', fqn=stack.fqn) finally: ui.unlock() self.cloudformation.delete_change_set( ChangeSetName=change_set_id ) # ensure current stack outputs are loaded self.get_outputs(stack.fqn) # infer which outputs may have changed refs_to_invalidate = [] for change in changes: resc_change = change.get('ResourceChange', {}) if resc_change.get('Type') == 'Add': continue # we don't care about anything new # scope of changes that can invalidate a change if resc_change and (resc_change.get('Replacement') == 'True' or 'Properties' in resc_change['Scope']): logger.debug('%s added to invalidation list for %s', resc_change['LogicalResourceId'], stack.fqn) refs_to_invalidate.append(resc_change['LogicalResourceId']) # invalidate cached outputs with inferred changes for output, props in old_template.get('Outputs', {}).items(): if any(r in str(props['Value']) for r in refs_to_invalidate): self._outputs[stack.fqn].pop(output) logger.debug('Removed %s from the outputs of %s', output, stack.fqn) # push values for new + invalidated outputs to outputs for output_name, output_params in \ stack.blueprint.get_output_definitions().items(): if output_name not in self._outputs[stack.fqn]: self._outputs[stack.fqn][output_name] = ( '<inferred-change: {}.{}={}>'.format( stack.fqn, output_name, str(output_params['Value']) ) ) # when creating a changeset for a new stack, CFN creates a temporary # stack with a status of REVIEW_IN_PROGRESS. this is only removed if # the changeset is executed or it is manually deleted. if change_type == 'CREATE': try: temp_stack = self.get_stack(stack.fqn) if self.is_stack_in_review(temp_stack): logger.debug('Removing temporary stack that is created ' 'with a ChangeSet of type "CREATE"') self.destroy_stack(temp_stack) except exceptions.StackDoesNotExist: # not an issue if the stack was already cleaned up logger.debug('Stack does not exist: %s', stack.fqn) return self.get_outputs(stack.fqn)