create CloudFormation outputs and enable 'Fn::GetAtt' to work.

This commit is contained in:
Joseph Lawson 2014-10-21 12:45:03 -04:00
commit 1d9ffafaa5
10 changed files with 269 additions and 8 deletions

View file

@ -3,7 +3,7 @@ import json
from moto.core import BaseBackend
from .parsing import ResourceMap
from .parsing import ResourceMap, OutputMap
from .utils import generate_stack_id
@ -19,10 +19,17 @@ class FakeStack(object):
self.resource_map = ResourceMap(stack_id, name, template_dict)
self.resource_map.create()
self.output_map = OutputMap(self.resource_map, template_dict)
self.output_map.create()
@property
def stack_resources(self):
return self.resource_map.values()
@property
def stack_outputs(self):
return self.output_map.values()
class CloudFormationBackend(BaseBackend):

View file

@ -8,6 +8,8 @@ from moto.elb import models as elb_models
from moto.iam import models as iam_models
from moto.sqs import models as sqs_models
from .utils import random_suffix
from boto.cloudformation.stack import Output
from boto.exception import BotoServerError
MODEL_MAP = {
"AWS::AutoScaling::AutoScalingGroup": autoscaling_models.FakeAutoScalingGroup,
@ -69,6 +71,19 @@ def clean_json(resource_json, resources_map):
else:
return resource
if 'Fn::GetAtt' in resource_json:
resource = resources_map[resource_json['Fn::GetAtt'][0]]
try:
return resource.get_cfn_attribute(resource_json['Fn::GetAtt'][1])
except NotImplementedError as n:
raise NotImplementedError(n.message.format(resource_json['Fn::GetAtt'][0]))
except AttributeError:
raise BotoServerError(
400,
'Bad Request',
'Template error: resource {0} does not support attribute type {1} in Fn::GetAtt'.format(
resource_json['Fn::GetAtt'][0], resource_json['Fn::GetAtt'][1]))
cleaned_json = {}
for key, value in resource_json.items():
cleaned_json[key] = clean_json(value, resources_map)
@ -122,6 +137,15 @@ def parse_resource(logical_id, resource_json, resources_map):
return resource
def parse_output(output_logical_id, output_json, resources_map):
output_json = clean_json(output_json, resources_map)
output = Output()
output.key = output_logical_id
output.value = output_json['Value']
output.description = output_json.get('Description')
return output
class ResourceMap(collections.Mapping):
"""
This is a lazy loading map for resources. This allows us to create resources
@ -180,3 +204,38 @@ class ResourceMap(collections.Mapping):
if isinstance(self[resource], ec2_models.TaggedEC2Resource):
tags['aws:cloudformation:logical-id'] = resource
ec2_models.ec2_backend.create_tags([self[resource].physical_resource_id],tags)
class OutputMap(collections.Mapping):
def __init__(self, resources, template):
self._template = template
self._output_json_map = template.get('Outputs')
# Create the default resources
self._resource_map = resources
self._parsed_outputs = dict()
def __getitem__(self, key):
output_logical_id = key
if output_logical_id in self._parsed_outputs:
return self._parsed_outputs[output_logical_id]
else:
output_json = self._output_json_map.get(output_logical_id)
new_output = parse_output(output_logical_id, output_json, self._resource_map)
self._parsed_outputs[output_logical_id] = new_output
return new_output
def __iter__(self):
return iter(self.outputs)
def __len__(self):
return len(self._output_json_map)
@property
def outputs(self):
return self._output_json_map.keys() if self._output_json_map else []
def create(self):
for output in self.outputs:
self[output]

View file

@ -88,7 +88,14 @@ DESCRIBE_STACKS_TEMPLATE = """<DescribeStacksResult>
<CreationTime>2010-07-27T22:28:28Z</CreationTime>
<StackStatus>CREATE_COMPLETE</StackStatus>
<DisableRollback>false</DisableRollback>
<Outputs></Outputs>
<Outputs>
{% for output in stack.stack_outputs %}
<member>
<OutputKey>{{ output.key }}</OutputKey>
<OutputValue>{{ output.value }}</OutputValue>
</member>
{% endfor %}
</Outputs>
</member>
{% endfor %}
</Stacks>