Add Fn::ImportValue support

This commit is contained in:
Jessie Nadler 2017-06-08 15:33:28 -04:00
parent 8e4c79625c
commit f5106f2cc8
4 changed files with 101 additions and 5 deletions

View File

@ -15,7 +15,7 @@ from .exceptions import ValidationError
class FakeStack(BaseModel):
def __init__(self, stack_id, name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None):
def __init__(self, stack_id, name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None, cross_stack_resources=None):
self.stack_id = stack_id
self.name = name
self.template = template
@ -30,6 +30,7 @@ class FakeStack(BaseModel):
resource_status_reason="User Initiated")
self.description = self.template_dict.get('Description')
self.cross_stack_resources = cross_stack_resources or []
self.resource_map = self._create_resource_map()
self.output_map = self._create_output_map()
self._add_stack_event("CREATE_COMPLETE")
@ -37,7 +38,7 @@ class FakeStack(BaseModel):
def _create_resource_map(self):
resource_map = ResourceMap(
self.stack_id, self.name, self.parameters, self.tags, self.region_name, self.template_dict)
self.stack_id, self.name, self.parameters, self.tags, self.region_name, self.template_dict, self.cross_stack_resources)
resource_map.create()
return resource_map
@ -148,6 +149,7 @@ class CloudFormationBackend(BaseBackend):
notification_arns=notification_arns,
tags=tags,
role_arn=role_arn,
cross_stack_resources=self.exports,
)
self.stacks[stack_id] = new_stack
self._validate_export_uniqueness(new_stack)

View File

@ -187,6 +187,12 @@ def clean_json(resource_json, resources_map):
return fn_sub_value
pass
if 'Fn::ImportValue' in resource_json:
cleaned_val = clean_json(resource_json['Fn::ImportValue'], resources_map)
values = [x.value for x in resources_map.cross_stack_resources.values() if x.name == cleaned_val]
if any(values):
return values[0]
cleaned_json = {}
for key, value in resource_json.items():
cleaned_val = clean_json(value, resources_map)
@ -326,13 +332,14 @@ class ResourceMap(collections.Mapping):
each resources is passed this lazy map that it can grab dependencies from.
"""
def __init__(self, stack_id, stack_name, parameters, tags, region_name, template):
def __init__(self, stack_id, stack_name, parameters, tags, region_name, template, cross_stack_resources):
self._template = template
self._resource_json_map = template['Resources']
self._region_name = region_name
self.input_parameters = parameters
self.tags = copy.deepcopy(tags)
self.resolved_parameters = {}
self.cross_stack_resources = cross_stack_resources
# Create the default resources
self._parsed_resources = {

View File

@ -5,7 +5,7 @@ import boto
import boto.s3
import boto.s3.key
from botocore.exceptions import ClientError
from moto import mock_cloudformation, mock_s3
from moto import mock_cloudformation, mock_s3, mock_sqs
import json
import sure # noqa
@ -80,9 +80,23 @@ dummy_output_template = {
}
}
dummy_import_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"Queue": {
"Type": "AWS::SQS::Queue",
"Properties": {
"QueueName": {"Fn::ImportValue": 'My VPC ID'},
"VisibilityTimeout": 60,
}
}
}
}
dummy_template_json = json.dumps(dummy_template)
dummy_update_template_json = json.dumps(dummy_template)
dummy_output_template_json = json.dumps(dummy_output_template)
dummy_import_template_json = json.dumps(dummy_import_template)
@mock_cloudformation
@ -501,3 +515,23 @@ def test_export_names_must_be_unique():
StackName="test_stack",
TemplateBody=dummy_output_template_json,
)
@mock_sqs
@mock_cloudformation
def test_stack_with_imports():
cf = boto3.resource('cloudformation', region_name='us-east-1')
ec2_resource = boto3.resource('sqs', region_name='us-east-1')
output_stack = cf.create_stack(
StackName="test_stack1",
TemplateBody=dummy_output_template_json,
)
import_stack = cf.create_stack(
StackName="test_stack2",
TemplateBody=dummy_import_template_json
)
output_stack.outputs.should.have.length_of(1)
output = output_stack.outputs[0]['OutputValue']
queue = ec2_resource.get_queue_by_name(QueueName=output)
queue.should_not.be.none

View File

@ -7,7 +7,7 @@ import sure # noqa
from moto.cloudformation.exceptions import ValidationError
from moto.cloudformation.models import FakeStack
from moto.cloudformation.parsing import resource_class_from_type, parse_condition
from moto.cloudformation.parsing import resource_class_from_type, parse_condition, Export
from moto.sqs.models import Queue
from moto.s3.models import FakeBucket
from boto.cloudformation.stack import Output
@ -105,6 +105,38 @@ sub_template = {
}
}
export_value_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"Queue": {
"Type": "AWS::SQS::Queue",
"Properties": {
"QueueName": {"Fn::Sub": '${AWS::StackName}-queue'},
"VisibilityTimeout": 60,
}
}
},
"Outputs": {
"Output1": {
"Value": "value",
"Export": {"Name": 'queue-us-west-1'}
}
}
}
import_value_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"Queue": {
"Type": "AWS::SQS::Queue",
"Properties": {
"QueueName": {"Fn::ImportValue": 'queue-us-west-1'},
"VisibilityTimeout": 60,
}
}
}
}
outputs_template = dict(list(dummy_template.items()) +
list(output_dict.items()))
bad_outputs_template = dict(
@ -120,6 +152,8 @@ get_attribute_outputs_template_json = json.dumps(
get_attribute_outputs_template)
split_select_template_json = json.dumps(split_select_template)
sub_template_json = json.dumps(sub_template)
export_value_template_json = json.dumps(export_value_template)
import_value_template_json = json.dumps(import_value_template)
def test_parse_stack_resources():
@ -327,3 +361,22 @@ def test_sub():
queue1 = stack.resource_map['Queue1']
queue2 = stack.resource_map['Queue2']
queue2.name.should.equal(queue1.name)
def test_import():
export_stack = FakeStack(
stack_id="test_id",
name="test_stack",
template=export_value_template_json,
parameters={},
region_name='us-west-1')
import_stack = FakeStack(
stack_id="test_id",
name="test_stack",
template=import_value_template_json,
parameters={},
region_name='us-west-1',
cross_stack_resources={export_stack.exports[0].value: export_stack.exports[0]})
queue = import_stack.resource_map['Queue']
queue.name.should.equal("value")