From 542248158f2f5df09de82d0a4c24c4d2ce7661aa Mon Sep 17 00:00:00 2001 From: Andrew Garrett Date: Wed, 29 Jun 2016 21:56:39 +0000 Subject: [PATCH] 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.) --- moto/cloudformation/models.py | 62 +++++++++++++++++-- moto/cloudformation/responses.py | 2 +- .../test_cloudformation_stack_crud.py | 36 +++++++++++ .../test_cloudformation_stack_crud_boto3.py | 39 ++++++++++++ 4 files changed, 132 insertions(+), 7 deletions(-) diff --git a/moto/cloudformation/models.py b/moto/cloudformation/models.py index d6298906f..d9d09410d 100644 --- a/moto/cloudformation/models.py +++ b/moto/cloudformation/models.py @@ -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) diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index 2b71cb8b1..9cab62a63 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -281,7 +281,7 @@ DESCRIBE_STACK_EVENTS_RESPONSE = """