Merge pull request #653 from 2rs2ts/describe-stack-events

Add CloudFormation:DescribeStackEvents
This commit is contained in:
Steve Pulec 2016-07-09 21:09:37 -04:00 committed by GitHub
commit 84d0c44bd3
4 changed files with 163 additions and 6 deletions

View file

@ -1,4 +1,5 @@
from __future__ import unicode_literals
from datetime import datetime
import json
import boto.cloudformation
@ -19,11 +20,14 @@ class FakeStack(object):
self.region_name = region_name
self.notification_arns = notification_arns if notification_arns else []
self.tags = tags if tags else {}
self.status = 'CREATE_COMPLETE'
self.events = []
self._add_stack_event("CREATE_IN_PROGRESS", resource_status_reason="User Initiated")
self.description = self.template_dict.get('Description')
self.resource_map = self._create_resource_map()
self.output_map = self._create_output_map()
self._add_stack_event("CREATE_COMPLETE")
self.status = 'CREATE_COMPLETE'
def _create_resource_map(self):
resource_map = ResourceMap(self.stack_id, self.name, self.parameters, self.tags, self.region_name, self.template_dict)
@ -35,6 +39,32 @@ class FakeStack(object):
output_map.create()
return output_map
def _add_stack_event(self, resource_status, resource_status_reason=None, resource_properties=None):
self.events.append(FakeEvent(
stack_id=self.stack_id,
stack_name=self.name,
logical_resource_id=self.name,
physical_resource_id=self.stack_id,
resource_type="AWS::CloudFormation::Stack",
resource_status=resource_status,
resource_status_reason=resource_status_reason,
resource_properties=resource_properties,
))
def _add_resource_event(self, logical_resource_id, resource_status, resource_status_reason=None, resource_properties=None):
# not used yet... feel free to help yourself
resource = self.resource_map[logical_resource_id]
self.events.append(FakeEvent(
stack_id=self.stack_id,
stack_name=self.name,
logical_resource_id=logical_resource_id,
physical_resource_id=resource.physical_resource_id,
resource_type=resource.type,
resource_status=resource_status,
resource_status_reason=resource_status_reason,
resource_properties=resource_properties,
))
@property
def stack_parameters(self):
return self.resource_map.resolved_parameters
@ -48,16 +78,33 @@ class FakeStack(object):
return self.output_map.values()
def update(self, template):
self._add_stack_event("UPDATE_IN_PROGRESS", resource_status_reason="User Initiated")
self.template = template
self.resource_map.update(json.loads(template))
self.output_map = self._create_output_map()
self._add_stack_event("UPDATE_COMPLETE")
self.status = "UPDATE_COMPLETE"
def delete(self):
self._add_stack_event("DELETE_IN_PROGRESS", resource_status_reason="User Initiated")
self.resource_map.delete()
self._add_stack_event("DELETE_COMPLETE")
self.status = "DELETE_COMPLETE"
class FakeEvent(object):
def __init__(self, stack_id, stack_name, logical_resource_id, physical_resource_id, resource_type, resource_status, resource_status_reason=None, resource_properties=None):
self.stack_id = stack_id
self.stack_name = stack_name
self.logical_resource_id = logical_resource_id
self.physical_resource_id = physical_resource_id
self.resource_type = resource_type
self.resource_status = resource_status
self.resource_status_reason = resource_status_reason
self.resource_properties = resource_properties
self.timestamp = datetime.utcnow()
class CloudFormationBackend(BaseBackend):
def __init__(self):
@ -97,12 +144,15 @@ class CloudFormationBackend(BaseBackend):
return self.stacks.values()
def get_stack(self, name_or_stack_id):
if name_or_stack_id in self.stacks:
# Lookup by stack id
return self.stacks.get(name_or_stack_id)
all_stacks = dict(self.deleted_stacks, **self.stacks)
if name_or_stack_id in all_stacks:
# Lookup by stack id - deleted stacks incldued
return all_stacks[name_or_stack_id]
else:
# Lookup by stack name
return [stack for stack in self.stacks.values() if stack.name == name_or_stack_id][0]
# Lookup by stack name - undeleted stacks only
for stack in self.stacks.values():
if stack.name == name_or_stack_id:
return stack
def update_stack(self, name, template):
stack = self.get_stack(name)

View file

@ -91,6 +91,13 @@ class CloudFormationResponse(BaseResponse):
template = self.response_template(DESCRIBE_STACK_RESOURCES_RESPONSE)
return template.render(stack=stack)
def describe_stack_events(self):
stack_name = self._get_param('StackName')
stack = self.cloudformation_backend.get_stack(stack_name)
template = self.response_template(DESCRIBE_STACK_EVENTS_RESPONSE)
return template.render(stack=stack)
def list_stacks(self):
stacks = self.cloudformation_backend.list_stacks()
template = self.response_template(LIST_STACKS_RESPONSE)
@ -269,6 +276,31 @@ DESCRIBE_STACK_RESOURCES_RESPONSE = """<DescribeStackResourcesResponse>
</DescribeStackResourcesResponse>"""
DESCRIBE_STACK_EVENTS_RESPONSE = """<DescribeStackEventsResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
<DescribeStackEventsResult>
<StackEvents>
{% for event in stack.events %}
<member>
<Timestamp>{{ event.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ') }}</Timestamp>
<ResourceStatus>{{ event.resource_status }}</ResourceStatus>
<StackId>{{ event.stack_id }}</StackId>
<EventId>{{ event.event_id }}</EventId>
<LogicalResourceId>{{ event.logical_resource_id }}</LogicalResourceId>
{% if event.resource_status_reason %}<ResourceStatusReason>{{ event.resource_status_reason }}</ResourceStatusReason>{% endif %}
<StackName>{{ event.stack_name }}</StackName>
<PhysicalResourceId>{{ event.physical_resource_id }}</PhysicalResourceId>
{% if event.resource_properties %}<ResourceProperties>{{ event.resource_properties }}</ResourceProperties>{% endif %}
<ResourceType>{{ event.resource_type }}</ResourceType>
</member>
{% endfor %}
</StackEvents>
</DescribeStackEventsResult>
<ResponseMetadata>
<RequestId>b9b4b068-3a41-11e5-94eb-example</RequestId>
</ResponseMetadata>
</DescribeStackEventsResponse>"""
LIST_STACKS_RESPONSE = """<ListStacksResponse>
<ListStacksResult>
<StackSummaries>