handle short form function in cfn yaml template (#1103)

This commit is contained in:
Toshiya Kawasaki 2017-09-08 03:28:15 +09:00 committed by Jack Danger
parent 0d122ef86f
commit 2f6f42a183
5 changed files with 172 additions and 4 deletions

View File

@ -9,7 +9,7 @@ from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from .parsing import ResourceMap, OutputMap
from .utils import generate_stack_id
from .utils import generate_stack_id, yaml_tag_constructor
from .exceptions import ValidationError
@ -74,6 +74,7 @@ class FakeStack(BaseModel):
))
def _parse_template(self):
yaml.add_multi_constructor('', yaml_tag_constructor)
try:
self.template_dict = yaml.load(self.template)
except yaml.parser.ParserError:

View File

@ -391,8 +391,7 @@ LIST_STACKS_RESOURCES_RESPONSE = """<ListStackResourcesResponse>
GET_TEMPLATE_RESPONSE_TEMPLATE = """<GetTemplateResponse>
<GetTemplateResult>
<TemplateBody>{{ stack.template }}
</TemplateBody>
<TemplateBody>{{ stack.template }}</TemplateBody>
</GetTemplateResult>
<ResponseMetadata>
<RequestId>b9b4b068-3a41-11e5-94eb-example</RequestId>

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
import uuid
import six
import random
import yaml
def generate_stack_id(stack_name):
@ -13,3 +14,22 @@ def random_suffix():
size = 12
chars = list(range(10)) + ['A-Z']
return ''.join(six.text_type(random.choice(chars)) for x in range(size))
def yaml_tag_constructor(loader, tag, node):
"""convert shorthand intrinsic function to full name
"""
def _f(loader, tag, node):
if tag == '!GetAtt':
return node.value.split('.')
elif type(node) == yaml.SequenceNode:
return loader.construct_sequence(node)
else:
return node.value
if tag == '!Ref':
key = 'Ref'
else:
key = 'Fn::{}'.format(tag[1:])
return {key: _f(loader, tag, node)}

View File

@ -39,6 +39,68 @@ dummy_template = {
}
}
dummy_template_yaml = """---
AWSTemplateFormatVersion: 2010-09-09
Description: Stack1 with yaml template
Resources:
EC2Instance1:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-d3adb33f
KeyName: dummy
InstanceType: t2.micro
Tags:
- Key: Description
Value: Test tag
- Key: Name
Value: Name tag for tests
"""
dummy_template_yaml_with_short_form_func = """---
AWSTemplateFormatVersion: 2010-09-09
Description: Stack1 with yaml template
Resources:
EC2Instance1:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-d3adb33f
KeyName: !Join [ ":", [ du, m, my ] ]
InstanceType: t2.micro
Tags:
- Key: Description
Value: Test tag
- Key: Name
Value: Name tag for tests
"""
dummy_template_yaml_with_ref = """---
AWSTemplateFormatVersion: 2010-09-09
Description: Stack1 with yaml template
Parameters:
TagDescription:
Type: String
TagName:
Type: String
Resources:
EC2Instance1:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-d3adb33f
KeyName: dummy
InstanceType: t2.micro
Tags:
- Key: Description
Value:
Ref: TagDescription
- Key: Name
Value: !Ref TagName
"""
dummy_update_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
@ -110,6 +172,46 @@ def test_boto3_create_stack():
cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal(
dummy_template)
@mock_cloudformation
def test_boto3_create_stack_with_yaml():
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
cf_conn.create_stack(
StackName="test_stack",
TemplateBody=dummy_template_yaml,
)
cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal(
dummy_template_yaml)
@mock_cloudformation
def test_boto3_create_stack_with_short_form_func_yaml():
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
cf_conn.create_stack(
StackName="test_stack",
TemplateBody=dummy_template_yaml_with_short_form_func,
)
cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal(
dummy_template_yaml_with_short_form_func)
@mock_cloudformation
def test_boto3_create_stack_with_ref_yaml():
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
params = [
{'ParameterKey': 'TagDescription', 'ParameterValue': 'desc_ref'},
{'ParameterKey': 'TagName', 'ParameterValue': 'name_ref'},
]
cf_conn.create_stack(
StackName="test_stack",
TemplateBody=dummy_template_yaml_with_ref,
Parameters=params
)
cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal(
dummy_template_yaml_with_ref)
@mock_cloudformation
def test_creating_stacks_across_regions():
@ -150,7 +252,6 @@ def test_create_stack_with_role_arn():
TemplateBody=dummy_template_json,
RoleARN='arn:aws:iam::123456789012:role/moto',
)
stack = list(cf.stacks.all())[0]
stack.role_arn.should.equal('arn:aws:iam::123456789012:role/moto')

View File

@ -10,8 +10,11 @@ from moto.cloudformation.models import FakeStack
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 moto.cloudformation.utils import yaml_tag_constructor
from boto.cloudformation.stack import Output
dummy_template = {
"AWSTemplateFormatVersion": "2010-09-09",
@ -380,3 +383,47 @@ def test_import():
queue = import_stack.resource_map['Queue']
queue.name.should.equal("value")
def test_short_form_func_in_yaml_teamplate():
template = """---
KeyB64: !Base64 valueToEncode
KeyRef: !Ref foo
KeyAnd: !And
- A
- B
KeyEquals: !Equals [A, B]
KeyIf: !If [A, B, C]
KeyNot: !Not [A]
KeyOr: !Or [A, B]
KeyFindInMap: !FindInMap [A, B, C]
KeyGetAtt: !GetAtt A.B
KeyGetAZs: !GetAZs A
KeyImportValue: !ImportValue A
KeyJoin: !Join [ ":", [A, B, C] ]
KeySelect: !Select [A, B]
KeySplit: !Split [A, B]
KeySub: !Sub A
"""
yaml.add_multi_constructor('', yaml_tag_constructor)
template_dict = yaml.load(template)
key_and_expects = [
['KeyRef', {'Ref': 'foo'}],
['KeyB64', {'Fn::Base64': 'valueToEncode'}],
['KeyAnd', {'Fn::And': ['A', 'B']}],
['KeyEquals', {'Fn::Equals': ['A', 'B']}],
['KeyIf', {'Fn::If': ['A', 'B', 'C']}],
['KeyNot', {'Fn::Not': ['A']}],
['KeyOr', {'Fn::Or': ['A', 'B']}],
['KeyFindInMap', {'Fn::FindInMap': ['A', 'B', 'C']}],
['KeyGetAtt', {'Fn::GetAtt': ['A', 'B']}],
['KeyGetAZs', {'Fn::GetAZs': 'A'}],
['KeyImportValue', {'Fn::ImportValue': 'A'}],
['KeyJoin', {'Fn::Join': [ ":", [ 'A', 'B', 'C' ] ]}],
['KeySelect', {'Fn::Select': ['A', 'B']}],
['KeySplit', {'Fn::Split': ['A', 'B']}],
['KeySub', {'Fn::Sub': 'A'}],
]
for k, v in key_and_expects:
template_dict.should.have.key(k).which.should.be.equal(v)