b41739a63e
If there is some error during parsing template or env file, CSafeLoader errors doesn't contains snippet from template. So, we need to use SafeLoader for more informative error with snippet from parsed template. Change-Id: I79039164533c032db66c933c4e8882b8942961eb Related-bug: #1496361
133 lines
4.4 KiB
Python
133 lines
4.4 KiB
Python
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import itertools
|
|
import re
|
|
|
|
from oslo_config import cfg
|
|
from oslo_serialization import jsonutils
|
|
import six
|
|
import yaml
|
|
|
|
from heat.common import exception
|
|
from heat.common.i18n import _
|
|
|
|
cfg.CONF.import_opt('max_template_size', 'heat.common.config')
|
|
|
|
if hasattr(yaml, 'CSafeLoader'):
|
|
yaml_loader = yaml.CSafeLoader
|
|
else:
|
|
yaml_loader = yaml.SafeLoader
|
|
|
|
if hasattr(yaml, 'CSafeDumper'):
|
|
yaml_dumper = yaml.CSafeDumper
|
|
else:
|
|
yaml_dumper = yaml.SafeDumper
|
|
|
|
|
|
def _construct_yaml_str(self, node):
|
|
# Override the default string handling function
|
|
# to always return unicode objects
|
|
return self.construct_scalar(node)
|
|
yaml_loader.add_constructor(u'tag:yaml.org,2002:str', _construct_yaml_str)
|
|
# Unquoted dates like 2013-05-23 in yaml files get loaded as objects of type
|
|
# datetime.data which causes problems in API layer when being processed by
|
|
# openstack.common.jsonutils. Therefore, make unicode string out of timestamps
|
|
# until jsonutils can handle dates.
|
|
yaml_loader.add_constructor(u'tag:yaml.org,2002:timestamp',
|
|
_construct_yaml_str)
|
|
|
|
|
|
def simple_parse(tmpl_str):
|
|
try:
|
|
tpl = jsonutils.loads(tmpl_str)
|
|
except ValueError:
|
|
try:
|
|
tpl = yaml.load(tmpl_str, Loader=yaml_loader)
|
|
except yaml.YAMLError:
|
|
# NOTE(prazumovsky): we need to return more informative error for
|
|
# user, so use SafeLoader, which return error message with template
|
|
# snippet where error has been occurred.
|
|
try:
|
|
tpl = yaml.load(tmpl_str, Loader=yaml.SafeLoader)
|
|
except yaml.YAMLError as yea:
|
|
yea = six.text_type(yea)
|
|
msg = _('Error parsing template: %s') % yea
|
|
raise ValueError(msg)
|
|
else:
|
|
if tpl is None:
|
|
tpl = {}
|
|
|
|
if not isinstance(tpl, dict):
|
|
raise ValueError(_('The template is not a JSON object '
|
|
'or YAML mapping.'))
|
|
|
|
return tpl
|
|
|
|
|
|
def parse(tmpl_str):
|
|
"""Takes a string and returns a dict containing the parsed structure.
|
|
|
|
This includes determination of whether the string is using the
|
|
JSON or YAML format.
|
|
"""
|
|
if len(tmpl_str) > cfg.CONF.max_template_size:
|
|
msg = (_('Template exceeds maximum allowed size (%s bytes)') %
|
|
cfg.CONF.max_template_size)
|
|
raise exception.RequestLimitExceeded(message=msg)
|
|
tpl = simple_parse(tmpl_str)
|
|
# 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 convert_json_to_yaml(json_str):
|
|
"""Convert AWS JSON template format to Heat YAML format.
|
|
|
|
:param json_str: a string containing the AWS JSON template format.
|
|
:returns: the equivalent string containing the Heat YAML format.
|
|
"""
|
|
|
|
# Replace AWS format version with Heat format version
|
|
json_str = re.sub('"AWSTemplateFormatVersion"\s*:\s*"[^"]+"\s*,',
|
|
'', json_str)
|
|
|
|
# insert a sortable order into the key to preserve file ordering
|
|
key_order = itertools.count()
|
|
|
|
def order_key(matchobj):
|
|
key = '%s"__%05d__order__%s" :' % (
|
|
matchobj.group(1),
|
|
next(key_order),
|
|
matchobj.group(2))
|
|
return key
|
|
key_re = re.compile('(\s*)"([^"]+)"\s*:')
|
|
json_str = key_re.sub(order_key, json_str)
|
|
|
|
# parse the string as json to a python structure
|
|
tpl = yaml.load(json_str, Loader=yaml_loader)
|
|
|
|
# dump python structure to yaml
|
|
tpl["HeatTemplateFormatVersion"] = '2012-12-12'
|
|
yml = yaml.dump(tpl, Dumper=yaml_dumper)
|
|
|
|
# remove ordering from key names
|
|
yml = re.sub('__\d*__order__', '', yml)
|
|
|
|
# convert integer keys back to string
|
|
yml = re.sub('([\s,{])(\d+)(\s*):', r"\1'\2'\3:", yml)
|
|
return yml
|