diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index 71a60371a..744b1d08e 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -4,6 +4,7 @@ import functools import logging import copy import warnings +import re from moto.autoscaling import models as autoscaling_models from moto.awslambda import models as lambda_models @@ -167,6 +168,25 @@ def clean_json(resource_json, resources_map): select_list = clean_json(resource_json['Fn::Select'][1], resources_map) return select_list[select_index] + if 'Fn::Sub' in resource_json: + if isinstance(resource_json['Fn::Sub'], list): + warnings.warn( + "Tried to parse Fn::Sub with variable mapping but it's not supported by moto's CloudFormation implementation") + else: + fn_sub_value = clean_json(resource_json['Fn::Sub'], resources_map) + to_sub = re.findall('(?=\${)[^!^"]*?}', fn_sub_value) + literals = re.findall('(?=\${!)[^"]*?}', fn_sub_value) + for sub in to_sub: + if '.' in sub: + cleaned_ref = clean_json({'Fn::GetAtt': re.findall('(?<=\${)[^"]*?(?=})', sub)[0].split('.')}, resources_map) + else: + cleaned_ref = clean_json({'Ref': re.findall('(?<=\${)[^"]*?(?=})', sub)[0]}, resources_map) + fn_sub_value = fn_sub_value.replace(sub, cleaned_ref) + for literal in literals: + fn_sub_value = fn_sub_value.replace(literal, literal.replace('!', '')) + return fn_sub_value + pass + cleaned_json = {} for key, value in resource_json.items(): cleaned_val = clean_json(value, resources_map) diff --git a/tests/test_cloudformation/test_stack_parsing.py b/tests/test_cloudformation/test_stack_parsing.py index 7b582b9b5..594515468 100644 --- a/tests/test_cloudformation/test_stack_parsing.py +++ b/tests/test_cloudformation/test_stack_parsing.py @@ -85,6 +85,26 @@ split_select_template = { } } +sub_template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "Queue1": { + "Type": "AWS::SQS::Queue", + "Properties": { + "QueueName": {"Fn::Sub": '${AWS::StackName}-queue-${!Literal}'}, + "VisibilityTimeout": 60, + } + }, + "Queue2": { + "Type": "AWS::SQS::Queue", + "Properties": { + "QueueName": {"Fn::Sub": '${Queue1.QueueName}'}, + "VisibilityTimeout": 60, + } + }, + } +} + outputs_template = dict(list(dummy_template.items()) + list(output_dict.items())) bad_outputs_template = dict( @@ -99,6 +119,7 @@ bad_output_template_json = json.dumps(bad_outputs_template) 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) def test_parse_stack_resources(): @@ -294,3 +315,15 @@ def test_parse_split_and_select(): queue = stack.resource_map['Queue'] queue.name.should.equal("myqueue") + +def test_sub(): + stack = FakeStack( + stack_id="test_id", + name="test_stack", + template=sub_template_json, + parameters={}, + region_name='us-west-1') + + queue1 = stack.resource_map['Queue1'] + queue2 = stack.resource_map['Queue2'] + queue2.name.should.equal(queue1.name)