Implement the meat for DescribeStackEvents
Right now this just adds events for the stack itself via the lifecycle methods of the FakeStack object, but it is possible to add other kinds of events (I left a method for that should someone need inspiration later.)
This commit is contained in:
parent
2a6f607ae5
commit
542248158f
@ -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)
|
||||
|
@ -281,7 +281,7 @@ DESCRIBE_STACK_EVENTS_RESPONSE = """<DescribeStackEventsResponse xmlns="http://c
|
||||
<StackEvents>
|
||||
{% for event in stack.events %}
|
||||
<member>
|
||||
<Timestamp>{{ event.timestamp }}</Timestamp>
|
||||
<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>
|
||||
|
@ -354,3 +354,39 @@ def test_update_stack_when_rolled_back():
|
||||
ex.error_code.should.equal('ValidationError')
|
||||
ex.reason.should.equal('Bad Request')
|
||||
ex.status.should.equal(400)
|
||||
|
||||
@mock_cloudformation
|
||||
def test_describe_stack_events_shows_create_update_and_delete():
|
||||
conn = boto.connect_cloudformation()
|
||||
stack_id = conn.create_stack("test_stack", template_body=dummy_template_json)
|
||||
conn.update_stack(stack_id, template_body=dummy_template_json2)
|
||||
conn.delete_stack(stack_id)
|
||||
|
||||
# assert begins and ends with stack events
|
||||
events = conn.describe_stack_events(stack_id)
|
||||
events[0].resource_type.should.equal("AWS::CloudFormation::Stack")
|
||||
events[-1].resource_type.should.equal("AWS::CloudFormation::Stack")
|
||||
|
||||
# testing ordering of stack events without assuming resource events will not exist
|
||||
stack_events_to_look_for = iter([
|
||||
("CREATE_IN_PROGRESS", "User Initiated"), ("CREATE_COMPLETE", None),
|
||||
("UPDATE_IN_PROGRESS", "User Initiated"), ("UPDATE_COMPLETE", None),
|
||||
("DELETE_IN_PROGRESS", "User Initiated"), ("DELETE_COMPLETE", None)])
|
||||
try:
|
||||
for event in events:
|
||||
event.stack_id.should.equal(stack_id)
|
||||
event.stack_name.should.equal("test_stack")
|
||||
|
||||
if event.resource_type == "AWS::CloudFormation::Stack":
|
||||
event.logical_resource_id.should.equal("test_stack")
|
||||
event.physical_resource_id.should.equal(stack_id)
|
||||
|
||||
status_to_look_for, reason_to_look_for = next(stack_events_to_look_for)
|
||||
event.resource_status.should.equal(status_to_look_for)
|
||||
if reason_to_look_for is not None:
|
||||
event.resource_status_reason.should.equal(reason_to_look_for)
|
||||
except StopIteration:
|
||||
assert False, "Too many stack events"
|
||||
|
||||
list(stack_events_to_look_for).should.be.empty
|
||||
|
||||
|
@ -312,3 +312,42 @@ def test_stack_tags():
|
||||
expected_tag_items = set(
|
||||
item for items in [tag.items() for tag in tags] for item in items)
|
||||
observed_tag_items.should.equal(expected_tag_items)
|
||||
|
||||
@mock_cloudformation
|
||||
def test_stack_events():
|
||||
cf = boto3.resource('cloudformation', region_name='us-east-1')
|
||||
stack = cf.create_stack(
|
||||
StackName="test_stack",
|
||||
TemplateBody=dummy_template_json,
|
||||
)
|
||||
stack.update(TemplateBody=dummy_update_template_json)
|
||||
stack = cf.Stack(stack.stack_id)
|
||||
stack.delete()
|
||||
|
||||
# assert begins and ends with stack events
|
||||
events = list(stack.events.all())
|
||||
events[0].resource_type.should.equal("AWS::CloudFormation::Stack")
|
||||
events[-1].resource_type.should.equal("AWS::CloudFormation::Stack")
|
||||
|
||||
# testing ordering of stack events without assuming resource events will not exist
|
||||
stack_events_to_look_for = iter([
|
||||
("CREATE_IN_PROGRESS", "User Initiated"), ("CREATE_COMPLETE", None),
|
||||
("UPDATE_IN_PROGRESS", "User Initiated"), ("UPDATE_COMPLETE", None),
|
||||
("DELETE_IN_PROGRESS", "User Initiated"), ("DELETE_COMPLETE", None)])
|
||||
try:
|
||||
for event in events:
|
||||
event.stack_id.should.equal(stack.stack_id)
|
||||
event.stack_name.should.equal("test_stack")
|
||||
|
||||
if event.resource_type == "AWS::CloudFormation::Stack":
|
||||
event.logical_resource_id.should.equal("test_stack")
|
||||
event.physical_resource_id.should.equal(stack.stack_id)
|
||||
|
||||
status_to_look_for, reason_to_look_for = next(stack_events_to_look_for)
|
||||
event.resource_status.should.equal(status_to_look_for)
|
||||
if reason_to_look_for is not None:
|
||||
event.resource_status_reason.should.equal(reason_to_look_for)
|
||||
except StopIteration:
|
||||
assert False, "Too many stack events"
|
||||
|
||||
list(stack_events_to_look_for).should.be.empty
|
||||
|
Loading…
Reference in New Issue
Block a user