def describe_changeset(self, change_set_id, stack_name, **kwargs): """ Call Cloudformation to describe a changeset :param change_set_id: ID of the changeset :param stack_name: Name of the CloudFormation stack :return: dictionary of changes described in the changeset. """ paginator = self._client.get_paginator("describe_change_set") response_iterator = paginator.paginate(ChangeSetName=change_set_id, StackName=stack_name) changes = {"Add": [], "Modify": [], "Remove": []} changes_showcase = {"Add": "+ Add", "Modify": "* Modify", "Remove": "- Delete"} changeset = False for item in response_iterator: cf_changes = item.get("Changes") for change in cf_changes: changeset = True resource_props = change.get("ResourceChange") action = resource_props.get("Action") changes[action].append( { "LogicalResourceId": resource_props.get("LogicalResourceId"), "ResourceType": resource_props.get("ResourceType"), "Replacement": "N/A" if resource_props.get("Replacement") is None else resource_props.get("Replacement"), } ) for k, v in changes.items(): for value in v: row_color = self.deploy_color.get_changeset_action_color(action=k) pprint_columns( columns=[ changes_showcase.get(k, k), value["LogicalResourceId"], value["ResourceType"], value["Replacement"], ], width=kwargs["width"], margin=kwargs["margin"], format_string=DESCRIBE_CHANGESET_FORMAT_STRING, format_args=kwargs["format_args"], columns_dict=DESCRIBE_CHANGESET_DEFAULT_ARGS.copy(), color=row_color, ) if not changeset: # There can be cases where there are no changes, # but could be an an addition of a SNS notification topic. pprint_columns( columns=["-", "-", "-", "-"], width=kwargs["width"], margin=kwargs["margin"], format_string=DESCRIBE_CHANGESET_FORMAT_STRING, format_args=kwargs["format_args"], columns_dict=DESCRIBE_CHANGESET_DEFAULT_ARGS.copy(), ) return changes
def to_be_decorated(*args, **kwargs): pprint_columns( columns=["A", "B", "C"], width=kwargs["width"], margin=kwargs["margin"], format_args=kwargs["format_args"], format_string=TABLE_FORMAT_STRING, columns_dict=TABLE_FORMAT_ARGS.copy(), )
def _stack_outputs(self, stack_outputs, **kwargs): for output in stack_outputs: pprint_columns( columns=[" - ".join([output["OutputKey"], output.get("Description", "")]), output["OutputValue"]], width=kwargs["width"], margin=kwargs["margin"], format_string=OUTPUTS_FORMAT_STRING, format_args=kwargs["format_args"], columns_dict=OUTPUTS_DEFAULTS_ARGS.copy(), )
def describe_stack_events(self, stack_name, time_stamp_marker, **kwargs): """ Calls CloudFormation to get current stack events :param stack_name: Name or ID of the stack :param time_stamp_marker: last event time on the stack to start streaming events from. :return: """ stack_change_in_progress = True events = set() retry_attempts = 0 while stack_change_in_progress and retry_attempts <= self.max_attempts: try: # Only sleep if there have been no retry_attempts time.sleep(self.client_sleep if retry_attempts == 0 else 0) describe_stacks_resp = self._client.describe_stacks( StackName=stack_name) paginator = self._client.get_paginator("describe_stack_events") response_iterator = paginator.paginate(StackName=stack_name) stack_status = describe_stacks_resp["Stacks"][0]["StackStatus"] for event_items in response_iterator: for event in event_items["StackEvents"]: if event["EventId"] not in events and utc_to_timestamp( event["Timestamp"]) > time_stamp_marker: events.add(event["EventId"]) row_color = self.deploy_color.get_stack_events_status_color( status=event["ResourceStatus"]) pprint_columns( columns=[ event["ResourceStatus"], event["ResourceType"], event["LogicalResourceId"], event.get("ResourceStatusReason", "-"), ], width=kwargs["width"], margin=kwargs["margin"], format_string= DESCRIBE_STACK_EVENTS_FORMAT_STRING, format_args=kwargs["format_args"], columns_dict=DESCRIBE_STACK_EVENTS_DEFAULT_ARGS .copy(), color=row_color, ) if self._check_stack_complete(stack_status): stack_change_in_progress = False break except botocore.exceptions.ClientError: retry_attempts = retry_attempts + 1 if retry_attempts > self.max_attempts: raise # Sleep in exponential backoff mode time.sleep(math.pow(self.backoff, retry_attempts))
def _display_stack_outputs(stack_outputs: List[Dict], **kwargs) -> None: for counter, output in enumerate(stack_outputs): for k, v in [ ("Key", output.get("OutputKey")), ("Description", output.get("Description", "-")), ("Value", output.get("OutputValue")), ]: pprint_columns( columns=["{k:<{0}}{v:<{0}}".format(MIN_OFFSET, k=k, v=v)], width=kwargs["width"], margin=kwargs["margin"], format_string=OUTPUTS_FORMAT_STRING, format_args=kwargs["format_args"], columns_dict=OUTPUTS_DEFAULTS_ARGS.copy(), color="green", replace_whitespace=False, break_long_words=False, drop_whitespace=False, ) newline_per_item(stack_outputs, counter)
def describe_stack_events(self, stack_name, time_stamp_marker, **kwargs): """ Calls CloudFormation to get current stack events :param stack_name: Name or ID of the stack :param time_stamp_marker: last event time on the stack to start streaming events from. :param kwargs: Other arguments to pass to pprint_columns() """ stack_change_in_progress = True events = set() retry_attempts = 0 while stack_change_in_progress and retry_attempts <= self.max_attempts: try: # Only sleep if there have been no retry_attempts time.sleep(0 if retry_attempts else self.client_sleep) describe_stacks_resp = self._client.describe_stacks( StackName=stack_name) paginator = self._client.get_paginator("describe_stack_events") response_iterator = paginator.paginate(StackName=stack_name) stack_status = describe_stacks_resp["Stacks"][0]["StackStatus"] latest_time_stamp_marker = time_stamp_marker for event_items in response_iterator: for event in event_items["StackEvents"]: if event["EventId"] not in events and utc_to_timestamp( event["Timestamp"]) > time_stamp_marker: events.add(event["EventId"]) latest_time_stamp_marker = max( latest_time_stamp_marker, utc_to_timestamp(event["Timestamp"])) row_color = self.deploy_color.get_stack_events_status_color( status=event["ResourceStatus"]) pprint_columns( columns=[ event["ResourceStatus"], event["ResourceType"], event["LogicalResourceId"], event.get("ResourceStatusReason", "-"), ], width=kwargs["width"], margin=kwargs["margin"], format_string= DESCRIBE_STACK_EVENTS_FORMAT_STRING, format_args=kwargs["format_args"], columns_dict=DESCRIBE_STACK_EVENTS_DEFAULT_ARGS .copy(), color=row_color, ) # Skip already shown old event entries elif utc_to_timestamp( event["Timestamp"]) <= time_stamp_marker: time_stamp_marker = latest_time_stamp_marker break else: # go to next loop if not break from inside loop time_stamp_marker = latest_time_stamp_marker # update marker if all events are new continue break # reached here only if break from inner loop! if self._check_stack_complete(stack_status): stack_change_in_progress = False break except botocore.exceptions.ClientError as ex: retry_attempts = retry_attempts + 1 if retry_attempts > self.max_attempts: LOG.error("Describing stack events for %s failed: %s", stack_name, str(ex)) return # Sleep in exponential backoff mode time.sleep(math.pow(self.backoff, retry_attempts))