"version" section should be required in template

"HeatTemplateFormatVersion" as default while a template missing version
section now in heat. But then the server can't parse the template in right
way, not translate hot format to cfn. Some errors raised such as:
  Error : At least one Resources member must be defined.
  but "resources" section has one resource.
  Error: The Parameter (image_id) was not defined in template.
  but "image_id" was defined in "parameter" section.

So if we cannot determine the template version, we should raise an
exception. This patch will check whether version section is in template,
if not we should raise exception.

Change-Id: Idc2ba2b9ae099d4509dfd81914aa9bb8bd3bfefb
Closes-Bug: #1267735
This commit is contained in:
huangtianhua 2014-01-10 16:18:01 +08:00
parent f4337bc58c
commit ae5647f8d5
11 changed files with 62 additions and 83 deletions

View File

@ -28,7 +28,7 @@ from ..plugin import docker_container # noqa
template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Test template",
"Parameters": {},
"Resources": {

View File

@ -169,6 +169,7 @@ class FakeAutoScale(object):
class ScalingGroupTest(HeatTestCase):
group_template = template_format.parse('''
HeatTemplateFormatVersion: "2012-12-12"
Description: "Rackspace Auto Scale"
Parameters: {}
Resources:
@ -330,6 +331,7 @@ class ScalingGroupTest(HeatTestCase):
class PolicyTest(HeatTestCase):
policy_template = template_format.parse('''
HeatTemplateFormatVersion: "2012-12-12"
Description: "Rackspace Auto Scale"
Parameters: {}
Resources:
@ -472,6 +474,7 @@ class PolicyTest(HeatTestCase):
class WebHookTest(HeatTestCase):
webhook_template = template_format.parse('''
HeatTemplateFormatVersion: "2012-12-12"
Description: "Rackspace Auto Scale"
Parameters: {}
Resources:

View File

@ -69,28 +69,14 @@ def parse(tmpl_str):
else:
if tpl is None:
tpl = {}
if u'heat_template_version' not in tpl:
default_for_missing(tpl, u'HeatTemplateFormatVersion',
HEAT_VERSIONS)
# Looking for supported version keys in the loaded template
if not ('HeatTemplateFormatVersion' in tpl
or 'heat_template_version' in tpl
or 'AWSTemplateFormatVersion' in tpl):
raise ValueError(_("Template format version not found."))
return tpl
def default_for_missing(tpl, version_param, versions):
'''
Checks a parsed template for missing version and sections.
This is currently only applied to YAML templates.
'''
# if version is missing, implicitly use the lastest one
if version_param not in tpl:
tpl[version_param] = versions[-1]
# create empty placeholders for any of the main dict sections
for param in (u'Parameters', u'Mappings', u'Resources', u'Outputs'):
if param not in tpl:
tpl[param] = {}
def convert_json_to_yaml(json_str):
'''Convert a string containing the AWS JSON template format
to an equivalent string containing the Heat YAML format.

View File

@ -3,10 +3,6 @@
"Description" : "Template to test Neutron resources",
"Parameters" : {
},
"Resources" : {
"network": {
"Type": "OS::Neutron::Net",

View File

@ -30,6 +30,7 @@ from heat.tests.common import HeatTestCase
from heat.tests import utils
policy_path = os.path.dirname(os.path.realpath(__file__)) + "/policy/"
template = {u'AWSTemplateFormatVersion': u'2010-09-09', u'Foo': u'bar'}
class CfnStackControllerTest(HeatTestCase):
@ -464,7 +465,6 @@ class CfnStackControllerTest(HeatTestCase):
def test_create(self):
# Format a dummy request
stack_name = "wordpress"
template = {u'Foo': u'bar'}
json_template = json.dumps(template)
params = {'Action': 'CreateStack', 'StackName': stack_name,
'TemplateBody': '%s' % json_template,
@ -511,7 +511,6 @@ class CfnStackControllerTest(HeatTestCase):
def test_create_rollback(self):
# Format a dummy request
stack_name = "wordpress"
template = {u'Foo': u'bar'}
json_template = json.dumps(template)
params = {'Action': 'CreateStack', 'StackName': stack_name,
'TemplateBody': '%s' % json_template,
@ -558,7 +557,6 @@ class CfnStackControllerTest(HeatTestCase):
def test_create_onfailure_true(self):
# Format a dummy request
stack_name = "wordpress"
template = {u'Foo': u'bar'}
json_template = json.dumps(template)
params = {'Action': 'CreateStack', 'StackName': stack_name,
'TemplateBody': '%s' % json_template,
@ -605,7 +603,6 @@ class CfnStackControllerTest(HeatTestCase):
def test_create_onfailure_false_delete(self):
# Format a dummy request
stack_name = "wordpress"
template = {u'Foo': u'bar'}
json_template = json.dumps(template)
params = {'Action': 'CreateStack', 'StackName': stack_name,
'TemplateBody': '%s' % json_template,
@ -652,7 +649,6 @@ class CfnStackControllerTest(HeatTestCase):
def test_create_onfailure_false_rollback(self):
# Format a dummy request
stack_name = "wordpress"
template = {u'Foo': u'bar'}
json_template = json.dumps(template)
params = {'Action': 'CreateStack', 'StackName': stack_name,
'TemplateBody': '%s' % json_template,
@ -699,7 +695,6 @@ class CfnStackControllerTest(HeatTestCase):
def test_create_onfailure_err(self):
# Format a dummy request
stack_name = "wordpress"
template = {u'Foo': u'bar'}
json_template = json.dumps(template)
params = {'Action': 'CreateStack', 'StackName': stack_name,
'TemplateBody': '%s' % json_template,
@ -739,7 +734,6 @@ class CfnStackControllerTest(HeatTestCase):
def test_create_err_rpcerr(self):
# Format a dummy request
stack_name = "wordpress"
template = {u'Foo': u'bar'}
json_template = json.dumps(template)
params = {'Action': 'CreateStack', 'StackName': stack_name,
'TemplateBody': '%s' % json_template,
@ -809,7 +803,6 @@ class CfnStackControllerTest(HeatTestCase):
def test_create_err_exists(self):
# Format a dummy request
stack_name = "wordpress"
template = {u'Foo': u'bar'}
json_template = json.dumps(template)
params = {'Action': 'CreateStack', 'StackName': stack_name,
'TemplateBody': '%s' % json_template,
@ -845,7 +838,6 @@ class CfnStackControllerTest(HeatTestCase):
def test_create_err_engine(self):
# Format a dummy request
stack_name = "wordpress"
template = {u'Foo': u'bar'}
json_template = json.dumps(template)
params = {'Action': 'CreateStack', 'StackName': stack_name,
'TemplateBody': '%s' % json_template,
@ -881,7 +873,6 @@ class CfnStackControllerTest(HeatTestCase):
def test_update(self):
# Format a dummy request
stack_name = "wordpress"
template = {u'Foo': u'bar'}
json_template = json.dumps(template)
params = {'Action': 'UpdateStack', 'StackName': stack_name,
'TemplateBody': '%s' % json_template,
@ -929,7 +920,6 @@ class CfnStackControllerTest(HeatTestCase):
def test_update_bad_name(self):
stack_name = "wibble"
template = {u'Foo': u'bar'}
json_template = json.dumps(template)
params = {'Action': 'UpdateStack', 'StackName': stack_name,
'TemplateBody': '%s' % json_template,
@ -961,7 +951,6 @@ class CfnStackControllerTest(HeatTestCase):
# Format a dummy request
stack_name = "wordpress"
identity = dict(identifier.HeatIdentifier('t', stack_name, '6'))
template = {u'Foo': u'bar'}
params = {'Action': 'GetTemplate', 'StackName': stack_name}
dummy_req = self._dummy_GET_request(params)
self._stub_enforce(dummy_req, 'GetTemplate')
@ -1092,12 +1081,10 @@ class CfnStackControllerTest(HeatTestCase):
def test_bad_resources_in_template(self):
# Format a dummy request
json_template = {
'template': {
'AWSTemplateFormatVersion': '2010-09-09',
'Resources': {
'Type': 'AWS: : EC2: : Instance',
},
}
'AWSTemplateFormatVersion': '2010-09-09',
'Resources': {
'Type': 'AWS: : EC2: : Instance',
},
}
params = {'Action': 'ValidateTemplate',
'TemplateBody': '%s' % json.dumps(json_template)}

View File

@ -64,8 +64,12 @@ def to_remote_error(error):
class InstantiationDataTest(HeatTestCase):
def test_format_parse(self):
data = {"key1": ["val1[0]", "val1[1]"], "key2": "val2"}
json_repr = '{ "key1": [ "val1[0]", "val1[1]" ], "key2": "val2" }'
data = {"AWSTemplateFormatVersion": "2010-09-09",
"key1": ["val1[0]", "val1[1]"],
"key2": "val2"}
json_repr = '{"AWSTemplateFormatVersion" : "2010-09-09",' \
'"key1": [ "val1[0]", "val1[1]" ], ' \
'"key2": "val2" }'
parsed = stacks.InstantiationData.format_parse(json_repr, 'foo')
self.assertEqual(parsed, data)
@ -77,6 +81,7 @@ class InstantiationDataTest(HeatTestCase):
def test_format_parse_invalid_message(self):
# make sure the parser error gets through to the caller.
bad_temp = '''
heat_template_version: '2012-12-12'
parameters:
KeyName:
type: string
@ -86,7 +91,7 @@ parameters:
parse_ex = self.assertRaises(webob.exc.HTTPBadRequest,
stacks.InstantiationData.format_parse,
bad_temp, 'foo')
self.assertIn('line 3, column 3', str(parse_ex))
self.assertIn('line 4, column 3', str(parse_ex))
def test_stack_name(self):
body = {'stack_name': 'wibble'}
@ -105,20 +110,18 @@ parameters:
self.assertEqual(data.template(), template)
def test_template_string_json(self):
template = '{"foo": "bar", "blarg": "wibble"}'
template = '{"heat_template_version": "2012-12-12",' \
'"foo": "bar", "blarg": "wibble"}'
body = {'template': template}
data = stacks.InstantiationData(body)
self.assertEqual(data.template(), json.loads(template))
def test_template_string_yaml(self):
template = '''foo: bar
template = '''HeatTemplateFormatVersion: 2012-12-12
foo: bar
blarg: wibble
'''
parsed = {u'HeatTemplateFormatVersion': u'2012-12-12',
u'Mappings': {},
u'Outputs': {},
u'Parameters': {},
u'Resources': {},
u'blarg': u'wibble',
u'foo': u'bar'}
@ -127,7 +130,9 @@ blarg: wibble
self.assertEqual(data.template(), parsed)
def test_template_url(self):
template = {'foo': 'bar', 'blarg': 'wibble'}
template = {'heat_template_version': '2013-05-23',
'foo': 'bar',
'blarg': 'wibble'}
url = 'http://example.com/template'
body = {'template_url': url}
data = stacks.InstantiationData(body)

View File

@ -320,7 +320,7 @@ Outputs:
def test_nested_stack_three_deep(self):
root_template = '''
HeatTemplateFormat: 2012-12-12
HeatTemplateFormatVersion: 2012-12-12
Resources:
Nested:
Type: AWS::CloudFormation::Stack
@ -328,7 +328,7 @@ Resources:
TemplateURL: 'https://server.test/depth1.template'
'''
depth1_template = '''
HeatTemplateFormat: 2012-12-12
HeatTemplateFormatVersion: 2012-12-12
Resources:
Nested:
Type: AWS::CloudFormation::Stack
@ -336,7 +336,7 @@ Resources:
TemplateURL: 'https://server.test/depth2.template'
'''
depth2_template = '''
HeatTemplateFormat: 2012-12-12
HeatTemplateFormatVersion: 2012-12-12
Resources:
Nested:
Type: AWS::CloudFormation::Stack
@ -360,7 +360,7 @@ Resources:
def test_nested_stack_four_deep(self):
root_template = '''
HeatTemplateFormat: 2012-12-12
HeatTemplateFormatVersion: 2012-12-12
Resources:
Nested:
Type: AWS::CloudFormation::Stack
@ -368,7 +368,7 @@ Resources:
TemplateURL: 'https://server.test/depth1.template'
'''
depth1_template = '''
HeatTemplateFormat: 2012-12-12
HeatTemplateFormatVersion: 2012-12-12
Resources:
Nested:
Type: AWS::CloudFormation::Stack
@ -376,7 +376,7 @@ Resources:
TemplateURL: 'https://server.test/depth2.template'
'''
depth2_template = '''
HeatTemplateFormat: 2012-12-12
HeatTemplateFormatVersion: 2012-12-12
Resources:
Nested:
Type: AWS::CloudFormation::Stack
@ -384,7 +384,7 @@ Resources:
TemplateURL: 'https://server.test/depth3.template'
'''
depth3_template = '''
HeatTemplateFormat: 2012-12-12
HeatTemplateFormatVersion: 2012-12-12
Resources:
Nested:
Type: AWS::CloudFormation::Stack
@ -415,7 +415,7 @@ Resources:
def test_nested_stack_four_wide(self):
root_template = '''
HeatTemplateFormat: 2012-12-12
HeatTemplateFormatVersion: 2012-12-12
Resources:
Nested:
Type: AWS::CloudFormation::Stack
@ -460,7 +460,7 @@ Resources:
def test_nested_stack_infinite_recursion(self):
template = '''
HeatTemplateFormat: 2012-12-12
HeatTemplateFormatVersion: 2012-12-12
Resources:
Nested:
Type: AWS::CloudFormation::Stack

View File

@ -104,6 +104,7 @@ class ParserTest(HeatTestCase):
mapping_template = template_format.parse('''{
"AWSTemplateFormatVersion" : "2010-09-09",
"Mappings" : {
"ValidMapping" : {
"TestKey" : { "TestValue" : "wibble" }
@ -787,10 +788,12 @@ class StackTest(HeatTestCase):
self.assertEqual(1, stack.total_resources())
def _setup_nested(self, name):
nested_tpl = ('{"Resources":{'
nested_tpl = ('{"HeatTemplateFormatVersion" : "2012-12-12",'
'"Resources":{'
'"A": {"Type": "GenericResourceType"},'
'"B": {"Type": "GenericResourceType"}}}')
tpl = {'Resources':
tpl = {'HeatTemplateFormatVersion': "2012-12-12",
'Resources':
{'A': {'Type': 'AWS::CloudFormation::Stack',
'Properties':
{'TemplateURL': 'http://server.test/nested.json'}},

View File

@ -92,6 +92,7 @@ class ProviderTemplateTest(HeatTestCase):
def test_to_parameters(self):
"""Tests property conversion to parameter values."""
provider = {
'HeatTemplateFormatVersion': '2012-12-12',
'Parameters': {
'Foo': {'Type': 'String'},
'AList': {'Type': 'CommaDelimitedList'},
@ -163,6 +164,7 @@ class ProviderTemplateTest(HeatTestCase):
def test_attributes_extra(self):
provider = {
'HeatTemplateFormatVersion': '2012-12-12',
'Outputs': {
'Foo': {'Value': 'bar'},
'Blarg': {'Value': 'wibble'},
@ -221,6 +223,7 @@ class ProviderTemplateTest(HeatTestCase):
def test_properties_normal(self):
provider = {
'HeatTemplateFormatVersion': '2012-12-12',
'Parameters': {
'Foo': {'Type': 'String'},
'Blarg': {'Type': 'String', 'Default': 'wibble'},
@ -417,7 +420,9 @@ class ProviderTemplateTest(HeatTestCase):
parser.Template({}),
stack_id=str(uuid.uuid4()))
minimal_temp = json.dumps({'Parameters': {}, 'Resources': {}})
minimal_temp = json.dumps({'HeatTemplateFormatVersion': '2012-12-12',
'Parameters': {},
'Resources': {}})
self.m.StubOutWithMock(urlfetch, "get")
urlfetch.get(test_templ_name,
allowed_schemes=('http', 'https',

View File

@ -35,6 +35,7 @@ ws_res_snippet = {"Type": "some_magic_type",
param_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Parameters" : {
"KeyName" : {
"Description" : "KeyName",
@ -54,6 +55,7 @@ param_template = '''
simple_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Parameters" : {},
"Resources" : {
"WebServer": {

View File

@ -60,8 +60,6 @@ class JsonToYamlTest(HeatTestCase):
del(yml[u'HeatTemplateFormatVersion'])
jsn = template_format.parse(json_str)
template_format.default_for_missing(jsn, 'AWSTemplateFormatVersion',
template_format.CFN_VERSIONS)
if u'AWSTemplateFormatVersion' in jsn:
del(jsn[u'AWSTemplateFormatVersion'])
@ -81,18 +79,6 @@ class JsonToYamlTest(HeatTestCase):
class YamlMinimalTest(HeatTestCase):
def test_minimal_yaml(self):
yaml1 = ''
yaml2 = '''HeatTemplateFormatVersion: '2012-12-12'
Parameters: {}
Mappings: {}
Resources: {}
Outputs: {}
'''
tpl1 = template_format.parse(yaml1)
tpl2 = template_format.parse(yaml2)
self.assertEqual(tpl1, tpl2)
def test_long_yaml(self):
template = {'HeatTemplateVersion': '2012-12-12'}
config.cfg.CONF.set_override('max_template_size', 1024)
@ -105,6 +91,16 @@ Outputs: {}
msg = 'Request limit exceeded: Template exceeds maximum allowed size.'
self.assertEqual(msg, str(ex))
def test_parse_no_version_format(self):
yaml = ''
self.assertRaises(ValueError, template_format.parse, yaml)
yaml2 = '''Parameters: {}
Mappings: {}
Resources: {}
Outputs: {}
'''
self.assertRaises(ValueError, template_format.parse, yaml2)
class YamlParseExceptions(HeatTestCase):
@ -143,13 +139,9 @@ class JsonYamlResolvedCompareTest(HeatTestCase):
def compare_stacks(self, json_file, yaml_file, parameters):
t1 = self.load_template(json_file)
template_format.default_for_missing(t1, 'AWSTemplateFormatVersion',
template_format.CFN_VERSIONS)
del(t1[u'AWSTemplateFormatVersion'])
t2 = self.load_template(yaml_file)
del(t2[u'HeatTemplateFormatVersion'])
del(t1[u'AWSTemplateFormatVersion'])
stack1 = utils.parse_stack(t1, parameters)
stack2 = utils.parse_stack(t2, parameters)